Thought we'd share one of our dev blogs posted on our website (www.lumatap.com):
Dev blog for October 2019
Crustacean Creation
There is so much to do to make a game, it astounds me how many get finished. Apart from making the actual game (art, game objects, scenes, code) there is website stuff, trying to build an audience on social media, legal business stuff, interfacing with all the game stores (Google Play, Apple, Steam) and daily stuff like managing the version control system and making backups. We're several months into our development and, while we're definitely past the halfway point, there's still a bunch to do. Just making an incidental character takes a lot of steps. As an example, I thought I would walk through how we're doing it.
Meet the crab. When we were designing the underwater levels (oh yes, there are underwater levels) we decided on a few major mechanics and then added a few minor things until it seemed well rounded. The crab is a minor character whose job is to generally annoy the sloth and add some charm to the water scene. He started out as just a crude sketch. I knew I wanted a fiddler crab. They have one big claw that they use to intimidate each other, and generally look a bit silly waving it about.
To begin I did an image search for fiddler crabs, got a page full of results on one monitor, and opened up Blender (a 3D modeling program) on the other. All our models in Sloth Quest are made of various planes in layers that are viewed flat in 2D. The layers are to separate body parts and define which ones are in front or behind others. The crab body is in one plane and his legs are in another behind the body. Each plane is a connected mesh of triangles that form the solid parts that later get painted.
I wanted the crab to be able to walk around on the bottom and to jump up and grab the sloth when he was near and overhead. This required a couple of animations, and animations require bones. Bones get attached to the mesh (or vice versa) and make it deform by dragging the mesh when the bone moves. The armature of bones for the crab was actually more complicated than you might imagine. Crabs have ten legs, but I left a few off to keep them more visually distinct: I only made six. Each leg got two bones to form the upper and lower limb parts. The big claw got a third limb bone to allow it to reach up and a fourth to open and close the claw. The body got one bone and I gave each eye stalk a bone, though I ended up not animating them. All these bones are organized in a hierarchy under the body bone, which in turn is a child of the root bone, a special bone that will move the entire character as it walks.
Before animating, I took a few extra steps and formed IK (inverse kinematics) chains with each limb. In brief, the IK chain lets you position the tip of the limb (the hand or foot) and the other limb bones are automatically repositioned to match. This saves a tremendous amount of time during animation. In the end, the crab legs have nineteen bones, but to animate I only have to position eight (which is still a lot). I made four animations for the crab: standing idle (like he's breathing), reaching up, jumping up and walking. The walk cycle is the most complicated as I needed to coordinate the movement of four legs. The dope sheet image shows each keyframe as a white diamond. The frame numbers representing time are along the bottom scale. I positioned a limb on each keyframe and the animation system interpolates the movement between the positions.
There's a couple other steps involved before getting the crab into the Unity game engine. I had to break the mesh up logically and "unwrap" the resulting pieces into a square. This square is called a UV map (UV refers to the X and Y axes of the square). The UV map defines the areas that will be painted to produce an image (texture) and how it relates to the model's mesh geometry. When making a UV map you generally want to fill the whole square with the mesh parts, leaving as little empty space as possible. Empty space is just wasted memory and bloats the project. It's not very critical a detail, but I kinda like tinkering with the pieces to find an efficient arrangement, like doing a jigsaw puzzle.
So after you create the mesh, build the armature, attach the mesh, animate the armature and make a UV map, you can now export the file. There's a few quirky things you need to figure out to get an acceptable result, but after doing it hundreds of times you get used to it. The resulting FBX file is imported into Unity where it can become an object in the game. But wait, every object has colors, right? We still need to paint it.
We use 3d-Coat to paint our models. It's a complete modelling / retopo / painting system that lets you paint using PBR (physically based rendering) materials in real time and it's fast and sleek. It's not without it's quirks, but it really makes painting fun and satisfying. To paint the crab I imported the FBX into 3d-Coat and set up a UV map of 256 x 256 pixels. You can paint in logical layers just like in Photoshop and later shift them up and down or change their properties in a non-destructive way. I used a couple of layers for different parts of the crab for convenience. After painting, I exported the color texture map (just a square picture really) as a PNG file. Our game is 2d and doesn't use any fancy lighting effects (like normal maps, roughness maps, metallic maps) so I only needed the one color map in this case.
Back in Unity I imported the color map and created a material for the crab. The material marries the color map with the shader (the micro-program that draw the pixels). Next, I created an animator controller for the crab. This defines and controls the different states the crab can assume. I created a state for each animation and a couple extra for when the crab is grabbed onto the sloth and to allow walking left (the walking right animation is played backwards). To finish, I added a variable parameters to trigger the different states and created transitions between the states that would occur when the various parameters changed.
I opened a test scene and dropped the crab model in it. I applied the crab material to the model in the scene and also added the animator controller. To allow the crab to interact with forces and other objects in the physics engine I added both a rigid body and a circle collider. At this point, the crab is visible in the scene and can potentially animate, but that's about it.
To make the crab a character, he needs behavior in the form of C# code. I created an empty script file and began to write some code. It starts out declaring some variables I'll need.
enum CrabState { Idle, WalkRight, WalkLeft, Attack, GrabbedSloth, Release } CrabState CurState; int WalkParamId; int ReachUpParamId; int GrabJumpParamId; int GrabbedOnParamId; Animator Anim; Rigidbody2D BodyRB; Transform BodyTr; Transform ClawTr; bool OnGround; RaycastHit2D[] GroundTestResults; float GroundCheckTimer; bool Jump; bool PushOffSloth; float WaterTimer; float ActionTime; float StateTimer; AudioSource AudioSrc;
Now for some action. The first thing was to find the various parts of the crab that would be manipulated: the animator controller, it's parameters, the crab rigid body, etc. The Unity engine will call the Awake() function exactly once when this object is being created.
void Awake() { Anim = GetComponent<Animator>(); WalkParamId = Animator.StringToHash("walk"); ReachUpParamId = Animator.StringToHash("reach_up"); GrabJumpParamId = Animator.StringToHash("grab_jump"); GrabbedOnParamId = Animator.StringToHash("grabbed_on"); BodyRB = GetComponent<Rigidbody2D>(); BodyTr = transform; ClawTr = transform.Find("Armature/root/body/arm1.L/arm2.L/arm3.L/claw.L"); GroundTestResults = new RaycastHit2D[4]; AudioSrc = GetComponent<AudioSource>(); }
I created a SetState() function to mirror the various states the animator state machine has. They mostly set up the animator, except for the state GrabbedSloth. To grab the sloth with the crab I create physics joint that binds the two rigid bodies together. In this case, a hinge type joint is used so the crab can flob about while hanging on.
void SetState(CrabState st) { CurState = st; StateTimer = 0; switch(st) { case CrabState.Idle: Anim.SetFloat(WalkParamId, 0); Anim.SetBool(ReachUpParamId, false); Anim.SetBool(GrabbedOnParamId, false); Anim.applyRootMotion = true; ActionTime = 1f + Random.value * 2f; break; case CrabState.WalkRight: Anim.SetFloat(WalkParamId, 0.2f); Anim.SetBool(ReachUpParamId, false); Anim.applyRootMotion = true; ActionTime = 0.3f + Random.value; break; case CrabState.WalkLeft: Anim.SetFloat(WalkParamId, -0.2f); Anim.SetBool(ReachUpParamId, false); Anim.applyRootMotion = true; ActionTime = 0.3f + Random.value; break; case CrabState.Attack: Anim.SetFloat(WalkParamId, 0); Anim.SetBool(ReachUpParamId, true); Anim.applyRootMotion = false; break; case CrabState.GrabbedSloth: // Create joint to attach crab HingeJoint2D joint = gameObject.AddComponent<HingeJoint2D>(); joint.autoConfigureConnectedAnchor = false; Transform slothTr = Sloth.GetInstance().GetBodyTransform(); joint.connectedBody = slothTr.GetComponent<Rigidbody2D>(); // Adjust to point near leg-body joint joint.connectedAnchor = new Vector2(0.15f, 0.05f + Random.value * 0.1f); joint.anchor = BodyTr.InverseTransformPoint(ClawTr.position); Anim.SetFloat(WalkParamId, 0); Anim.SetBool(GrabbedOnParamId, true); AudioSrc.clip = SoundFxCollision.GetInstance().GetClip(SoundFxCollision.SoundMaterial.Stick, SoundFxCollision.SoundMaterial.Flesh); AudioSrc.Play(); break; } }
The Update() function gets called every drawing frame, hopefully 60 or more times a second. Here's where some important decisions get made. While the crab is idle, he just waits a bit, then, if he's on the ground, I pick a random direction and start walking. But if the sloth is near he will try to attack.
void Update() { StateTimer += Time.deltaTime; switch(CurState) { case CrabState.Idle: if (StateTimer > ActionTime && OnGround) { float dir = Random.value - 0.5f; if (dir < 0) SetState(CrabState.WalkLeft); else SetState(CrabState.WalkRight); } else if (IsSlothNear()) SetState(CrabState.Attack); break;
While the crab is walking, he's still looking for the sloth to attack. After a short walk, he stands idle again.
case CrabState.WalkLeft: case CrabState.WalkRight: if (StateTimer > ActionTime) SetState(CrabState.Idle); else if (IsSlothNear()) SetState(CrabState.Attack); break;
To attack, I trigger the attack animation. I added an event to the animation so that when the crab should leave the ground, it calls a function that turns the Jump variable to true. More on this later.
case CrabState.Attack: if (!IsSlothNear()) SetState(CrabState.Idle); else if (OnGround) Anim.SetTrigger(GrabJumpParamId); break;
While the crab is grabbed onto the sloth I have to manually move the joint around so it lines up with the big claw. In general, the animation system and the physics system don't play well together. When you have an object driven by both of them, the animation system typically out-bullies the physics. After holding on for 12 seconds, the crab will let go. To prevent instant re-grabbing I have the crab push away from the sloth.
case CrabState.GrabbedSloth: if (StateTimer > 12f) { Sloth sloth = Sloth.GetInstance(); if (!sloth.IsDead()) { HingeJoint2D joint = GetComponent<HingeJoint2D>(); if (joint != null) Destroy(joint); PushOffSloth = true; } SetState(CrabState.Release); } else { // Maintain joint at crab claw as anim transitions to rest pose HingeJoint2D joint = GetComponent<HingeJoint2D>(); joint.anchor = BodyTr.InverseTransformPoint(ClawTr.position); } break;
After releasing the sloth, and after a short delay, the crab returns to idle.
case CrabState.Release: if (StateTimer > 0.5f) SetState(CrabState.Idle); break; } }
The FixedUpdate() function gets called every physics tick, which should be at a fixed interval, usually 60 hertz. It's the place to move physics objects (rigid bodies). At the start, I check if the Jump variable was set (from the jump animation event) and if so, I apply an upward force to the crab. This is a one-time force, so I reset Jump to false until the next time he jumps.
void FixedUpdate() { if (Jump) { Jump = false; BodyRB.bodyType = RigidbodyType2D.Dynamic; BodyRB.AddForce(new Vector2(0, 2f), ForceMode2D.Impulse); }
Similar to Jump there's a variable to push away. Here I calculate a vector away from the sloth and give a gentle boost in that direction.
if (PushOffSloth) { PushOffSloth = false; Vector2 dir = BodyTr.position - Sloth.GetInstance().GetBodyTransform().position; dir = dir.normalized * 0.5f; BodyRB.AddForce(dir, ForceMode2D.Impulse); }
In the rest of the function I handle some environmental control. To keep the crab legs down I apply a small torque to rotate his body if it's tilted too much. Every half second I probe downwards from his body to see if there's a ground object present (ground objects are found in the wall layer). If there's no ground I prevent him from walking. Thirdly, I check if there was a collision with a water object lately, if not, I'll apply full gravity. Normally, when underwater, the gravity force built into the rigid body is weakened.
// Maintain horizontal posture float zrot = BodyRB.rotation; if (zrot < -2f) BodyRB.AddTorque(0.2f, ForceMode2D.Force); else if (zrot > 2f) BodyRB.AddTorque(-0.2f, ForceMode2D.Force); // Check for ground GroundCheckTimer -= Time.fixedDeltaTime; if (GroundCheckTimer <= 0) { GroundCheckTimer = 0.5f; int cnt = Physics2D.RaycastNonAlloc(BodyTr.position, Vector2.down, GroundTestResults, 0.1f, 1 << GameRoot.LayerWall); OnGround = cnt > 0; // Stop walk if not on ground if (!OnGround && (CurState == CrabState.WalkLeft || CurState == CrabState.WalkRight)) SetState(CrabState.Idle); } WaterTimer -= Time.fixedDeltaTime; // Left water? if (WaterTimer <= 0) { // Body no longer buoyant BodyRB.gravityScale = 1f; } }
This function checks if the sloth is attackable. The crab will attack if the sloth enters a rectangular region that is above the crab.
bool IsSlothNear() { Sloth sloth = Sloth.GetInstance(); if (sloth.IsDead()) return false; Vector3 slothPos = sloth.GetBodyTransform().position; Vector3 pos = BodyTr.position; // Sloth above crab? if (slothPos.y > pos.y + 0.1f) { if (Mathf.Abs(slothPos.x - pos.x) <= 0.4f) return true; } return false; }
This function gets called from another tiny script that handles collisions with the crab claw. Collision handler code gets called from the physics engine when object colliders overlap.
public void SlothHitCrab() { if (CurState == CrabState.Attack) SetState(CrabState.GrabbedSloth); }
This is the collision handler for the crab body. Here I'm only interested if the crab is still colliding with some water object. If so, I set a timer that, if expired, indicates the crab has been out of the water long enough to take some action (as you can see above in FixedUpdate()). While underwater, the crab experiences only a weak gravity.
void OnTriggerStay2D(Collider2D coll) { // In water? if (coll.gameObject.layer == GameRoot.LayerWater) { // Reset water timer WaterTimer = 1f; // Body sinks slowly BodyRB.gravityScale = 0.3f; } }
Lastly is that little callback from the jump animation--here is the handler it calls.
// Animation event void JumpEvent() { Jump = true; }
While I certainly left out a few details, that sums up the efforts to get the crab character in the game. Of course, the finished crabs must still be placed into the scenes. After that, you get something like this: