pyg_engine

pyg_engine - A Python game engine with Rust-powered native performance.

 1"""
 2pyg_engine - A Python game engine with Rust-powered native performance.
 3"""
 4
 5from pyg_engine.engine import DrawCommand, Engine, EngineHandle, Input, UpdateContext
 6
 7try:
 8    from pyg_engine.pyg_engine_native import (
 9        Vec2,
10        Vec3,
11        Color,
12        Time,
13        GameObject,
14        MeshComponent,
15        TransformComponent,
16        CameraAspectMode,
17        MouseButton,
18        Keys,
19        version as _version_func,
20    )
21    # Expose version as a module-level attribute (from native binary)
22    version = _version_func()  # type: ignore
23except ImportError:
24    # If native module isn't built yet, provide a helpful error message
25    Vec2 = None  # type: ignore
26    Vec3 = None  # type: ignore
27    Color = None  # type: ignore
28    Time = None  # type: ignore
29    GameObject = None  # type: ignore
30    MeshComponent = None  # type: ignore
31    TransformComponent = None  # type: ignore
32    CameraAspectMode = None  # type: ignore
33    MouseButton = None  # type: ignore
34    Keys = None  # type: ignore
35    version = None  # type: ignore
36
37__version__ = "1.2.0"
38__author__ = "Aram Aprahamian"
39__description__ = "A Python game engine with Rust-powered native performance"
40
41__all__ = [
42    "Engine",
43    "EngineHandle",
44    "DrawCommand",
45    "Input",
46    "UpdateContext",
47    "Vec2",
48    "Vec3",
49    "Color",
50    "Time",
51    "GameObject",
52    "MeshComponent",
53    "TransformComponent",
54    "CameraAspectMode",
55    "MouseButton",
56    "Keys",
57    "version",
58]
class Engine:
 585class Engine:
 586    """
 587    Main game engine class providing core functionality with structured logging.
 588    
 589    This class wraps the Rust implementation and provides a Python-friendly API
 590    with full access to the tracing-based logging system.
 591    
 592    Attributes:
 593        version (str): The engine version number.
 594    
 595    Example:
 596        Basic usage with console logging:
 597        >>> engine = Engine()
 598        >>> engine.log_info("Engine started")
 599        >>> engine.log_warn("Low memory warning")
 600        >>> engine.log_error("Failed to load resource")
 601        
 602        With file logging enabled:
 603        >>> engine = Engine(
 604        ...     enable_file_logging=True,
 605        ...     log_directory="./logs",
 606        ...     log_level="DEBUG"
 607        ... )
 608        >>> engine.log_debug("Debug information")
 609    """
 610    
 611    def __init__(
 612        self,
 613        enable_file_logging: bool = False,
 614        log_directory: Optional[str] = None,
 615        log_level: Optional[str] = None,
 616    ) -> None:
 617        """
 618        Initialize a new Engine instance with optional logging configuration.
 619        
 620        Args:
 621            enable_file_logging: Enable logging to files (default: False).
 622                Files are rotated daily and stored in the log directory.
 623            log_directory: Directory path for log files (default: "./logs").
 624                Only used if enable_file_logging is True.
 625            log_level: Minimum log level to display. Options:
 626                - "TRACE": Most verbose, includes all logs
 627                - "DEBUG": Debug and higher
 628                - "INFO": Info and higher (default)
 629                - "WARN": Warnings and errors only
 630                - "ERROR": Errors only
 631        
 632        Example:
 633            >>> # Console only with INFO level (default)
 634            >>> engine = Engine()
 635            
 636            >>> # Enable file logging with DEBUG level
 637            >>> engine = Engine(
 638            ...     enable_file_logging=True,
 639            ...     log_directory="./my_logs",
 640            ...     log_level="DEBUG"
 641            ... )
 642        """
 643        self._engine = _RustEngine(
 644            enable_file_logging=enable_file_logging,
 645            log_directory=log_directory,
 646            log_level=log_level,
 647        )
 648        self._input = Input(self)
 649        self._runtime_state = _RUNTIME_STATE_IDLE
 650        self._window_icon_path: Optional[str] = None
 651    
 652    @property
 653    def input(self) -> Input:
 654        """
 655        Get the input manager for the engine.
 656        
 657        Returns:
 658            Input: The input manager instance.
 659        """
 660        return self._input
 661
 662    @property
 663    def is_running(self) -> bool:
 664        """Return whether the engine is currently running in any loop mode."""
 665        return self._runtime_state != _RUNTIME_STATE_IDLE
 666
 667    def _ensure_not_running(self, entrypoint_name: str) -> None:
 668        if self._runtime_state != _RUNTIME_STATE_IDLE:
 669            raise RuntimeError(
 670                f"Cannot call {entrypoint_name} while engine is already running "
 671                f"in '{self._runtime_state}' mode."
 672            )
 673
 674    def get_handle(self) -> EngineHandle:
 675        """
 676        Get a thread-safe handle to the engine that can be passed to background threads.
 677        
 678        Returns:
 679            EngineHandle: A handle used to queue commands from other threads.
 680        """
 681        return EngineHandle(self._engine.get_handle())
 682    
 683    def log(self, message: str) -> None:
 684        """
 685        Log a message at INFO level (default log method).
 686        
 687        This is an alias for log_info() for backward compatibility.
 688        
 689        Args:
 690            message: The message to log.
 691        """
 692        self._engine.log(message)
 693    
 694    def log_trace(self, message: str) -> None:
 695        """
 696        Log a message at TRACE level (most verbose).
 697        
 698        Use for very detailed debugging information.
 699        Only visible when log level is set to TRACE.
 700        
 701        Args:
 702            message: The message to log.
 703        """
 704        self._engine.log_trace(message)
 705    
 706    def log_debug(self, message: str) -> None:
 707        """
 708        Log a message at DEBUG level.
 709        
 710        Use for debugging information that's useful during development.
 711        Visible when log level is DEBUG or TRACE.
 712        
 713        Args:
 714            message: The message to log.
 715        """
 716        self._engine.log_debug(message)
 717    
 718    def log_info(self, message: str) -> None:
 719        """
 720        Log a message at INFO level.
 721        
 722        Use for general informational messages about engine operation.
 723        This is the default log level.
 724        
 725        Args:
 726            message: The message to log.
 727        """
 728        self._engine.log_info(message)
 729    
 730    def log_warn(self, message: str) -> None:
 731        """
 732        Log a message at WARN level.
 733        
 734        Use for warnings that don't prevent operation but indicate
 735        potential issues.
 736        
 737        Args:
 738            message: The message to log.
 739        """
 740        self._engine.log_warn(message)
 741    
 742    def log_error(self, message: str) -> None:
 743        """
 744        Log a message at ERROR level.
 745        
 746        Use for errors that may affect engine operation.
 747        
 748        Args:
 749            message: The message to log.
 750        """
 751        self._engine.log_error(message)
 752
 753    def set_window_title(self, title: str) -> None:
 754        """
 755        Set the window title.
 756        
 757        Args:
 758            title: The new window title.
 759        """
 760        self._engine.set_window_title(title)
 761
 762    def set_window_icon(self, icon_path: str) -> None:
 763        """
 764        Set the window icon from an image file path.
 765
 766        The path is remembered and will be reused for future `run()` /
 767        `start_manual()` calls unless overridden per call.
 768        """
 769        self._window_icon_path = icon_path
 770        self._engine.set_window_icon(icon_path)
 771
 772    def get_display_size(self) -> tuple[int, int]:
 773        """Get the current display size (window client size) in pixels."""
 774        return self._engine.get_display_size()
 775
 776    def start_manual(
 777        self,
 778        title: str = "PyG Engine",
 779        width: int = 1280,
 780        height: int = 720,
 781        resizable: bool = True,
 782        background_color: Optional[Any] = None,
 783        vsync: bool = True,
 784        redraw_on_change_only: bool = True,
 785        show_fps_in_title: bool = False,
 786        icon_path: Optional[str] = None,
 787        min_width: Optional[int] = None,
 788        min_height: Optional[int] = None,
 789    ) -> None:
 790        """
 791        Start the engine in manual-loop mode without entering a blocking loop.
 792
 793        This mode is for advanced use cases where Python controls the frame loop
 794        manually using:
 795        `poll_events()`, `update()`, and `render()`.
 796
 797        Raises:
 798            RuntimeError: If the engine is already running in another loop mode.
 799
 800        Args:
 801            title: Window title.
 802            width: Initial window width.
 803            height: Initial window height.
 804            resizable: Whether the window can be resized.
 805            background_color: Optional `pyg_engine.Color`.
 806            vsync: Enable/disable vertical sync.
 807            redraw_on_change_only: When True (default), only redraw on scene changes.
 808            show_fps_in_title: When True, appends current FPS to window title.
 809            icon_path: Optional icon file path. If omitted, uses the most recent
 810                `set_window_icon(...)` value when present, otherwise the built-in
 811                default icon.
 812            min_width: Minimum window width in pixels (default: 640).
 813            min_height: Minimum window height in pixels (default: 480).
 814        """
 815        self._ensure_not_running("start_manual()")
 816        resolved_icon_path = (
 817            icon_path if icon_path is not None else self._window_icon_path
 818        )
 819        self._engine.initialize(
 820            title=title,
 821            width=width,
 822            height=height,
 823            resizable=resizable,
 824            background_color=background_color,
 825            vsync=vsync,
 826            redraw_on_change_only=redraw_on_change_only,
 827            show_fps_in_title=show_fps_in_title,
 828            icon_path=resolved_icon_path,
 829            min_width=min_width,
 830            min_height=min_height,
 831        )
 832        self._runtime_state = _RUNTIME_STATE_MANUAL
 833
 834    def poll_events(self) -> bool:
 835        """
 836        Poll events from the window system.
 837        
 838        Returns:
 839            bool: True if the loop should continue, False if exit requested.
 840        """
 841        should_continue = self._engine.poll_events()
 842        if not should_continue and self._runtime_state in (
 843            _RUNTIME_STATE_MANUAL,
 844            _RUNTIME_STATE_RUNNING_CALLBACK,
 845        ):
 846            self._runtime_state = _RUNTIME_STATE_IDLE
 847        return should_continue
 848
 849    def update(self) -> None:
 850        """Run a single update step."""
 851        self._engine.update()
 852
 853    def render(self) -> None:
 854        """Render a single frame."""
 855        self._engine.render()
 856
 857    def run(
 858        self,
 859        title: str = "PyG Engine",
 860        width: int = 1280,
 861        height: int = 720,
 862        resizable: bool = True,
 863        background_color: Optional[Any] = None,
 864        vsync: bool = True,
 865        redraw_on_change_only: bool = True,
 866        show_fps_in_title: bool = False,
 867        icon_path: Optional[str] = None,
 868        min_width: Optional[int] = None,
 869        min_height: Optional[int] = None,
 870        *,
 871        update: Optional[Callable[..., object]] = None,
 872        max_delta_time: Optional[float] = 0.1,
 873        user_data: Any = None,
 874    ) -> None:
 875        """
 876        Run the engine and start the frame loop.
 877
 878        Default mode (no callback):
 879        - Enter the native blocking loop until close.
 880
 881        Callback mode (`update=...`):
 882        - Start a Python-managed loop and invoke callback once per frame.
 883        - Callback can return `False` or call `context.stop()` to exit.
 884        - See `UpdateContext` for injected values.
 885        - Frame order is: poll events -> native update -> callback -> render.
 886          (Future GameObject script updates are intended to run in native update.)
 887
 888        Raises:
 889            RuntimeError: If the engine is already running in another loop mode.
 890
 891        Args:
 892            title: Window title.
 893            width: Initial window width.
 894            height: Initial window height.
 895            resizable: Whether the window can be resized.
 896            background_color: Optional `pyg_engine.Color`.
 897            vsync: Enable/disable vertical sync.
 898            redraw_on_change_only: When True (default), only redraw on scene changes.
 899            show_fps_in_title: When True, appends current FPS to window title.
 900            icon_path: Optional icon file path. If omitted, uses the most recent
 901                `set_window_icon(...)` value when present, otherwise the built-in
 902                default icon.
 903            min_width: Minimum window width in pixels (default: 640).
 904            min_height: Minimum window height in pixels (default: 480).
 905            update: Optional callback invoked once per frame. When omitted,
 906                the native blocking run loop is used.
 907            max_delta_time: Clamp callback `dt` to this value in seconds.
 908                Only used in callback mode (`update` provided).
 909            user_data: Arbitrary object exposed via callback context.
 910                Only used in callback mode (`update` provided).
 911        """
 912        self._ensure_not_running("run()")
 913
 914        if update is None:
 915            resolved_icon_path = (
 916                icon_path if icon_path is not None else self._window_icon_path
 917            )
 918            self._runtime_state = _RUNTIME_STATE_RUNNING_BLOCKING
 919            try:
 920                self._engine.run(
 921                    title=title,
 922                    width=width,
 923                    height=height,
 924                    resizable=resizable,
 925                    background_color=background_color,
 926                    vsync=vsync,
 927                    redraw_on_change_only=redraw_on_change_only,
 928                    show_fps_in_title=show_fps_in_title,
 929                    icon_path=resolved_icon_path,
 930                    min_width=min_width,
 931                    min_height=min_height,
 932                )
 933            finally:
 934                self._runtime_state = _RUNTIME_STATE_IDLE
 935            return
 936
 937        if max_delta_time is not None and max_delta_time <= 0.0:
 938            raise ValueError("max_delta_time must be > 0.0 or None")
 939
 940        invoke_callback = _compile_update_callback(update)
 941
 942        self.start_manual(
 943            title=title,
 944            width=width,
 945            height=height,
 946            resizable=resizable,
 947            background_color=background_color,
 948            vsync=vsync,
 949            redraw_on_change_only=redraw_on_change_only,
 950            show_fps_in_title=show_fps_in_title,
 951            icon_path=icon_path,
 952            min_width=min_width,
 953            min_height=min_height,
 954        )
 955        self._runtime_state = _RUNTIME_STATE_RUNNING_CALLBACK
 956
 957        context = UpdateContext(self, user_data=user_data)
 958
 959        native_engine = self._engine
 960        poll_events = native_engine.poll_events
 961        update_step = native_engine.update
 962        render_frame = native_engine.render
 963
 964        try:
 965            while True:
 966                if not poll_events():
 967                    break
 968
 969                # Update native systems first so callback gets current dt/input.
 970                update_step()
 971
 972                context.delta_time = native_engine.delta_time
 973                if max_delta_time is not None and context.delta_time > max_delta_time:
 974                    context.delta_time = max_delta_time
 975                context.elapsed_time = native_engine.elapsed_time
 976
 977                callback_result = invoke_callback(context)
 978                if callback_result is False or context._should_stop:
 979                    break
 980
 981                render_frame()
 982                context.frame += 1
 983        finally:
 984            self._runtime_state = _RUNTIME_STATE_IDLE
 985
 986    def add_game_object(self, game_object: Any) -> Optional[int]:
 987        """
 988        Add a `pyg_engine.GameObject` to the runtime scene.
 989
 990        Returns:
 991            The runtime object id, or None if add failed.
 992        """
 993        return self._engine.add_game_object(game_object)
 994
 995    def create_game_object(self, name: Optional[str] = None) -> Optional[int]:
 996        """
 997        Create and add a runtime GameObject.
 998
 999        Returns:
1000            The runtime object id, or None if creation failed.
1001        """
1002        return self._engine.create_game_object(name)
1003
1004    def remove_game_object(self, object_id: int) -> None:
1005        """Remove a runtime GameObject by id."""
1006        self._engine.remove_game_object(object_id)
1007
1008    def set_game_object_position(self, object_id: int, position: Any) -> bool:
1009        """
1010        Update a runtime GameObject position by id.
1011
1012        Returns:
1013            True if the object exists and was updated, False otherwise.
1014        """
1015        return self._engine.set_game_object_position(object_id, position)
1016
1017    @property
1018    def camera_object_id(self) -> Optional[int]:
1019        """Get the runtime id of the active camera GameObject."""
1020        return self._engine.camera_object_id()
1021
1022    def get_camera_position(self) -> Any:
1023        """Get the active camera world position."""
1024        return self._engine.get_camera_position()
1025
1026    def set_camera_position(self, position: Any) -> bool:
1027        """Set the active camera world position."""
1028        return self._engine.set_camera_position(position)
1029
1030    def get_camera_viewport_size(self) -> tuple[float, float]:
1031        """Get the active camera viewport size in world units."""
1032        return self._engine.get_camera_viewport_size()
1033
1034    def set_camera_viewport_size(self, width: float, height: float) -> bool:
1035        """Set the active camera viewport size in world units."""
1036        return self._engine.set_camera_viewport_size(width, height)
1037
1038    def get_camera_aspect_mode(self) -> str:
1039        """Get the camera aspect handling mode."""
1040        return self._engine.get_camera_aspect_mode()
1041
1042    def set_camera_aspect_mode(self, mode: str) -> bool:
1043        """Set the camera aspect handling mode."""
1044        return self._engine.set_camera_aspect_mode(mode)
1045
1046    def set_camera_background_color(self, color: Any) -> None:
1047        """Set the active camera background clear color."""
1048        self._engine.set_camera_background_color(color)
1049
1050    def get_camera_background_color(self) -> Any:
1051        """Get the active camera background clear color."""
1052        return self._engine.get_camera_background_color()
1053
1054    def world_to_screen(self, world_position: Any) -> tuple[float, float]:
1055        """Convert world-space coordinates to screen-space pixel coordinates."""
1056        return self._engine.world_to_screen(world_position)
1057
1058    def screen_to_world(self, screen_x: float, screen_y: float) -> Any:
1059        """Convert screen-space pixel coordinates to world-space coordinates."""
1060        return self._engine.screen_to_world(screen_x, screen_y)
1061
1062    def clear_draw_commands(self) -> None:
1063        """Clear all immediate-mode drawing commands."""
1064        self._engine.clear_draw_commands()
1065
1066    def add_draw_commands(self, commands: list[Any]) -> None:
1067        """Submit many draw commands in one call."""
1068        self._engine.add_draw_commands(commands)
1069
1070    def draw_pixel(
1071        self,
1072        x: int,
1073        y: int,
1074        color: Any,
1075        draw_order: float = 0.0,
1076    ) -> None:
1077        """Draw a pixel in window coordinates."""
1078        self._engine.draw_pixel(x, y, color, draw_order=draw_order)
1079
1080    def draw_line(
1081        self,
1082        start_x: float,
1083        start_y: float,
1084        end_x: float,
1085        end_y: float,
1086        color: Any,
1087        thickness: float = 1.0,
1088        draw_order: float = 0.0,
1089    ) -> None:
1090        """Draw a line in window coordinates."""
1091        self._engine.draw_line(
1092            start_x,
1093            start_y,
1094            end_x,
1095            end_y,
1096            color,
1097            thickness=thickness,
1098            draw_order=draw_order,
1099        )
1100
1101    def draw_rectangle(
1102        self,
1103        x: float,
1104        y: float,
1105        width: float,
1106        height: float,
1107        color: Any,
1108        filled: bool = True,
1109        thickness: float = 1.0,
1110        draw_order: float = 0.0,
1111    ) -> None:
1112        """Draw a rectangle in window coordinates."""
1113        self._engine.draw_rectangle(
1114            x,
1115            y,
1116            width,
1117            height,
1118            color,
1119            filled=filled,
1120            thickness=thickness,
1121            draw_order=draw_order,
1122        )
1123
1124    def draw_circle(
1125        self,
1126        center_x: float,
1127        center_y: float,
1128        radius: float,
1129        color: Any,
1130        filled: bool = True,
1131        thickness: float = 1.0,
1132        segments: int = 32,
1133        draw_order: float = 0.0,
1134    ) -> None:
1135        """Draw a circle in window coordinates."""
1136        self._engine.draw_circle(
1137            center_x,
1138            center_y,
1139            radius,
1140            color,
1141            filled=filled,
1142            thickness=thickness,
1143            segments=segments,
1144            draw_order=draw_order,
1145        )
1146
1147    def draw_gradient_rect(
1148        self,
1149        x: float,
1150        y: float,
1151        width: float,
1152        height: float,
1153        top_left: Any,
1154        bottom_left: Any,
1155        bottom_right: Any,
1156        top_right: Any,
1157        draw_order: float = 0.0,
1158    ) -> None:
1159        """Draw a gradient rectangle with per-corner colors."""
1160        self._engine.draw_gradient_rect(
1161            x,
1162            y,
1163            width,
1164            height,
1165            top_left,
1166            bottom_left,
1167            bottom_right,
1168            top_right,
1169            draw_order=draw_order,
1170        )
1171
1172    def draw_image(
1173        self,
1174        x: float,
1175        y: float,
1176        width: float,
1177        height: float,
1178        texture_path: str,
1179        draw_order: float = 0.0,
1180    ) -> None:
1181        """Draw an image from a file path."""
1182        self._engine.draw_image(
1183            x,
1184            y,
1185            width,
1186            height,
1187            texture_path,
1188            draw_order=draw_order,
1189        )
1190
1191    def draw_image_from_bytes(
1192        self,
1193        x: float,
1194        y: float,
1195        width: float,
1196        height: float,
1197        texture_key: str,
1198        rgba: bytes,
1199        texture_width: int,
1200        texture_height: int,
1201        draw_order: float = 0.0,
1202    ) -> None:
1203        """Draw an image from raw RGBA bytes."""
1204        self._engine.draw_image_from_bytes(
1205            x,
1206            y,
1207            width,
1208            height,
1209            texture_key,
1210            rgba,
1211            texture_width,
1212            texture_height,
1213            draw_order=draw_order,
1214        )
1215
1216    def draw_text(
1217        self,
1218        text: str,
1219        x: float,
1220        y: float,
1221        color: Any,
1222        font_size: float = 24.0,
1223        font_path: Optional[str] = None,
1224        letter_spacing: float = 0.0,
1225        line_spacing: float = 0.0,
1226        draw_order: float = 0.0,
1227    ) -> None:
1228        """
1229        Draw text in window coordinates.
1230
1231        By default, this uses the engine's built-in open-source font.
1232        Provide `font_path` to use a custom TTF/OTF font file.
1233        """
1234        self._engine.draw_text(
1235            text,
1236            x,
1237            y,
1238            color,
1239            font_size=font_size,
1240            font_path=font_path,
1241            letter_spacing=letter_spacing,
1242            line_spacing=line_spacing,
1243            draw_order=draw_order,
1244        )
1245    
1246    @property
1247    def version(self) -> str:
1248        """
1249        Get the engine version.
1250        
1251        Returns:
1252            The version string (e.g., "1.2.0").
1253        """
1254        return self._engine.version
1255
1256    @property
1257    def delta_time(self) -> float:
1258        """Get the time since the last frame in seconds."""
1259        return self._engine.delta_time
1260
1261    @property
1262    def elapsed_time(self) -> float:
1263        """Get the total elapsed time in seconds since the engine started."""
1264        return self._engine.elapsed_time

