Tutorial: Red Baron#
In this chapter we will build a Side-Scrolling shoter together, step by step.
The techniques of creating a parallax backgrounds, managing speed and velocity and generating enemies is common in games and after seeing it here you should be able to incorporate it into your own projects.
Based on:
https://github.com/kantel/pygamezero/tree/master/tappyplane
License: Attribution-NonCommercial-ShareAlike 4.0 International
##b Step 1: Create a base framework
Create a base framework: You need a world on which actors can be placed.
Your last line must be world.run()
.
from miniworlds import *
world = World(800, 480)
// your code here
world.run()
Prepare your folder#
You must place images for backgrounds, player and enemies in a directory images
inside your code directory.
my_code
|
|--images
|----images/planered1.png
|----images/background.png
|----images/groundgrass.png
|----images/shipbeige.png
|----images/shipblue.png
|----images/shipgreen.png
|----images/shippink.png
|----images/shipyellow.png
(You can find the images in this repository: miniworlds-cookbook - red baron )
Create backgrounds#
With the following code, you can generate two backgrounds.
You need two backgrounds to generate a infinite scrolling-effect:
Generate this background next to each other so they are filling the complete screen:
back0 = Actor()
back0.add_costume("background")
back0.size = world.width, world.height
back1 = Actor(world.width, 0)
back1.size = world.width, world.height
back1.add_costume("background")
backs = [back0, back1]
Now we animate the backgrounds:
Both backgrounds move constantly from left to right.
if a background leaves the left side of screen, it will be be moved to the right side.
@world.register
def act(self):
for back in backs:
back.x -= 1
if back.x <= - world.width:
back.x = world.width
for ground in grounds:
ground.x -= 2
if ground.x <= - world.width:
ground.x = world.width
This will generate a *infinite scrolling background.
Step 2: Create a plane class#
Create a plane class#
Create a plane class as template for your player.
class Plane(Actor):
def on_setup(self):
self.add_costume("planered1")
Create a plane instance#
At the end of your code before world.run()
create an instance of this template-class:
plane = Plane(100, world.height / 2)
Add physics#
Now we add physics to the plane-Actor.
Modify the on_setup()
-Method of Plane:
def on_setup(self):
self.add_costume("planered1")
self.gravity = 0.1
self.velocity_y = 0
velocity
describes the current velocity. The Plane will movevelocity
steps in y-direction.gravity
describes the current gravity: The y-velocity of player will be reduced by gravity in every frame.
Simulate physics#
Physics can be simulated in the act()
-Method of Actor:
def act(self):
self.velocity_y += self.gravity
self.velocity_y *= 0.9 # friction
self.y += self.velocity_y
This will add the velocity to the y-coordinates of player. The gravity constantly reduces the velocity.
Line 3 will smoothen the simulation.
Add force on key press#
You must use the on_key_down
event to add a force to the actor:
def on_key_down_w(self):
self.velocity_y -= 5
Step 3: Add enemies#
Import randint and choice for randomization at the beginning of your code:
from random import randint, choice
Add an enemy class#
Add an enemy class as template:
class Enemy(Actor):
def on_setup(self):
enemy.add_costume(choice(enemyships))
def reset(self):
self.x = randint(world.width + 50, world.width + 500)
self.y = randint(25, world.height - 85)
The on_setup-Method adds a random costume to the actor.
Add enemy instances from class-template#
Add mutliple enemies to the world with a for-loop at the end of your code before world.run().
enemies = []
for _ in range(10):
enemy = Enemy()
enemy.reset()
enemies.append(enemy)
Move enemies#
Modify the on_setup
-Method:
def on_setup(self):
self.add_costume(choice(enemyships))
self.speed = -1.5
self.speed
defines how many steps the Enemy-Actor will move in x-direction each frame.
Add an act()-Method
:
def act(self):
self.x += self.speed
if self.x <= -self.width:
self.reset()
Step 3: Add shooting#
Add a Bullet-Class to your Actor:
class Bullet(Actor):
def on_setup(self):
self.add_costume("laserred")
self.x = plane.x
self.y = plane.y
self.speed = 25
self.fire = False
def act(self):
self.x += self.speed
def on_detecting_enemy(self, enemy):
enemy.reset()
def on_detecting_not_on_world(self):
self.remove()
The Bullet has sensors
- With the method on_detecting_enemy
it can detect all Actors with of class `Enemey’ and call their reset()-Methods.
With on_detecting_not_on_world
it an detect, if Bullet is on the world.
Complete Code:#
from miniworlds import *
from random import randint, choice
# based on https://github.com/kantel/pygamezero/tree/master/tappyplane
world = World(800, 480)
left = world.width / 2
bottom = world.height / 2
bottomground = world.height - 35
no_enemies = 10
enemyships = ["shipbeige", "shipblue", "shipgreen", "shippink", "shipyellow"]
# Add backgrounds
back0 = Actor()
back0.add_costume("background")
back0.size = world.width, world.height
back1 = Actor(world.width, 0)
back1.size = world.width, world.height
back1.add_costume("background")
backs = [back0, back1]
ground0 = Actor((0, bottomground))
ground0.add_costume("groundgrass")
ground0.width = world.width
ground0.costume.is_scaled = True
ground1 = Actor((world.width, bottomground))
ground1.add_costume("groundgrass")
ground1.width = world.width
ground1.costume.is_scaled = True
grounds = [ground0, ground1]
groundlevel = world.height - 85
@world.register
def act(self):
for back in backs:
back.x -= 1
if back.x <= - world.width:
back.x = world.width
for ground in grounds:
ground.x -= 2
if ground.x <= - world.width:
ground.x = world.width
class Plane(Actor):
def on_setup(self):
self.add_costume("planered1")
self.gravity = 0.1
self.velocity_y = 0
self.fire = False
def act(self):
self.velocity_y += self.gravity
self.velocity_y *= 0.9 # friction
self.y += self.velocity_y
if self.y >= groundlevel:
self.y = groundlevel
self.velocity_y = 0
if self.y <= 20:
self.y = 20
self.velocity_y = 0
def on_key_down_w(self):
self.velocity_y -= 5
def on_key_down_d(self):
if not self.fire:
self.fire = True
bullet = Bullet()
@timer(frames=30)
def downtime():
self.fire = False
class Bullet(Actor):
def on_setup(self):
self.add_costume("laserred")
self.x = plane.x
self.y = plane.y
self.speed = 25
self.fire = False
def act(self):
self.x += self.speed
def on_detecting_enemy(self, enemy):
enemy.reset()
def on_detecting_not_on_world(self):
self.remove()
class Enemy(Actor):
def on_setup(self):
self.add_costume(choice(enemyships))
self.speed = -1.5
def reset(self):
self.x = randint(world.width + 50, world.width + 500)
self.y = randint(25, groundlevel)
def act(self):
self.x += self.speed
if self.x <= -self.width:
self.reset()
plane = Plane((100, world.height / 2))
enemies = []
for _ in range(no_enemies):
enemy = Enemy()
enemy.reset()
enemies.append(enemy)
world.run()