Getting Started 2 - ZackWilde27/Z3dPy GitHub Wiki
So the earlier page went over what makes a scene, but now it's time to make a real game.
I'll be showing how to make a Flappy Bird clone. The models and the finished script can be found in the examples folder included in the zip or in the repo.

(This was recorded before I added coloured shading to the engine.)
So, set up a project like normal, except we'll need random.
import z3dpy as zp
import random as rand
myCamera = zp.Cam([0, 0, 3])
zp.screenSize = (1280, 720)
Next we create all the Things.
"Things" are objects in the game world, they can hold multiple meshes, support the built-in physics/collisions, and more.
To make objects with multiple colours, split up the mesh into pieces, colour them separately, then combine them into a Thing.
# Use the LoadMesh function to load an OBJ file
birdBody = zp.LoadMesh("mesh/bird.obj")
birdBeak = zp.LoadMesh("mesh/beak.obj")
# Set the colour of each part
zp.MeshSetColour(birdBody, [255, 214, 91])
zp.MeshSetColour(birdBeak, [255, 10, 18])
# Group them into one Thing
bird = zp.Thing([birdBody, birdBeak], [0, 0, 5])
To enable physics for our bird, give it a physics body with:
zp.ThingSetupPhysics(bird)
Now we define the rest of our objects
# Ground
planeMesh = zp.LoadMesh("plane.obj")
plane = zp.Thing([planeMesh], [0, 5, 0])
# Pipe
pipeMesh = zp.LoadMesh("pipe.obj")
otherPipeMesh = zp.LoadMesh("pipe.obj")
zp.MeshSetRot(otherPipeMesh, [0, 0, 180])
# Setting colour
zp.MeshSetColour(pipeMesh, [0, 255, 0])
zp.MeshSetColour(otherPipeMesh, [0, 255, 0])
pipe = zp.Thing([pipeMesh, otherPipeMesh], [10, 0, 5])
# Pipe Pair
def ResetPipePair(z):
zp.ThingSetPos(pipe, [10, z, 5]))
ResetPipePair(-1)
Now for the draw loop, using the RasterThings() function, making sure to include HandlePhysicsFloor() for our bird.
# Raster Loop
while True:
clock.tick(30)
# Filling with a sky colour
screen.fill("#88E5FF")
# HandlePhysicsFloor(thingList, floorHeight)
zp.HandlePhysicsFloor([bird], 5)
zp.SetInternalCam(myCamera)
# Draw the screen
for tri in zp.RasterThings([bird, plane, pipe]):
normal = zp.TriGetNormal(tri)
# Taking the facing direction of the triangle and averaging y and z, so it's like an angled light source
colour = zp.VectorMulF(zp.TriGetColour(tri), (-normal[1] + -normal[2]) * 0.5)
pygame.draw.polygon(screen, colour, ((tri[0][0], tri[0][1]), (tri[1][0], tri[1][1]), (tri[2][0], tri[2][1]))))
# Update display
pygame.display.flip()
Inside this loop, before rendering the screen, we need to add game logic.
First of course, a jump button for the bird.
# Input
keys = pygame.key.get_pressed()
# Jumping
if keys[pygame.K_e]:
zp.ThingSetVelocityY(bird, -12)
zp.HandlePhysicsFloor([bird], 5)
After the bird has moved, follow it with the camera.
zp.CamChase(myCamera, zp.VectorSub(zp.ThingGetPos(bird), [0, 0, 5]))
# Make sure the camera continues looking forward when it moves
zp.CamSetTargetDir(myCamera, [0, 0, 1])
# Update the internal camera to the new location
zp.SetInternalCam(myCamera)
Probably should have included this before the camera move, but next is tilting the bird by it's velocity to make it feel more animated.
# Tilting Bird
zp.ThingSetRot(bird, [0, 0, min(zp.ThingGetVelocityY(bird) * 2, 40)])
Lastly, move the pipes across the screen, and reset them if they leave the edge of the screen.
# Moving Pipe Pairs
zp.ThingSubPos(pipe, [0.2, 0, 0])
if zp.ThingGetPosX(pipe) <= -8:
SpawnPipePair(rand.random() * 3 + 1)
Final Script:
import pygame
import z3dpy as zp
import random as rand
# PyGame Stuff
pygame.init()
screen = pygame.display.set_mode((1280, 720))
clock = pygame.time.Clock()
zp.screenSize = (1280, 720)
myCamera = zp.Camera([0, 0, 0])
# Use the LoadMesh function to load an OBJ file
birdBody = zp.LoadMesh("mesh/bird.obj")
birdBeak = zp.LoadMesh("mesh/beak.obj")
# Set the colour of each part
zp.MeshSetColour(birdBody, [255, 214, 91])
zp.MeshSetColour(birdBeak, [255, 10, 18])
# Group them into one Thing
bird = zp.Thing([birdBody, birdBeak], [0, 0, 5])
zp.ThingSetupPhysics(bird)
# Ground
planeMesh = zp.LoadMesh("plane.obj")
plane = zp.Thing([planeMesh], [0, 5, 0])
# Pipe
pipeMesh = zp.LoadMesh("pipe.obj")
otherPipeMesh = zp.LoadMesh("pipe.obj")
zp.MeshSetRot(otherPipeMesh, [0, 0, 180])
# Setting the colour to green
zp.MeshSetColour(pipeMesh, [0, 255, 0])
zp.MeshSetColour(pipMesh, [0, 255, 0])
pipe = zp.Thing([pipeMesh, otherPipeMesh], [10, 0, 5])
# Pipe Pair
def ResetPipePair(z):
zp.ThingSetPos(pipe, [10, z, 5]))
ResetPipePair(-1)
# Raster Loops
done = False
while not done:
# PyGame Stuff
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
clock.tick(30)
# Filling with a sky colour
screen.fill("#88E5FF")
# Input
keys = pygame.key.get_pressed()
if keys[pygame.K_e]:
zp.ThingSetVelocityY(bird, -12)
zp.HandlePhysics([bird], 5)
# Follow the bird
zp.CamChase(myCamera, zp.VectorSub(zp.ThingGetPos(bird), [0, 0, 5]))
zp.CamSetTargetDir(myCamera, [0, 0, 1])
zp.SetInternalCam(myCamera)
# Tilting Bird
zp.ThingSetRot(bird, [0, 0, min(zp.ThingGetVelocityY(bird) * 2, 40)])
# Moving Pipe Pairs
zp.ThingSubPos(pipe, [0.2, 0, 0])
if zp.ThingGetPosX(pipe) <= -8:
SpawnPipePair(rand.random() * 3 + 1)
# Drawing
for tri in zp.RasterThings([bird, plane, pipe]):
normal = zp.TriGetNormal(tri)
pygame.draw.polygon(screen, zp.VectorMaxF(zp.VectorMul(normal, -255), ((tri[0][0], tri[0][1]), (tri[1][0], tri[1][1]), (tri[2][0], tri[2][1]))))
# Update display
pygame.display.flip()