The last time, I talked about the vision for The Pilgrimage and broke its development down into three phases. Currently I’m at the beginning of the initial adventure game prototype that will cover most of the main game loop and game mechanics. In the last few weeks I have looked at four things:
Last time, the player could move along the x-axis by moving left and right along the floor and on the y-axis by using doors to move between floors. However, despite the game being 2D, I was interested in letting the player and NPCs move along the z-axis at different points so there will be multiple xy-planes to move in. It makes the world feel a little less flat. The example I wanted to use for this for the prototype is a cell that the player will start off in, though in the prototype at the moment, the player starts outside of the cell and can unlocked and move into it.
The basic design for travelling on the z-axis is as follows:
In order to make sure that objects that move between the z-axis are in front of or behind the right objects, I am using sorting layers. The reason why I can’t use z values to determine sorting order is due to the player having priority over every object at a particular z value. Unfortunately the priority for rendering in 2D is sorting layer > order in layer > z value. At the moment, there are currently two sorting layers: Default
and Back
. When there are more than two layers, it will probably be easier to label them according to their z value e.g. Layer 0, Layer 1, Layer 2 etc. so it will be easy to switch between them.
Initially I was using Collider2D
and Rigidbody2D
in Unity for characters and walls. However, this lead to a couple of problems:
Therefore, I found that I needed to switch from 2D physics to Unity’s regular 3D colliders that are very thin. The z-axis gateways, however, have to be wider on the z-axis so that characters will collide for both the foreground and background z values.
For picking up items, 2D colliders still work well with mouse clicks so I will be keeping them for now.
I did not want to go into too much detail for the animations, though I found as I started working on them, I wanted to make them at least passable. I wanted to at least communicate “this one is the player who is a prisoner, and these are the guards.” I think in the end, the guards’ animations came across quite while the player’s not so much. This is due to two main reasons. The first is that the walk cycle of the player was done with eight total frames rather than the twelve in the guards’ walk cycle. The second reason is that I used Krita for creating the player’s animations but Libresprite for the guards’ animations. For the moment though, they’re a good enough for developing this prototype.
I found that 48x48 sprite size was large enough to be detailed enough without being overwhelmed by needing too much detail at larger sizes. The characters ended up being about 20x48 pixels when including arms. In terms of the style, particularly of the heads, although this might not be the final design, I liked the style that Pixel Chest is doing for their characters in Sons of Valhalla. I found that although they don’t use real life human proportions, they’re not “chibi” or “cartoony” either at about 5 to 5.5 heads tall. The main stylistic element I borrowed from Sons of Valhalla were the eyes. They’re just one or two pixels wide but the effect works very well. I found trying to make the eyes any bigger made the heads look too cartoony. In a way, they remind me of the way the eyes are done for characters in Darkest Dungeon where they appear permanently in shadow.
The image above shows few tests of heads. Most of them follow the style that I’m currently going for. The second column shows the different styles that I have attempted. The slightly narrower heads with single pixel eyes in the top right will probably work for any child characters in the game. Female characters are distinguished mainly by their smaller foreheads and generally narrower heads.
For the guards’ idle animation, I liked playing around with the shade of the pixels when he breathes in and the little bounce his torso gives when he breathes out.
I had been familar with Krita before and knew it was possible to create animations, however I found creating the animations and switching between frames to be quite awkward. There was also no way to preview the animations and Krita is quite awkward for creating single frame views of pixel art. Therefore, after creating the player animations, I tried creating the guard animations in Libresprite, the free fork of Asperite. As well as working natively with pixel art, I also liked the fact that there is a separate preview window that allows you to preview animations. You can also easily include multiple animations in the same file using tags and export them into separate sprite sheets. At the moment, I’ve found having a tag for the base character and layers for the head and torso, each leg and each arm to work quite well.
Now I needed character’s animations to run inside Unity. Fortunately, Unity provides an Animation Controller that has a state machine to support animations. I found that I needed three different parameters to switch between animations:
Originally I had three states that would correspond to each of these animations, though I quickly found I would need four states. The idle state would be split into idle facing left and idle facing right. Therefore if a character is walking to the left and then stops moving the idle animation facing left would play and the idle animation facing right would play when stopping after walking to the right. However, only a single idle parameter is needed. The animation controller with the states is shown below.
If multiple characters have the same state machines for controlling animations, like the player and the guards currently have, Unity provides an Animation Override Controller that allows you to override the animations being used for a particular Animation Controller. This reduces quite a lot of duplication.
While working on the animations and editing them, I found importing sprite animations into Unity to be very awkward. You need to make sure the sprite mode was multiple, then you would have to slice the sprites and then drag the spritesheet into the scene. This would generate extra objects including an Animation Controller and a Material as well as the animation. If you want to update an animation and add or remove frames, you would have to repeat this process. Therefore I decided to write a few editor scripts in order.
First I created an editor script that would allow me to create an animation with a much tidier interface. It exists as a menu item for any sprites that are set to multiple mode. This creates a window where I can set the size of each sprite (in the case of the characters this is 48x48 pixels), the frame rate and whether the animation will loop or not.
The spritesheet is sliced properly and then an animation clip is created with the same name as the spritesheet in title case and placed in the Animations directory. Sprites are sliced using the InternalSpriteUtility
. The code to slice the spritesheet is shown below:
Rect[] rects = InternalSpriteUtility.GenerateGridSpriteRectangles(texture, Vector2.zero, frameDimensions, Vector2.zero, false);
List<SpriteMetaData> metas = new List<SpriteMetaData>();
int rectNum = 0;
foreach (Rect rect in rects)
{
SpriteMetaData meta = new SpriteMetaData();
meta.rect = rect;
meta.name = filenameNoExtension + "_" + rectNum++;
metas.Add(meta);
}
// Only save if the number of sprites changes to avoid infinite reloads.
if (metas.ToArray().Length != importer.spritesheet.Length)
{
importer.spritesheet = metas.ToArray();
EditorUtility.SetDirty(importer);
importer.SaveAndReimport();
AssetDatabase.Refresh();
}
This triggers a reimport of the spritesheet with sprites and so I added an OnPostprocessSprites
callback that will update the animation clip if one currently exists:
private void OnPostprocessSprites(Texture2D texture, Sprite[] sprites)
{
var animationPath = SpriteAnimationGenerator.GetAnimationPath(assetPath);
// Recreate animation if it exists
if (File.Exists(animationPath))
{
var importer = (TextureImporter)TextureImporter.GetAtPath(assetPath);
var animationClip = AssetDatabase.LoadAssetAtPath<AnimationClip>(animationPath);
// Delay needed so texture can be loaded
EditorApplication.delayCall += () => { SpriteAnimationGenerator.UpdateAnimationClip(animationClip, assetPath, animationPath); };
}
}
The EditorApplication.delayCall
is important here as if the UpdateAnimationClip
is called without it, the sprites are not initialised at this point in time so the animation clip will be empty. However, putting it as part of the delayCall
means that it will run after all the inspectors update, so the sprites will be initialised. The main part of UpdateAnimationClip
looks like the following:
public static void UpdateAnimationClip(AnimationClip animationClip, string assetPath, string animationPath)
{
var texture = AssetDatabase.LoadAssetAtPath<Texture2D>(assetPath);
var importer = (TextureImporter)TextureImporter.GetAtPath(assetPath);
EditorCurveBinding spriteBinding = new EditorCurveBinding();
spriteBinding.type = typeof(SpriteRenderer);
spriteBinding.path = "";
spriteBinding.propertyName = "m_Sprite";
var sprites = AssetDatabase.LoadAllAssetRepresentationsAtPath(assetPath);
var keyFrames = importer.spritesheet.Select((s, index) =>
{
var objectReferenceKeyFrame = new ObjectReferenceKeyframe();
objectReferenceKeyFrame.time = index / animationClip.frameRate;
objectReferenceKeyFrame.value = sprites[index];
return objectReferenceKeyFrame;
}).ToArray();
AnimationUtility.SetObjectReferenceCurve(animationClip, spriteBinding, keyFrames);
var curveBinding = AnimationUtility.GetObjectReferenceCurveBindings(animationClip)[0];
var curve = AnimationUtility.GetObjectReferenceCurve(animationClip, curveBinding);
// Update Animation Controllers and Animation Override Controllers that use the animation
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
Finally, to support automatically updating the animation when the spritesheet is edited, I added a OnPostprocessTexture
that will slice the spritesheet if the animation clip currently exists and the spritesheet is edited.
void OnPostprocessTexture(Texture2D texture)
{
var animationPath = SpriteAnimationGenerator.GetAnimationPath(assetPath);
// Recreate animation if it exists
if (File.Exists(animationPath))
{
AssetDatabase.SaveAssets();
var importer = (TextureImporter)TextureImporter.GetAtPath(assetPath);
var animationClip = AssetDatabase.LoadAssetAtPath<AnimationClip>(animationPath);
// Should all be the same size so just get first one
var firstSpriteRect = importer.spritesheet[0];
// Delay needed so texture can be loaded
EditorApplication.delayCall += () => { SpriteAnimationGenerator.SliceSpritesheet(assetPath, new Vector2(firstSpriteRect.rect.width, firstSpriteRect.rect.height)); };
}
}
It was quite fun to delve into editor scripting in Unity for the first time. I had been aware of it existing when making games in Unity before but I thought it would be more useful for larger projects but something like this animation creation and automatic animation updater would be useful even for small projects that use pixel animations.
For sound, I wanted to have some basic sounds to represent object interaction. I thought at first about actually recording sounds, though I just wanted something rough to test the Unity engine rather than getting the sounds exactly right. Therefore I used rFXGen to generate sounds for five main events:
It made sense to have events 3 and 4 to use the same sound. Since these are player-centric, I made them 2D and stereo so the player’s position will not affect the volume of the sounds. I ended up creating an AudioManager
class for a single AudioSource
. At the moment, it’s difficult to have sounds overlap so it’s really just a thin wrapper. When more sounds are added including ambient, the logic to manage the sounds will probably be more involved to avoid sounds clashing.
In terms of sound, I think I’ll be next experimenting with 3D sounds for player and guard footsteps, so that the guards get louder as they get closer to the player.
As you may have noticed from the recordings of the game, I’ve also added custom cursor icons. I created a CursorManager
class that then makes calls to Cursor.SetCursor
just to have a central location to change the cursor icon.
Next time, I hope to look at pathfinding for the guards as well as a sneak system so our character can avoid being caught by the guards and escape from the dungeon. Then the player will actually have a game over state.