Building an A Level Platform Game Project — Part 6: Adding Enemies, Moving Hazards and Advanced Interactions
In Part 1, we planned the platform game and set realistic success criteria.
In Part 2, we created the game window and added basic player movement.
In Part 3, we added gravity and jumping.
In Part 4, we added platforms and collision detection.
In Part 5, we turned platforms into proper levels with routes, start points, finish points, collectables, hazards, score and lives.
Now the game is ready for the next step.
We need to make the level feel alive.
A static platform game can still be useful, but once we add enemies, moving hazards and more advanced interactions, the project becomes much more interesting. The player is no longer simply jumping from platform to platform. They now have to react, time their movements, avoid danger and make decisions.
For an A Level Computer Science project, this is an excellent stage. It introduces movement patterns, simple artificial intelligence, state changes, timing, object lists, collision detection, testing and balancing difficulty.
It is also the stage where students need to be careful.
Adding enemies and moving hazards can make the game better, but it can also make the project much harder to finish. The aim is not to add dozens of complicated features. The aim is to add a small number of well-planned interactions that can be tested and explained clearly.
Why Moving Objects Change the Game
So far, most of the game world has been static.
Platforms stay in one place.
Collectables wait to be collected.
Hazards sit in fixed positions.
The finish point waits for the player.
That is fine for an early level, but it can become predictable.
Moving hazards and enemies create challenge because the player must respond to change.
A moving enemy can patrol a platform.
A moving spike block can travel back and forth.
A falling object can appear at intervals.
A moving platform can carry the player across a gap.
A timed laser can switch on and off.
These features make the game feel more dynamic.
More importantly, they introduce useful programming problems.
The Aim for Part 6
The aim of this stage is:
Add enemies, moving hazards and more advanced interactions to make the level more challenging and engaging.
By the end of this stage, the game could include:
a simple patrolling enemy
a moving hazard
collision detection between the player and enemies
lives lost when touching danger
moving objects stored in lists
simple movement boundaries
testing evidence for enemy behaviour
improved level design using timing and risk
Not every student needs to add all of these features. It is better to add one or two well-tested features than six unfinished ones.
Start With One Enemy
The simplest enemy does not need complex artificial intelligence.
It can simply move left and right between two points.
For example, an enemy might patrol along a platform. When it reaches the left boundary, it turns right. When it reaches the right boundary, it turns left.
This is easy to understand, but still gives the player a real challenge.
A simple enemy could be represented as a rectangle:
enemy_rect = pygame.Rect(300, 540, 40, 40)
enemy_speed = 2
enemy_left_limit = 250
enemy_right_limit = 500
The enemy moves each frame:
enemy_rect.x += enemy_speed
Then the direction changes when the enemy reaches a boundary:
if enemy_rect.left <= enemy_left_limit:
enemy_speed = 2
if enemy_rect.right >= enemy_right_limit:
enemy_speed = -2
This creates a patrol movement.
It is not complicated, but it is enough to make the level more interesting.
Turning the Enemy Into Data
As the project grows, one enemy is not enough. The game may eventually need several enemies.
Instead of creating separate variables for every enemy, we can store enemies as data.
For example:
enemies = [
{
"rect": pygame.Rect(300, 540, 40, 40),
"speed": 2,
"left_limit": 250,
"right_limit": 500
},
{
"rect": pygame.Rect(500, 360, 40, 40),
"speed": 1,
"left_limit": 450,
"right_limit": 650
}
]
Then we can update all enemies using a loop:
for enemy in enemies:
enemy["rect"].x += enemy["speed"]
if enemy["rect"].left <= enemy["left_limit"]:
enemy["speed"] = abs(enemy["speed"])
if enemy["rect"].right >= enemy["right_limit"]:
enemy["speed"] = -abs(enemy["speed"])
This is a very useful A Level project idea.
The student can explain that enemies are stored in a list of dictionaries so that multiple enemies can be processed using the same code.
This is much better than writing separate code for enemy one, enemy two and enemy three.
Drawing Enemies
Drawing enemies is straightforward once they are stored in a list.
For example:
for enemy in enemies:
pygame.draw.rect(screen, (150, 0, 150), enemy["rect"])
At first, enemies can be simple purple rectangles.
Later, they could become sprites, robots, monsters, bugs, spikes or animated characters.
The important thing is not the artwork. The important thing is the behaviour.
A plain rectangle that moves correctly is better than a beautiful enemy that does not work.
Collision With Enemies
Once enemies exist, the player needs to interact with them.
The simplest rule is:
If the player touches an enemy, the player loses a life and returns to the start position.
Example code:
for enemy in enemies:
if player_rect.colliderect(enemy["rect"]):
lives -= 1
player_rect.x, player_rect.y = current_level["player_start"]
player_y_velocity = 0
If lives reach zero:
if lives <= 0:
game_over = True
This creates a clear lose condition.
It also creates excellent testing opportunities:
Does the enemy move?
Does the enemy turn around at the boundary?
Does the player lose a life after touching the enemy?
Does the player return to the start?
Does the game end when lives reach zero?
These are specific and testable.
Should the Player Defeat Enemies?
Some platform games allow the player to defeat enemies by jumping on them.
This is a more advanced interaction.
The game has to decide whether the player touched the enemy from the top or from the side.
If the player lands on top of the enemy, the enemy is removed.
If the player touches the enemy from the side, the player loses a life.
A simple version might check whether the player is falling and whether the bottom of the player is near the top of the enemy:
if player_rect.colliderect(enemy["rect"]):
if player_y_velocity > 0 and player_rect.bottom <= enemy["rect"].top + 15:
enemies.remove(enemy)
player_y_velocity = -8
score += 20
else:
lives -= 1
player_rect.x, player_rect.y = current_level["player_start"]
This is more complex and should be treated as an extension.
It is a good example of advanced collision response.
It also creates a useful design decision:
Should the game reward attacking enemies, or should the player simply avoid them?
Either answer is acceptable if the student can justify it.
Adding Moving Hazards
A moving hazard is similar to an enemy, but it does not need to look intelligent.
It might be:
a moving spike block
a swinging danger zone
a rising lava area
a moving electric barrier
a falling rock
a timed laser
A simple moving hazard could move vertically instead of horizontally.
Example:
moving_hazards = [
{
"rect": pygame.Rect(600, 400, 40, 40),
"speed": 2,
"top_limit": 300,
"bottom_limit": 520
}
]
Updating the hazard:
for hazard in moving_hazards:
hazard["rect"].y += hazard["speed"]
if hazard["rect"].top <= hazard["top_limit"]:
hazard["speed"] = abs(hazard["speed"])
if hazard["rect"].bottom >= hazard["bottom_limit"]:
hazard["speed"] = -abs(hazard["speed"])
This creates a moving danger area that the player must avoid.
The player now has to time the jump, not just make the jump.
That makes the level feel more alive.
Timed Hazards
Another useful interaction is a timed hazard.
For example, a laser could switch on and off every few seconds.
This introduces the idea of time-based state.
A simple timer variable might be:
hazard_timer += 1
Then:
if hazard_timer < 120:
laser_active = True
else:
laser_active = False
if hazard_timer > 240:
hazard_timer = 0
At 60 frames per second, this means:
laser active for about 2 seconds
laser inactive for about 2 seconds
cycle repeats
If the laser is active, it is drawn and collision is checked:
if laser_active:
pygame.draw.rect(screen, (255, 0, 0), laser_rect)
if player_rect.colliderect(laser_rect):
lives -= 1
player_rect.x, player_rect.y = current_level["player_start"]
This is a good extension feature because it introduces timing and changing states.
It also needs careful testing.
Moving Platforms
Moving platforms are especially interesting because they are both helpful and difficult.
A moving platform might carry the player across a gap.
That sounds simple, but it creates a tricky problem:
If the platform moves, should the player move with it?
If the player is standing on a platform moving right, the player should probably move right as well.
A simple moving platform could be stored like this:
moving_platforms = [
{
"rect": pygame.Rect(250, 420, 120, 20),
"speed": 2,
"left_limit": 200,
"right_limit": 500
}
]
Updating it:
for platform in moving_platforms:
platform["rect"].x += platform["speed"]
if platform["rect"].left <= platform["left_limit"]:
platform["speed"] = abs(platform["speed"])
if platform["rect"].right >= platform["right_limit"]:
platform["speed"] = -abs(platform["speed"])
If the player is standing on the moving platform, we may need to move the player with it:
if player_rect.colliderect(platform["rect"]) and player_y_velocity >= 0:
player_rect.bottom = platform["rect"].top
player_y_velocity = 0
on_ground = True
player_rect.x += platform["speed"]
This is more advanced than a moving enemy because it affects the player’s position.
For some students, moving platforms may be an excellent extension. For others, they may be too much for the main project.
Again, the key is scope.
The Danger of Adding Too Much
This is the stage where many student projects can become messy.
Once enemies work, students want more.
They want:
different enemies
bosses
power-ups
moving platforms
falling platforms
switches
keys
locked doors
weapons
animations
sound effects
particle effects
multiple routes
Some of these ideas are excellent, but too many can destroy the project.
A strong A Level project is not judged by how many features were imagined. It is judged by how well the chosen features were planned, implemented, tested and evaluated.
A sensible feature plan might be:
Essential for Part 6
one moving enemy
collision with enemy
losing a life
testing enemy behaviour
Desirable
two enemies with different speeds
one moving hazard
improved level layout
Extension
jumping on enemies to defeat them
moving platforms
timed hazards
different enemy types
simple enemy animation
This keeps the project controlled.
Making Difficulty Fair
Enemies and moving hazards should make the game more interesting, not unfair.
A fair challenge gives the player time to understand what is happening.
Good design questions include:
Can the player see the enemy before reaching it?
Is there enough time to react?
Is the safe route clear?
Does the enemy movement pattern make sense?
Can the player learn from a mistake?
Is the first enemy easier than later enemies?
A moving enemy placed immediately at the start of the level may feel unfair.
A moving enemy introduced after a safe first jump may feel much better.
Good games teach the player gradually.
Good projects should show that thought process.
Testing Enemies and Moving Hazards
This stage needs careful testing because moving objects create more possible outcomes.
Example test table:
| Test Number | Test | Expected Result | Actual Result | Pass/Fail |
|---|---|---|---|---|
| 1 | Start the level | Enemy appears in correct position | Enemy appears correctly | Pass |
| 2 | Observe enemy movement | Enemy moves between two limits | Enemy patrols correctly | Pass |
| 3 | Enemy reaches left limit | Enemy changes direction | Enemy turns correctly | Pass |
| 4 | Enemy reaches right limit | Enemy changes direction | Enemy turns correctly | Pass |
| 5 | Player touches enemy | Player loses one life | Life decreases by one | Pass |
| 6 | Player touches enemy with one life left | Game over occurs | Game over displays | Pass |
| 7 | Player avoids enemy and reaches finish | Level can still be completed | Level complete | Pass |
| 8 | Moving hazard reaches top limit | Hazard changes direction | Hazard turns correctly | Pass |
| 9 | Player touches moving hazard | Player loses life and restarts | Player restarts correctly | Pass |
| 10 | New user plays level | User understands enemy danger | User understood after first attempt | Pass |
Testing should include observation over time.
A moving enemy might work for the first few seconds but fail later if the boundary checks are wrong.
User Feedback
At this stage, user feedback becomes especially useful.
The student could ask a tester:
Was the enemy easy to understand?
Did the movement pattern feel fair?
Was the level too easy or too hard?
Did you know what to avoid?
Did the hazard look dangerous?
Did the game give enough feedback when you lost a life?
What would improve the level?
Useful feedback might say:
The enemy was difficult to see because it was too similar in colour to the platform.
That could lead to a design change.
Or:
The moving hazard was too fast, and the player had no time to react.
That could lead to reducing the hazard speed.
This is exactly the type of evidence students should collect.
Recording Bugs and Fixes
Enemies and moving hazards are likely to produce bugs.
Examples include:
enemy moves off the platform
enemy gets stuck at the boundary
enemy speed becomes zero
player loses several lives instantly from one collision
moving hazard does not reverse direction
player respawns directly onto a hazard
moving platform pushes the player through a wall
enemies keep moving after game over
These are not disasters. They are opportunities.
A good development log should record:
what the bug was
how it was found
what caused it
how it was fixed
how the fix was tested
For example:
Problem: When the player touched an enemy, several lives were lost at once because the collision was detected repeatedly over several frames.
Solution: I reset the player position immediately after the collision and added a short invulnerability period after losing a life.
Test: I deliberately collided with the enemy several times and checked that only one life was lost each time.
This is strong evidence of debugging.
Adding a Short Invulnerability Period
One common issue is that the player may lose multiple lives very quickly if they remain in contact with an enemy for several frames.
A simple solution is to add a temporary invulnerability period after damage.
Example:
invulnerable_timer = 0
Each frame:
if invulnerable_timer > 0:
invulnerable_timer -= 1
When the player touches an enemy:
if invulnerable_timer == 0:
lives -= 1
invulnerable_timer = 60
player_rect.x, player_rect.y = current_level["player_start"]
At 60 frames per second, this gives about one second before the player can be damaged again.
This is an excellent feature for stronger students because it introduces timed state management.
Making the Level Feel Alive Without Overcomplicating It
A level does not need dozens of features to feel alive.
One patrolling enemy can change how the player approaches a jump.
One moving hazard can make timing important.
One moving platform can create a new route.
The best approach is to add one feature, test it, improve it and then decide whether another feature is needed.
A controlled level might include:
one easy enemy near the start
one moving hazard in the middle
one harder enemy near the finish
collectables placed near danger for optional challenge
This creates variety without overwhelming the project.
Linking Back to Success Criteria
This stage can support success criteria such as:
The game includes at least one moving enemy.
The enemy changes direction at set boundaries.
The player loses a life after touching an enemy.
The game includes at least one moving hazard.
The player can avoid enemies and complete the level.
The game gives clear feedback when the player loses a life.
User testing is used to adjust difficulty.
The student should update the success criteria if new features are added.
This is important because projects often evolve.
The final evaluation should compare the completed game with the final agreed success criteria, not just a vague original idea.
Practical Task for Students
Part 6 Student Task
Add one enemy or moving hazard to your platform game.
Your program should include:
One moving enemy or hazard.
Movement between two limits.
Collision detection with the player.
A consequence when the player touches it.
A way for the player to avoid it.
Testing of movement and collision.
At least one adjustment after testing.
Screenshots or video evidence.
A development log entry explaining the feature.
A clear link back to the success criteria.
Extension Task
Add one advanced interaction, such as:
a second enemy with a different speed
jumping on enemies to defeat them
moving platforms
timed hazards
temporary invulnerability after damage
enemies stored in external level data
different enemy types
sound effects for damage or enemy defeat
Students should only attempt extensions once the first enemy or moving hazard works reliably.
Development Log Example
A good development log entry might look like this:
Development Stage
Adding enemies and moving hazards.
Aim
To make the level more challenging by adding a moving enemy that patrols between two points and causes the player to lose a life on contact.
What Was Added
enemy rectangle
enemy speed
left and right movement limits
enemy movement loop
collision detection with the player
life loss after collision
player reset after damage
Problems Found
The enemy initially moved off the platform.
The player sometimes lost more than one life from a single collision.
The first enemy position made the level too difficult for a new user.
Changes Made
Added left and right movement boundaries.
Reset the player to the start after collision.
Moved the enemy further into the level.
Reduced the enemy speed after user testing.
Evidence Collected
screenshot of the enemy position
short video of enemy movement
code showing enemy data
test table for enemy collision
user feedback about difficulty
notes explaining changes made after testing
This gives the project a clear development trail.
Personal Reflection: This Is Where Students See the Difference Between Animation and Interaction
Students often enjoy this stage because it looks more exciting. Enemies move. Hazards travel. The player has to react.
But the most important lesson is not animation.
It is interaction.
A moving enemy is not just a picture sliding across the screen. It has rules. It has limits. It changes direction. It affects the player. It changes the difficulty of the level.
This is a powerful idea in Computer Science.
The game becomes a system of interacting objects.
The player, platforms, enemies, hazards, collectables and finish point all have relationships with each other.
That is why a simple 2D game can become such a useful programming project.
It gives students a practical way to explore logic, data, testing and design.
Final Thoughts: A Level That Feels Alive
By adding enemies, moving hazards and advanced interactions, the platform game begins to feel much more alive.
The player is no longer just travelling through a static route.
They must watch, time, avoid, decide and react.
That makes the game more engaging.
It also makes the project stronger because the student can show more advanced programming ideas: lists of enemies, movement limits, collision consequences, timers, state variables, user testing and difficulty balancing.
The key is control.
One well-designed enemy is better than five unfinished ones.
One moving hazard that works reliably is better than a whole level full of broken ideas.
One carefully tested interaction is better than a long list of features that cannot be explained.
A good A Level project is not about adding everything.
It is about choosing the right features, building them properly, testing them carefully and explaining the decisions clearly.
In the next article, we can look at menus, lives, scoring, high scores and the wider game structure that turns a playable level into a more complete game.

No comments:
Post a Comment