A new design for the ‘bot’ enemies, and a new fire-jet projectile.
- Redesigned the ‘bot’ enemies to differentiate them from the player
- Added new enemies and drew / hooked up sprite art for some existing temp-box enemies
- Added code to let platforming actors crawl along surfaces (floor to wall, wall to ceiling, etc.)
- Added support for conveyor force on horizontal and vertical poles
- Experimented with pulling the shot back towards the player while in a catching stance
- Updated shader and palette code with some new features
A comparison of the old and new bot sprite design.
I added bots to the project when I realized that fighting only the pink mutant guys would get monotonous. The original enemy bots were based on the player’s sprite, I guess the implication being that they are part of the same robot classification. I removed their mouth-lines and added sunglasses to give them a more uniform, non-emoting look. Having a similar composition, though, one could still mistake them at a glance for the player. They were also difficult to hit while moving, owing to their small size and the nature of the player’s attack.
In the new design, the bodies are colored rectangles (or maybe cylinders) with sunglasses placed somewhere in their upper half. They have metallic, tube-like limbs, which don’t necessarily have to look like human arms or legs. To compensate for the lack of different head-shapes, I’ve added hats to some characters and changed up their glasses a bit.
I do prefer the earlier designs, but they just aren’t a good fit in practice. Maybe they could make appearances as NPCs, if the game goes down that route in the future.
I grabbed some test code that attaches actors to surfaces and poles and made them generic services that build on the platforming service.
Perimeter crawling (or just ‘hugging’) allows an actor to hug floors, walls and ceilings. It works by constantly pushing the actor into the connecting surface while monitoring for collisions with solids on the connecting side.
Inner turns are easy, but outer turns require some additional state for the transition. The ‘push into the surface’ force needs to be disabled briefly so that the actor gets a chance to move around the corner.
The movement will break on some slope configurations, and probably on surfaces that move as well.
Left: actors crawl along the top and bottom sides of a square as a strong force keeps them against the surface. Right: As the actors move around the corner, the ‘hugging’ force is temporarily disabled so that they get a chance to migrate to the vertical surfaces.
Why specifically does the strong pushing force need to be disabled around corners? It’s because in the platforming / physics code, actors always move horizontally first, position-correct against horizontal obstacles, then move vertically and position-correct against vertical obstacles. Because of this, if the pushing force were still active, it would prevent actors on horizontal surfaces (floors and ceilings) from transitioning to vertical surfaces.
Attaching Actors To Poles
This is separate from (and much simpler than) the player’s pole-hanging code, and is intended for things that are “on-rails.” Attachment can be configured for horizontal or vertical poles, but not both simultaneously. The actor configures a ‘connection line’ which runs parallel to compatible poles and is used to determine connections.
When an actor is attached, gravity is disabled, its velocity on the perpendicular axis is zeroed out, and it is slowly nudged along the other axis such that its connecting line and the pole are aligned. If the pole disappears or the actor “runs off the rails”, gravity is re-enabled.
Currently, this service only works with tilemapped poles. There might be a future need to support attachment to actor-based poles as well, but the implementation could be tricky and I don’t want to work on that unless it’s really necessary. In those cases, the actor pinning system might be sufficient.
Top: How they’re supposed to look. Bottom: some kind of precision issue causing incorrect colors to be selected from the palette in the shader.
I realized that there were still issues with my indexed palette shader with gamma correct rendering enabled. Offsetting palette index numbers to high ranges (close to 255) resulted in the wrong colors being selected, but not in all cases. I didn’t notice there was a problem until I started reserving higher indexes for new purposes. I believe the issue is that even though unGammaCorrectColorPrecise() is more accurate than the faster default variant, it still has some amount of rounding error.
I came to this solution:
- Set the ‘linear’ flag on the atlas texture. This cuts out the implicit color space conversions on the red channel. (It has no effect when gamma-correct rendering is disabled.)
- When using gamma-correct rendering, pre-correct the non-palettized art during startup. Affected pixels may have color accuracy issues, but it’s less noticeable than the palette index errors I was facing.
Color Anchoring, Do-Not-Palettize, Auxiliary Alpha
I worked on a flamethrower enemy this month, and found that I couldn’t palette-swap him without messing up colors that are used both for him and the fireball projectiles. I was able to squeeze in two possible workarounds: color anchoring, where designated pixels in the source art are prohibited from being affected by palette offsetting, and “do-not-palettize”, where the pixel is rendered as plain RGB.
The red channel provides a pixel’s base palette offset. If blue is greater than 0.5, that signals to the shader that additional offsetting is to be ignored for this pixel. If the alpha channel is <= 0.9, then the pixel is rendered with the existing RGB values and no palette, and an alpha of 1.0. To make up for loss of alpha on palettized pixels, the green channel is holds an auxiliary alpha value.
The main casualty is that non-palettized pixels effectively can’t have an alpha value. I’m happy with this arrangement so far, though it’s heavily constrained by the interface I use to assign metadata to sprite art. The top-left 32×16 pixels of every spritesheet are reserved for constructing the palette and specifying the above options. It’s not ideal, but I’d probably need to migrate to a more powerful / scriptable image editor before attempting to make improvements.
As a follow-up to last month’s work on routine-lists: I now have two actors making use of the routine-list state system to control behavior (the big guys at the start of this month’s footage, if you’re curious.) To recap, routines are basically an array of states that actors can advance through or jump around in via labels.
Many actors use routines as a means of implementing an initial sleep / dormant state. However, they typically just advance to a single per-tick callback function that implements all per-type behavior. So while they are part of the routine-list ecosystem, they aren’t really using it.
Anyways, these two particular actor-types weren’t in dire need of routine-list states. An if-elseif-else block is reasonable, up to about five or six states totalling fewer than 500 lines, in my opinion. It’s still nice to have a working example, though, and I caught and fixed a few bugs along the way.
A decent month overall. I put together some forking / merging level route ideas, but didn’t actually commit to any of it yet. The test map shown in this month’s footage started as an attempt to make the game’s first real level. I backed out of it pretty quickly. I also considered sub-weapons and power-ups, though nothing besides the shot pull-back is currently implemented or active.
I have a growing list of bugs to deal with and a stack of level + enemy sketches, and I’ll probably be dealing with both in August.
Codebase Issue Tickets: 45 (+0)*
Total LOC: 112885 (+6544)**
Core LOC: 36515 (+335)
Estimated Play-through Time: 6:31 (+~30s)***
* I don’t think I’ve looked at these at all this month.
** Most of this increase is a result of plugging in new animation definitions.
*** Not including the July test map captured in the monthly footage, which isn’t really a level in the sense that it has no checkpoints, no boss, and can’t be completed. So besides some gameplay changes, virtually nothing is different about the prototype levels this month. The additional 30 seconds can be attributed to me having a bad run.