Main game engine class providing core functionality with structured logging.

This class wraps the Rust implementation and provides a Python-friendly API with full access to the tracing-based logging system.

Attributes: version (str): The engine version number.

Example: Basic usage with console logging:

engine = Engine() engine.log_info("Engine started") engine.log_warn("Low memory warning") engine.log_error("Failed to load resource")

With file logging enabled:
>>> engine = Engine(
...     enable_file_logging=True,
...     log_directory="./logs",
...     log_level="DEBUG"
... )
>>> engine.log_debug("Debug information")
Engine( enable_file_logging: bool = False, log_directory: Optional[str] = None, log_level: Optional[str] = None)
611    def __init__(
612        self,
613        enable_file_logging: bool = False,
614        log_directory: Optional[str] = None,
615        log_level: Optional[str] = None,
616    ) -> None:
617        """
618        Initialize a new Engine instance with optional logging configuration.
619        
620        Args:
621            enable_file_logging: Enable logging to files (default: False).
622                Files are rotated daily and stored in the log directory.
623            log_directory: Directory path for log files (default: "./logs").
624                Only used if enable_file_logging is True.
625            log_level: Minimum log level to display. Options:
626                - "TRACE": Most verbose, includes all logs
627                - "DEBUG": Debug and higher
628                - "INFO": Info and higher (default)
629                - "WARN": Warnings and errors only
630                - "ERROR": Errors only
631        
632        Example:
633            >>> # Console only with INFO level (default)
634            >>> engine = Engine()
635            
636            >>> # Enable file logging with DEBUG level
637            >>> engine = Engine(
638            ...     enable_file_logging=True,
639            ...     log_directory="./my_logs",
640            ...     log_level="DEBUG"
641            ... )
642        """
643        self._engine = _RustEngine(
644            enable_file_logging=enable_file_logging,
645            log_directory=log_directory,
646            log_level=log_level,
647        )
648        self._input = Input(self)
649        self._runtime_state = _RUNTIME_STATE_IDLE
650        self._window_icon_path: Optional[str] = None

