Postmortem of Soulwielder

Published August 20, 2015
Advertisement
This is part 2 of the postmortem for the Week of Awesome game I made called Soulwielder.
This is about the development of the game; the first part was about the design of the game.

There were several things that daunted the creation of Soulwielder, notably the difficulty I had with making art, the level-creating tool I made, and the physics/collisions system I made.

Art
I started out trying to make the player with a higher resolution than I ended up with, and about 3-4 heads tall. He didn't look awful in the still version, but oh boy, when I started trying to animate him...

I technically had never even dealt with animation, especially not 2D animation. I don't know why I was so confident I could make 5 sprites with animations considering this. The walk animation looked about twice as bad as you'd expect someone's first walk animation to look.

After about five different attempts at making his walk look good, I went to bed; before I fell asleep, I thought I ought to take look at the art of Cave Story for help on the animation.

This is where I got most of my inspiration for how the art of the sprites ended up. I popped the game open and looked closely at the player and his walk animation.
The player was far from 4 heads tall in Cave Story, and his legs were actually so hard to see moving that I had to beat the first cave to get to an area where there was less rock and dirt poking up getting in the way of his feet. The most noticeable part of the animation was his bobbing up and down, not the legs at all. It was simple, but readable, and it worked very well for that style of art. When I adamantly played through Cave Story twice in a row a while ago, I didn't question how my character walked on legs that seemingly start 90% of the way down from his head, or look intently for his feet to see if the motions they were making looked like those you would actually take to walk. I just saw him bob up and down and thought he was walking.

