Dev Diaries - Building a JRPG - Part 2

Hello! It's me again, and it just feels so long since I've "thought about" game development. I especially feel like it has been forever since I've last blogged.

I think it's due to the crazy, and busy work stuff that I have had going on the past week. A project was running late, and I was scrambling to get as much done to show before a specific date.

What resulted with a delay was the fact that there had to be a few rewrites in the overall system. The architecture was debated back and forth for a while too, and ultimately the team, and I settled for the original design we had decided on.

No one is at fault for that. It just kind of happened because I had done a bulk of the implementation on my own while everyone was out on the holidays. Since I fairly started at the company recently, I tried my best to make use of the resources I had around me. I was able to get a full implementation working, but because of the holidays, people coming back lost context as to what was even going on.

There were times where I had to also defend my design decisions. Consequently, I think due to my lack of knowledge in the overall system, I could not put up a good argument to defend my technical design -- although I felt it was the best design for the project, I could not reason aggressively as to why it was the case. Again, that's due to my ignorance in the overall system.

All in all, I think the project is looking good now, and even though the target date wasn't hit, the project is in a state where I think it is actually implemented better than what I had initially before the rewrite. I really also have to thank my Engineering Manager for a helping hand, and having the patience to stick it through with me. 😄

Aside from the crazy work things, I've also started my graduate school courses this week for my online MSCS desgree at the University of Illinois Urbana-Champaign. I'm taking two (2) classes while trying to balance a full time job, and gearing up for the arrival of my daughter in late April. Work had me fall behind in classes already!

In order to catch up, I took most of the Saturday (Jan. 18) to catch up on some of the Week 1 lecture videos. Okay, not most of the day... in fact... well... I took all day!

I'm taking a graduate level Computer Graphics course, and another relating to Cloud Computing. The former is going to be handy for most of the stuff we will be working on together in these series of posts. So, look forward to my learnings... Until then, it's time to brush up on some of my college math skills. The return of Linear Algebra, and Vector Calculus! Woo...

Supernova Symbols

So how is that gaming life?

I am absolutely loving the Nintendo Switch. My wife is currently spending most of the time on it, and is already at Luca in Final Fantasy X. Keep in mind that we only really game for a total of 5 hours (at most) during the weekend.

I also bought a "new game" New Super Mario Bros. U Deluxe, and it is pretty good so far. It's a remake of the Wii U game, which I haven't played anyway. I am on the last stage of Acorn Plains. My first opinion of it is that It seems that the new SMB games are by default, easier than the old school 8/16-bit ones. The last SMB game I played, and beat was Super Mario World on the SNES, so it shows how long I've been under a rock.

As always, I'm making slow progress on Pokemon Red. I ended up catching that level 50 Zapdos, and went to Seafoam Islands in attempt to catch Articuno. Due to my tendencies in always being under leveled until the very end of games, I've decided to hold off in trying to catch Articuno for now. Instead, I'm continuing my journey to Cinnabar Island to get my 7th gym badge by defeating Blaine. I'm also going to keep racking up Pokemon for my Pokedex too. Turns out, my wife also wants a complete Pokedex, and she's on Yellow. We're going to sit down and actually do some trading one of these days.

Finally, Final Fantasy 6 has been progressing even slower now that there is so much happening in my life, lol. I'm still trying to turn that Cursed Shield into a Paladin Shield. 256 battles is a lot, and I'm trying to do this all in Dino Forest. My main party of Terra, Edgar, Sabin and Celes are all averaging Level 60 now, and I've decided to switch out my party members (keeping Edgar who has the shield) to try and level them up while I am at it. Overall, I didn't play much at all last week, but I did manage to beat a Brachiosaur. So, that's pretty cool. A bit underwhelming though since I was expecting large rewards from the battle.

Brachiosaur

I didn't know that the Brachiosaur is the most powerful "regular" enemy in the game! Wow!

Other Life Things

  • For the first time in my life, I forgot about a Doctor's appointment, and ended up getting called. I'll need to reschedule now 😦
  • Also, this reminds me... It's been like... 8 months since my last teeth cleaning... I need to get on that.
  • I also need to refill my glaucoma drops. I've been out...

Wow... I really need to stay on top my health... I proclaim February to be the month where I maintain my health, haha.

  • We're Week 25, Day 1 of the pregnancy now... It's getting closer! We're less than 15 weeks to go! Time is flying by.
  • I'm building a home gym. You know, New Year... new health-related stuff.. More on that later. 😄