Initialize a new Engine instance with optional logging configuration.

Args: enable_file_logging: Enable logging to files (default: False). Files are rotated daily and stored in the log directory. log_directory: Directory path for log files (default: "./logs"). Only used if enable_file_logging is True. log_level: Minimum log level to display. Options: - "TRACE": Most verbose, includes all logs - "DEBUG": Debug and higher - "INFO": Info and higher (default) - "WARN": Warnings and errors only - "ERROR": Errors only

Example:

Console only with INFO level (default)

engine = Engine()

>>> # Enable file logging with DEBUG level
>>> engine = Engine(
...     enable_file_logging=True,
...     log_directory="./my_logs",
...     log_level="DEBUG"
... )
input: Input
652    @property
653    def input(self) -> Input:
654        """
655        Get the input manager for the engine.
656        
657        Returns:
658            Input: The input manager instance.
659        """
660        return self._input

Get the input manager for the engine.

Returns: Input: The input manager instance.

is_running: bool
662    @property
663    def is_running(self) -> bool:
664        """Return whether the engine is currently running in any loop mode."""
665        return self._runtime_state != _RUNTIME_STATE_IDLE

Return whether the engine is currently running in any loop mode.

def get_handle(self) -> EngineHandle:
674    def get_handle(self) -> EngineHandle:
675        """
676        Get a thread-safe handle to the engine that can be passed to background threads.
677        
678        Returns:
679            EngineHandle: A handle used to queue commands from other threads.
680        """
681        return EngineHandle(self._engine.get_handle())

Get a thread-safe handle to the engine that can be passed to background threads.

Returns: EngineHandle: A handle used to queue commands from other threads.

def log(self, message: str) -> None:
683    def log(self, message: str) -> None:
684        """
685        Log a message at INFO level (default log method).
686        
687        This is an alias for log_info() for backward compatibility.
688        
689        Args:
690            message: The message to log.
691        """
692        self._engine.log(message)

Log a message at INFO level (default log method).

This is an alias for log_info() for backward compatibility.

Args: message: The message to log.

def log_trace(self, message: str) -> None:
694    def log_trace(self, message: str) -> None:
695        """
696        Log a message at TRACE level (most verbose).
697        
698        Use for very detailed debugging information.
699        Only visible when log level is set to TRACE.
700        
701        Args:
702            message: The message to log.
703        """
704        self._engine.log_trace(message)

Log a message at TRACE level (most verbose).

Use for very detailed debugging information. Only visible when log level is set to TRACE.

Args: message: The message to log.

def log_debug(self, message: str) -> None:
706    def log_debug(self, message: str) -> None:
707        """
708        Log a message at DEBUG level.
709        
710        Use for debugging information that's useful during development.
711        Visible when log level is DEBUG or TRACE.
712        
713        Args:
714            message: The message to log.
715        """
716        self._engine.log_debug(message)

Log a message at DEBUG level.

Use for debugging information that's useful during development. Visible when log level is DEBUG or TRACE.

Args: message: The message to log.

def log_info(self, message: str) -> None:
718    def log_info(self, message: str) -> None:
719        """
720        Log a message at INFO level.
721        
722        Use for general informational messages about engine operation.
723        This is the default log level.
724        
725        Args:
726            message: The message to log.
727        """
728        self._engine.log_info(message)

Log a message at INFO level.

Use for general informational messages about engine operation. This is the default log level.

Args: message: The message to log.

def log_warn(self, message: str) -> None:
730    def log_warn(self, message: str) -> None:
731        """
732        Log a message at WARN level.
733        
734        Use for warnings that don't prevent operation but indicate
735        potential issues.
736        
737        Args:
738            message: The message to log.
739        """
740        self._engine.log_warn(message)

Log a message at WARN level.

Use for warnings that don't prevent operation but indicate potential issues.

Args: message: The message to log.

def log_error(self, message: str) -> None:
742    def log_error(self, message: str) -> None:
743        """
744        Log a message at ERROR level.
745        
746        Use for errors that may affect engine operation.
747        
748        Args:
749            message: The message to log.
750        """
751        self._engine.log_error(message)

Log a message at ERROR level.

Use for errors that may affect engine operation.

Args: message: The message to log.

def set_window_title(self, title: str) -> None:
753    def set_window_title(self, title: str) -> None:
754        """
755        Set the window title.
756        
757        Args:
758            title: The new window title.
759        """
760        self._engine.set_window_title(title)

Set the window title.

Args: title: The new window title.

def set_window_icon(self, icon_path: str) -> None:
762    def set_window_icon(self, icon_path: str) -> None:
763        """
764        Set the window icon from an image file path.
765
766        The path is remembered and will be reused for future `run()` /
767        `start_manual()` calls unless overridden per call.
768        """
769        self._window_icon_path = icon_path
770        self._engine.set_window_icon(icon_path)

Set the window icon from an image file path.

The path is remembered and will be reused for future run() / start_manual() calls unless overridden per call.

def get_display_size(self) -> tuple[int, int]:
772    def get_display_size(self) -> tuple[int, int]:
773        """Get the current display size (window client size) in pixels."""
774        return self._engine.get_display_size()

Get the current display size (window client size) in pixels.

def start_manual( self, title: str = 'PyG Engine', width: int = 1280, height: int = 720, resizable: bool = True, background_color: Optional[Any] = None, vsync: bool = True, redraw_on_change_only: bool = True, show_fps_in_title: bool = False, icon_path: Optional[str] = None, min_width: Optional[int] = None, min_height: Optional[int] = None) -> None:
776    def start_manual(
777        self,
778        title: str = "PyG Engine",
779        width: int = 1280,
780        height: int = 720,
781        resizable: bool = True,
782        background_color: Optional[Any] = None,
783        vsync: bool = True,
784        redraw_on_change_only: bool = True,
785        show_fps_in_title: bool = False,
786        icon_path: Optional[str] = None,
787        min_width: Optional[int] = None,
788        min_height: Optional[int] = None,
789    ) -> None:
790        """
791        Start the engine in manual-loop mode without entering a blocking loop.
792
793        This mode is for advanced use cases where Python controls the frame loop
794        manually using:
795        `poll_events()`, `update()`, and `render()`.
796
797        Raises:
798            RuntimeError: If the engine is already running in another loop mode.
799
800        Args:
801            title: Window title.
802            width: Initial window width.
803            height: Initial window height.
804            resizable: Whether the window can be resized.
805            background_color: Optional `pyg_engine.Color`.
806            vsync: Enable/disable vertical sync.
807            redraw_on_change_only: When True (default), only redraw on scene changes.
808            show_fps_in_title: When True, appends current FPS to window title.
809            icon_path: Optional icon file path. If omitted, uses the most recent
810                `set_window_icon(...)` value when present, otherwise the built-in
811                default icon.
812            min_width: Minimum window width in pixels (default: 640).
813            min_height: Minimum window height in pixels (default: 480).
814        """
815        self._ensure_not_running("start_manual()")
816        resolved_icon_path = (
817            icon_path if icon_path is not None else self._window_icon_path
818        )
819        self._engine.initialize(
820            title=title,
821            width=width,
822            height=height,
823            resizable=resizable,
824            background_color=background_color,
825            vsync=vsync,
826            redraw_on_change_only=redraw_on_change_only,
827            show_fps_in_title=show_fps_in_title,
828            icon_path=resolved_icon_path,
829            min_width=min_width,
830            min_height=min_height,
831        )
832        self._runtime_state = _RUNTIME_STATE_MANUAL

Start the engine in manual-loop mode without entering a blocking loop.

This mode is for advanced use cases where Python controls the frame loop manually using: poll_events(), update(), and render().

Raises: RuntimeError: If the engine is already running in another loop mode.

Args: title: Window title. width: Initial window width. height: Initial window height. resizable: Whether the window can be resized. background_color: Optional pyg_engine.Color. vsync: Enable/disable vertical sync. redraw_on_change_only: When True (default), only redraw on scene changes. show_fps_in_title: When True, appends current FPS to window title. icon_path: Optional icon file path. If omitted, uses the most recent set_window_icon(...) value when present, otherwise the built-in default icon. min_width: Minimum window width in pixels (default: 640). min_height: Minimum window height in pixels (default: 480).

def poll_events(self) -> bool:
834    def poll_events(self) -> bool:
835        """
836        Poll events from the window system.
837        
838        Returns:
839            bool: True if the loop should continue, False if exit requested.
840        """
841        should_continue = self._engine.poll_events()
842        if not should_continue and self._runtime_state in (
843            _RUNTIME_STATE_MANUAL,
844            _RUNTIME_STATE_RUNNING_CALLBACK,
845        ):
846            self._runtime_state = _RUNTIME_STATE_IDLE
847        return should_continue

Poll events from the window system.

Returns: bool: True if the loop should continue, False if exit requested.

def update(self) -> None:
849    def update(self) -> None:
850        """Run a single update step."""
851        self._engine.update()

Run a single update step.

def render(self) -> None:
853    def render(self) -> None:
854        """Render a single frame."""
855        self._engine.render()

Render a single frame.

