Posts
Search
Contact
Cookies
About
RSS

Python and raylib - closer look at 2D

Added 28 Sep 2020, 2:05 a.m. edited 18 Jun 2023, 1:12 a.m.

Looking closer at raylib's 2d capabilities let get some stuff set up

from enum import Enum

# these are the different movement states for the player
class Moving(Enum):
    NOT = 0
    UP = 1
    DOWN = 2
    LEFT = 3
    RIGHT = 4
    DONE = 5
a sprite sheet from kenny.nl (highly recommended source of media)

A small "Enum" class allows us to create a simple state machine this will help up keep track of what is happening with our player character.

def getTxFromTile(source, x:int , y:int):
    ii = rl.ImageFromImage(source, [x*64,y*64,64,64])
    tx = rl.LoadTextureFromImage(ii)
    rl.UnloadImage(ii)
    return tx

cutting the "sprites" that I want out of an image, if fairly straight forward

sheet = rl.LoadImage(b"data/sokoban_tilesheet.png")

plTx = [
        getTxFromTile(sheet,0,5),getTxFromTile(sheet,1,5),getTxFromTile(sheet,2,5),
        getTxFromTile(sheet,3,5),getTxFromTile(sheet,4,5),getTxFromTile(sheet,5,5),
        getTxFromTile(sheet,0,7),getTxFromTile(sheet,1,7),getTxFromTile(sheet,2,7),
        getTxFromTile(sheet,3,7),getTxFromTile(sheet,4,7),getTxFromTile(sheet,5,7),
            ]


rl.UnloadImage(sheet)

Notice that once the textures are created and uploaded to the GPU you don't need to keep hold of the image...

Ideally I should have made a class of the player chararcter but as there is just one its often easiest just treating it as a special case, we'll look later a class to handle other entities in the game late - ie crates!

def updatePlayer(laFr, lisMoving, lmoveCount):
    f = 55
    
    # if moving update the move counter
    if lisMoving != Moving.DONE and lisMoving != Moving.NOT:
        f += laFr
        lmoveCount -= 1
        if lmoveCount == 0:
            lisMoving = Moving.DONE

This is the first part of updating the player, we have a move counter as we want to restrict the player to a grid of 64x64 pixel squares. A single press of a movement key will move the player till the move counter runs out as it reaches the next square

            
    # if not moving check for movement key presses
    if lisMoving == Moving.DONE or lisMoving == Moving.NOT:
        lisMoving = Moving.DONE
        if rl.IsKeyDown(rl.KEY_UP):
            lisMoving = Moving.UP
            lmoveCount = 64
        if rl.IsKeyDown(rl.KEY_DOWN):
            lisMoving = Moving.DOWN
            lmoveCount = 64
        if rl.IsKeyDown(rl.KEY_RIGHT):
            lisMoving = Moving.RIGHT
            lmoveCount = 64
        if rl.IsKeyDown(rl.KEY_LEFT):
            lisMoving = Moving.LEFT
            lmoveCount = 64

If we're not moving just check for key presses, notice the move counter gets set as does a direction.

    
    if lisMoving == Moving.DOWN:
        playerPos.y += 1
    if lisMoving == Moving.UP:
        playerPos.y -= 1
        f += 3
    if lisMoving == Moving.RIGHT:
        playerPos.x += 1
        f += 22
    if lisMoving == Moving.LEFT:
        playerPos.x -= 1
        f += 25
    
    return f, lisMoving, lmoveCount

finally depending on our movement state we can alter our position, notice the animation frame is off set depending on our direction. also note that this function is returning multiple values, which as mentioned might have been better as members of a class.

Here's the complete code for our simple animated sprite

#!/usr/bin/env python3

from raylib.static import rl, ffi
from raylib.colors import *

from enum import Enum

# these are the different movement states for the player
class Moving(Enum):
    NOT = 0
    UP = 1
    DOWN = 2
    LEFT = 3
    RIGHT = 4
    DONE = 5

    