Finally, let's get game dev'ing.

Setting Up the Project and Scene

We talked about gathering our assets in the last chapter. Now, let's create a project and start building our demo!

I'm going to experiment with something new in this part of the series. I'd like to take a stab in actually documenting the steps involved in creating these demos to make it even more interactive in building out a game.

I'm not sure if it will help when it comes to making these posts interesting to read, but I welcome feedback on whether, or not you think this works, helps, or makes understanding me even worse. 😄

We'll be using the latest Unity version for this as of January 2020 -- 2019.3.0f3.

First let's create our 2D project, and let's err... call it... RogueLikeThingy? Ponywolf, thanks for the inspiration on the name. 🐱

00-project.png

Let's now import all our assets. I don't think it matters whether, or not we organize them all right now. When the time comes, I think it's something we can fix then!

01-import.png

Right now, our scene is only SampleScene. We can make use of this existing one. What we want to do here is to start building our our scene. In order to do that, we will need to create a tilemap, and tileset.

The easiest thing to do first is to build our tileset. Going back to tileset.png, we'll select our 512x512 image, and have the sprite editor open along with our inspector.

Configue the sprite for tileset.png to have the following properties in the Import Settings:

  • Texture Type - Sprite (2D and UI)

  • Sprite Mode - Multiple

    • Pixels Per Unit - 32
    • Mesh Type - Tight
    • Extrude Edges - 0
  • Advanced

    • Filter Mode - Point (no filter)
  • Compression - None

Also, within the Sprite Editor, let's slice our sprites using a Custom Grid Cell Size with a 32x32 resolution per tile.

At the end of it all, the configuration settings should look something like this:

02-tileset-sprite-config.png

Apply the settings, and we are now on our way to building out our world!

03-individual-tiles-tileset.png

We're not quite there yet for our tileset. In order to use our tileset that we had just created, we'll have to create a tile palette. Make sure the Tile Palette view is open, and create a new palette. I'm calling mine World.

Now, simply drag the tileset asset from the Project tab into the Tile Palette window after creating the tile palette. Unity will save a bunch of new assets, and process everything. When it's finally done, you'll see... this!

05-tile-palette.png

Alright, our scene is horribly bland, so it's time to cut to the chase, and create a tilemap GameObject. I always forget how to do this for some reason, and now that I'm finally documenting this step, maybe I won't let this slip out of my mind anymore!

We'll need to right click on the SampleScene GameObject in the project hierarchy view, and add a new 2D GameObject. It should be TileMap.

04-creating-tilemap.png

We'll get the camera set up once we have our characters in view. Which now takes us to creating character sprite objects!

As noted in the last chapter, our scene will have two basic character objects in view. A single playable character representing Cloud, and an enemy character, Goblin. Let's create those objects now.

First, with Cloud, we'll need to divide the spritesheet to single tiles in which we can animate. The resolution of this spritesheet is 96x128.

Doing some math, we can easily figure out how large each slice should be for a single frame. Since the spritesheet shows 4 rows of 3 columns, we know that 128 px / 4 rows = 32 px / row, and 96 px / 3 cols = 32 px / col. Therefore, we should slice the spritesheet into 32x32 portions.

The settings are similar to the World tileset, and after slicing the tiles you should see that each 32x32 region fits perfectly into frame.

06-cloud-slice.png

Now, we can follow the same pattern for Goblin.

07-sliced-sprites.png

Okay, now let's actually start painting our scene with tiles. The basic scene I want to show is just to have the two characters be on a simple island. Yeah, nothing too fancy, but it is enough to show that the Jrpg.CharacterSystem module is working. 😎

With the first tilemap, we'll fill it up with some water. This specific tilemap should have a Sorting Layer of Water (we create this) and the Order in Layer is set to 0. To make things more manageable, and easier to read, let's also rename this tilemap from the default of Tilemap to Water displayed in the project Hierarchy view.

08-sorting-layer.png

Now, let's create a new Tilemap GameObject, and call this tilemap Land. The sorting order here will be set to 0.

Draw the island, and your scene should look similar to this:

09-scene-0.png

While we are at it, let's make things a bit more interesting here, and add a new tilemap layer called EnvironmentObjects to house all the objects on top of land. Let's add a boat, and some other random objects on the land.

10-scene-1.png

Hm! Now our scene is much more interesting!

Creating the Character GameObjects