def run( self, title: str = 'PyG Engine', width: int = 1280, height: int = 720, resizable: bool = True, background_color: Optional[Any] = None, vsync: bool = True, redraw_on_change_only: bool = True, show_fps_in_title: bool = False, icon_path: Optional[str] = None, min_width: Optional[int] = None, min_height: Optional[int] = None, *, update: Optional[Callable[..., object]] = None, max_delta_time: Optional[float] = 0.1, user_data: Any = None) -> None:
857    def run(
858        self,
859        title: str = "PyG Engine",
860        width: int = 1280,
861        height: int = 720,
862        resizable: bool = True,
863        background_color: Optional[Any] = None,
864        vsync: bool = True,
865        redraw_on_change_only: bool = True,
866        show_fps_in_title: bool = False,
867        icon_path: Optional[str] = None,
868        min_width: Optional[int] = None,
869        min_height: Optional[int] = None,
870        *,
871        update: Optional[Callable[..., object]] = None,
872        max_delta_time: Optional[float] = 0.1,
873        user_data: Any = None,
874    ) -> None:
875        """
876        Run the engine and start the frame loop.
877
878        Default mode (no callback):
879        - Enter the native blocking loop until close.
880
881        Callback mode (`update=...`):
882        - Start a Python-managed loop and invoke callback once per frame.
883        - Callback can return `False` or call `context.stop()` to exit.
884        - See `UpdateContext` for injected values.
885        - Frame order is: poll events -> native update -> callback -> render.
886          (Future GameObject script updates are intended to run in native update.)
887
888        Raises:
889            RuntimeError: If the engine is already running in another loop mode.
890
891        Args:
892            title: Window title.
893            width: Initial window width.
894            height: Initial window height.
895            resizable: Whether the window can be resized.
896            background_color: Optional `pyg_engine.Color`.
897            vsync: Enable/disable vertical sync.
898            redraw_on_change_only: When True (default), only redraw on scene changes.
899            show_fps_in_title: When True, appends current FPS to window title.
900            icon_path: Optional icon file path. If omitted, uses the most recent
901                `set_window_icon(...)` value when present, otherwise the built-in
902                default icon.
903            min_width: Minimum window width in pixels (default: 640).
904            min_height: Minimum window height in pixels (default: 480).
905            update: Optional callback invoked once per frame. When omitted,
906                the native blocking run loop is used.
907            max_delta_time: Clamp callback `dt` to this value in seconds.
908                Only used in callback mode (`update` provided).
909            user_data: Arbitrary object exposed via callback context.
910                Only used in callback mode (`update` provided).
911        """
912        self._ensure_not_running("run()")
913
914        if update is None:
915            resolved_icon_path = (
916                icon_path if icon_path is not None else self._window_icon_path
917            )
918            self._runtime_state = _RUNTIME_STATE_RUNNING_BLOCKING
919            try:
920                self._engine.run(
921                    title=title,
922                    width=width,
923                    height=height,
924                    resizable=resizable,
925                    background_color=background_color,
926                    vsync=vsync,
927                    redraw_on_change_only=redraw_on_change_only,
928                    show_fps_in_title=show_fps_in_title,
929                    icon_path=resolved_icon_path,
930                    min_width=min_width,
931                    min_height=min_height,
932                )
933            finally:
934                self._runtime_state = _RUNTIME_STATE_IDLE
935            return
936
937        if max_delta_time is not None and max_delta_time <= 0.0:
938            raise ValueError("max_delta_time must be > 0.0 or None")
939
940        invoke_callback = _compile_update_callback(update)
941
942        self.start_manual(
943            title=title,
944            width=width,
945            height=height,
946            resizable=resizable,
947            background_color=background_color,
948            vsync=vsync,
949            redraw_on_change_only=redraw_on_change_only,
950            show_fps_in_title=show_fps_in_title,
951            icon_path=icon_path,
952            min_width=min_width,
953            min_height=min_height,
954        )
955        self._runtime_state = _RUNTIME_STATE_RUNNING_CALLBACK
956
957        context = UpdateContext(self, user_data=user_data)
958
959        native_engine = self._engine
960        poll_events = native_engine.poll_events
961        update_step = native_engine.update
962        render_frame = native_engine.render
963
964        try:
965            while True:
966                if not poll_events():
967                    break
968
969                # Update native systems first so callback gets current dt/input.
970                update_step()
971
972                context.delta_time = native_engine.delta_time
973                if max_delta_time is not None and context.delta_time > max_delta_time:
974                    context.delta_time = max_delta_time
975                context.elapsed_time = native_engine.elapsed_time
976
977                callback_result = invoke_callback(context)
978                if callback_result is False or context._should_stop:
979                    break
980
981                render_frame()
982                context.frame += 1
983        finally:
984            self._runtime_state = _RUNTIME_STATE_IDLE

Run the engine and start the frame loop.

Default mode (no callback):

  • Enter the native blocking loop until close.

Callback mode (update=...):

  • Start a Python-managed loop and invoke callback once per frame.
  • Callback can return False or call context.stop() to exit.
  • See UpdateContext for injected values.
  • Frame order is: poll events -> native update -> callback -> render. (Future GameObject script updates are intended to run in native update.)

Raises: RuntimeError: If the engine is already running in another loop mode.

Args: title: Window title. width: Initial window width. height: Initial window height. resizable: Whether the window can be resized. background_color: Optional pyg_engine.Color. vsync: Enable/disable vertical sync. redraw_on_change_only: When True (default), only redraw on scene changes. show_fps_in_title: When True, appends current FPS to window title. icon_path: Optional icon file path. If omitted, uses the most recent set_window_icon(...) value when present, otherwise the built-in default icon. min_width: Minimum window width in pixels (default: 640). min_height: Minimum window height in pixels (default: 480). update: Optional callback invoked once per frame. When omitted, the native blocking run loop is used. max_delta_time: Clamp callback dt to this value in seconds. Only used in callback mode (update provided). user_data: Arbitrary object exposed via callback context. Only used in callback mode (update provided).

def add_game_object(self, game_object: Any) -> Optional[int]:
986    def add_game_object(self, game_object: Any) -> Optional[int]:
987        """
988        Add a `pyg_engine.GameObject` to the runtime scene.
989
990        Returns:
991            The runtime object id, or None if add failed.
992        """
993        return self._engine.add_game_object(game_object)

Add a pyg_engine.GameObject to the runtime scene.

Returns: The runtime object id, or None if add failed.

def create_game_object(self, name: Optional[str] = None) -> Optional[int]:
 995    def create_game_object(self, name: Optional[str] = None) -> Optional[int]:
 996        """
 997        Create and add a runtime GameObject.
 998
 999        Returns:
1000            The runtime object id, or None if creation failed.
1001        """
1002        return self._engine.create_game_object(name)

Create and add a runtime GameObject.

Returns: The runtime object id, or None if creation failed.

def remove_game_object(self, object_id: int) -> None:
1004    def remove_game_object(self, object_id: int) -> None:
1005        """Remove a runtime GameObject by id."""
1006        self._engine.remove_game_object(object_id)

Remove a runtime GameObject by id.

def set_game_object_position(self, object_id: int, position: Any) -> bool:
1008    def set_game_object_position(self, object_id: int, position: Any) -> bool:
1009        """
1010        Update a runtime GameObject position by id.
1011
1012        Returns:
1013            True if the object exists and was updated, False otherwise.
1014        """
1015        return self._engine.set_game_object_position(object_id, position)

Update a runtime GameObject position by id.

Returns: True if the object exists and was updated, False otherwise.

camera_object_id: Optional[int]
1017    @property
1018    def camera_object_id(self) -> Optional[int]:
1019        """Get the runtime id of the active camera GameObject."""
1020        return self._engine.camera_object_id()

Get the runtime id of the active camera GameObject.

def get_camera_position(self) -> Any:
1022    def get_camera_position(self) -> Any:
1023        """Get the active camera world position."""
1024        return self._engine.get_camera_position()

Get the active camera world position.

def set_camera_position(self, position: Any) -> bool:
1026    def set_camera_position(self, position: Any) -> bool:
1027        """Set the active camera world position."""
1028        return self._engine.set_camera_position(position)

Set the active camera world position.

def get_camera_viewport_size(self) -> tuple[float, float]:
1030    def get_camera_viewport_size(self) -> tuple[float, float]:
1031        """Get the active camera viewport size in world units."""
1032        return self._engine.get_camera_viewport_size()

Get the active camera viewport size in world units.

def set_camera_viewport_size(self, width: float, height: float) -> bool:
1034    def set_camera_viewport_size(self, width: float, height: float) -> bool:
1035        """Set the active camera viewport size in world units."""
1036        return self._engine.set_camera_viewport_size(width, height)

Set the active camera viewport size in world units.

def get_camera_aspect_mode(self) -> str:
1038    def get_camera_aspect_mode(self) -> str:
1039        """Get the camera aspect handling mode."""
1040        return self._engine.get_camera_aspect_mode()

Get the camera aspect handling mode.

def set_camera_aspect_mode(self, mode: str) -> bool:
1042    def set_camera_aspect_mode(self, mode: str) -> bool:
1043        """Set the camera aspect handling mode."""
1044        return self._engine.set_camera_aspect_mode(mode)

Set the camera aspect handling mode.

def set_camera_background_color(self, color: Any) -> None:
1046    def set_camera_background_color(self, color: Any) -> None:
1047        """Set the active camera background clear color."""
1048        self._engine.set_camera_background_color(color)

Set the active camera background clear color.

def get_camera_background_color(self) -> Any:
1050    def get_camera_background_color(self) -> Any:
1051        """Get the active camera background clear color."""
1052        return self._engine.get_camera_background_color()

Get the active camera background clear color.

def world_to_screen(self, world_position: Any) -> tuple[float, float]:
1054    def world_to_screen(self, world_position: Any) -> tuple[float, float]:
1055        """Convert world-space coordinates to screen-space pixel coordinates."""
1056        return self._engine.world_to_screen(world_position)

Convert world-space coordinates to screen-space pixel coordinates.

def screen_to_world(self, screen_x: float, screen_y: float) -> Any:
1058    def screen_to_world(self, screen_x: float, screen_y: float) -> Any:
1059        """Convert screen-space pixel coordinates to world-space coordinates."""
1060        return self._engine.screen_to_world(screen_x, screen_y)

Convert screen-space pixel coordinates to world-space coordinates.

def clear_draw_commands(self) -> None:
1062    def clear_draw_commands(self) -> None:
1063        """Clear all immediate-mode drawing commands."""
1064        self._engine.clear_draw_commands()

Clear all immediate-mode drawing commands.

def add_draw_commands(self, commands: list[typing.Any]) -> None:
1066    def add_draw_commands(self, commands: list[Any]) -> None:
1067        """Submit many draw commands in one call."""
1068        self._engine.add_draw_commands(commands)

Submit many draw commands in one call.

def draw_pixel(self, x: int, y: int, color: Any, draw_order: float = 0.0) -> None:
1070    def draw_pixel(
1071        self,
1072        x: int,
1073        y: int,
1074        color: Any,
1075        draw_order: float = 0.0,
1076    ) -> None:
1077        """Draw a pixel in window coordinates."""
1078        self._engine.draw_pixel(x, y, color, draw_order=draw_order)

Draw a pixel in window coordinates.

def draw_line( self, start_x: float, start_y: float, end_x: float, end_y: float, color: Any, thickness: float = 1.0, draw_order: float = 0.0) -> None:
1080    def draw_line(
1081        self,
1082        start_x: float,
1083        start_y: float,
1084        end_x: float,
1085        end_y: float,
1086        color: Any,
1087        thickness: float = 1.0,
1088        draw_order: float = 0.0,
1089    ) -> None:
1090        """Draw a line in window coordinates."""
1091        self._engine.draw_line(
1092            start_x,
1093            start_y,
1094            end_x,
1095            end_y,
1096            color,
1097            thickness=thickness,
1098            draw_order=draw_order,
1099        )

Draw a line in window coordinates.

def draw_rectangle( self, x: float, y: float, width: float, height: float, color: Any, filled: bool = True, thickness: float = 1.0, draw_order: float = 0.0) -> None:
1101    def draw_rectangle(
1102        self,
1103        x: float,
1104        y: float,
1105        width: float,
1106        height: float,
1107        color: Any,
1108        filled: bool = True,
1109        thickness: float = 1.0,
1110        draw_order: float = 0.0,
1111    ) -> None:
1112        """Draw a rectangle in window coordinates."""
1113        self._engine.draw_rectangle(
1114            x,
1115            y,
1116            width,
1117            height,
1118            color,
1119            filled=filled,
1120            thickness=thickness,
1121            draw_order=draw_order,
1122        )

Draw a rectangle in window coordinates.

def draw_circle( self, center_x: float, center_y: float, radius: float, color: Any, filled: bool = True, thickness: float = 1.0, segments: int = 32, draw_order: float = 0.0) -> None:
1124    def draw_circle(
1125        self,
1126        center_x: float,
1127        center_y: float,
1128        radius: float,
1129        color: Any,
1130        filled: bool = True,
1131        thickness: float = 1.0,
1132        segments: int = 32,
1133        draw_order: float = 0.0,
1134    ) -> None:
1135        """Draw a circle in window coordinates."""
1136        self._engine.draw_circle(
1137            center_x,
1138            center_y,
1139            radius,
1140            color,
1141            filled=filled,
1142            thickness=thickness,
1143            segments=segments,
1144            draw_order=draw_order,
1145        )

Draw a circle in window coordinates.

def draw_gradient_rect( self, x: float, y: float, width: float, height: float, top_left: Any, bottom_left: Any, bottom_right: Any, top_right: Any, draw_order: float = 0.0) -> None:
1147    def draw_gradient_rect(
1148        self,
1149        x: float,
1150        y: float,
1151        width: float,
1152        height: float,
1153        top_left: Any,
1154        bottom_left: Any,
1155        bottom_right: Any,
1156        top_right: Any,
1157        draw_order: float = 0.0,
1158    ) -> None:
1159        """Draw a gradient rectangle with per-corner colors."""
1160        self._engine.draw_gradient_rect(
1161            x,
1162            y,
1163            width,
1164            height,
1165            top_left,
1166            bottom_left,
1167            bottom_right,
1168            top_right,
1169            draw_order=draw_order,
1170        )

Draw a gradient rectangle with per-corner colors.

def draw_image( self, x: float, y: float, width: float, height: float, texture_path: str, draw_order: float = 0.0) -> None:
1172    def draw_image(
1173        self,
1174        x: float,
1175        y: float,
1176        width: float,
1177        height: float,
1178        texture_path: str,
1179        draw_order: float = 0.0,
1180    ) -> None:
1181        """Draw an image from a file path."""
1182        self._engine.draw_image(
1183            x,
1184            y,
1185            width,
1186            height,
1187            texture_path,
1188            draw_order=draw_order,
1189        )

Draw an image from a file path.

def draw_image_from_bytes( self, x: float, y: float, width: float, height: float, texture_key: str, rgba: bytes, texture_width: int, texture_height: int, draw_order: float = 0.0) -> None:
1191    def draw_image_from_bytes(
1192        self,
1193        x: float,
1194        y: float,
1195        width: float,
1196        height: float,
1197        texture_key: str,
1198        rgba: bytes,
1199        texture_width: int,
1200        texture_height: int,
1201        draw_order: float = 0.0,
1202    ) -> None:
1203        """Draw an image from raw RGBA bytes."""
1204        self._engine.draw_image_from_bytes(
1205            x,
1206            y,
1207            width,
1208            height,
1209            texture_key,
1210            rgba,
1211            texture_width,
1212            texture_height,
1213            draw_order=draw_order,
1214        )

Draw an image from raw RGBA bytes.

def draw_text( self, text: str, x: float, y: float, color: Any, font_size: float = 24.0, font_path: Optional[str] = None, letter_spacing: float = 0.0, line_spacing: float = 0.0, draw_order: float = 0.0) -> None:
1216    def draw_text(
1217        self,
1218        text: str,
1219        x: float,
1220        y: float,
1221        color: Any,
1222        font_size: float = 24.0,
1223        font_path: Optional[str] = None,
1224        letter_spacing: float = 0.0,
1225        line_spacing: float = 0.0,
1226        draw_order: float = 0.0,
1227    ) -> None:
1228        """
1229        Draw text in window coordinates.
1230
1231        By default, this uses the engine's built-in open-source font.
1232        Provide `font_path` to use a custom TTF/OTF font file.
1233        """
1234        self._engine.draw_text(
1235            text,
1236            x,
1237            y,
1238            color,
1239            font_size=font_size,
1240            font_path=font_path,
1241            letter_spacing=letter_spacing,
1242            line_spacing=line_spacing,
1243            draw_order=draw_order,
1244        )

Draw text in window coordinates.

By default, this uses the engine's built-in open-source font. Provide font_path to use a custom TTF/OTF font file.

version: str
1246    @property
1247    def version(self) -> str:
1248        """
1249        Get the engine version.
1250        
1251        Returns:
1252            The version string (e.g., "1.2.0").
1253        """
1254        return self._engine.version

Get the engine version.

Returns: The version string (e.g., "1.2.0").

delta_time: float
1256    @property
1257    def delta_time(self) -> float:
1258        """Get the time since the last frame in seconds."""
1259        return self._engine.delta_time

Get the time since the last frame in seconds.

elapsed_time: float
1261    @property
1262    def elapsed_time(self) -> float:
1263        """Get the total elapsed time in seconds since the engine started."""
1264        return self._engine.elapsed_time

Get the total elapsed time in seconds since the engine started.

class EngineHandle:
 31class EngineHandle:
 32    """
 33    Thread-safe handle to the engine that can be passed to background threads.
 34    
 35    Use this handle to queue commands like adding objects or drawing from other threads.
 36    """
 37    def __init__(self, inner: _RustEngineHandle) -> None:
 38        self._inner = inner
 39
 40    def add_game_object(self, game_object: Any) -> None:
 41        """
 42        Add a `pyg_engine.GameObject` to the runtime scene.
 43        
 44        This is thread-safe and will be processed on the next engine update.
 45        """
 46        self._inner.add_game_object(game_object)
 47
 48    def remove_game_object(self, object_id: int) -> None:
 49        """Remove a runtime GameObject by id via command queue."""
 50        self._inner.remove_game_object(object_id)
 51
 52    def set_game_object_position(self, object_id: int, position: Any) -> None:
 53        """Update a runtime GameObject position by id via command queue."""
 54        self._inner.set_game_object_position(object_id, position)
 55
 56    def set_camera_position(self, position: Any) -> None:
 57        """Update the active camera world position via command queue."""
 58        self._inner.set_camera_position(position)
 59
 60    def set_camera_viewport_size(self, width: float, height: float) -> None:
 61        """Update the active camera viewport size in world units via command queue."""
 62        self._inner.set_camera_viewport_size(width, height)
 63
 64    def set_camera_aspect_mode(self, mode: str) -> None:
 65        """Update camera aspect handling mode via command queue."""
 66        self._inner.set_camera_aspect_mode(mode)
 67
 68    def set_camera_background_color(self, color: Any) -> None:
 69        """Update the active camera background clear color via command queue."""
 70        self._inner.set_camera_background_color(color)
 71
 72    def clear_draw_commands(self) -> None:
 73        """Clear all immediate-mode drawing commands via command queue."""
 74        self._inner.clear_draw_commands()
 75
 76    def add_draw_commands(self, commands: list[Any]) -> None:
 77        """Submit many draw commands via command queue in one call."""
 78        self._inner.add_draw_commands(commands)
 79
 80    def draw_pixel(
 81        self,
 82        x: int,
 83        y: int,
 84        color: Any,
 85        draw_order: float = 0.0,
 86    ) -> None:
 87        """Draw a pixel in window coordinates via command queue."""
 88        self._inner.draw_pixel(x, y, color, draw_order=draw_order)
 89
 90    def draw_line(
 91        self,
 92        start_x: float,
 93        start_y: float,
 94        end_x: float,
 95        end_y: float,
 96        color: Any,
 97        thickness: float = 1.0,
 98        draw_order: float = 0.0,
 99    ) -> None:
100        """Draw a line in window coordinates via command queue."""
101        self._inner.draw_line(
102            start_x,
103            start_y,
104            end_x,
105            end_y,
106            color,
107            thickness=thickness,
108            draw_order=draw_order,
109        )
110
111    def draw_rectangle(
112        self,
113        x: float,
114        y: float,
115        width: float,
116        height: float,
117        color: Any,
118        filled: bool = True,
119        thickness: float = 1.0,
120        draw_order: float = 0.0,
121    ) -> None:
122        """Draw a rectangle in window coordinates via command queue."""
123        self._inner.draw_rectangle(
124            x,
125            y,
126            width,
127            height,
128            color,
129            filled=filled,
130            thickness=thickness,
131            draw_order=draw_order,
132        )
133
134    def draw_circle(
135        self,
136        center_x: float,
137        center_y: float,
138        radius: float,
139        color: Any,
140        filled: bool = True,
141        thickness: float = 1.0,
142        segments: int = 32,
143        draw_order: float = 0.0,
144    ) -> None:
145        """Draw a circle in window coordinates via command queue."""
146        self._inner.draw_circle(
147            center_x,
148            center_y,
149            radius,
150            color,
151            filled=filled,
152            thickness=thickness,
153            segments=segments,
154            draw_order=draw_order,
155        )
156
157    def draw_gradient_rect(
158        self,
159        x: float,
160        y: float,
161        width: float,
162        height: float,
163        top_left: Any,
164        bottom_left: Any,
165        bottom_right: Any,
166        top_right: Any,
167        draw_order: float = 0.0,
168    ) -> None:
169        """Draw a gradient rectangle with per-corner colors via command queue."""
170        self._inner.draw_gradient_rect(
171            x,
172            y,
173            width,
174            height,
175            top_left,
176            bottom_left,
177            bottom_right,
178            top_right,
179            draw_order=draw_order,
180        )
181
182    def draw_image(
183        self,
184        x: float,
185        y: float,
186        width: float,
187        height: float,
188        texture_path: str,
189        draw_order: float = 0.0,
190    ) -> None:
191        """Draw an image from a file path via command queue."""
192        self._inner.draw_image(
193            x,
194            y,
195            width,
196            height,
197            texture_path,
198            draw_order=draw_order,
199        )
200
201    def draw_image_from_bytes(
202        self,
203        x: float,
204        y: float,
205        width: float,
206        height: float,
207        texture_key: str,
208        rgba: bytes,
209        texture_width: int,
210        texture_height: int,
211        draw_order: float = 0.0,
212    ) -> None:
213        """Draw an image from raw RGBA bytes via command queue."""
214        self._inner.draw_image_from_bytes(
215            x,
216            y,
217            width,
218            height,
219            texture_key,
220            rgba,
221            texture_width,
222            texture_height,
223            draw_order=draw_order,
224        )
225
226    def draw_text(
227        self,
228        text: str,
229        x: float,
230        y: float,
231        color: Any,
232        font_size: float = 24.0,
233        font_path: Optional[str] = None,
234        letter_spacing: float = 0.0,
235        line_spacing: float = 0.0,
236        draw_order: float = 0.0,
237    ) -> None:
238        """
239        Draw text via command queue.
240
241        By default, this uses the engine's built-in open-source font.
242        Provide `font_path` to use a custom TTF/OTF font file.
243        """
244        self._inner.draw_text(
245            text,
246            x,
247            y,
248            color,
249            font_size=font_size,
250            font_path=font_path,
251            letter_spacing=letter_spacing,
252            line_spacing=line_spacing,
253            draw_order=draw_order,
254        )

Thread-safe handle to the engine that can be passed to background threads.

Use this handle to queue commands like adding objects or drawing from other threads.

EngineHandle(inner: EngineHandle)
37    def __init__(self, inner: _RustEngineHandle) -> None:
38        self._inner = inner
def add_game_object(self, game_object: Any) -> None:
40    def add_game_object(self, game_object: Any) -> None:
41        """
42        Add a `pyg_engine.GameObject` to the runtime scene.
43        
44        This is thread-safe and will be processed on the next engine update.
45        """
46        self._inner.add_game_object(game_object)

Add a pyg_engine.GameObject to the runtime scene.

This is thread-safe and will be processed on the next engine update.

def remove_game_object(self, object_id: int) -> None:
48    def remove_game_object(self, object_id: int) -> None:
49        """Remove a runtime GameObject by id via command queue."""
50        self._inner.remove_game_object(object_id)

Remove a runtime GameObject by id via command queue.

def set_game_object_position(self, object_id: int, position: Any) -> None:
52    def set_game_object_position(self, object_id: int, position: Any) -> None:
53        """Update a runtime GameObject position by id via command queue."""
54        self._inner.set_game_object_position(object_id, position)

Update a runtime GameObject position by id via command queue.

def set_camera_position(self, position: Any) -> None:
56    def set_camera_position(self, position: Any) -> None:
57        """Update the active camera world position via command queue."""
58        self._inner.set_camera_position(position)

Update the active camera world position via command queue.

def set_camera_viewport_size(self, width: float, height: float) -> None:
60    def set_camera_viewport_size(self, width: float, height: float) -> None:
61        """Update the active camera viewport size in world units via command queue."""
62        self._inner.set_camera_viewport_size(width, height)

Update the active camera viewport size in world units via command queue.

def set_camera_aspect_mode(self, mode: str) -> None:
64    def set_camera_aspect_mode(self, mode: str) -> None:
65        """Update camera aspect handling mode via command queue."""
66        self._inner.set_camera_aspect_mode(mode)

Update camera aspect handling mode via command queue.

def set_camera_background_color(self, color: Any) -> None:
68    def set_camera_background_color(self, color: Any) -> None:
69        """Update the active camera background clear color via command queue."""
70        self._inner.set_camera_background_color(color)

Update the active camera background clear color via command queue.

def clear_draw_commands(self) -> None:
72    def clear_draw_commands(self) -> None:
73        """Clear all immediate-mode drawing commands via command queue."""
74        self._inner.clear_draw_commands()

Clear all immediate-mode drawing commands via command queue.

def add_draw_commands(self, commands: list[typing.Any]) -> None:
76    def add_draw_commands(self, commands: list[Any]) -> None:
77        """Submit many draw commands via command queue in one call."""
78        self._inner.add_draw_commands(commands)

Submit many draw commands via command queue in one call.

def draw_pixel(self, x: int, y: int, color: Any, draw_order: float = 0.0) -> None:
80    def draw_pixel(
81        self,
82        x: int,
83        y: int,
84        color: Any,
85        draw_order: float = 0.0,
86    ) -> None:
87        """Draw a pixel in window coordinates via command queue."""
88        self._inner.draw_pixel(x, y, color, draw_order=draw_order)

Draw a pixel in window coordinates via command queue.

def draw_line( self, start_x: float, start_y: float, end_x: float, end_y: float, color: Any, thickness: float = 1.0, draw_order: float = 0.0) -> None:
 90    def draw_line(
 91        self,
 92        start_x: float,
 93        start_y: float,
 94        end_x: float,
 95        end_y: float,
 96        color: Any,
 97        thickness: float = 1.0,
 98        draw_order: float = 0.0,
 99    ) -> None:
100        """Draw a line in window coordinates via command queue."""
101        self._inner.draw_line(
102            start_x,
103            start_y,
104            end_x,
105            end_y,
106            color,
107            thickness=thickness,
108            draw_order=draw_order,
109        )

Draw a line in window coordinates via command queue.

def draw_rectangle( self, x: float, y: float, width: float, height: float, color: Any, filled: bool = True, thickness: float = 1.0, draw_order: float = 0.0) -> None:
111    def draw_rectangle(
112        self,
113        x: float,
114        y: float,
115        width: float,
116        height: float,
117        color: Any,
118        filled: bool = True,
119        thickness: float = 1.0,
120        draw_order: float = 0.0,
121    ) -> None:
122        """Draw a rectangle in window coordinates via command queue."""
123        self._inner.draw_rectangle(
124            x,
125            y,
126            width,
127            height,
128            color,
129            filled=filled,
130            thickness=thickness,
131            draw_order=draw_order,
132        )

Draw a rectangle in window coordinates via command queue.

def draw_circle( self, center_x: float, center_y: float, radius: float, color: Any, filled: bool = True, thickness: float = 1.0, segments: int = 32, draw_order: float = 0.0) -> None:
134    def draw_circle(
135        self,
136        center_x: float,
137        center_y: float,
138        radius: float,
139        color: Any,
140        filled: bool = True,
141        thickness: float = 1.0,
142        segments: int = 32,
143        draw_order: float = 0.0,
144    ) -> None:
145        """Draw a circle in window coordinates via command queue."""
146        self._inner.draw_circle(
147            center_x,
148            center_y,
149            radius,
150            color,
151            filled=filled,
152            thickness=thickness,
153            segments=segments,
154            draw_order=draw_order,
155        )

Draw a circle in window coordinates via command queue.

def draw_gradient_rect( self, x: float, y: float, width: float, height: float, top_left: Any, bottom_left: Any, bottom_right: Any, top_right: Any, draw_order: float = 0.0) -> None:
157    def draw_gradient_rect(
158        self,
159        x: float,
160        y: float,
161        width: float,
162        height: float,
163        top_left: Any,
164        bottom_left: Any,
165        bottom_right: Any,
166        top_right: Any,
167        draw_order: float = 0.0,
168    ) -> None:
169        """Draw a gradient rectangle with per-corner colors via command queue."""
170        self._inner.draw_gradient_rect(
171            x,
172            y,
173            width,
174            height,
175            top_left,
176            bottom_left,
177            bottom_right,
178            top_right,
179            draw_order=draw_order,
180        )

Draw a gradient rectangle with per-corner colors via command queue.

def draw_image( self, x: float, y: float, width: float, height: float, texture_path: str, draw_order: float = 0.0) -> None:
182    def draw_image(
183        self,
184        x: float,
185        y: float,
186        width: float,
187        height: float,
188        texture_path: str,
189        draw_order: float = 0.0,
190    ) -> None:
191        """Draw an image from a file path via command queue."""
192        self._inner.draw_image(
193            x,
194            y,
195            width,
196            height,
197            texture_path,
198            draw_order=draw_order,
199        )

Draw an image from a file path via command queue.

def draw_image_from_bytes( self, x: float, y: float, width: float, height: float, texture_key: str, rgba: bytes, texture_width: int, texture_height: int, draw_order: float = 0.0) -> None:
201    def draw_image_from_bytes(
202        self,
203        x: float,
204        y: float,
205        width: float,
206        height: float,
207        texture_key: str,
208        rgba: bytes,
209        texture_width: int,
210        texture_height: int,
211        draw_order: float = 0.0,
212    ) -> None:
213        """Draw an image from raw RGBA bytes via command queue."""
214        self._inner.draw_image_from_bytes(
215            x,
216            y,
217            width,
218            height,
219            texture_key,
220            rgba,
221            texture_width,
222            texture_height,
223            draw_order=draw_order,
224        )

Draw an image from raw RGBA bytes via command queue.

def draw_text( self, text: str, x: float, y: float, color: Any, font_size: float = 24.0, font_path: Optional[str] = None, letter_spacing: float = 0.0, line_spacing: float = 0.0, draw_order: float = 0.0) -> None:
226    def draw_text(
227        self,
228        text: str,
229        x: float,
230        y: float,
231        color: Any,
232        font_size: float = 24.0,
233        font_path: Optional[str] = None,
234        letter_spacing: float = 0.0,
235        line_spacing: float = 0.0,
236        draw_order: float = 0.0,
237    ) -> None:
238        """
239        Draw text via command queue.
240
241        By default, this uses the engine's built-in open-source font.
242        Provide `font_path` to use a custom TTF/OTF font file.
243        """
244        self._inner.draw_text(
245            text,
246            x,
247            y,
248            color,
249            font_size=font_size,
250            font_path=font_path,
251            letter_spacing=letter_spacing,
252            line_spacing=line_spacing,
253            draw_order=draw_order,
254        )

Draw text via command queue.

By default, this uses the engine's built-in open-source font. Provide font_path to use a custom TTF/OTF font file.

class DrawCommand:

Python-side draw command builder used for bulk submission.

def pixel(x, y, color, draw_order=0.0):
def line(start_x, start_y, end_x, end_y, color, thickness=1.0, draw_order=0.0):
def rectangle( x, y, width, height, color, filled=True, thickness=1.0, draw_order=0.0):
def circle( center_x, center_y, radius, color, filled=True, thickness=1.0, segments=32, draw_order=0.0):
def gradient_rect( x, y, width, height, top_left, bottom_left, bottom_right, top_right, draw_order=0.0):
def image(x, y, width, height, texture_path, draw_order=0.0):
def image_from_bytes( x, y, width, height, texture_key, rgba, texture_width, texture_height, draw_order=0.0):
def text( text, x, y, color, font_size=24.0, font_path=None, letter_spacing=0.0, line_spacing=0.0, draw_order=0.0):
class Input:
257class Input:
258    """
259    Handles keyboard, mouse, and joystick input.
260    
261    This class provides methods to check the current state of input devices
262    and events. It is accessed via the `engine.input` property.
263    """
264    def __init__(self, engine: "Engine") -> None:
265        self._engine = engine._engine
266    
267    def key_down(self, key: str) -> bool:
268        """Check if a keyboard key is currently held down."""
269        return self._engine.key_down(key)
270    
271    def key_pressed(self, key: str) -> bool:
272        """Check if a keyboard key was pressed this frame."""
273        return self._engine.key_pressed(key)
274    
275    def key_released(self, key: str) -> bool:
276        """Check if a keyboard key was released this frame."""
277        return self._engine.key_released(key)
278    
279    def mouse_button_down(self, button: MouseButton) -> bool:
280        """Check if a mouse button is currently held down."""
281        return self._engine.mouse_button_down(button)
282    
283    def mouse_button_pressed(self, button: MouseButton) -> bool:
284        """Check if a mouse button was pressed this frame."""
285        return self._engine.mouse_button_pressed(button)
286    
287    def mouse_button_released(self, button: MouseButton) -> bool:
288        """Check if a mouse button was released this frame."""
289        return self._engine.mouse_button_released(button)
290    
291    @property
292    def mouse_position(self) -> tuple[float, float]:
293        """Get the current mouse position in window coordinates."""
294        return self._engine.mouse_position()
295    
296    @property
297    def mouse_delta(self) -> tuple[float, float]:
298        """Get the mouse movement delta for this frame."""
299        return self._engine.mouse_delta()
300    
301    @property
302    def mouse_wheel(self) -> tuple[float, float]:
303        """Get the mouse wheel delta accumulated this frame."""
304        return self._engine.mouse_wheel()
305    
306    def axis(self, name: str) -> float:
307        """Get the current value of a logical axis (-1.0 to 1.0)."""
308        return self._engine.axis(name)
309    
310    def axis_previous(self, name: str) -> float:
311        """Get the previous frame's value of a logical axis."""
312        return self._engine.axis_previous(name)
313
314    def axis_names(self) -> list[str]:
315        """List all configured logical axis names."""
316        return self._engine.axis_names()
317
318    def action_down(self, action_name: str) -> bool:
319        """Check whether an action is currently active (held)."""
320        return self._engine.action_down(action_name)
321
322    def action_pressed(self, action_name: str) -> bool:
323        """Check whether an action was pressed this frame."""
324        return self._engine.action_pressed(action_name)
325
326    def action_released(self, action_name: str) -> bool:
327        """Check whether an action was released this frame."""
328        return self._engine.action_released(action_name)
329
330    def action_names(self) -> list[str]:
331        """List all configured action names."""
332        return self._engine.action_names()
333
334    def reset_bindings_to_defaults(self) -> None:
335        """Restore default axis and action bindings."""
336        self._engine.reset_input_bindings_to_defaults()
337
338    def set_axis_keys(
339        self,
340        name: str,
341        positive_keys: list[str],
342        negative_keys: list[str],
343        sensitivity: float = 1.0,
344    ) -> None:
345        """Set keyboard keys for an axis (replaces existing keyboard keys)."""
346        self._engine.set_axis_keys(name, positive_keys, negative_keys, sensitivity=sensitivity)
347
348    def add_axis_positive_key(self, axis_name: str, key: str) -> None:
349        """Add one positive key to an axis binding."""
350        self._engine.add_axis_positive_key(axis_name, key)
351
352    def add_axis_negative_key(self, axis_name: str, key: str) -> None:
353        """Add one negative key to an axis binding."""
354        self._engine.add_axis_negative_key(axis_name, key)
355
356    def remove_axis_positive_key(self, axis_name: str, key: str) -> bool:
357        """Remove one positive key from an axis binding."""
358        return self._engine.remove_axis_positive_key(axis_name, key)
359
360    def remove_axis_negative_key(self, axis_name: str, key: str) -> bool:
361        """Remove one negative key from an axis binding."""
362        return self._engine.remove_axis_negative_key(axis_name, key)
363
364    def remove_axis(self, axis_name: str) -> bool:
365        """Remove a logical axis binding."""
366        return self._engine.remove_axis(axis_name)
367
368    def set_axis_mouse(
369        self,
370        name: str,
371        mouse_axis: str,
372        sensitivity: float = 1.0,
373        invert: bool = False,
374    ) -> bool:
375        """Bind an axis to mouse input (x/y/wheel)."""
376        return self._engine.set_axis_mouse(
377            name,
378            mouse_axis,
379            sensitivity=sensitivity,
380            invert=invert,
381        )
382
383    def set_action_keys(self, action_name: str, keys: list[str]) -> None:
384        """Set keyboard bindings for an action (replaces existing keyboard keys)."""
385        self._engine.set_action_keys(action_name, keys)
386
387    def add_action_key(self, action_name: str, key: str) -> None:
388        """Add one keyboard key to an action binding."""
389        self._engine.add_action_key(action_name, key)
390
391    def remove_action_key(self, action_name: str, key: str) -> bool:
392        """Remove one keyboard key from an action binding."""
393        return self._engine.remove_action_key(action_name, key)
394
395    def set_action_mouse_buttons(self, action_name: str, buttons: list[str]) -> None:
396        """Set mouse-button bindings for an action (replaces existing mouse buttons)."""
397        self._engine.set_action_mouse_buttons(action_name, buttons)
398
399    def add_action_mouse_button(self, action_name: str, button: MouseButton) -> None:
400        """Add one mouse button to an action binding."""
401        self._engine.add_action_mouse_button(action_name, button)
402
403    def remove_action_mouse_button(self, action_name: str, button: MouseButton) -> bool:
404        """Remove one mouse button from an action binding."""
405        return self._engine.remove_action_mouse_button(action_name, button)
406
407    def clear_action_bindings(self, action_name: str) -> None:
408        """Clear all bindings (keyboard/mouse/joystick) for an action."""
409        self._engine.clear_action_bindings(action_name)

Handles keyboard, mouse, and joystick input.

This class provides methods to check the current state of input devices and events. It is accessed via the engine.input property.

Input(engine: Engine)
264    def __init__(self, engine: "Engine") -> None:
265        self._engine = engine._engine
def key_down(self, key: str) -> bool:
267    def key_down(self, key: str) -> bool:
268        """Check if a keyboard key is currently held down."""
269        return self._engine.key_down(key)

Check if a keyboard key is currently held down.

def key_pressed(self, key: str) -> bool:
271    def key_pressed(self, key: str) -> bool:
272        """Check if a keyboard key was pressed this frame."""
273        return self._engine.key_pressed(key)

Check if a keyboard key was pressed this frame.

def key_released(self, key: str) -> bool:
275    def key_released(self, key: str) -> bool:
276        """Check if a keyboard key was released this frame."""
277        return self._engine.key_released(key)

Check if a keyboard key was released this frame.

def mouse_button_down(self, button: MouseButton) -> bool:
279    def mouse_button_down(self, button: MouseButton) -> bool:
280        """Check if a mouse button is currently held down."""
281        return self._engine.mouse_button_down(button)

Check if a mouse button is currently held down.

def mouse_button_pressed(self, button: MouseButton) -> bool:
283    def mouse_button_pressed(self, button: MouseButton) -> bool:
284        """Check if a mouse button was pressed this frame."""
285        return self._engine.mouse_button_pressed(button)

Check if a mouse button was pressed this frame.

def mouse_button_released(self, button: MouseButton) -> bool:
287    def mouse_button_released(self, button: MouseButton) -> bool:
288        """Check if a mouse button was released this frame."""
289        return self._engine.mouse_button_released(button)

Check if a mouse button was released this frame.

mouse_position: tuple[float, float]
291    @property
292    def mouse_position(self) -> tuple[float, float]:
293        """Get the current mouse position in window coordinates."""
294        return self._engine.mouse_position()

Get the current mouse position in window coordinates.

mouse_delta: tuple[float, float]
296    @property
297    def mouse_delta(self) -> tuple[float, float]:
298        """Get the mouse movement delta for this frame."""
299        return self._engine.mouse_delta()

Get the mouse movement delta for this frame.

mouse_wheel: tuple[float, float]
301    @property
302    def mouse_wheel(self) -> tuple[float, float]:
303        """Get the mouse wheel delta accumulated this frame."""
304        return self._engine.mouse_wheel()

Get the mouse wheel delta accumulated this frame.

def axis(self, name: str) -> float:
306    def axis(self, name: str) -> float:
307        """Get the current value of a logical axis (-1.0 to 1.0)."""
308        return self._engine.axis(name)

Get the current value of a logical axis (-1.0 to 1.0).

def axis_previous(self, name: str) -> float:
310    def axis_previous(self, name: str) -> float:
311        """Get the previous frame's value of a logical axis."""
312        return self._engine.axis_previous(name)

Get the previous frame's value of a logical axis.

def axis_names(self) -> list[str]:
314    def axis_names(self) -> list[str]:
315        """List all configured logical axis names."""
316        return self._engine.axis_names()

List all configured logical axis names.

def action_down(self, action_name: str) -> bool:
318    def action_down(self, action_name: str) -> bool:
319        """Check whether an action is currently active (held)."""
320        return self._engine.action_down(action_name)

Check whether an action is currently active (held).

def action_pressed(self, action_name: str) -> bool:
322    def action_pressed(self, action_name: str) -> bool:
323        """Check whether an action was pressed this frame."""
324        return self._engine.action_pressed(action_name)

Check whether an action was pressed this frame.

def action_released(self, action_name: str) -> bool:
326    def action_released(self, action_name: str) -> bool:
327        """Check whether an action was released this frame."""
328        return self._engine.action_released(action_name)

Check whether an action was released this frame.

def action_names(self) -> list[str]:
330    def action_names(self) -> list[str]:
331        """List all configured action names."""
332        return self._engine.action_names()

List all configured action names.

def reset_bindings_to_defaults(self) -> None:
334    def reset_bindings_to_defaults(self) -> None:
335        """Restore default axis and action bindings."""
336        self._engine.reset_input_bindings_to_defaults()

Restore default axis and action bindings.

def set_axis_keys( self, name: str, positive_keys: list[str], negative_keys: list[str], sensitivity: float = 1.0) -> None:
338    def set_axis_keys(
339        self,
340        name: str,
341        positive_keys: list[str],
342        negative_keys: list[str],
343        sensitivity: float = 1.0,
344    ) -> None:
345        """Set keyboard keys for an axis (replaces existing keyboard keys)."""
346        self._engine.set_axis_keys(name, positive_keys, negative_keys, sensitivity=sensitivity)

Set keyboard keys for an axis (replaces existing keyboard keys).

def add_axis_positive_key(self, axis_name: str, key: str) -> None:
348    def add_axis_positive_key(self, axis_name: str, key: str) -> None:
349        """Add one positive key to an axis binding."""
350        self._engine.add_axis_positive_key(axis_name, key)

Add one positive key to an axis binding.

def add_axis_negative_key(self, axis_name: str, key: str) -> None:
352    def add_axis_negative_key(self, axis_name: str, key: str) -> None:
353        """Add one negative key to an axis binding."""
354        self._engine.add_axis_negative_key(axis_name, key)

Add one negative key to an axis binding.

def remove_axis_positive_key(self, axis_name: str, key: str) -> bool:
356    def remove_axis_positive_key(self, axis_name: str, key: str) -> bool:
357        """Remove one positive key from an axis binding."""
358        return self._engine.remove_axis_positive_key(axis_name, key)

Remove one positive key from an axis binding.

def remove_axis_negative_key(self, axis_name: str, key: str) -> bool:
360    def remove_axis_negative_key(self, axis_name: str, key: str) -> bool:
361        """Remove one negative key from an axis binding."""
362        return self._engine.remove_axis_negative_key(axis_name, key)

Remove one negative key from an axis binding.

def remove_axis(self, axis_name: str) -> bool:
364    def remove_axis(self, axis_name: str) -> bool:
365        """Remove a logical axis binding."""
366        return self._engine.remove_axis(axis_name)

Remove a logical axis binding.

def set_axis_mouse( self, name: str, mouse_axis: str, sensitivity: float = 1.0, invert: bool = False) -> bool:
368    def set_axis_mouse(
369        self,
370        name: str,
371        mouse_axis: str,
372        sensitivity: float = 1.0,
373        invert: bool = False,
374    ) -> bool:
375        """Bind an axis to mouse input (x/y/wheel)."""
376        return self._engine.set_axis_mouse(
377            name,
378            mouse_axis,
379            sensitivity=sensitivity,
380            invert=invert,
381        )

Bind an axis to mouse input (x/y/wheel).

