Source code for rubato.game

"""
The main game module. It controls everything in the game.
"""
from __future__ import annotations
from typing import TYPE_CHECKING, Dict
import sdl2, sdl2.sdlttf
import sys

from . import Time, Display, Debug, Radio, Events, Font, PrintError, Camera, IdError, Draw

if TYPE_CHECKING:
    from . import Scene

[docs]class GameProperties(type): """ Defines static property methods for Game. Warning: This is only a metaclass for the class below it, so you wont be able to access this class. To use the property methods here, simply access them as you would any other Game property. """ @property def state(cls) -> int: # test: skip """ The state of the game. The game states are:: Game.RUNNING Game.STOPPED Game.PAUSED """ return cls._state @state.setter def state(cls, new: int): # test: skip cls._state = new if cls._state == Game.STOPPED: sdl2.SDL_PushEvent(sdl2.SDL_Event(sdl2.SDL_QUIT)) @property def camera(cls) -> Camera: # test: skip """ A shortcut getter allowing easy access to the current camera. This is a get-only property. Note: Returns a pointer to the current camera object. This is so you can access/change the current camera properties faster, but you'd still need to use :func:`Game.current.camera <rubato.struct.scene.Scene.camera>` to access the camera directly. Returns: Camera: The current scene's camera """ return cls.current.camera
# THIS IS A STATIC CLASS
[docs]class Game(metaclass=GameProperties): """ The main game class. Attributes: name (str): The title of the game window. debug (bool): Whether to use debug-mode. show_fps (bool): Whether to show fps. debug_font (Font): What font to draw debug text in. """ RUNNING = 1 STOPPED = 2 PAUSED = 3 name: str = "" debug: bool = False show_fps: bool = False debug_font: Font _state: int = STOPPED _initialized = False _scenes: Dict[str, Scene] = {} _scene_id : int = 0 _current: str = "" @classmethod @property def current(cls) -> Scene: # test: skip """ The current scene. Get-only. Returns: The current scene. """ return cls._scenes.get(cls._current)
[docs] @classmethod def set_scene(cls, scene_id: str): # test: skip """ Changes the current scene. Takes effect on the next frame. Args: scene_id (str): The id of the new scene. """ cls._current = scene_id
@classmethod def _add(cls, scene: Scene): # test: skip """ Add a scene to the game. Also set the current scene if this is the first added scene. Args: scene (Scene): The scene to add. scene_id (str): The id of the scene. Raises: IdError: The given scene id is already used. """ if scene.name is None: scene._id = "scene" + str(cls._scene_id) # pylint: disable=protected-access if scene.name in cls._scenes: raise IdError(f"A scene with name '{scene.name}' has already been added.") if not cls._scenes: cls.set_scene(scene.name) cls._scenes[scene.name] = scene cls._scene_id += 1
[docs] @classmethod def quit(cls): # test: skip """Quit the game and close the python process.""" Radio.broadcast(Events.EXIT) cls.state = cls.STOPPED sys.stdout.flush() sdl2.sdlttf.TTF_Quit() sdl2.SDL_Quit() sys.exit(0)
[docs] @classmethod def start(cls): # test: skip """ Starts the main game loop. Called automatically by :meth:`rubato.begin`. """ cls.state = cls.RUNNING try: cls.loop() except KeyboardInterrupt: cls.quit() except PrintError as e: sys.stdout.flush() raise e except (Exception,) as e: sys.stdout.flush() raise type(e)( str(e) + "\nRubato Error-ed. Was it our fault? Issue tracker: " "https://github.com/rubatopy/rubato/issues" ).with_traceback(sys.exc_info()[2]) finally: sys.stdout.flush()
[docs] @classmethod def loop(cls): # test: skip """ Rubato's main game loop. Called automatically by :meth:`rubato.Game.start`. """ while True: # start timing the update loop Time._frame_start = Time.now() # pylint: disable= protected-access # Pump SDL events Radio.pump() # Event handling if Radio.handle(): cls.quit() # process delayed calls Time.process_calls() cls.update() curr = cls.current if curr: # pylint: disable=using-constant-test if cls.state == Game.PAUSED: # process user set pause update curr.private_paused_update() else: # normal update curr.private_update() # fixed update Time.physics_counter += Time.delta_time while Time.physics_counter >= Time.fixed_delta: if cls.state != Game.PAUSED: curr.private_fixed_update() Time.physics_counter -= Time.fixed_delta curr.private_draw() else: Draw.clear() cls.draw() Draw.dump() if cls.show_fps: Debug.draw_fps(cls.debug_font) # update renderers Display.renderer.present() # use delay to cap the fps if need be if Time.capped: delay = Time.normal_delta - (1000 * Time.delta_time) if delay > 0: sdl2.SDL_Delay(int(delay)) # dont allow updates to occur more than once in a millisecond # this will likely never occur but is a failsafe while Time.now() == Time.frame_start: # pylint: disable= comparison-with-callable sdl2.SDL_Delay(1) # clock the time the update call took Time.delta_time = (Time.now() - Time.frame_start) / 1000 # pylint: disable= comparison-with-callable
[docs] @staticmethod def update(): # test: skip """An overrideable method for updating the game. Called once per frame, before the current scene updates.""" pass
[docs] @staticmethod def draw(): # test: skip """An overrideable method for drawing the game. Called once per frame, after the current scene draws.""" pass