def updatePlayer(laFr, lisMoving, lmoveCount):
    f = 55
    
    # if moving update the move counter
    if lisMoving != Moving.DONE and lisMoving != Moving.NOT:
        f += laFr
        lmoveCount -= 1
        if lmoveCount == 0:
            lisMoving = Moving.DONE
            
    # if not moving check for movement key presses
    if lisMoving == Moving.DONE or lisMoving == Moving.NOT:
        lisMoving = Moving.DONE
        if rl.IsKeyDown(rl.KEY_UP):
            lisMoving = Moving.UP
            lmoveCount = 64
        if rl.IsKeyDown(rl.KEY_DOWN):
            lisMoving = Moving.DOWN
            lmoveCount = 64
        if rl.IsKeyDown(rl.KEY_RIGHT):
            lisMoving = Moving.RIGHT
            lmoveCount = 64
        if rl.IsKeyDown(rl.KEY_LEFT):
            lisMoving = Moving.LEFT
            lmoveCount = 64
    
    if lisMoving == Moving.DOWN:
        playerPos.y += 1
    if lisMoving == Moving.UP:
        playerPos.y -= 1
        f += 3
    if lisMoving == Moving.RIGHT:
        playerPos.x += 1
        f += 22
    if lisMoving == Moving.LEFT:
        playerPos.x -= 1
        f += 25
    
    return f, lisMoving, lmoveCount

def getTxFromSheet(source, x:int , y:int):
    ii = rl.ImageFromImage(source, [x*64,y*64,64,64])
    tx = rl.LoadTextureFromImage(ii)
    rl.UnloadImage(ii)
    return tx



#// Initialization
#//--------------------------------------------------------------------------------------
screenWidth = 800;
screenHeight = 450;

rl.SetConfigFlags(rl.FLAG_MSAA_4X_HINT| rl.FLAG_WINDOW_RESIZABLE)  # Enable Multi Sampling Anti Aliasing 4x (if available)

rl.InitWindow(screenWidth, screenHeight, b"step 2")

sheet = rl.LoadImage(b"data/sokoban_tilesheet.png")

# load in all the tiles...
tiles = []
for y in range(0,8):
    for x in range(0,11):
        tiles.append(getTxFromSheet(sheet,x,y))
                     
rl.UnloadImage(sheet)

frame = 0
aFr = 0

isMoving = Moving.NOT
moveCount = 0
rl.SetTargetFPS(60)
playerPos = ffi.new("struct Vector2 *",[ screenWidth/2, screenHeight/2])

while not rl.WindowShouldClose():            #// Detect window close button or ESC key
    #// Update
    #//----------------------------------------------------------------------------------

    frame+=1
    if not(frame % 6):    # every 6th frame
        aFr+=1            # increment player frame
        if aFr == 3:      # does it need to wrap (return to zero)
            aFr=0

    # update the player tells us the animation frame for the player
    # movement status and the count through the move
    playerFrame, isMoving, moveCount = updatePlayer(aFr, isMoving, moveCount)
    
    #//----------------------------------------------------------------------------------

    #// Draw
    #//----------------------------------------------------------------------------------
    rl.BeginDrawing()
    rl.ClearBackground(RAYWHITE)

    # draw the player
    rl.DrawTexture(tiles[playerFrame], int(playerPos.x), int(playerPos.y), WHITE)

    if rl.IsKeyDown(rl.KEY_SPACE):
        i=0
        for y in range(0,8):
            for x in range(0,11):
                rl.DrawTexture(tiles[i], x*66, y*48, [255,255,255,128])
                rl.DrawText(rl.TextFormat(b"i: %i",ffi.cast("int", i)), x*66, y*48, 20, BLACK)
                i+=1
            
        
    rl.DrawFPS(10, 10)
    #rl.DrawText(f"Frame: {int(frame)}".encode('UTF-8') , 10, 28, 20, MAROON)
    rl.DrawText(rl.TextFormat(b"Frame: %i",ffi.cast("int", frame)), 10, 30, 20, MAROON)
    rl.DrawText(rl.TextFormat(b"a fr : %i",ffi.cast("int", aFr)), 10, 60, 20, MAROON)

    rl.EndDrawing()

for i in tiles:
    rl.UnloadTexture(i)