def set_action_keys(self, action_name: str, keys: list[str]) -> None:
383    def set_action_keys(self, action_name: str, keys: list[str]) -> None:
384        """Set keyboard bindings for an action (replaces existing keyboard keys)."""
385        self._engine.set_action_keys(action_name, keys)

Set keyboard bindings for an action (replaces existing keyboard keys).

def add_action_key(self, action_name: str, key: str) -> None:
387    def add_action_key(self, action_name: str, key: str) -> None:
388        """Add one keyboard key to an action binding."""
389        self._engine.add_action_key(action_name, key)

Add one keyboard key to an action binding.

def remove_action_key(self, action_name: str, key: str) -> bool:
391    def remove_action_key(self, action_name: str, key: str) -> bool:
392        """Remove one keyboard key from an action binding."""
393        return self._engine.remove_action_key(action_name, key)

Remove one keyboard key from an action binding.

def set_action_mouse_buttons(self, action_name: str, buttons: list[str]) -> None:
395    def set_action_mouse_buttons(self, action_name: str, buttons: list[str]) -> None:
396        """Set mouse-button bindings for an action (replaces existing mouse buttons)."""
397        self._engine.set_action_mouse_buttons(action_name, buttons)

Set mouse-button bindings for an action (replaces existing mouse buttons).

def add_action_mouse_button(self, action_name: str, button: MouseButton) -> None:
399    def add_action_mouse_button(self, action_name: str, button: MouseButton) -> None:
400        """Add one mouse button to an action binding."""
401        self._engine.add_action_mouse_button(action_name, button)

Add one mouse button to an action binding.

def remove_action_mouse_button(self, action_name: str, button: MouseButton) -> bool:
403    def remove_action_mouse_button(self, action_name: str, button: MouseButton) -> bool:
404        """Remove one mouse button from an action binding."""
405        return self._engine.remove_action_mouse_button(action_name, button)

Remove one mouse button from an action binding.

def clear_action_bindings(self, action_name: str) -> None:
407    def clear_action_bindings(self, action_name: str) -> None:
408        """Clear all bindings (keyboard/mouse/joystick) for an action."""
409        self._engine.clear_action_bindings(action_name)

Clear all bindings (keyboard/mouse/joystick) for an action.

class UpdateContext:
412class UpdateContext:
413    """
414    Mutable frame context passed to function-based engine update callbacks.
415
416    This object is reused each frame to keep callback overhead low.
417    """
418
419    __slots__ = (
420        "engine",
421        "input",
422        "delta_time",
423        "elapsed_time",
424        "frame",
425        "user_data",
426        "_should_stop",
427    )
428
429    def __init__(self, engine: "Engine", user_data: Any = None) -> None:
430        self.engine = engine
431        self.input = engine.input
432        self.delta_time = 0.0
433        self.elapsed_time = 0.0
434        self.frame = 0
435        self.user_data = user_data
436        self._should_stop = False
437
438    def stop(self) -> None:
439        """Request that `Engine.run(update=...)` exits after this frame."""
440        self._should_stop = True
441
442    @property
443    def should_stop(self) -> bool:
444        """Return whether `stop()` has been requested."""
445        return self._should_stop

Mutable frame context passed to function-based engine update callbacks.

This object is reused each frame to keep callback overhead low.

UpdateContext(engine: Engine, user_data: Any = None)
429    def __init__(self, engine: "Engine", user_data: Any = None) -> None:
430        self.engine = engine
431        self.input = engine.input
432        self.delta_time = 0.0
433        self.elapsed_time = 0.0
434        self.frame = 0
435        self.user_data = user_data
436        self._should_stop = False
engine
input
delta_time
elapsed_time
frame
user_data
def stop(self) -> None:
438    def stop(self) -> None:
439        """Request that `Engine.run(update=...)` exits after this frame."""
440        self._should_stop = True

Request that Engine.run(update=...) exits after this frame.

should_stop: bool
442    @property
443    def should_stop(self) -> bool:
444        """Return whether `stop()` has been requested."""
445        return self._should_stop

Return whether stop() has been requested.

class Vec2:

Python wrapper for 2D Vector

def add(self, /, other):
def add_scalar(self, /, scalar):
def subtract(self, /, other):
def subtract_scalar(self, /, scalar):
def multiply(self, /, other):
def multiply_scalar(self, /, scalar):
def divide(self, /, other):
def divide_scalar(self, /, scalar):
def length(self, /):
def normalize(self, /):
def dot(self, /, other):
def cross(self, /, other):
def distance(self, /, other):
def lerp(self, /, other, t):
x
y
ZERO = Vec2(0, 0)
UP = Vec2(0, 1)
DOWN = Vec2(0, -1)
LEFT = Vec2(-1, 0)
RIGHT = Vec2(1, 0)
class Vec3:

Python wrapper for 3D Vector

def add(self, /, other):
def add_scalar(self, /, scalar):
def subtract(self, /, other):
def subtract_scalar(self, /, scalar):
def multiply(self, /, other):
def multiply_scalar(self, /, scalar):
def divide(self, /, other):
def divide_scalar(self, /, scalar):
def length(self, /):
def normalize(self, /):
def dot(self, /, other):
def cross(self, /, other):
def distance(self, /, other):
def lerp(self, /, other, t):
z
x
y
ZERO = Vec3(0, 0, 0)
UP = Vec3(0, 1, 0)
DOWN = Vec3(0, -1, 0)
LEFT = Vec3(-1, 0, 0)
RIGHT = Vec3(1, 0, 0)
FORWARD = Vec3(0, 0, 1)
BACK = Vec3(0, 0, -1)
class Color:

Python wrapper for Color

def rgb(r, g, b):
def rgba(r, g, b, a):
def from_hex(hex):
def from_hsv(h, s, v, a):
def set_r(self, /, r):
def set_g(self, /, g):
def set_b(self, /, b):
def set_a(self, /, a):
def with_alpha(self, /, a):
def lerp(self, /, other, t):
r
a
g
b
TRANSPARENT = Color(r: 0, g: 0, b: 0, a: 0)
BLACK = Color(r: 0, g: 0, b: 0, a: 1)
WHITE = Color(r: 1, g: 1, b: 1, a: 1)
GRAY = Color(r: 0.5, g: 0.5, b: 0.5, a: 1)
GREY = Color(r: 0.5, g: 0.5, b: 0.5, a: 1)
DARK_GRAY = Color(r: 0.25, g: 0.25, b: 0.25, a: 1)
DARK_GREY = Color(r: 0.25, g: 0.25, b: 0.25, a: 1)
LIGHT_GRAY = Color(r: 0.75, g: 0.75, b: 0.75, a: 1)
LIGHT_GREY = Color(r: 0.75, g: 0.75, b: 0.75, a: 1)
RED = Color(r: 1, g: 0, b: 0, a: 1)
GREEN = Color(r: 0, g: 1, b: 0, a: 1)
LIME = Color(r: 0, g: 1, b: 0, a: 1)
BLUE = Color(r: 0, g: 0, b: 1, a: 1)
YELLOW = Color(r: 1, g: 1, b: 0, a: 1)
CYAN = Color(r: 0, g: 1, b: 1, a: 1)
MAGENTA = Color(r: 1, g: 0, b: 1, a: 1)
ORANGE = Color(r: 1, g: 0.647, b: 0, a: 1)
PINK = Color(r: 1, g: 0.753, b: 0.796, a: 1)
PURPLE = Color(r: 0.502, g: 0, b: 0.502, a: 1)
BROWN = Color(r: 0.647, g: 0.165, b: 0.165, a: 1)
class Time:

Python wrapper for Time.

def tick(self, /):
elapsed_time
delta_time
class GameObject:

Python wrapper for GameObject.

def set_name(self, /, name):
def update(self, /, time=None):

Update this object manually with an optional Time value.

def has_mesh_component(self, /):
def add_mesh_component(self, /, mesh_component):
def set_mesh_component(self, /, mesh_component):
def remove_mesh_component(self, /):
def mesh_component(self, /):
def set_mesh_geometry_rectangle(self, /, width, height):
def set_mesh_geometry_circle(self, /, radius, segments=32):
def set_mesh_fill_color(self, /, color):
def mesh_fill_color(self, /):
def set_mesh_image_path(self, /, image_path):
def mesh_image_path(self, /):
def set_mesh_visible(self, /, visible):
def mesh_visible(self, /):
def set_mesh_draw_order(self, /, draw_order):
def mesh_draw_order(self, /):
id
name
active
scale
position
rotation
class MeshComponent:

Python wrapper for MeshComponent.

def set_geometry_rectangle(self, /, width, height):
def set_geometry_circle(self, /, radius, segments=32):
def set_fill_color(self, /, color):
def fill_color(self, /):
def set_image_path(self, /, image_path):
def image_path(self, /):
visible
draw_order
name
class TransformComponent:

Python wrapper for TransformComponent.

rotation
name
scale
position
class CameraAspectMode:
STRETCH = 'stretch'
MATCH_HORIZONTAL = 'match_horizontal'
MATCH_VERTICAL = 'match_vertical'
FIT_BOTH = 'fit_both'
FILL_BOTH = 'fill_both'
class MouseButton:
LEFT = 'left'
RIGHT = 'right'
MIDDLE = 'middle'
class Keys:
A = 'a'
B = 'b'
C = 'c'
D = 'd'
E = 'e'
F = 'f'
G = 'g'
H = 'h'
I = 'i'
J = 'j'
K = 'k'
L = 'l'
M = 'm'
N = 'n'
O = 'o'
P = 'p'
Q = 'q'
R = 'r'
S = 's'
T = 't'
U = 'u'
V = 'v'
W = 'w'
X = 'x'
Y = 'y'
Z = 'z'
NUM_0 = '0'
NUM_1 = '1'
NUM_2 = '2'
NUM_3 = '3'
NUM_4 = '4'
NUM_5 = '5'
NUM_6 = '6'
NUM_7 = '7'
NUM_8 = '8'
NUM_9 = '9'
MINUS = '-'
EQUALS = '='
LEFT_BRACKET = '['
RIGHT_BRACKET = ']'
BACKSLASH = '\\'
SEMICOLON = ';'
APOSTROPHE = "'"
COMMA = ','
PERIOD = '.'
SLASH = '/'
GRAVE = '`'
ESCAPE = 'Escape'
ENTER = 'Enter'
SPACE = 'Space'
BACKSPACE = 'Backspace'
TAB = 'Tab'
ARROW_UP = 'ArrowUp'
ARROW_DOWN = 'ArrowDown'
ARROW_LEFT = 'ArrowLeft'
ARROW_RIGHT = 'ArrowRight'
SHIFT = 'Shift'
CONTROL = 'Control'
ALT = 'Alt'
SUPER = 'Super'
CAPS_LOCK = 'CapsLock'
NUM_LOCK = 'NumLock'
SCROLL_LOCK = 'ScrollLock'
INSERT = 'Insert'
DELETE = 'Delete'
HOME = 'Home'
END = 'End'
PAGE_UP = 'PageUp'
PAGE_DOWN = 'PageDown'
PRINT_SCREEN = 'PrintScreen'
PAUSE = 'Pause'
CONTEXT_MENU = 'ContextMenu'
F1 = 'F1'
F2 = 'F2'
F3 = 'F3'
F4 = 'F4'
F5 = 'F5'
F6 = 'F6'
F7 = 'F7'
F8 = 'F8'
F9 = 'F9'
F10 = 'F10'
F11 = 'F11'
F12 = 'F12'
version = '1.2.0'