/pygess

Pygame Essentials. Provides for template classes to make pygame easier to use

Primary LanguagePython

title
Guide: How to use PyGess

Introduction

This document will walk you through the implementation of the PyGess library.

PyGess is a physics engine designed to manage game objects, their interactions, and their updates within a game world. It provides classes and functions to handle entities, game objects, and worlds, along with utility functions to manage these components.

We will cover:

  1. The GameObjects class and its methods.
  2. The World class and its methods.
  3. The entity and MovingEntity classes.
  4. Utility functions for managing worlds and entities.
  5. A test script to demonstrate usage.

GameObjects class

The GameObjects class is responsible for storing and updating a list of entities. It acts like a sprite group but is tailored for entities.

Initialization


The constructor initializes the list of entities.

class GameObjects:
    '''
    # Description
    A class which stores list of Entities. Also updates them.
    Basically a sprite group for entities instead.
    
    # Functions
    ## Init
    ```
    def __init__(self, entities:list) -> None:
        self.entities = entities
    ```
    Initializes entity list.

Update method


The update method ensures all entities in the list are unique and calls their update method.


    ## update
    ```
    def update(self):
        from . import entity as ent
        self.entities = list(set(self.entities))
        for entity in self.entities:
            if isinstance(entity, (ent.Entity)):
                entity.update()
    ```
    Updates all entities in list.
    
    '''
    def __init__(self, entities:list) -> None:
        self.entities = entities
        
    def update(self):
        from . import entity as ent
        self.entities = list(set(self.entities))
        for entity in self.entities:
            if isinstance(entity, (ent.Entity)):
                entity.update()
            
    def append(self, *entity):
        for e in entity:
            if e != None:
                self.entities = list(self.entities)
                self.entities.append(e)

Append method


The append method adds new entities to the list.


    ## update
    ```
    def update(self):
        from . import entity as ent
        self.entities = list(set(self.entities))
        for entity in self.entities:
            if isinstance(entity, (ent.Entity)):
                entity.update()
    ```
    Updates all entities in list.
    
    '''
    def __init__(self, entities:list) -> None:
        self.entities = entities
        
    def update(self):
        from . import entity as ent
        self.entities = list(set(self.entities))
        for entity in self.entities:
            if isinstance(entity, (ent.Entity)):
                entity.update()
            
    def append(self, *entity):
        for e in entity:
            if e != None:
                self.entities = list(self.entities)
                self.entities.append(e)

World class

The World class manages the game world, including gravity, time, and game objects.

Initialization


The constructor initializes the world with gravity, game objects, and other necessary attributes.

class World:
    def __init__(self, grav: tuple, *game_objects) -> None:
        self.gravity = pyg.math.Vector2(grav)
        self.dt = 0
        self.prev_time = time.time()
        self.is_active = False
        
        self.__counter = 0
        self.__counter2 = 0
        
        self.__objects = GameObjects(game_objects)
        self.runtime_obj = copy.deepcopy(self.__objects)
        self.__base_state = self.runtime_obj
        
        worlds.append(self)
        
    def update(self):
        if not self.is_active:
            self.__counter2 = 0
            return
        
        if self.__counter2 == 0:
            self.__reset()
        
        if self.__counter == 0:
            self.__set_runtime()

Update method


The update method handles the world's update logic, including resetting and setting runtime states.


        self.runtime_obj.update()
        
        self.__counter += 1
        self.__counter2 += 1
    
    def add_gameobj(self, *gameobj):
        for obj in gameobj:
            self.__objects.append(obj)
    
    def load_new_object(self, *gameobj):
        for obj in gameobj:
            self.runtime_obj.append(obj)
        
    def update_delta_time(self):

Add and load game objects


The add_gameobj and load_new_object methods add game objects to the world and runtime objects, respectively.


        self.runtime_obj.update()
        
        self.__counter += 1
        self.__counter2 += 1
    
    def add_gameobj(self, *gameobj):
        for obj in gameobj:
            self.__objects.append(obj)
    
    def load_new_object(self, *gameobj):
        for obj in gameobj:
            self.runtime_obj.append(obj)
        
    def update_delta_time(self):

Update delta time


The update_delta_time method updates the delta time for the world.


        if self.is_active: 
            self.dt = min(time.time() - self.prev_time, 0.1)
            self.prev_time = time.time()
            return
        self.dt = get_active_world().dt
        self.dt = min(self.dt, 0.1)
    
    def __set_runtime(self):
        ent = []
        for e in self.__objects.entities:
            if e.rect not in data.all_rects:
                data.all_rects.append(e.rect)
            en = copy.deepcopy(e)
            en.id = uuid.uuid4()
            en.parent = e
            ent.append(copy.deepcopy(en))
        
        self.runtime_obj = GameObjects(ent)
        self.__base_state = GameObjects(copy.deepcopy(ent))
        
    def __reset(self):
        ent = []
        for e in self.__base_state.entities:
            if e.rect not in data.all_rects:
                data.all_rects.append(e.rect)
            ent.append(copy.deepcopy(e))
        
        self.runtime_obj = GameObjects(ent)

Set runtime and reset methods


The __set_runtime and __reset methods manage the runtime state of the world.


        if self.is_active: 
            self.dt = min(time.time() - self.prev_time, 0.1)
            self.prev_time = time.time()
            return
        self.dt = get_active_world().dt
        self.dt = min(self.dt, 0.1)
    
    def __set_runtime(self):
        ent = []
        for e in self.__objects.entities:
            if e.rect not in data.all_rects:
                data.all_rects.append(e.rect)
            en = copy.deepcopy(e)
            en.id = uuid.uuid4()
            en.parent = e
            ent.append(copy.deepcopy(en))
        
        self.runtime_obj = GameObjects(ent)
        self.__base_state = GameObjects(copy.deepcopy(ent))
        
    def __reset(self):
        ent = []
        for e in self.__base_state.entities:
            if e.rect not in data.all_rects:
                data.all_rects.append(e.rect)
            ent.append(copy.deepcopy(e))
        
        self.runtime_obj = GameObjects(ent)

Deactivate method


The _deactivate method deactivates the world.


    def _deactivate(self):
        self.is_active = False

Utility functions

Get active world


The get_active_world function returns the currently active world.

def get_active_world() -> World:
    for world in worlds:
        if world.is_active:
            return world

Debug no active worlds


The _debug_no_active_worlds function counts the number of active worlds.

def _debug_no_active_worlds():
    counter = 0
    
    for world in worlds:
        if world.is_active:
            counter += 1
    
    return counter

Set active world


The set_active_world function sets a given world as active and deactivates the current active world if any.

def set_active_world(world:World):
    if _debug_no_active_worlds() > 0:
        get_active_world()._deactivate()
        
        
    world.is_active = True

Get all worlds


The get_all_worlds function returns all worlds.

def get_all_worlds():
    return worlds

Update delta time for all worlds


The update_delta_time function updates the delta time for all worlds.

def update_delta_time():
    for w in get_all_worlds():
        w.update_delta_time()

Update all worlds


The update_worlds function updates all worlds.

def update_worlds():
    for w in get_all_worlds():
        w.update()

Prefab instance functions


The first_instance_of_prefab_in_world and all_instances_of_prefab_in_world functions find instances of a prefab entity in a world.

def first_instance_of_prefab_in_world(world, prefab_entity) -> 'entity.Entity':
    for e in world.runtime_obj.entities:
        if e.parent.id == prefab_entity.id:
            return e


def all_instances_of_prefab_in_world(world, prefab_entity) -> list:
    instances = []
    for e in world.runtime_obj.entities:
        if e.parent.id == prefab_entity.id:
            instances.append(e)
    
    return instances

Entity class

The entity class represents a game entity with position, dimensions, and optional color or image.

Initialization


The constructor initializes the entity with position, dimensions, and optional color or image.

class Entity(pyg.sprite.DirtySprite):
    def __init__(self, position: tuple, dimensions: tuple, color=None, image_path=None) -> None:
    
        pyg.sprite.DirtySprite.__init__(self)
        self.pos = pyg.math.Vector2(position)
        self.dimensions = dimensions
        self.color = color
        
        self.parent = None
        self.id = uuid.uuid4()

Update method


The update method updates the entity's position, checks for collisions, and updates its sprite group.


    def get_all_obj_colliding_with(self):
        return self._colliding_objects

    def update(self):
        self.active_world = physics.get_active_world()

        self.update_rect()
        self.check_collisions()
        
        if self in self.spr_group:
            self.spr_group.remove(self)
            self.spr_group.update()
            self.spr_group.add(self)
        else:
            self.spr_group.update()
            
        self.spr_group.draw(pyg.display.get_surface())

Collision methods


The update_rect, check_collisions, is_colliding_with, and get_all_obj_colliding_with methods handle collision detection and response.


        self._colliding_objects = []
            
        data.all_rects.append(self.rect)
    
    def update_rect(self):
        index = data.all_rects.index(self.rect)
        data.all_rects.remove(self.rect)
        
        self.rect.topleft = self.pos
        self.rect.size = self.dimensions
        
        data.all_rects.insert(index, self.rect)
    
    def check_collisions(self):
        self._colliding_objects = [r for r in data.all_rects if r != self.rect and self.rect.colliderect(r)]
    
    def is_colliding_with(self, rect):
        return rect in self._colliding_objects

    def get_all_obj_colliding_with(self):
        return self._colliding_objects

    def update(self):
        self.active_world = physics.get_active_world()

        self.update_rect()
        self.check_collisions()
        
        if self in self.spr_group:
            self.spr_group.remove(self)
            self.spr_group.update()
            self.spr_group.add(self)
        else:
            self.spr_group.update()
            
        self.spr_group.draw(pyg.display.get_surface())

MovingEntity class

The MovingEntity class extends entity to include velocity and gravity effects.

Initialization


The constructor initializes the moving entity with position, dimensions, velocity, and optional color or image.

class MovingEntity(Entity):
    def __init__(self, position: tuple, dimensions: tuple, velocity: tuple, color:tuple=None, image_path=None) -> None:
        super().__init__(position, dimensions, color=color, image_path=image_path)
        self.velocity = pyg.math.Vector2(velocity)
        self.orignal_vel = pyg.math.Vector2(velocity)
        self.is_affected_by_gravity = False

    def update(self):
        self.active_world = physics.get_active_world()

Update method


The update method updates the entity's position, checks for collisions, and moves the entity.


        self.update_rect()
        self.check_collisions()
        self.move()
        
        if self in self.spr_group:
            self.spr_group.remove(self)
            self.spr_group.update()
            self.spr_group.add(self)
        else:
            self.spr_group.update()
            
        self.spr_group.draw(pyg.display.get_surface())
        
    def move(self):
        self.pos += self.velocity * self.active_world.dt
        
        if not self.is_affected_by_gravity:
            self.velocity.y = 0
            return
        
        self.velocity += self.active_world.gravity
        
    def set_gravitified(self, bool:bool):
        self.is_affected_by_gravity = bool

Move method


The move method updates the entity's position based on its velocity and gravity.


        self.update_rect()
        self.check_collisions()
        self.move()
        
        if self in self.spr_group:
            self.spr_group.remove(self)
            self.spr_group.update()
            self.spr_group.add(self)
        else:
            self.spr_group.update()
            
        self.spr_group.draw(pyg.display.get_surface())
        
    def move(self):
        self.pos += self.velocity * self.active_world.dt
        
        if not self.is_affected_by_gravity:
            self.velocity.y = 0
            return
        
        self.velocity += self.active_world.gravity
        
    def set_gravitified(self, bool:bool):
        self.is_affected_by_gravity = bool

Set gravity method


The set_gravitified method enables or disables gravity for the entity.


        self.update_rect()
        self.check_collisions()
        self.move()
        
        if self in self.spr_group:
            self.spr_group.remove(self)
            self.spr_group.update()
            self.spr_group.add(self)
        else:
            self.spr_group.update()
            
        self.spr_group.draw(pyg.display.get_surface())
        
    def move(self):
        self.pos += self.velocity * self.active_world.dt
        
        if not self.is_affected_by_gravity:
            self.velocity.y = 0
            return
        
        self.velocity += self.active_world.gravity
        
    def set_gravitified(self, bool:bool):
        self.is_affected_by_gravity = bool

Data and colors

Data module


The data module contains a list of all rectangles for collision detection.

all_rects = []

Colors module


The colors module defines some basic colors.

WHITE = (255, 255, 255)
BLACK = (0, 0, 0)

RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)

Initialization

Init module


The __init__ module imports necessary components and sets the version.

from . import entity
from . import data
from . import colors
from . import physics
from . import entity
import time


__version__ = '1.0.1'

Test script

Here is a test script to demonstrate the usage of PyGess:

import pygess
import time

# Create entities
entity1 = pygess.entity.Entity((50, 50), (10, 10), color=pygess.colors.RED)
entity2 = pygess.entity.MovingEntity((100, 100), (10, 10), (1, 1), color=pygess.colors.BLUE)

# Create a world with gravity
world = pygess.physics.World((0, 9.8), entity1, entity2)

# Set the world as active
pygess.physics.set_active_world(world)

# Main loop
while True:
    pygess.physics.update_worlds()
    time.sleep(0.016)  # Simulate 60 FPS

This script creates a world with two entities, sets the world as active, and continuously updates the world in a loop.

Powered by Swimm