I also noticed that a similar method I planned on using for Soulwielder was being used in CaveStory (I think, don't quote me on that): sprites were made in a small size, then scaled up in a pixel-perfect way to make them bigger and more readable, but maintain their sharp, pixelated, blocky style.
If you double the size of the sprites (I think this is what is done in Cave Story), then every pixel is made of a square of 4 pixels of the same color, but you don't get to use those extra pixels; you stick to the constraints of the pixels you have at the lowest size, never changing the scaled-up version of the file.

For me, the size was 16x16, scaled up 2x to 32x32. I liked the player best this way.

Ultimately, he ended up less than 2 heads tall (10 pixels of head and helmet, 6 pixels of body and legs).
His walk animation was a single frame, where he lowers down a bit and his one-pixel-tall, two-pixels-wide feet go out at his sides and become two pixels tall and one wide.

"This looks stupid," I believe I remember thinking before seeing it play. "He doesn't look like he's walking at all, it just looks like he's spreading his legs out and shrinking".
But I put it in Unity and gave it animation, and it read well and it looked nice and stylized. I liked it a lot after seeing it play.

Here he is, sized up to 8x for easier viewing:

Player_run_8x.png

Now, I'm not saying the art of Soulwielder is objectively great...I suppose I'm just saying it is relatively great compared to my first walk animations...

Once I got the player's running, looking-up, looking-down-while-midair, etc. animations in-game and working properly with the movement and aiming controls, I felt pretty glad with how he turned out.

Ultimately, I ended up making the humans and the 'goblin' in a similar way, with similar walk animations.

Everything else is also made in some format similar to 16x16 and then sized up twice, from the flowers to the grass-and-dirt tileset and the bricks used to make the humans' houses.

The buildings don't look great; I would've liked to have made a roof tile tile to add at the top of them, but time didn't really permit it and I knew I'd probably need a good bit of time to get it right. So I just made the buildings out of one seamless square tile found from SpiderDave on OpenGameArt, somewhere within the depths of the many tiles he posted here.

Unfortunately, background art didn't make it in, either; I just threw a vertical gradient into the background because time was running out and at least the gradient looked a little better than using a single, flat background color with the camera setting.

Here's a little screenshot where almost all of the environmental assets can be seen, as well as some of the goblins and the player:

EnvironmentScreenshot.png

The Environment Editing Tool
Now we enter the programming-related issues I faced.

I thought I'd be making a platformer with low-res graphics for the Week of Awesome, because I found nice-looking resources for the environment on OpenGameArt.org, and I wanted to make a game like that. So I figured I could prepare for it well by creating a little Unity script that could place down sprites to make the game's levels.

It was one day before the competition that I started on this.
I wanted it to let you select a sprite in the Project view and place it in the Scene view by clicking. I didn't want it to just throw down a bunch of sprites and organize them as children of the GameObject with the script, though.

I wanted it to be able to 'bake' the sprites into 1024x1024 textures and arrange them on a grid covering the whole map. This would be more efficient, I figured; when I inevitably got carried away with the placing of flowers later, I wouldn't have 90 different sprites in one area, each with their own GameObject, when I could have one with all of those sprites built into it.

I'd dealt with this sort of code in a game I was making before the compo, called Realm Crawler, where I had dungeon generation that made the dungeons by drawing tiling textures onto a grid of textures, so that each e.g. 8x8 or 16x16 tile of the dungeon didn't have to be a separate sprite, which would be a massive number of GameObjects and sprites.

It involved turning a world position of a pixel into its local position in a texture, based on where that texture was.

The creation of this tool took longer than I'd anticipated, of course; I didn't manage to whip it up in one day, although I got a lot of planning and thought through the most difficult parts of the code in that day.

So I was making this tool during the competition, which was unfortunate and all. It took a few days to get it right, I think (it's a bit of a fuzz of confusion and mild panic), but I didn't devote that time entirely to it; I also planned and designed during it.

The tool was a bit hackneyed and annoying to use, but I'd expected as much.

Rather than trying to make it as an editor script, I decided to make it a normal script, which means it only operates in-game. You put down a GameObject, give it the environment-editing tool script, and then go into Play mode.

You put down your sprites by selecting them in the Project view and clicking to place them. For now, they're just instantiated GameObjects parented to the GameObject that has the script. Before you exit play mode, you copy that GameObject. You then delete the old GameObject and paste the one you copied, to retain the changes you made in Play mode. If you don't remember to copy before you quit play mode, you can kiss goodbye the environment you just made.

I did it this way because it's easier to make a script work in play mode than it is in editor mode; that's just something I've always noticed about extending Unity's editor. It has a lot of gotchas that I've witnessed before, but that I wasn't confident I could avoid. It would just be too much trouble to make a proper editor script in a few days.

To 'bake' the environment, you just pressed a button in the inspector after exiting play mode and pasting the environment. This button was added by a simple inspector script for the environment-editing tool script.

A new GameObject is created in the scene with the textures as children of it, all of which you can set the sorting layers for.

If you wanted to put the baked environment in another scene, you'd have to copy-paste the non-baked version over, then bake it there; because the textures I made for the grid were sort of anomalous little mystery textures generated by code, they only survived in the scene they were made in.

The miscellaneous issues regarding the baking of maps with this tool lasted a few days into the competition, if I remember correctly, then I got it working pretty solidly.

Physics and Collisions
The most panic-inducing difficulty I faced is here: physics and collisions.

There isn't really a way to use Unity's collision system right out of the box to make objects hit things or bounce off of them, but still retain reliable, highly-specific control over how your entities move. You either use physics with rigidbodies so collisions actually stop objects, which entails apply forces to the object, or you use colliders with kinematic rigidbodies and define your own system for how things stop when they hit objects, or bounce off of other objects or whatever you want. Kinematic rigidbody colliders don't collide with static colliders; they only collide with rigidbody colliders.

This means you can't just make an object that isn't subjected to Unity's gravity and physics, but have it actually hit static colliders and bounce back off them, or get affected by them at all.

My solution was to have scripts that handled the movement of objects and their collisions by using Unity's various, very-useful Physics2D methods. Everything that wanted to move the object would give this script its movement every frame, and then this script, in its LateUpdate function (to be sure all other scripts had given the movement they desired), it would apply that movement and clear the movement vector.

I was planning on making a special one for bats, but what with the startling lack of bats in the game, you can probably see that script didn't end up being made.

The only one I made was a GroundedMovement script, for movement of entities subject to gravity.

It would determine if you were midair or grounded by box-casting down from the entity, and setting you to grounded if there was ground there, or midair if there was no ground there.
When you became newly grounded or newly midair, it would call a delegate that other scripts could add functions to, to detect when grounded happened. The player used the landing one to reduce the sideways velocity by half so you wouldn't slide so much after you landed.

It used the Physics2D.BoxCast method to check for walls and ground.

I separated the walls and ground into two different colliders and had the midair BoxCast only detect walls. That was not a very good idea.

The reason I thought this was a good idea was because the box cast was given a direction of movementThisFrame.normalized and a distance of movementThisFrame.magnitude, so I couldn't figure out how better to distinguish between ground and walls. I thought of making it detect if what you hit was ground (and thus you should be grounded) by checking if your movementThisFrame.y was negative (meaning you're falling down), but that wouldn't work if your side hit a wall while falling; you'd become grounded instead of bouncing off.

I thought the only way around this was to just commit to using a completely mad setup for colliders where you use one collider for the ground, or anything you could stand on, and a separate one for the walls, or the things you can't stand on and bounce off of instead.

The midair movement would box cast along your movementThisFrame.direction with movementThisFrame.magnitude, as usual, and only detect wall-type colliders. When you hit a wall collider, you had your X velocity altered to bounce you back based on your 'bounciness' multiplier.

A separate box cast would check beneath you for ground, and make you become grounded if there was ground down there. If I was smart, I probably would've made this only happen if you had negative Y velocity, so you didn't just suddenly become grounded while shooting upwards if you happened to be above a ground collider during that, but I probably didn't do that...

The problem with this was not only that I had to set up the scenes with plenty more colliders, but also how carefully you had to set them up. If the player falls on a wall-type collider, they get stuck on it, as they aren't losing their negative Y velocity, so they keep falling down onto it. There was a way to wiggle out of this with midair movement and dashing, but of course, we didn't want that very disruptive glitch in our game.

If I put the wall collider too high up, the player would land on it instead of the ground (and we already discussed how fun that was). If it was too low, so that the top of it was under the top of the ground collider, bad things could happen.
You would get caught on the top of the wall if you approached it from the side, and then you'd usually just end up getting slowly scooted over until you were inside the platform, eventually taking you off the wall collider until you were falling forever down with no ground to catch you, essentially ending up in the Twilight Zone. I could've avoided this by using a single wall collider the whole width of the cliff/platform, but for some reason I thought using one on each wall was better - and that would have just been making things less bad, not making things function properly.

Another very significant problem was how it reacted when you didn't bounce hard off of walls, but rather, moved into them with low sideways velocity. For example, if I stood at the foot of a wall, jumped, then started moving sideways midair, then I'd get stuck on the wall; the X direction added to the box cast would point the box towards the wall, causing it to hit, and then you'd be placed at the centroid instead of going the full Y distance you had, so you'd get stuck or manage to gradually go more and more up, letting you scale walls just by pressing yourself against them. The enemies would do this; they'd vault past you if you jump over them, then they'd fall off the platform, then just scoot back on up and come after you again.
It was August 15th, very early in the morning (I go to bed at about 7:00 AM and get up after noon the same day, so this was late into my day cycle) when this issue was finally resolved, near the time I went to bed. I thought I was utterly done for, and that my game would have to be a single-platform game where you fought the demons on a flat village with no jumps.

I finally found the solution near 5:00 AM or so.

Rather than box casting along 'movementThisFrame.direction' by 'movementThisFrame.magnitude' distance, I just had to box cast once for the X, and once for the Y. The X box cast would go straight left or right, the Y box cast would go straight up or down, and their distance would be movementThisFrame.x or y.

I'd cast them both against a single, holy layer, called "Environment" or "Solid", the only layer I used for the environmental colliders.

The sideways box cast would cause the X velocity to be 'bounced' if it hit something, otherwise it would move your X by movementThisFrame.x normally.
The vertical box cast would cause you to be grounded if it hit something and the movementThisFrame.y was negative. There was on risk of hitting the side of a collider while falling like this, because the box cast would go directly down, not along the movementThisFrame.direction.
It would cause you to bump your head if your movementThisFrame.y was positive. This was the same as hitting your side against something, but your Y velocity got bounced by it.

This worked wonderfully. It felt quite good to be able to put a collider down and have it act as a ceiling, ground, and walls all at once, without meticulously adjusting two different collider's edges only so things could work sometimes.

This fix was probably the only reason the project ended up coming through; if I hadn't found a solution to this, I probably would've ended up making flat levels so the walls wouldn't be a problem, or if I didn't make such a compromise, I probably would have not submitted the game in time.

I could also set the bounciness to 0 so the player only loses their velocity upon hitting a wall, rather than having it redirected backwards. I believe Cave Story does something like this; you can jump into a wall, rub up against it as your jump carries you higher, get over the top, and then keep going to stand on the top of it. This is a bit more comfortable, but I'm not sure if I like it for Soulwielder yet. The bouncing ended up in the final build, although I may change it later after more playtesting.



Anyway, that's about all I could say about the woes I faced during the development of Soulwielder. Seeing the length of this post, maybe I shouldn't have written "about all I could say"...

It was definitely not a calm, sunny ride, the physics being the major cause of panic and self-directed mutterings of "I'm screwed", but the game ended up OK, I think: the art looks arguably decent and there aren't any major glitches to the general gameplay. I personally like the gameplay, although it's far too easy (as discussed in the first part of the postmortem), and I'm proud of how much work I dumped into this project in only a single week.

I will likely be working on the game more in the future, including updating the environment tool to be less of a hassle to use so I can more easily make levels. I want it to be a little longer and have more enemies and levels.

If you're interested in how that goes, you can follow this journal.
3 likes 1 comments

Comments

Eck


When I adamantly played through Cave Story twice in a row a while ago, I didn't question how my character walked on legs that seemingly start 90% of the way down from his head, or look intently for his feet to see if the motions they were making looked like those you would actually take to walk. I just saw him bob up and down and thought he was walking.

It's funny how much smoke and mirrors goes into making games. Last year when I started getting serious about making games, I started doing analysis of various game effects or mechanics and realized how simplified some things really were. If the game is fun, people's minds will fill in a lot of the gaps for you so don't strive for a perfectly accurate walking animation. Close is good enough. :)

I thought the 2-frame animations were good. Everyone was moving around so fast you can't really see the details of what's going on, but it looked like they were running around panicked which was exactly the effect you were going for. Good job! :)

August 21, 2015 03:08 AM
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Advertisement

Latest Entries

Advertisement