As of right now, here's what our scene looks like with the tilemap layers:

  • Default
  • Water
  • Land
  • Environment

11-sorting-layers.png

Environment is the top-most layer, while Water is the base.

Let's start adding Cloud and Goblin into the scene.

For Cloud, We'll want to create a Sprite GameObject on the Environment layer, and have the renderer output. Aside from that, everything else should be left as-is. You'll instantly see Cloud standing on top of the island. If you don't, it's probably there, just that you may have to re-position the sprite.

12-scene-cloud.png

Of course, do the same thing for Goblin! Since our Goblin friend is green, I decided to place him on top of the dirt, so it is easier to see given the contrast.

13-scene-goblin.png

I think now we're just about ready to start building out movement for Cloud. Let's divide the task into 3 phases:

  1. Provide animation to the characters.
  2. Create the animation states.
  3. Implement the event handling to transition from state-to-state along with object translation on the scene.

The process isn't new to us by now, but it is worth explaining in more detail here.

Providing Animation

For with the Cloud GameObject selected, we'll want to add a new component to the object in addition to the Sprite Renderer comopnent. RigidBody 2D will add some basic physics to allow this GameObject to be moved in the context of this scene.

14-rigidbody-2d.png

A Rigidbody 2D component places an object under the control of the physics engine. Many concepts familiar from the standard Rigidbody component carry over to Rigidbody 2D; the differences are that in 2D, objects can only move in the XY plane and can only rotate on an axis perpendicular to that plane.

Configure this RigidBody 2D component on Cloud to just have a 0 value on the Gravity Scale , and Freeze Rotation on the z-axis.

15-rigidbody-2d-settings.png

Now, we want to be able to make this sprite object be animateable. (Err... is that a word?) Animable? Animateable? Apparently, it's "animatable" -- https://developer.apple.com/documentation/swiftui/animatable

So, let's create an Animator component in the GameObject. This will allow us to attach a controller with animations to Cloud.

Bringing up the Animation view, we can begin creating animations for Cloud walking up, down, left, and right. Make sure the Cloud object is selected so that we can create animations for the Animator component attached.

Create the following animations:

  • CloudUp
  • CloudDown
  • CloudLeft
  • CloudRight

For each of these animations, we'll just take the 3 frames corresponding to the appropriate direction and sample them at 5 frames per second. The exact sampling is to your taste, but I have found that 5 frames looks pretty good.

16-animation-cloud.png

17-animation-cloud.gif

Repeat this same pattern with Goblin. 😄

Here's what the animations should end up looking like after adding them to the sprite objects:

18-cloud-complete-animation.gif

Creating Animation States

Going back to Cloud, we'll fire up to the Animator view and start providing edges to the animation states. By default, the transition is from Entry to CloudUp. This doesn't make too much sense because we want the character to be standing still until the player fires some event to begin moving in a specific direction.

19-state-machine-default.png

Let's take care of that and create a new state called CloudIdle, which will represent Cloud not moving. The transition from the Entry state should then land on CloudIdle. This willl also be the default state.

20-state-machine-idle-transition.png

Now from the CloudIdle state, we for sure, can take transition on all directions. Let's add transitions to the CloudUp, CloudDown, CloudLeft, and CloudRight states.

21-state-machine-direction-transitions.png

When a player wants to switch directions, they must transition to the new directional state, but let's actually make this cleaner and have it go back to the CloudIdle state first. We'll write code to take into consideration of the next state to transition to.

Now, the state machine will start to look like the following:

22-state-machine-direction-transitions-2.png

Let's configure all transitions to not have any leg time in between switching from state to state. To fix this, select each transition, and uncheck Has Exit Time. In addition to that, we should set Transition Duration (s) to 0.

Finally let's create some parameters for our Animator to reference so that it knows how to transition between states. These parameters of course will be boolean type parameters corresponding to their directions.

  • WalkingUp
  • WalkingDown
  • WalkingLeft
  • WalkingRight

When transitioning from CloudIdle to any direction, we will just check if the Walking* boolean variables are set to true, and we transition back from the Cloud* state to CloudIdle if the Walking* boolean variable is false. This is logically sufficient for now.

23-state-transition-param.png

Rest Stop!

Rest stop

I think now is a good stopping point. The next part will be a bit longer and code-heavy in that we'll have to start programming the animation states of the characters. Look forward to that in the next chapter, and hopefully some of the beginnings of basic collision/battle logic being discussed!

See ya!