Okay, you've probably caught on now: I have been too busy actually working on the game to give super detailed updates on this blog. Hopefully you've enjoyed the development videos I've been posting, because for the time being that's what I will continue to post.
Here's the last few video I've put together that show my progress:
1. Super Dash
I'm testing out an ability which lets the player go really fast.
2. Step Triggers
These are switches that you activate by stepping on a switch on the ground.
3. Delay Triggers
A special entity that simply delays a trigger and then relays it to the next one. Good for sequenced events.
4. Idle Animation
Allison put together a nice idle animation for the main character.
5. 3-hit combat demos
Playing with a simple 3-hit combo system.
...same thing, experimenting with variable knockback forces. Also we've started prototyping a new environment.
Lately I've been moving. Not much time for blog posts, but I've still got some work to show! More videos!
1. First up: falling down transitions. I wanted to put a little twist on the fade out / fade in 'falling' transition. This was the first draft:
2. After some feedback, here's what I ended up with as a final draft. Way better.
3. Just for fun, I added some bugs.
4. Some people suggested that the bugs shouldn't be squished when you're holding still. Someone else took it further and said that they should climb up you. So I did that.
Although I haven't been updating this blog as regularly as before, it's only because I'm too busy with the actual game programming it self. I'd like to share a few videos that I've created over the past month or so.
Just a fun little demo of dynamic floor tile scripts.
2. Persistence
Here's a video where I demonstrate values persisting between level loads. When the bridge spawns, the player can leave the screen and come back and it will still be there.
3. Dashing and Torches
In this video, I've swapped out the old (temporary) character art for a new (also temporary) sprite that represents the actual dimensions of the character. We chose a large head so that the player can fit in a more square-like area because we want the visual appearance to be close to the collision box, so the player can gauge whether or not something is going to collide with the player. This video also features dashing and dash-turning. And some fireballs. And a shield.
4. Switches
Implemented some switches. You can also see the design of the main character taking shape. One of the switch types is a basic toggle, the other is timer-based.
5. Fog
A cool fog effect.
6. Doors
A set of switches that controls the opening and closing of a door.
7. Section entry events / group destroy triggers.
This final video shows how something can occur when a room is entered (the door is closed) and how destroying a group of tagged entities (3 blobs, in this case) can trigger something else (door opening, wall torches being lit, fog fading away.
I've actually been spending a considerable amount of time lately working with the artist on the project, Allison. We've been slowly nailing the style down for a number of things and anticipate future art development to become more and more rapid as we settle into a groove. Refer to the following screenshot:
Anyway, here's what's new since last update:
1. Improved cave tiles. I'm not going to write too much here, but things are looking better now. We're still going to have another pass at them later.
2. Parallax tiles underground. You can't see it in motion from the screenshot, but the underground spikes are parallaxed, meaning they give the illusion of depth by scrolling at a slower rate than the foreground.
3. Player proportions. You're seeing a temporary placeholder sprite as we test the final dimensions of the character. The goal here was to keep the player's collision model at exactly 1x1 tile (that's 32x32 in-game units--actually, we're using 30x30 so that the player can comfortably fit between two tiles). The older character also had these dimensions, but was much taller. It's pretty important to me that the player can accurately and easily gauge, visually, the collision model of the player (so that they know when they're going to get hit by an enemy or a projectile), so the closer the character is to a 32x32 box, the better. Obviously we can't make the graphic square, though, because that would look silly.
4. Better stylistic lighting. Since lights can be any texture we want, we chose to paint a nice cartoony glow instead of the straight gradient of the old textures.
It's been a couple weeks since my last substantial blog post, so I'd like to share what I've been working on during the last week: a new monster I've tentatively dubbed the Lurker, but I plan on giving it a more original name in the future. Note that enemy development isn't normally going to take a week per enemy--I was busy with some personal things. On top of that, this enemy is more complex than my first enemy, the Blob, and required a few engine infrastructure changes to implement. I'll go over those in detail, too!
I wanted to add a new enemy which will appear in the first area of the game, Gloomstone Cave. The idea is that you'll be facing this enemy in a few spots, but initially you won't have a permanent weapon to defeat it with. So, you must come up with more creative ways to pass the first few of them, whether it be total avoidance or using an alternate means of damaging them. Later on, when you have a weapon, they won't be quite as menacing, but the initial encounters should definitely impart fear into the player.
The Lurker waits underground in a certain spot and emerges if the player gets too close, bursting out of a hole in the ground, blowing debris everywhere. Here is mytemp MSPaint art that I used as a placeholder for the enemy sprites so I could start working on the programming immediately:
This is why I'm working with an artist. Body/front/side/back views.
I took a series of videos as I developed the Lurker, so you can see how it's evolved. At the end, I'll share the source code so you can see what the latest programming model against my engine looks like!
Part 1: The Lurker Emerges
This video shows the lurker enemy popping out and swaying in a sinusoidal motion. It doesn't do anything interactive yet.
Part 2: Moving around, basic attack
The lurker moves around to avoid the player, and randomly attacks him if he gets close. Then it gets stuck in the ground, and that's it.
Part 3: Real art, attacking, taking damage
At this point, Allison, the artist, started working on the graphics. The initial design was a cool and creepy monster with a sideways mouth, with plans to animate it chomping the player. You can see the down-facing graphic in this video. Also, I made it so you can hit the head with weapons, and a very basic 'death' animation for the enemy.
Part 4: Better art, cool hit colors, refined movement. Better death animation.
I make various (subtle) changes to the movement behavior of the enemy. Allisonchanged the head design of the monster to a more traditional shape, drew the other angles of the head, and animated the arms on the body segments. So now it's like a creepy dragon caterpillar. I added a more satisfying death explosion animation.
Part 5: Bigger, badder, more explosive
We increased the size of monster. A way better death animation with a bunch of explosions for the head, and a prettier hue shift during hits.
Part 6: Snapping animations, cave shards. Job's done!
I put the monster in its natural habitat, the cave. Allison added animation frames for the head so I can control its mouth--when it pulls back, it opens its mouth, and clamps shut as it attacks. Also, it opens its mouth in pain when I hit it. Allison also made better looking shards that explode out of the ground when the lurker emerges, and I added a rumbling sound (and a temporary hit sound) for the emerging stage. Check out the final product!
Source Code
Disclaimer: I don't typically comment code except for explanations of 'why' (not 'how'--see this article) but I've marked this example up extensively for clarity. I also don't typically have classes that are anywhere near this large; my average class size is quite small, generally following the Single Responsibility Principle. I would probably consider breaking each enemy state into its own class. But, for the purposes of this example, I'm keeping this all in one class (for now).
Anyway, the full source code can be found on this pastebin, right here.
That's all for now. Next I'm going to be fleshing out a bit more of the first cave area.
Doing things a bit out of order this week: a video first! Keep in mind, this is a tech video about framerates:
The first part of the video shows the game running at a rate of 20 physical frames per second. That is, the game updates its world model 20 times per second. Although the real rate will probably be 60 updates per second (as is common), the very small limit of 20 serves to illustrate the new feature better.
In this first part of the video, the game loop is running rather naively. Although the game only pushing out 20 physical frames per second, it's actually rendering hundreds of frames per second (on my 5 year old desktop, it's something like 750-850 FPS for the demo scene). But the game is still choppy, because it's still only updating the world model 20 times per second! With 750 rendered frames per second, it's actually rendering the same frame over and over about 37 times before the next physical update. What a waste!
The second part of the video is much smoother. You might think that I pumped up the physical frames per second to a much more reasonable number, like 60 or 200 (or maybe not--I think YouTube caps the framerate at something fairly low). But I didn't--the game is actually still running at 20 frames a second. Except now, I've introduced frame interpolation.
Frame interpolation is exactly what it sounds like--in between any two frames (namely, the last update and the update before that one), I am able to calculate an interpolated position. For example, let's say a monster was at position (30, 40) at frame #53, and at position (40, 50) at frame #54. The sequence of events is as follows:
1. The game update loop begins frame #54 (that is, the physical world updates--this has nothing to do with rendering!)
2. All entities (actually, just entities that need to be updated--more on this later) have their transformation stamped/saved. This occurs before anything else, so the stamped values represent the position, rotation, scale, etc. of the entity as it was at the end of frame #53.
3. The update loop runs as normal for frame #54. Entities might move around via controllers, scripts, collisions, or whatever.
4. At this point, the monster has a stamped position of (30, 40) and a current position of (40, 50).
Now that the update is complete, the rendering process begins. Let's say we're running at 20 physical frames per second--that's a whopping 50 milliseconds before the next update must occur, and we'll use the time to draw as many frames as possible. Let's say that rendering a frame takes 5 milliseconds.
1. The game loop needs to accumulate 50 ms before the next physical update. We set the counter to 0.
2. The interpolation value is calculated as 0/50, or 0
3. The game renders with an interpolation value of 0, which means the monster is drawn at (30, 40).
4. The counter is incremented to 5, so the interpolation value is now 5/50, or 0.1
5. The game renders with an interpolation value of 0.1, which means the monster is drawn at (31, 41).
6. This process continues until the interpolation value exceeds 1, at which point the monster will be drawn near (40, 50) and the next update will occur.
This exact process is what you're seeing in the second part of the video. Of course, it's still a little messed up--you can see a noticeable lag in when the sword swings and the enemies are hit. No matter how smooth the rendering is, a game updated at 20 frames per second is going to play weird and have lag.
Other than the points below, that's all for this week. I've also implemented a shield, but I'd rather wait until I have real graphics instead of the hideous temp art before showing off that one. Until next week!
Some points of interest / objections:
Why not just run at a variable timestep, updating as often as possible?
A lot of games do this. Sometimes it's a good solution, sometimes it isn't; I've read both sides of the argument. In the end, I've chosen for a fixed timestep because it yields repeatable and reliable physics results. Variable timesteps can lead to quirky behavior, such as giving those with a lower (or higher) framerate an 'advantage' in certain situations.
If the interpolation is linear and the object is moving non-linearly (e.g. sinusoidal or accelerated movement), won't that look weird?
No, not really. It's almost totally unnoticeable at 60 FPS.
So why bother at all?
On some machines, I was getting a very noticeable stutter when the timestep was capped at 60 FPS. The very popular "Fix Your Timestep" article illustrates why this happens (near the end). This stutter was a result of 'temporal aliasing.'