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]
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")
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" ... )
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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).
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.
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
Falseor callcontext.stop()to exit. - See
UpdateContextfor 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).
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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").
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
Python-side draw command builder used for bulk submission.
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.
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.
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.
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.
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.
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.
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.
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).
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.
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.
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).
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.
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.
330 def action_names(self) -> list[str]: 331 """List all configured action names.""" 332 return self._engine.action_names()
List all configured action names.
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.
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).
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.
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.
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.
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.
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.
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).
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).
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.
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.
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.
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.
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.
Python wrapper for 2D Vector
Python wrapper for 3D Vector
Python wrapper for Color
Python wrapper for Time.
Python wrapper for GameObject.
Python wrapper for MeshComponent.
Python wrapper for TransformComponent.