Enemies
Most enemies in most games are a few lines each. The SCEE tutorial's chapter 8 has seven, which the ai example puts in one program, chosen with L1 and R1.
Chasing and fleeing#
An enemy that chases steps towards the player, a pixel in each direction it is off by, and one that flees steps away:
// chase steps the enemy a pixel towards the player each way it is off.
func chase() {
enemy.X += sign(player.X - enemy.X)
enemy.Y += sign(player.Y - enemy.Y)
}
// flee steps the enemy a pixel away from the player each way.
func flee() {
enemy.X -= sign(player.X - enemy.X)
enemy.Y -= sign(player.Y - enemy.Y)
}
It is not clever, it goes straight for the player and never round anything, but it is what most chasing in 2D games was. A fleeing enemy is kept on the screen, or it leaves it.
Moving on its own#
Bouncing reverses a speed at each edge. Wandering goes one way until it meets an edge, then picks another at random. A set pattern reads its moves from a table, one a step, the way a game scripted a dive or a boss's dance:
// followPattern takes the set pattern's next step every other frame, and
// starts it again from the same place at its end.
func followPattern() {
if frames%2 != 0 {
return
}
speedX, speedY = 0, 0
switch steps[step] {
case left:
speedX = -2
case right:
speedX = 2
case up:
speedY = -2
case down:
speedY = 2
}
move()
step++
if step == int32(len(steps)) {
step = 0
enemy.X, enemy.Y = 100, 100
}
}
Flies move a random small amount every frame, which reads as buzzing, and a wave swings an enemy from side to side by the sine of the frame count as it falls.
States#
A game puts behaviours like these behind a state, which the enemy changes when something happens: it sees the player, it is hit, a timer runs out. Here the state changes at random every few seconds, the start of every game's state machine:
// changeStates moves the enemy as its state says, and picks another state
// at random every stateTime frames.
func changeStates() {
stateClock++
if stateClock > stateTime {
state = yaroze.Rand() % states
stateClock = 0
step = 0
}
switch state {
case attack:
chase()
case evade:
flee()
case bouncing:
bounceOff()
move()
case patterned:
followPattern()
case wandering:
wander()
move()
case normal:
// Side to side, turning gently at the edges.
if enemy.X > screenW-int16(enemy.W) {
speedX--
}
if enemy.X < 0 {
speedX++
}
enemy.X += speedX
}
keepOn(&enemy)
}
The enemy's state is one variable, and each frame does what that state says. Adding a behaviour is adding a state, and the conditions for leaving it.
Random numbers#
Rand is the C library's, the same sequence every time from the same Srand. That makes a game's randomness repeat exactly from one run to the next unless it is seeded from something that varies, such as the frame the player first pressed start on. The repeat is useful in testing, which is why the example seeds with a constant.