Zombie game
Written by: Gyula Rabai
Zombie game is a simple top-down survival shooter game built with Python and RGame. This game demonstrates the capabilities of the RGame game engine. It uses traditional controls (WASD) to move the main character areound and the mouse to use the weapon. The source files show how easy and efficient it is to use RGame.
Download: ZombieGame.zip
Game Overview
Survive as long as possible against waves of zombies. Shoot zombies, collect better guns, and try to reach higher levels. Each level spawns more zombies and they move faster.
How to Run the Game
Make sure you have Python installed, then simply run:
python MainScript.py
Controls
- Move: W / A / S / D
- Shoot: Left Mouse Button (hold for rapid fire)
- Pick up better gun: Touch the grey rectangle (gun pickup) that spawns after each level
Gameplay Mechanics
Weapons & Upgrades
- You start with a basic pistol.
- After each level, a better gun spawns randomly on the map (grey rectangle).
- Touch the gun with your player to upgrade.
- Upgrades increase: Rate of Fire, Magazine size, Reload speed (some weapons)
- At level 5+, you may get a shotgun (fires spread of pellets).
Zombies
- Spawn at the left edge of the screen.
- Chase the player directly.
- More zombies and faster speed each level.
- Collision with player = Game Over.
Levels
- Each level lasts 10 seconds or until all zombies are killed.
- After level ends a new gun spawns, zombies get faster.
- Level number shown in top-left corner.
Game Over
Touching a zombie ends the game with a big red "Game Over!" message.
Technical Information
Main Files
- MainScript.py: Core game logic, player, levels, UI
- Zombie.py: Zombie AI and behavior
- Bullet.py: Bullet logic (collision, optional auto-aim)
- Gun.py: Base gun + Revolver + Shotgun implementations
- RGameLib.py: Custom game engine / rendering library
Key Features in Code
- Custom 2D vector math (RG_Vector2D, RG_Velocity2D)
- Simple physics system with deltaTime
- Object pooling not used: objects are created/deactivated
- Basic collision detection (circle-circle)
Tips for Better Performance
- Collect every gun upgrade: especially rate of fire and magazine size.
- Keep moving: standing still makes you easy to hit.
- Shotgun (level 5+) is great for crowds but has slow reload.
- Try to clear zombies quickly to get more time before the next wave.
Source code
MainScript.py
from Zombie import*
from Bullet import*
from Gun import*
from RGameLib import*
# This is the rest of the code
# Do not worry if you do not understand any of it
# We will cover this later.
class Player(RG_Script):
def Start(self):
self.Speed = 7.5
self.MainWindow.Bind("w", self.Up)
self.MainWindow.Bind("<KeyRelease-w>", self.UpR)
self.MainWindow.Bind("s", self.Down)
self.MainWindow.Bind("<KeyRelease-s>", self.DownR)
self.MainWindow.Bind("d", self.Right)
self.MainWindow.Bind("<KeyRelease-d>", self.RightR)
self.MainWindow.Bind("a", self.Left)
self.MainWindow.Bind("<KeyRelease-a>", self.LeftR)
self.up = False
self.down = False
self.right = False
self.left = False
self.gun = Gun(self.MainScript,appearance=RG_App_Circle(self.MainWindow.Screen))
self.gun.Position = self.Position
self.gun.rateOfFire = 1
self.gun.reloadSpeed = 2
self.gun.magSize = 2
self.gun.ammo = RandomInt(0, 2)
pass
def Up(self, args):
self.up = True
def UpR(self, args):
self.up = False
def Down(self, args):
self.down = True
def DownR(self, args):
self.down = False
def Right(self, args):
self.right = True
def RightR(self, args):
self.right = False
def Left(self, args):
self.left = True
def LeftR(self, args):
self.left = False
def PhysicsTick(self, deltaTime):
self.Bounce()
if(self.MainScript.GameOverTxt.Activated):
self.Velocity.SetSpeed(0)
return
if(self.up):
self.Velocity.Y = 1
if(self.down):
self.Velocity.Y = -1
if not (self.up or self.down):
self.Velocity.Y = 0
if(self.right):
self.Velocity.X = 1
if(self.left):
self.Velocity.X = -1
if not (self.left or self.right):
self.Velocity.X = 0
if(self.Velocity.GetlengthSqr() != 0):
self.Velocity.SetSpeed(self.Speed)
if(self.MainWindow.Mouse.Left):
self.gun.Shoot(self.MainWindow.Mouse.Position)
if self.MainScript.gun is None:
return
if self.MainScript.gun.Position.VectorTo(self.Position).GetLength() < 25 and not (self.MainScript.gun is self.gun):
self.gun.Deactivate()
self.gun = self.MainScript.gun
self.gun.Position = self.Position
def Render(self):
if self.gun.Appearance.Visible:
self.gun.Appearance.Visible = False
pass
pass
def PlayerSetUp(self):
# Making an appearance (a circle)
playerApp = RG_App_Circle(
self.MainWindow.Screen,
radius = 25,
color = "light yellow",
outlineWidth = 1,
offset = RG_Vector2D(25,25)
)
# Making a position (the centre of the screen)
playerPos = RG_Position2D(
self.MainWindow.WindowWidth/2,
self.MainWindow.WindowHeight/2)
# Creating the actual player with the position and appearance
self.Player = Player(self,
position = playerPos,
appearance = playerApp)
def TextsSetUp(self):
self.LvlTxtPos = RG_Position2D(335, self.MainWindow.WindowHeight - 10)
self.LvlTxt = RG_Label(self, self.LvlTxtPos, fontSize=23, text = "Level 1")
# Same thing with the number of ammo remaining
self.AmmoTxtPos = RG_Position2D(335, self.MainWindow.WindowHeight-52)
self.AmmoTxt = RG_Label(self, self.AmmoTxtPos, fontSize=17, text = "Ammo: 1")
# Time left before next level
self.TimeTxtPos = RG_Position2D(335, self.MainWindow.WindowHeight - 35)
self.TimeTxt = RG_Label(self, self.TimeTxtPos, fontSize=17, text = "0")
# Rate of Fire
self.FireRatePos = RG_Position2D(485, self.MainWindow.WindowHeight - 52)
self.FireRate = RG_Label(self, self.FireRatePos, fontSize=17, text = "1")
# The Game Over text
self.GameOverTxtPos = RG_Position2D(self.MainWindow.WindowWidth/2-100, self.MainWindow.WindowHeight/2)
self.GameOverTxt = RG_Label(self, self.GameOverTxtPos , fontSize=50, text = "Game Over!", fontColor="Red")
self.GameOverTxt.Deactivate()
def CreateZombie(self: RG_MainScript):
height = RandomInt(0,self.MainWindow.WindowHeight)
zombPos = RG_Position2D(0, height)
zombApp = RG_App_Circle(self.MainWindow.Screen,
radius = 25,
color = "green",
outlineWidth = 2,
offset = RG_Vector2D(25,25))
zomb = Zombie(self,
position = zombPos,
appearance = zombApp
)
zomb.Speed = self.ZombieSpeed
self.Zombies.append(zomb)
def DataInit(self):
self.level = 1
self.TimePoint = RG_TimePoint() # The timer used to measure the time elapsed since the level started
self.spawned = False # weather the zombies have been spawned for the level
self.Zombies = [] # A list to store the zombies in
self.Bullets = []# A list to store the bullets in
self.ZombieSpeed = 1
self.gun = None
def CreateGun(self, position):
self.oldGun = self.Player.gun
if self.level < 5:
self.gun = Gun(self, position = position, appearance=RG_App_Rectangle(self.MainWindow.Screen, RG_Size(20,10),color="Grey"))
else:
self.gun = Gun(self, position = position, appearance=RG_App_Rectangle(self.MainWindow.Screen, RG_Size(50,10),color="Grey"))
# Saying that this bit of code and memory is called MainScript
# and also that MainScript is a type of 'RG_MainScript' meaning it
# can be run
# The first thing that happens in the program
def Main(self):
# Setting up window
self.MainWindow.WindowTitle = "The Zombie Game"
self.MainWindow.WindowBackground = "light green"
PlayerSetUp(self) # Making a player
TextsSetUp(self) # Making all the texts
# Making variables
DataInit(self)
self.levelDuration = 10 # The time in seconds for how long a level lasts
def PhysicsTick(self, deltaTime):
# If zombies have not been spawned spawn them
if not self.spawned:
# Do this code for how many numbers there are between 0 and 'self.level':
# I.e. Do this 'self.level' times. I.e. create that many zombies
for x in range(self.level):
CreateZombie(self) # Creating a zombie
self.spawned = True
self.level += 1
def Render(self):
# check if we need to move onto the next level
NoZombiesLeft = len(self.Zombies) == 0
NoTimeLeft = self.TimePoint.Diff() >= self.levelDuration
if NoTimeLeft or NoZombiesLeft:
# Do stuff to set up next level
self.LvlTxt.Appearance.Text = "Level " + str(self.level)
self.spawned = False
self.TimePoint.Now()
if self.level > 0:
if (not (self.gun is self.Player.gun)) and self.gun != None:
self.gun.Deactivate()
SpawnGun(self, RG_Position2D(
RandomInt(50,self.MainWindow.WindowWidth),
RandomInt(0,self.MainWindow.WindowHeight)))
self.ZombieSpeed += 0.3
# Display the amount of ammo remaining
self.AmmoTxt.Appearance.Text = "Ammo: " + str(self.Player.gun.ammo) + f"/{self.Player.gun.magSize}"
self.FireRate.Appearance.Text = "Fire Rate: " + str(self.Player.gun.rateOfFire)
# Display the time left before next level.
timeLeft = self.levelDuration - self.TimePoint.Diff()
self.TimeTxt.Appearance.Text = "Time Remaining: "+ format(timeLeft, ".2f")
def SpawnGun(self, position):
CreateGun(self, position)
self.gun.rateOfFire = self.oldGun.rateOfFire + 1
self.gun.magSize = self.oldGun.magSize + 1
self.gun.ammo = RandomInt(0,self.gun.magSize)
self.gun.reloadSpeed = 2
pass
Run(RG_MainScript(None, Main, PhysicsTick, Render))
Zombie.py
from RGameLib import*
class Zombie(RG_Script):
def Start(self):
self.Speed = 3
pass
def PhysicsTick(self, deltaTime):
if(self.MainScript.GameOverTxt.Activated):
self.Velocity.SetSpeed(0)
return
self.Velocity = self.Position.VectorTo(self.MainScript.Player.Position)
self.Velocity = self.Velocity.ToVelocity()
if not (self.Velocity.GetLength() < self.Appearance.Dimensions.Radius + self.MainScript.Player.Appearance.Dimensions.Radius):
self.Velocity.SetSpeed(self.Speed)
else:
self.Velocity.SetSpeed(0)
self.Position = self.MainScript.Player.Position
self.MainScript.GameOverTxt.Activate()
pass
def Render(self):
pass
pass
Gun.py
from RGameLib import *
from Bullet import *
class Gun(RG_Script):
ROFTimer = RG_TimePoint()
reloadTimer = RG_TimePoint()
startedReload = False
rateOfFire = 2
reloadSpeed = 1
magSize = 3
ammo = 3
bulletSpeed = 10
aimAssist = 2
def PhysicsTick(self, deltaTime):
if(self.ammo == 0 and not self.startedReload):
self.startedReload = True
self.reloadTimer.Now()
if(self.reloadSpeed > self.reloadTimer.Diff() and self.startedReload):
return
if(self.startedReload):
self.ammo = self.magSize
self.startedReload = False
def Shoot(self, targetPosition):
if(self.reloadSpeed > self.reloadTimer.Diff() and self.startedReload):
return
if self.ammo == 0:
return
if self.ROFTimer.Diff() < (1/self.rateOfFire): return
self.ammo -= 1
self.Pos = RG_Position2D(self.Position.X, self.Position.Y)
self.Vel = self.Position.VectorTo(targetPosition).ToVelocity()
self.Vel.SetSpeed(self.bulletSpeed)
self.MainScript.Bullets.append(
Bullet(self.MainScript,
position = self.Pos,
velocity = self.Vel,
appearance = RG_App_Circle(self.MainWindow.Screen, radius = 2, color = "grey")))
self.ROFTimer.Now()
class Revolver(Gun):
def Start(self):
self.reloadSpeed = 2
self.rateOfFire = 3
class Shotgun(Gun):
def Start(self):
self.reloadSpeed = 4
self.rateOfFire = 0.5
def Shoot(self, targetPosition):
if(self.reloadSpeed > self.reloadTimer.Diff() and self.startedReload):
return
if self.ammo == 0:
return
if self.ROFTimer.Diff() < (1/self.rateOfFire): return
self.ammo -= 1
self.Pos = RG_Position2D(self.Position.X, self.Position.Y)
self.Vel = self.Position.VectorTo(targetPosition).ToVelocity()
self.Vel.SetSpeed(self.bulletSpeed)
for i in range(5):
randVel = RG_Velocity2D(RandomFloat()*6, RandomFloat()*6)
self.Vel += randVel
self.MainScript.Bullets.append(
Bullet(self.MainScript,
position = self.Pos,
velocity = self.Vel,
appearance = RG_App_Circle(self.MainWindow.Screen, radius = 1, color = "grey")))
self.MainScript.Bullets[-1].aim = 1
self.ROFTimer.Now()
Bullet.py
from Zombie import*
from RGameLib import*
class Bullet(RG_Script):
aim = 5
def Start(self):
self.Speed = 10
pass
def PhysicsTick(self, deltaTime):
self.Velocity.SetSpeed(self.Speed)
zombs = self.MainScript.Zombies
minZ = None
if len(zombs) != 0:
minZ = zombs[0]
minD = 10000
for Zomb in zombs:
if self.Position.VectorTo(Zomb.Position).GetLength() < minD:
minD = self.Position.VectorTo(Zomb.Position).GetLength()
minZ = Zomb
if(self.Position.VectorTo(Zomb.Position).GetLength() < Zomb.Appearance.Dimensions.Radius):
Zomb.Deactivate()
self.MainScript.Zombies.remove(Zomb)
self.Deactivate()
self.MainScript.Bullets.remove(self)
return
if(self.Bounce()):
self.Deactivate()
self.MainScript.Bullets.remove(self)
# Uncomment for auto-aiming bullets.
#if len(zombs) != 0:
# toZomb = self.Position.VectorTo(minZ.Position)
# toZomb.SetLength(self.aim)
# self.Velocity += toZomb * deltaTime
pass
def Render(self):
pass
pass