Vampire Shooting Game (Sample)
In this article, I’ll introduce a simple game where you try to survive while escaping from approaching monsters.
(This article only provides sample code.)
1. Prepare the Assets
Use Pyxel Editor to draw the background assets.
Download the resource file from the link below
(Click the Download button in the top-right corner):
Sample Code
Below is the complete sample code used in this game.
# sprite.py
import pyxel
import math
import random
class BaseSprite:
def __init__(self, x, y, u, v, spd, w=8, h=8):
""" Constructor """
self.x = x
self.y = y
self.u = u
self.v = v
self.spd = spd
self.w = w
self.h = h
self.vx = 0
self.vy = 0
self.right_flg = True # Facing right flag
def update(self):
""" Update """
self.x += self.vx
self.y += self.vy
def draw(self):
""" Draw """
u = 0 if self.right_flg else 8
pyxel.blt(
self.x, self.y, 0,
self.u + u, self.v, self.w, self.h, 0
)
def get_center(self):
""" Get center position """
return (self.x + self.w / 2, self.y + self.h / 2)
def set_center(self, x, y):
""" Set center position """
self.x = x - self.w / 2
self.y = y - self.h / 2
def move(self, deg):
""" Move by angle """
rad = (deg * math.pi) / 180
self.vx = self.spd * math.cos(rad)
self.vy = self.spd * math.sin(rad)
if deg == 90 or deg == 270:
return
self.right_flg = not (90 < deg < 270)
def stop(self):
""" Stop movement """
self.vx = 0
self.vy = 0
def intersects(self, other):
""" Rectangle collision (AABB) """
if other.x + other.w < self.x:
return False
if self.x + self.w < other.x:
return False
if other.y + other.h < self.y:
return False
if self.y + self.h < other.y:
return False
return True
def contains_center(self, other):
""" Check if center point is inside another rectangle """
x, y = self.get_center()
if other.x + other.w < x:
return False
if x < other.x:
return False
if other.y + other.h < y:
return False
if y < other.y:
return False
return True
def get_distance(self, other):
""" Calculate distance """
dx = self.x - other.x
dy = self.y - other.y
return math.sqrt(dx * dx + dy * dy)
def get_direction(self, other):
""" Calculate direction angle """
dx = other.x - self.x
dy = other.y - self.y
rad = math.atan2(dy, dx)
return (rad * (180 / math.pi) + 360) % 360
class PlayerSprite(BaseSprite):
def __init__(self, x, y, u, v, spd, game):
""" Constructor """
super().__init__(x, y, u, v, spd)
self.shot_interval = 12
self.shot_counter = 0
self.game = game
def update(self):
""" Update """
super().update()
self.shot_counter += 1
if self.shot_interval < self.shot_counter:
self.shot_counter = 0
self.game.on_shot_event(self)
class Monster(BaseSprite):
def __init__(self, x, y, u, v, spd, think_interval, target):
""" Constructor """
super().__init__(x, y, u, v, spd)
self.think_interval = think_interval
self.think_counter = 0
self.target = target
def update(self):
""" Update """
super().update()
self.think_counter += 1
if self.think_interval < self.think_counter:
self.think_counter = 0
direction = self.get_direction(self.target)
self.move(direction)
class Bullet(BaseSprite):
def __init__(self, x, y, spd, life=10):
""" Constructor """
super().__init__(x, y, 0, 0, spd)
self.life = life
def update(self):
""" Update """
super().update()
self.life -= 1
if self.life < 0:
self.stop()
def draw(self):
""" Draw """
pyxel.rect(self.x, self.y, 2, 2, 7)
def is_dead(self):
""" Dead flag """
return self.life < 0
class Particle(BaseSprite):
def __init__(self, x, y, life=10):
""" Constructor """
super().__init__(x, y, 0, 0, 0)
self.life = life
self.area_r = 8
self.circ_r = 0
self.off_x = 0
self.off_y = 0
def update(self):
""" Update """
super().update()
self.life -= 1
if self.life < 0:
self.stop()
self.circ_r = random.randint(2, 4)
self.off_x = random.randint(0, self.area_r) - self.area_r / 2
self.off_y = random.randint(0, self.area_r) - self.area_r / 2
def draw(self):
""" Draw """
pyxel.circ(
self.x + self.off_x,
self.y + self.off_y,
self.circ_r,
7
)
def is_dead(self):
""" Dead flag """
return self.life < 0
# main.py
import pyxel
import random
import sprite
W, H = 160, 120
START_X = W / 2
START_Y = H / 2 - 10
MODE_TITLE = "title"
MODE_PLAY = "play"
MODE_GAME_OVER = "game_over"
PLAYER_SPD = 1.2
BULLET_SPD = 2.4
MONSTERS = [
{"u": 32, "v": 72, "spd": 0.1, "think_interval": 60},
{"u": 48, "v": 72, "spd": 0.12, "think_interval": 90},
{"u": 0, "v": 80, "spd": 0.14, "think_interval": 120},
{"u": 16, "v": 80, "spd": 0.16, "think_interval": 150},
{"u": 32, "v": 80, "spd": 0.25, "think_interval": 180},
]
class Game:
def __init__(self):
""" Constructor """
self.score = 0
self.game_mode = MODE_TITLE
self.player = sprite.PlayerSprite(
START_X, START_Y, 0, 72, PLAYER_SPD, self
)
self.reset()
pyxel.init(W, H, title="Hello, Pyxel!!")
pyxel.load("vampire.pyxres")
pyxel.run(self.update, self.draw)
def update(self):
""" Update """
self.control()
if self.game_mode != MODE_PLAY:
return
self.player.update()
self.overlap_area(self.player)
for monster in self.monsters[:]:
monster.update()
self.overlap_area(monster)
if self.player.contains_center(monster):
self.player.stop()
self.game_mode = MODE_GAME_OVER
pyxel.play(0, 16, loop=False)
for bullet in self.bullets[:]:
bullet.update()
if bullet.is_dead():
self.bullets.remove(bullet)
else:
for monster in self.monsters[:]:
if monster.intersects(bullet):
self.bullets.remove(bullet)
self.append_particle(monster)
self.kick_area(monster)
self.score += 10
break
for particle in self.particles[:]:
particle.update()
if particle.is_dead():
self.particles.remove(particle)
def draw(self):
""" Draw """
pyxel.cls(0)
pyxel.bltm(0, 0, 0, 0, 128, 192, 128, 0)
self.player.draw()
for monster in self.monsters:
monster.draw()
for bullet in self.bullets:
bullet.draw()
for particle in self.particles:
particle.draw()
if self.game_mode == MODE_TITLE:
msg = "SPACE TO PLAY"
pyxel.text(W / 2 - len(msg) * 2, H / 2, msg, 7)
msg = "CONTROL: WASD"
pyxel.text(W / 2 - len(msg) * 2, H / 2 + 10, msg, 7)
elif self.game_mode == MODE_GAME_OVER:
msg = "GAME OVER"
pyxel.text(W / 2 - len(msg) * 2, H / 2, msg, 7)
pyxel.text(10, 10, f"SCORE:{self.score:04}", 7)
def reset(self):
""" Reset stage """
self.player.x = START_X
self.player.y = START_Y
self.monsters = []
for _ in range(64):
x = random.randint(0, W)
y = random.randint(0, H)
item = random.choice(MONSTERS)
monster = sprite.Monster(
x, y,
item["u"],
item["v"],
item["spd"],
item["think_interval"],
self.player
)
if monster.get_distance(self.player) < 24:
continue
monster.move(monster.get_direction(self.player))
self.monsters.append(monster)
self.bullets = []
self.particles = []
def control(self):
""" Control """
if pyxel.btnp(pyxel.KEY_SPACE):
if self.game_mode == MODE_TITLE:
self.game_mode = MODE_PLAY
elif self.game_mode == MODE_GAME_OVER:
self.game_mode = MODE_TITLE
self.reset()
return
if self.game_mode == MODE_PLAY:
if pyxel.btnp(pyxel.KEY_W):
self.player.move(270)
elif pyxel.btnr(pyxel.KEY_W):
self.player.stop()
if pyxel.btnp(pyxel.KEY_A):
self.player.move(180)
elif pyxel.btnr(pyxel.KEY_A):
self.player.stop()
if pyxel.btnp(pyxel.KEY_S):
self.player.move(90)
elif pyxel.btnr(pyxel.KEY_S):
self.player.stop()
if pyxel.btnp(pyxel.KEY_D):
self.player.move(0)
elif pyxel.btnr(pyxel.KEY_D):
self.player.stop()
def overlap_area(self, spr):
""" Screen wrap """
if W < spr.x:
spr.x = 0
if spr.x < 0:
spr.x = W
if H < spr.y:
spr.y = 0
if spr.y < 0:
spr.y = H
def kick_area(self, spr):
""" Force move to screen edge """
if random.randint(0, 1) == 0:
spr.set_center(0, random.randint(0, H))
else:
spr.set_center(random.randint(0, W), 0)
pyxel.play(1, 1, loop=False)
def get_nearest_monster(self):
""" Get nearest monster """
nearest = None
dist_min = 999
for monster in self.monsters:
dist = monster.get_distance(self.player)
if dist < dist_min:
dist_min = dist
nearest = monster
return nearest
def on_shot_event(self, spr):
""" Fire bullet """
if not self.monsters:
return
x, y = spr.get_center()
bullet = sprite.Bullet(x, y, BULLET_SPD)
monster = self.get_nearest_monster()
bullet.move(self.player.get_direction(monster))
self.bullets.append(bullet)
def append_particle(self, spr):
""" Spawn particle """
x, y = spr.get_center()
self.particles.append(sprite.Particle(x, y))
def main():
""" Main entry """
Game()
if __name__ == "__main__":
main()
Result
The game will run like this:
Final Words
Thank you very much for reading.
I hope this series helps you get started with game development. ޱ(ఠ皿ఠ)ว
(If you enjoyed this article, a 👍 would be greatly appreciated!)


Top comments (0)