Two Generic Princesses

by Make More Engines

Aerial View of the George Mason Fairfax Campus PHOTOBOMBED BY PRINCESSES


Tileset


On-hit effects


Enemy with stun effect

Technical Design

This is a breakdown of all the triumphs we enjoyed and the pain we endured in creating a game from the bottom up.

The Engine

2GP's engine was built from scratch, using only the limited 2D rendering libraries of SDL. Great efficiency is gained through the use of tiles to divide the world. Each 16px by 16px square you see on the ground when you play the game is a single tile. Tiles do more than just hold information about what to draw on the floor at a specific location, though.

Each tile contains a list of every entity that is currently occupying it. Each entity also keeps a list of every tile that it is occupying. After entities move, these lists are updated. The lists are used to make both rendering and collision detection efficient and easy. Having both lists allows us to create entities of any size and have them interact with the game properly with no additional coding. More importantly, hundreds of enemies can exist in a level without a noticeable performance hit, even if a large number of them are on the current screen.

Rendering Scheme

The rendering engine is very simple. Two variables keep track of the x and y world coordinates of the current camera view. A camera width and height determine how much of the world can be seen (we ended up keeping static width and heights to avoid scaling our sprites, but the game is capable of dynamic zooming). With the box formed by the camera view, it costs nothing to determine which tiles are visible on the screen. For each tile, the tiles graphic is first drawn. Next, the tile adds a reference to each entity that is occupying it to a set to be drawn later. The nature of the set prevents multiple entities from being rendered more than once. Then, each particle effect that is attached to the entity is added to the set. Once this has been accomplished for each tile, the entities are then sorted by Y values, so that they will overlap eachother in the correct order when drawn. Finally, entities and their particles are rendered to the screen.

Collision Detection

Collision detection between entities is simple. For each entity on the screen, we look at each tile it occupies. From those tiles, we generate a list of unique possible collisions with the entity we're looking at. Collisions are all based on axis-aligned rectangular boxes, so they are extremely quick to compute.

Collision detection between entities and the world is a bit different. Since the world is already divided into tiles which represent the terrain, it made sense to create a mask of the same size filled with values that are either zero (pathing allowed) or non-zero (pathing not allowed). When entities move, the leading edge of their collision box is tested against the collision mask. If a collision occurs, the entity is pushed backwards so that its final location is on the edge of the tile it collided with.

Map Creation

We used the Tiled Map Editor to design levels from open source tile sets. The program allows one to paint a level from a palette of tile images and then converts that image to a text file of numbers representing the type of tile at each grid location. We wrote a parser to load the level data into our game.

Gameplay

Enemy AI

The general enemy template allows for three general behaviors: aggressive, passive, and none. The motivation behind this is twofold: to save computaion and to make the enemies appear as though they have a field of vision. Depending on how close an enemy is to a princess, he will exhibit one of these behaviors. At close distances, the aggressive behavior is called. When the enemy is within the player's sight but not close enough to be aggressive, then the passive behavior is called (this usually results in the enemy standing still or wandering slowly). When enemies are too far away from the players to affect the game, we skip AI calculations for those enemies and move on to another task.

Implementing an individual state machine as well as the larger distance-based state machine for each enemy was an exercise in creativity. The enemies can choose from these actions: selecting a target, attacking their target, turning around, and moving. The enemies have cooldowns for switching between behaviors so that actions such as turning or attacking would not be called in each frame.

Attacks

Attacks knock an entity back based on the mass of the entity and speed of the attack. Status effects are indicated with a bouncing dot over the entity's head, and entities that are hit will show a small explosion that depends on the direction from which the attack came. Particles for status effects and on-hite effects each have their own animation and duration.

The magical princess's attacks are combinations of three elements, and each spell produces a unique effect. The three-element sequence is parsed and matched to a pattern, e.g., AAB, ABC, or ABA, and each element in the sequence is assigned a multiplier based on this pattern. Damage in each magic attack is stored in a map containing an entry for each damage type, and the status effects an attack will cause are stored in another map of status effect durations. The entries of the damage and status maps are then multiplied by corresponding entries in the multiplier map to calculate the final effect on the target.
The damage and status effects for a spell consisting of three fire elements are slightly reduced considering the lack of strategy involved in casting that sequence. The spell would have 100% of the default fire damage and status effect duration. A sequence such as fire-earth-fire would carry the same effects as a pure fire spell, but would then add earth effects: 100% * fire effects + 25% * earth effects. That ABA pattern also produces a special, singular effect in which the element in the middle ignores any immunities in the target. So one could hit a Britney Spears enemy with this attack and deal the full damage and status effects, whereas fire-fire-earth would only have the typical 100% fire effect on that particular enemy.