📘 FULL TUTORIAL: How Your
Enemy System Works
Your enemies.c and enemies.h files form a basic enemy manager:
Let’s break everything down clearly and logically.
🧩 1. The Header File (enemies.h)
The header establishes what the rest of the game can access from this enemy system.
✔ 1.1 Including SGDK and
resources
#include <genesis.h>
#include "res_enemies.h"
You import:
✔ 1.2 Header guard
#ifndef ENEMIES_H
#define ENEMIES_H
Prevents double-including this file.
✔ 1.3 Enemy structure
definition
typedef struct Enemy
{
u8 status;
fix32 x;
fix32 y;
fix32 vel_x;
Sprite* sprite;
s8 health;
const SpriteDefinition* graphic;
};
This struct describes one enemy:
🏷 Structure fields
|
Field |
Meaning |
|
u8 status |
0 = inactive, 1 = active |
|
fix32 x / y |
fixed-point position of the enemy |
|
fix32 vel_x |
horizontal movement speed |
|
Sprite* sprite |
pointer to the SGDK sprite instance |
|
s8 health |
hit points |
|
const SpriteDefinition* graphic |
which sprite graphic to use |
This means every enemy has:
✔ 1.4 Declaring global
variables
extern struct Enemy current_enemies[10];
extern Sprite* fly;
extern Sprite* snail;
extern Sprite* bee;
extern Sprite* crab;
extern fix32 enemy_x[4];
extern fix32 enemy_velx[4];
extern fix32 enemy_y[4];
These declarations mean:
The arrays (enemy_x, enemy_velx, enemy_y) seem to be your earlier system—possibly left over—but still declared.
✔ 1.5 Declaring enemy
functions
extern void createEnemies();
extern void manageEnemies();
Other parts of your game can call these functions to:
📘 2. The Source File (enemies.c)
This is where all variables are actually defined and all logic is implemented.
🐞 2.1 Enemy sprite pointers
Sprite* fly;
Sprite* snail;
Sprite* bee;
Sprite* crab;
These appear leftover—they are not currently used for
anything.
(Your actual sprites live inside current_enemies[i].sprite.)
📍 2.2 Initial arrays of
positions and velocities
fix32 enemy_x[4] = {...};
fix32 enemy_velx[4] = {...};
fix32 enemy_y[4] = {...};
These also seem unused now—replaced by the new Enemy struct system.
👾 2.3 Defining the enemy
pool
struct Enemy current_enemies[10] =
{
{1, FIX32(50), FIX32(80), FIX32(1.5), NULL, 1, &img_fly },
{1, FIX32(30), FIX32(40), FIX32(-0.5),NULL, 1, &img_snail },
{1, FIX32(70), FIX32(120), FIX32(2.0), NULL, 1, &img_bee },
{1, FIX32(90), FIX32(180), FIX32(-0.5),NULL, 1, &img_crab },
// Six inactive "blank" enemies
{0, FIX32(50), FIX32(80), FIX32(1.5), NULL, 1, &img_fly },
...
};
✔ This creates a pool of
10 enemies:
Each enemy has:
This is a classic "object pool".
🛠 3. Enemy Creation (createEnemies())
This function:
✔ 3.1 Load palettes
PAL_setPalette(PAL2, img_snail.palette->data, DMA);
PAL_setPalette(PAL1, img_bee.palette->data, DMA);
You assign color palettes for:
Fly and crab use whatever default palette is assigned.
✔ 3.2 Loop through all 10
enemy slots
for (u8 i = 0; i < 10; i++)
{
if (current_enemies[i].status != 0)
{
...
}
}
Only active enemies get sprites.
✔ 3.3 Automatically pick
a palette
u8 pal = (i <= 1) ? PAL2
: (i <= 3) ? PAL1
: PAL0;
This means:
A quick-and-dirty way of sorting palettes.
✔ 3.4 Create the sprite
current_enemies[i].sprite = SPR_addSprite(
current_enemies[i].graphic,
fix32ToInt(current_enemies[i].x),
fix32ToInt(current_enemies[i].y),
TILE_ATTR(pal, FALSE, FALSE, FALSE)
);
This does the real work:
Now the enemies are fully visible on screen.
🚶 4. Enemy Updating (manageEnemies())
This function is meant to be called every frame.
✔ 4.1 Move and update
every active enemy
for (u8 i = 0; i < 10; i++)
{
if (current_enemies[i].status != 0)
{
current_enemies[i].x += current_enemies[i].vel_x;
SPR_setPosition(
current_enemies[i].sprite,
fix32ToInt(current_enemies[i].x),
fix32ToInt(current_enemies[i].y)
);
}
}
What happens here:
This is the core movement logic of your enemies.
🎯 Summary: What Your
Enemy System Does
✔ Defines an Enemy struct
to hold:
✔ Creates a pool of 10
enemies
✔ Initializes each active
enemy by:
✔ Updates enemies every
frame by:
This creates a modular, expandable enemy system suitable for any Genesis game.