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.

bird

(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()