pixzig 0.13.0__py3-none-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pixzig/__init__.py ADDED
@@ -0,0 +1,13 @@
1
+ from ._native import PixzigError
2
+ from .app import PixzigApp
3
+ from .constants import GamepadAxis, GamepadButton, Key, MouseAxis, MouseButton
4
+
5
+ __all__ = [
6
+ "PixzigApp",
7
+ "PixzigError",
8
+ "Key",
9
+ "MouseButton",
10
+ "MouseAxis",
11
+ "GamepadButton",
12
+ "GamepadAxis",
13
+ ]
pixzig/_native.py ADDED
@@ -0,0 +1,221 @@
1
+ """Low-level ctypes bindings to libpixzig_ffi.
2
+
3
+ Internal module: game code should use the classes in `pixzig` (App, Sprite,
4
+ shapes, text, input, keyboard/mouse/gamepad, constants) instead of calling
5
+ anything here directly.
6
+ """
7
+ import ctypes
8
+ import os
9
+ import sys
10
+
11
+
12
+ class PixzigError(Exception):
13
+ """Raised when a pixzig engine call fails."""
14
+
15
+
16
+ def _library_filename() -> str:
17
+ if sys.platform == "darwin":
18
+ return "libpixzig_ffi.dylib"
19
+ if sys.platform == "win32":
20
+ return "pixzig_ffi.dll"
21
+ return "libpixzig_ffi.so"
22
+
23
+
24
+ def _find_library() -> str:
25
+ name = _library_filename()
26
+ here = os.path.dirname(os.path.abspath(__file__))
27
+ candidates = [
28
+ os.path.join(here, name),
29
+ os.path.join(here, "..", "..", "zig-out", "python", name),
30
+ ]
31
+ for path in candidates:
32
+ if os.path.exists(path):
33
+ return path
34
+ raise OSError(
35
+ f"Could not find {name}. Build it with 'zig build python-ffi' from "
36
+ f"the pixzig repo root, or place a copy next to {__file__}."
37
+ )
38
+
39
+
40
+ _lib = ctypes.CDLL(_find_library())
41
+
42
+
43
+ class _PzEngine(ctypes.Structure):
44
+ """Opaque engine handle; never inspected from Python."""
45
+
46
+
47
+ PzEnginePtr = ctypes.POINTER(_PzEngine)
48
+
49
+
50
+ class _PzSprite(ctypes.Structure):
51
+ """Opaque sprite handle; never inspected from Python."""
52
+
53
+
54
+ PzSpritePtr = ctypes.POINTER(_PzSprite)
55
+
56
+
57
+ class _PzCamera(ctypes.Structure):
58
+ """Opaque camera handle; never inspected from Python."""
59
+
60
+
61
+ PzCameraPtr = ctypes.POINTER(_PzCamera)
62
+
63
+
64
+ class _PzTilemapRenderer(ctypes.Structure):
65
+ """Opaque tilemap renderer handle; never inspected from Python."""
66
+
67
+
68
+ PzTilemapRendererPtr = ctypes.POINTER(_PzTilemapRenderer)
69
+
70
+
71
+ class _PzAssetManifest(ctypes.Structure):
72
+ """Opaque asset manifest handle; never inspected from Python."""
73
+
74
+
75
+ PzAssetManifestPtr = ctypes.POINTER(_PzAssetManifest)
76
+
77
+
78
+ class _PzActionMap(ctypes.Structure):
79
+ """Opaque action map handle; never inspected from Python."""
80
+
81
+
82
+ PzActionMapPtr = ctypes.POINTER(_PzActionMap)
83
+
84
+
85
+ def _sig(name, argtypes, restype):
86
+ fn = getattr(_lib, name)
87
+ fn.argtypes = argtypes
88
+ fn.restype = restype
89
+ return fn
90
+
91
+
92
+ def last_error() -> str:
93
+ msg = _lib.pz_last_error()
94
+ return msg.decode("utf-8") if msg else "unknown error"
95
+
96
+
97
+ def check(ok: bool) -> None:
98
+ """Raises PixzigError with the engine's last error message if `ok` is falsy."""
99
+ if not ok:
100
+ raise PixzigError(last_error())
101
+
102
+
103
+ c_float = ctypes.c_float
104
+ c_int = ctypes.c_int
105
+ c_int32 = ctypes.c_int32
106
+ c_uint8 = ctypes.c_uint8
107
+ c_bool = ctypes.c_bool
108
+ c_char_p = ctypes.c_char_p
109
+
110
+ # --- Lifecycle ---------------------------------------------------------
111
+ pz_last_error = _sig("pz_last_error", [], c_char_p)
112
+ pz_init = _sig("pz_init", [c_char_p, c_int32, c_int32], PzEnginePtr)
113
+ pz_deinit = _sig("pz_deinit", [PzEnginePtr], None)
114
+
115
+ # --- Frame stepping ------------------------------------------------------
116
+ pz_should_close = _sig("pz_should_close", [PzEnginePtr], c_bool)
117
+ pz_poll_events = _sig("pz_poll_events", [PzEnginePtr], None)
118
+ pz_update_input = _sig("pz_update_input", [PzEnginePtr], None)
119
+ pz_swap_buffers = _sig("pz_swap_buffers", [PzEnginePtr], None)
120
+ pz_render_begin = _sig("pz_render_begin", [PzEnginePtr], None)
121
+ pz_render_begin_world = _sig("pz_render_begin_world", [PzEnginePtr, PzCameraPtr], None)
122
+ pz_render_clear = _sig("pz_render_clear", [PzEnginePtr, c_float, c_float, c_float, c_float], None)
123
+ pz_render_end = _sig("pz_render_end", [PzEnginePtr], None)
124
+
125
+ # --- Input ---------------------------------------------------------------
126
+ pz_key_down = _sig("pz_key_down", [PzEnginePtr, c_int], c_bool)
127
+ pz_key_pressed = _sig("pz_key_pressed", [PzEnginePtr, c_int], c_bool)
128
+ pz_key_released = _sig("pz_key_released", [PzEnginePtr, c_int], c_bool)
129
+ pz_mouse_pos = _sig("pz_mouse_pos", [PzEnginePtr, ctypes.POINTER(c_float), ctypes.POINTER(c_float)], None)
130
+ pz_mouse_button_down = _sig("pz_mouse_button_down", [PzEnginePtr, c_int], c_bool)
131
+ pz_mouse_button_pressed = _sig("pz_mouse_button_pressed", [PzEnginePtr, c_int], c_bool)
132
+ pz_mouse_button_released = _sig("pz_mouse_button_released", [PzEnginePtr, c_int], c_bool)
133
+ pz_gamepad_connected = _sig("pz_gamepad_connected", [PzEnginePtr, c_int], c_bool)
134
+ pz_gamepad_button_down = _sig("pz_gamepad_button_down", [PzEnginePtr, c_int, c_int], c_bool)
135
+ pz_gamepad_button_pressed = _sig("pz_gamepad_button_pressed", [PzEnginePtr, c_int, c_int], c_bool)
136
+ pz_gamepad_button_released = _sig("pz_gamepad_button_released", [PzEnginePtr, c_int, c_int], c_bool)
137
+ pz_gamepad_axis = _sig("pz_gamepad_axis", [PzEnginePtr, c_int, c_int], c_float)
138
+
139
+ # --- Resources -------------------------------------------------------------
140
+ pz_load_texture = _sig("pz_load_texture", [PzEnginePtr, c_char_p, c_char_p], c_int32)
141
+ pz_texture_sub = _sig("pz_texture_sub", [PzEnginePtr, c_char_p, c_char_p, c_int32, c_int32, c_int32, c_int32], c_int32)
142
+ pz_load_font = _sig("pz_load_font", [PzEnginePtr, c_char_p, c_char_p, c_float], c_int32)
143
+ pz_set_default_font = _sig("pz_set_default_font", [PzEnginePtr, c_char_p], c_int32)
144
+
145
+ # --- Sprites -----------------------------------------------------------
146
+ pz_sprite_create = _sig("pz_sprite_create", [PzEnginePtr, c_char_p], PzSpritePtr)
147
+ pz_sprite_set_pos = _sig("pz_sprite_set_pos", [PzSpritePtr, c_int32, c_int32], None)
148
+ pz_sprite_get_rect = _sig(
149
+ "pz_sprite_get_rect",
150
+ [PzSpritePtr, ctypes.POINTER(c_float), ctypes.POINTER(c_float), ctypes.POINTER(c_float), ctypes.POINTER(c_float)],
151
+ None,
152
+ )
153
+ pz_sprite_draw = _sig("pz_sprite_draw", [PzSpritePtr], None)
154
+ pz_sprite_destroy = _sig("pz_sprite_destroy", [PzSpritePtr], None)
155
+
156
+ # --- Camera ----------------------------------------------------------------
157
+ pz_camera_create = _sig("pz_camera_create", [PzEnginePtr], PzCameraPtr)
158
+ pz_camera_destroy = _sig("pz_camera_destroy", [PzCameraPtr], None)
159
+ pz_camera_set_pos = _sig("pz_camera_set_pos", [PzCameraPtr, c_float, c_float], None)
160
+ pz_camera_get_pos = _sig("pz_camera_get_pos", [PzCameraPtr, ctypes.POINTER(c_float), ctypes.POINTER(c_float)], None)
161
+ pz_camera_set_zoom = _sig("pz_camera_set_zoom", [PzCameraPtr, c_float], None)
162
+ pz_camera_get_zoom = _sig("pz_camera_get_zoom", [PzCameraPtr], c_float)
163
+ pz_camera_set_bounds = _sig("pz_camera_set_bounds", [PzCameraPtr, c_float, c_float, c_float, c_float], None)
164
+ pz_camera_clear_bounds = _sig("pz_camera_clear_bounds", [PzCameraPtr], None)
165
+
166
+ # --- Tilemap -----------------------------------------------------------
167
+ pz_load_tilemap = _sig("pz_load_tilemap", [PzEnginePtr, c_char_p, c_char_p], c_int32)
168
+ pz_tilemap_renderer_create = _sig(
169
+ "pz_tilemap_renderer_create", [PzEnginePtr, c_char_p, c_char_p], PzTilemapRendererPtr
170
+ )
171
+ pz_tilemap_renderer_destroy = _sig("pz_tilemap_renderer_destroy", [PzTilemapRendererPtr], None)
172
+ pz_tilemap_pixel_size = _sig(
173
+ "pz_tilemap_pixel_size",
174
+ [PzTilemapRendererPtr, c_int32, ctypes.POINTER(c_float), ctypes.POINTER(c_float)],
175
+ None,
176
+ )
177
+ pz_tilemap_render = _sig("pz_tilemap_render", [PzTilemapRendererPtr, PzCameraPtr], None)
178
+ pz_tilemap_render_below = _sig("pz_tilemap_render_below", [PzTilemapRendererPtr, PzCameraPtr, c_float], None)
179
+ pz_tilemap_render_above = _sig("pz_tilemap_render_above", [PzTilemapRendererPtr, PzCameraPtr, c_float], None)
180
+ pz_tilemap_check_reload = _sig("pz_tilemap_check_reload", [PzTilemapRendererPtr], c_bool)
181
+
182
+ # --- Asset manifests ---------------------------------------------------
183
+ pz_manifest_load = _sig("pz_manifest_load", [PzEnginePtr, c_char_p], PzAssetManifestPtr)
184
+ pz_manifest_load_group = _sig("pz_manifest_load_group", [PzAssetManifestPtr, c_char_p], c_int32)
185
+ pz_manifest_unload_group = _sig("pz_manifest_unload_group", [PzAssetManifestPtr, c_char_p], None)
186
+ pz_manifest_destroy = _sig("pz_manifest_destroy", [PzAssetManifestPtr], None)
187
+
188
+ # --- Action maps ---------------------------------------------------------
189
+ pz_action_map_create = _sig("pz_action_map_create", [PzEnginePtr], PzActionMapPtr)
190
+ pz_action_map_destroy = _sig("pz_action_map_destroy", [PzActionMapPtr], None)
191
+ pz_action_map_update = _sig("pz_action_map_update", [PzActionMapPtr, ctypes.c_double], None)
192
+ pz_action_bind_key = _sig("pz_action_bind_key", [PzActionMapPtr, c_int32, c_int], c_int32)
193
+ pz_action_bind_mouse_button = _sig("pz_action_bind_mouse_button", [PzActionMapPtr, c_int32, c_int], c_int32)
194
+ pz_action_bind_gamepad_button = _sig("pz_action_bind_gamepad_button", [PzActionMapPtr, c_int32, c_int], c_int32)
195
+ pz_action_bind_axis_buttons = _sig(
196
+ "pz_action_bind_axis_buttons", [PzActionMapPtr, c_int32, c_int, c_int], c_int32
197
+ )
198
+ pz_action_bind_axis_gamepad = _sig(
199
+ "pz_action_bind_axis_gamepad", [PzActionMapPtr, c_int32, c_int, c_float], c_int32
200
+ )
201
+ pz_action_bind_axis_mouse = _sig(
202
+ "pz_action_bind_axis_mouse", [PzActionMapPtr, c_int32, c_int, c_float, c_float], c_int32
203
+ )
204
+ pz_action_up = _sig("pz_action_up", [PzActionMapPtr, c_int32], c_bool)
205
+ pz_action_down = _sig("pz_action_down", [PzActionMapPtr, c_int32], c_bool)
206
+ pz_action_pressed = _sig("pz_action_pressed", [PzActionMapPtr, c_int32], c_bool)
207
+ pz_action_released = _sig("pz_action_released", [PzActionMapPtr, c_int32], c_bool)
208
+ pz_action_axis = _sig("pz_action_axis", [PzActionMapPtr, c_int32], c_float)
209
+
210
+ # --- Shapes and text -----------------------------------------------------
211
+ pz_draw_filled_rect = _sig(
212
+ "pz_draw_filled_rect",
213
+ [PzEnginePtr, c_float, c_float, c_float, c_float, c_float, c_float, c_float, c_float],
214
+ None,
215
+ )
216
+ pz_draw_rect = _sig(
217
+ "pz_draw_rect",
218
+ [PzEnginePtr, c_float, c_float, c_float, c_float, c_float, c_float, c_float, c_float, c_uint8],
219
+ None,
220
+ )
221
+ pz_draw_string = _sig("pz_draw_string", [PzEnginePtr, c_char_p, c_int32, c_int32], None)
pixzig/action.py ADDED
@@ -0,0 +1,120 @@
1
+ """Action mapping: bind named actions/axes to physical inputs, then query
2
+ them by name instead of checking raw keyboard/mouse/gamepad state directly.
3
+ Create via `PixzigApp.create_action_map()`.
4
+
5
+ actions = app.create_action_map()
6
+ actions.bind_key("jump", Key.SPACE)
7
+ actions.bind_gamepad_button("jump", GamepadButton.A)
8
+ actions.bind_axis_buttons("move_x", Key.A, Key.D)
9
+
10
+ # once per update tick:
11
+ actions.update()
12
+ if actions.pressed("jump"): ...
13
+ dx = actions.axis("move_x")
14
+
15
+ Multiple bindings on the same action are OR'ed together; multiple bindings
16
+ on the same axis accumulate (sum). Action/axis names are arbitrary strings
17
+ assigned to internal slots on first use in a `bind_*` call -- there's a
18
+ fixed capacity (64 actions, 32 axes) shared with the underlying engine.
19
+ """
20
+ from . import _native as _n
21
+
22
+
23
+ class ActionMap:
24
+ def __init__(self, handle):
25
+ self._handle = handle
26
+ self._destroyed = False
27
+ self._action_slots: dict[str, int] = {}
28
+ self._axis_slots: dict[str, int] = {}
29
+
30
+ def _check_alive(self) -> None:
31
+ if self._destroyed:
32
+ raise _n.PixzigError("action map already destroyed")
33
+
34
+ def _slot_for_bind(self, slots: dict, name: str, max_slots: int, kind: str) -> int:
35
+ slot = slots.get(name)
36
+ if slot is not None:
37
+ return slot
38
+ if len(slots) >= max_slots:
39
+ raise _n.PixzigError(f"too many {kind}s bound (max {max_slots})")
40
+ slot = len(slots)
41
+ slots[name] = slot
42
+ return slot
43
+
44
+ def _slot_for_query(self, slots: dict, name: str, kind: str) -> int:
45
+ slot = slots.get(name)
46
+ if slot is None:
47
+ raise _n.PixzigError(f"unknown {kind} '{name}' -- bind it before querying")
48
+ return slot
49
+
50
+ # --- Binding -----------------------------------------------------------
51
+
52
+ def bind_key(self, action: str, key: int) -> None:
53
+ self._check_alive()
54
+ slot = self._slot_for_bind(self._action_slots, action, 64, "action")
55
+ _n.check(_n.pz_action_bind_key(self._handle, slot, key) == 0)
56
+
57
+ def bind_mouse_button(self, action: str, button: int) -> None:
58
+ self._check_alive()
59
+ slot = self._slot_for_bind(self._action_slots, action, 64, "action")
60
+ _n.check(_n.pz_action_bind_mouse_button(self._handle, slot, button) == 0)
61
+
62
+ def bind_gamepad_button(self, action: str, button: int) -> None:
63
+ self._check_alive()
64
+ slot = self._slot_for_bind(self._action_slots, action, 64, "action")
65
+ _n.check(_n.pz_action_bind_gamepad_button(self._handle, slot, button) == 0)
66
+
67
+ def bind_axis_buttons(self, axis: str, negative_key: int, positive_key: int) -> None:
68
+ self._check_alive()
69
+ slot = self._slot_for_bind(self._axis_slots, axis, 32, "axis")
70
+ _n.check(_n.pz_action_bind_axis_buttons(self._handle, slot, negative_key, positive_key) == 0)
71
+
72
+ def bind_axis_gamepad(self, axis: str, gamepad_axis: int, deadzone: float = 0.18) -> None:
73
+ self._check_alive()
74
+ slot = self._slot_for_bind(self._axis_slots, axis, 32, "axis")
75
+ _n.check(_n.pz_action_bind_axis_gamepad(self._handle, slot, gamepad_axis, float(deadzone)) == 0)
76
+
77
+ def bind_axis_mouse(self, axis: str, mouse_axis: int, sensitivity: float = 1.0, clamp: float = 1.0) -> None:
78
+ self._check_alive()
79
+ slot = self._slot_for_bind(self._axis_slots, axis, 32, "axis")
80
+ _n.check(
81
+ _n.pz_action_bind_axis_mouse(self._handle, slot, mouse_axis, float(sensitivity), float(clamp)) == 0
82
+ )
83
+
84
+ # --- Per-tick update -----------------------------------------------------
85
+
86
+ def update(self) -> None:
87
+ self._check_alive()
88
+ _n.pz_action_map_update(self._handle, 0.0)
89
+
90
+ # --- Query -----------------------------------------------------------
91
+
92
+ def up(self, action: str) -> bool:
93
+ self._check_alive()
94
+ slot = self._slot_for_query(self._action_slots, action, "action")
95
+ return bool(_n.pz_action_up(self._handle, slot))
96
+
97
+ def down(self, action: str) -> bool:
98
+ self._check_alive()
99
+ slot = self._slot_for_query(self._action_slots, action, "action")
100
+ return bool(_n.pz_action_down(self._handle, slot))
101
+
102
+ def pressed(self, action: str) -> bool:
103
+ self._check_alive()
104
+ slot = self._slot_for_query(self._action_slots, action, "action")
105
+ return bool(_n.pz_action_pressed(self._handle, slot))
106
+
107
+ def released(self, action: str) -> bool:
108
+ self._check_alive()
109
+ slot = self._slot_for_query(self._action_slots, action, "action")
110
+ return bool(_n.pz_action_released(self._handle, slot))
111
+
112
+ def axis(self, axis: str) -> float:
113
+ self._check_alive()
114
+ slot = self._slot_for_query(self._axis_slots, axis, "axis")
115
+ return _n.pz_action_axis(self._handle, slot)
116
+
117
+ def destroy(self) -> None:
118
+ if not self._destroyed:
119
+ _n.pz_action_map_destroy(self._handle)
120
+ self._destroyed = True
pixzig/app.py ADDED
@@ -0,0 +1,157 @@
1
+ """PixzigApp: the base class for a pixzig game written in Python.
2
+
3
+ Subclass it, override `update` and `render`, and call `run()`. The fixed-
4
+ timestep loop below mirrors pixzig's own `PixzigAppRunner.gameLoopCore`
5
+ (src/pixzig/pixzig.zig), just owned by Python instead of Zig.
6
+ """
7
+ import os
8
+ import time
9
+
10
+ from . import _native as _n
11
+ from .action import ActionMap
12
+ from .camera import Camera
13
+ from .input import Gamepad, Keyboard, Mouse
14
+ from .manifest import AssetManifest
15
+ from .shapes import Shapes
16
+ from .sprite import Sprite
17
+ from .text import Text
18
+ from .tilemap import TileMapRenderer
19
+
20
+
21
+ class PixzigApp:
22
+ def __init__(self, title: str, width: int = 800, height: int = 480, update_hz: float = 120.0):
23
+ eng = _n.pz_init(title.encode("utf-8"), int(width), int(height))
24
+ if not eng:
25
+ raise _n.PixzigError(_n.last_error())
26
+ self._eng = eng
27
+ self._update_step_ms = 1000.0 / update_hz
28
+ self._lag = 0.0
29
+ self._curr_time = time.perf_counter() * 1000.0
30
+ self._running = True
31
+
32
+ self.keyboard = Keyboard(eng)
33
+ self.mouse = Mouse(eng)
34
+ self.shapes = Shapes(eng)
35
+ self.text = Text(eng)
36
+
37
+ def gamepad(self, index: int) -> Gamepad:
38
+ return Gamepad(self._eng, index)
39
+
40
+ # --- Resources -----------------------------------------------------
41
+
42
+ def load_texture(self, name: str, path: str) -> None:
43
+ _n.check(_n.pz_load_texture(self._eng, name.encode("utf-8"), path.encode("utf-8")) == 0)
44
+
45
+ def create_subtexture(self, base_name: str, new_name: str, x: int, y: int, w: int, h: int) -> None:
46
+ _n.check(
47
+ _n.pz_texture_sub(
48
+ self._eng, base_name.encode("utf-8"), new_name.encode("utf-8"), int(x), int(y), int(w), int(h)
49
+ )
50
+ == 0
51
+ )
52
+
53
+ def load_sprite(self, texture_name: str) -> Sprite:
54
+ handle = _n.pz_sprite_create(self._eng, texture_name.encode("utf-8"))
55
+ if not handle:
56
+ raise _n.PixzigError(_n.last_error())
57
+ return Sprite(handle)
58
+
59
+ def load_tilemap(self, name: str, path: str) -> None:
60
+ _n.check(_n.pz_load_tilemap(self._eng, name.encode("utf-8"), path.encode("utf-8")) == 0)
61
+
62
+ def create_tilemap_renderer(self, map_name: str, texture_name: str) -> TileMapRenderer:
63
+ handle = _n.pz_tilemap_renderer_create(self._eng, map_name.encode("utf-8"), texture_name.encode("utf-8"))
64
+ if not handle:
65
+ raise _n.PixzigError(_n.last_error())
66
+ return TileMapRenderer(handle)
67
+
68
+ def load_manifest(self, path: str) -> AssetManifest:
69
+ # A relative path is resolved (by AssetManifest.loadFromFile, Zig
70
+ # side) against the running executable's own directory -- for a
71
+ # packaged Zig build that's the game, but under Python it would be
72
+ # the Python interpreter's install directory. Resolve to an absolute
73
+ # path against the current working directory here instead, matching
74
+ # the cwd-relative convention load_texture/load_tilemap already use.
75
+ abs_path = os.path.abspath(path)
76
+ handle = _n.pz_manifest_load(self._eng, abs_path.encode("utf-8"))
77
+ if not handle:
78
+ raise _n.PixzigError(_n.last_error())
79
+ return AssetManifest(handle)
80
+
81
+ # --- Action mapping ---------------------------------------------------
82
+
83
+ def create_action_map(self) -> ActionMap:
84
+ handle = _n.pz_action_map_create(self._eng)
85
+ if not handle:
86
+ raise _n.PixzigError(_n.last_error())
87
+ return ActionMap(handle)
88
+
89
+ # --- Camera ----------------------------------------------------------
90
+
91
+ def create_camera(self) -> Camera:
92
+ handle = _n.pz_camera_create(self._eng)
93
+ if not handle:
94
+ raise _n.PixzigError(_n.last_error())
95
+ return Camera(handle)
96
+
97
+ # --- Rendering ---------------------------------------------------------
98
+ # `render()` must bracket its own drawing with `render_begin()`/`render_end()`
99
+ # -- there's no implicit pass around it. Call `render_begin()` with no
100
+ # arguments for screen-space (UI) drawing, or `render_begin(camera)` for
101
+ # world-space drawing -- e.g. interleaved with
102
+ # `TileMapRenderer.render_below`/`render_above`:
103
+ #
104
+ # def render(self):
105
+ # self.render_begin(self.camera)
106
+ # self.tilemap_renderer.render_below(self.camera, 1.0)
107
+ # self.player_sprite.draw()
108
+ # self.tilemap_renderer.render_above(self.camera, 1.0)
109
+ # self.render_end()
110
+ # self.render_begin()
111
+ # self.text.draw("HUD text", 10, 10)
112
+ # self.render_end()
113
+
114
+ def render_begin(self, camera: Camera = None) -> None:
115
+ if camera is None:
116
+ _n.pz_render_begin(self._eng)
117
+ else:
118
+ _n.pz_render_begin_world(self._eng, camera._handle)
119
+
120
+ def render_end(self) -> None:
121
+ _n.pz_render_end(self._eng)
122
+
123
+ # --- Overridable hooks -----------------------------------------------
124
+
125
+ def update(self, dt_ms: float) -> bool:
126
+ """Called at a fixed timestep. Return False to quit."""
127
+ return True
128
+
129
+ def render(self) -> None:
130
+ """Called once per displayed frame, after the screen is cleared."""
131
+ pass
132
+
133
+ # --- Loop --------------------------------------------------------------
134
+
135
+ def quit(self) -> None:
136
+ self._running = False
137
+
138
+ def run(self) -> None:
139
+ try:
140
+ while self._running and not _n.pz_should_close(self._eng):
141
+ now = time.perf_counter() * 1000.0
142
+ self._lag += now - self._curr_time
143
+ self._curr_time = now
144
+
145
+ _n.pz_poll_events(self._eng)
146
+
147
+ while self._lag > self._update_step_ms:
148
+ self._lag -= self._update_step_ms
149
+ _n.pz_update_input(self._eng)
150
+ if not self.update(self._update_step_ms):
151
+ return
152
+
153
+ _n.pz_render_clear(self._eng, 0.0, 0.0, 0.0, 1.0)
154
+ self.render()
155
+ _n.pz_swap_buffers(self._eng)
156
+ finally:
157
+ _n.pz_deinit(self._eng)
pixzig/camera.py ADDED
@@ -0,0 +1,47 @@
1
+ """2D camera. Create via `PixzigApp.create_camera()`."""
2
+ import ctypes
3
+
4
+ from . import _native as _n
5
+
6
+
7
+ class Camera:
8
+ def __init__(self, handle):
9
+ self._handle = handle
10
+ self._destroyed = False
11
+
12
+ def _check_alive(self) -> None:
13
+ if self._destroyed:
14
+ raise _n.PixzigError("camera already destroyed")
15
+
16
+ def set_pos(self, x: float, y: float) -> None:
17
+ self._check_alive()
18
+ _n.pz_camera_set_pos(self._handle, float(x), float(y))
19
+
20
+ @property
21
+ def pos(self):
22
+ self._check_alive()
23
+ x, y = ctypes.c_float(), ctypes.c_float()
24
+ _n.pz_camera_get_pos(self._handle, ctypes.byref(x), ctypes.byref(y))
25
+ return (x.value, y.value)
26
+
27
+ def set_zoom(self, zoom: float) -> None:
28
+ self._check_alive()
29
+ _n.pz_camera_set_zoom(self._handle, float(zoom))
30
+
31
+ @property
32
+ def zoom(self) -> float:
33
+ self._check_alive()
34
+ return _n.pz_camera_get_zoom(self._handle)
35
+
36
+ def set_bounds(self, l: float, t: float, r: float, b: float) -> None:
37
+ self._check_alive()
38
+ _n.pz_camera_set_bounds(self._handle, float(l), float(t), float(r), float(b))
39
+
40
+ def clear_bounds(self) -> None:
41
+ self._check_alive()
42
+ _n.pz_camera_clear_bounds(self._handle)
43
+
44
+ def destroy(self) -> None:
45
+ if not self._destroyed:
46
+ _n.pz_camera_destroy(self._handle)
47
+ self._destroyed = True
pixzig/constants.py ADDED
@@ -0,0 +1,133 @@
1
+ """Key, mouse, and gamepad codes. Values match GLFW's own codes exactly, so
2
+ they're plain ints under the hood -- but code should always use these names
3
+ rather than the raw numbers.
4
+ """
5
+
6
+
7
+ class Key:
8
+ SPACE = 32
9
+ APOSTROPHE = 39
10
+ COMMA = 44
11
+ MINUS = 45
12
+ PERIOD = 46
13
+ SLASH = 47
14
+ ZERO = 48
15
+ ONE = 49
16
+ TWO = 50
17
+ THREE = 51
18
+ FOUR = 52
19
+ FIVE = 53
20
+ SIX = 54
21
+ SEVEN = 55
22
+ EIGHT = 56
23
+ NINE = 57
24
+ SEMICOLON = 59
25
+ EQUAL = 61
26
+ A = 65
27
+ B = 66
28
+ C = 67
29
+ D = 68
30
+ E = 69
31
+ F = 70
32
+ G = 71
33
+ H = 72
34
+ I = 73
35
+ J = 74
36
+ K = 75
37
+ L = 76
38
+ M = 77
39
+ N = 78
40
+ O = 79
41
+ P = 80
42
+ Q = 81
43
+ R = 82
44
+ S = 83
45
+ T = 84
46
+ U = 85
47
+ V = 86
48
+ W = 87
49
+ X = 88
50
+ Y = 89
51
+ Z = 90
52
+ LEFT_BRACKET = 91
53
+ BACKSLASH = 92
54
+ RIGHT_BRACKET = 93
55
+ GRAVE_ACCENT = 96
56
+
57
+ ESCAPE = 256
58
+ ENTER = 257
59
+ TAB = 258
60
+ BACKSPACE = 259
61
+ INSERT = 260
62
+ DELETE = 261
63
+ RIGHT = 262
64
+ LEFT = 263
65
+ DOWN = 264
66
+ UP = 265
67
+ PAGE_UP = 266
68
+ PAGE_DOWN = 267
69
+ HOME = 268
70
+ END = 269
71
+ CAPS_LOCK = 280
72
+ SCROLL_LOCK = 281
73
+ NUM_LOCK = 282
74
+ PRINT_SCREEN = 283
75
+ PAUSE = 284
76
+ F1 = 290
77
+ F2 = 291
78
+ F3 = 292
79
+ F4 = 293
80
+ F5 = 294
81
+ F6 = 295
82
+ F7 = 296
83
+ F8 = 297
84
+ F9 = 298
85
+ F10 = 299
86
+ F11 = 300
87
+ F12 = 301
88
+ LEFT_SHIFT = 340
89
+ LEFT_CONTROL = 341
90
+ LEFT_ALT = 342
91
+ LEFT_SUPER = 343
92
+ RIGHT_SHIFT = 344
93
+ RIGHT_CONTROL = 345
94
+ RIGHT_ALT = 346
95
+ RIGHT_SUPER = 347
96
+
97
+
98
+ class MouseButton:
99
+ LEFT = 0
100
+ RIGHT = 1
101
+ MIDDLE = 2
102
+
103
+
104
+ class GamepadButton:
105
+ A = 0
106
+ B = 1
107
+ X = 2
108
+ Y = 3
109
+ LEFT_BUMPER = 4
110
+ RIGHT_BUMPER = 5
111
+ BACK = 6
112
+ START = 7
113
+ GUIDE = 8
114
+ LEFT_THUMB = 9
115
+ RIGHT_THUMB = 10
116
+ DPAD_UP = 11
117
+ DPAD_RIGHT = 12
118
+ DPAD_DOWN = 13
119
+ DPAD_LEFT = 14
120
+
121
+
122
+ class GamepadAxis:
123
+ LEFT_X = 0
124
+ LEFT_Y = 1
125
+ RIGHT_X = 2
126
+ RIGHT_Y = 3
127
+ LEFT_TRIGGER = 4
128
+ RIGHT_TRIGGER = 5
129
+
130
+
131
+ class MouseAxis:
132
+ X = 0
133
+ Y = 1
pixzig/input.py ADDED
@@ -0,0 +1,61 @@
1
+ """Keyboard, mouse, and gamepad state, read each frame from the engine."""
2
+ import ctypes
3
+
4
+ from . import _native as _n
5
+
6
+
7
+ class Keyboard:
8
+ def __init__(self, eng):
9
+ self._eng = eng
10
+
11
+ def down(self, key: int) -> bool:
12
+ return bool(_n.pz_key_down(self._eng, key))
13
+
14
+ def pressed(self, key: int) -> bool:
15
+ return bool(_n.pz_key_pressed(self._eng, key))
16
+
17
+ def released(self, key: int) -> bool:
18
+ return bool(_n.pz_key_released(self._eng, key))
19
+
20
+
21
+ class Mouse:
22
+ def __init__(self, eng):
23
+ self._eng = eng
24
+
25
+ @property
26
+ def pos(self):
27
+ x = ctypes.c_float()
28
+ y = ctypes.c_float()
29
+ _n.pz_mouse_pos(self._eng, ctypes.byref(x), ctypes.byref(y))
30
+ return (x.value, y.value)
31
+
32
+ def down(self, button: int) -> bool:
33
+ return bool(_n.pz_mouse_button_down(self._eng, button))
34
+
35
+ def pressed(self, button: int) -> bool:
36
+ return bool(_n.pz_mouse_button_pressed(self._eng, button))
37
+
38
+ def released(self, button: int) -> bool:
39
+ return bool(_n.pz_mouse_button_released(self._eng, button))
40
+
41
+
42
+ class Gamepad:
43
+ def __init__(self, eng, index: int):
44
+ self._eng = eng
45
+ self.index = index
46
+
47
+ @property
48
+ def connected(self) -> bool:
49
+ return bool(_n.pz_gamepad_connected(self._eng, self.index))
50
+
51
+ def down(self, button: int) -> bool:
52
+ return bool(_n.pz_gamepad_button_down(self._eng, self.index, button))
53
+
54
+ def pressed(self, button: int) -> bool:
55
+ return bool(_n.pz_gamepad_button_pressed(self._eng, self.index, button))
56
+
57
+ def released(self, button: int) -> bool:
58
+ return bool(_n.pz_gamepad_button_released(self._eng, self.index, button))
59
+
60
+ def axis(self, axis: int) -> float:
61
+ return _n.pz_gamepad_axis(self._eng, self.index, axis)
pixzig/manifest.py ADDED
@@ -0,0 +1,46 @@
1
+ """Asset manifests. Load via `PixzigApp.load_manifest(path)`.
2
+
3
+ A manifest is a JSON file describing named assets (textures, atlases, fonts,
4
+ tilemaps) grouped into named groups, e.g.:
5
+
6
+ {
7
+ "root": "assets",
8
+ "groups": { "boot": ["ui"], "level1": ["tiles", "level1_map"] },
9
+ "assets": [
10
+ { "id": "ui", "kind": "atlas", "path": "ui" },
11
+ { "id": "tiles", "kind": "texture", "path": "mario_grassish2.png" },
12
+ { "id": "level1_map", "kind": "tilemap", "path": "level1a.tmx" }
13
+ ]
14
+ }
15
+
16
+ A group named "boot" is loaded automatically as soon as the manifest is
17
+ opened. Loading a group registers its assets in the engine's resource
18
+ manager under their manifest id, so they're then usable directly by id with
19
+ `PixzigApp.load_sprite`, `PixzigApp.create_tilemap_renderer`, and
20
+ `Text.set_font` -- no separate step needed to "get" an asset out of the
21
+ manifest.
22
+ """
23
+ from . import _native as _n
24
+
25
+
26
+ class AssetManifest:
27
+ def __init__(self, handle):
28
+ self._handle = handle
29
+ self._destroyed = False
30
+
31
+ def _check_alive(self) -> None:
32
+ if self._destroyed:
33
+ raise _n.PixzigError("asset manifest already destroyed")
34
+
35
+ def load_group(self, name: str) -> None:
36
+ self._check_alive()
37
+ _n.check(_n.pz_manifest_load_group(self._handle, name.encode("utf-8")) == 0)
38
+
39
+ def unload_group(self, name: str) -> None:
40
+ self._check_alive()
41
+ _n.pz_manifest_unload_group(self._handle, name.encode("utf-8"))
42
+
43
+ def destroy(self) -> None:
44
+ if not self._destroyed:
45
+ _n.pz_manifest_destroy(self._handle)
46
+ self._destroyed = True
pixzig/pixzig_ffi.dll ADDED
Binary file
pixzig/shapes.py ADDED
@@ -0,0 +1,24 @@
1
+ """Rectangle drawing. Colors are (r, g, b) or (r, g, b, a) tuples, 0-255."""
2
+ from . import _native as _n
3
+
4
+
5
+ def _normalize_color(color):
6
+ if len(color) == 3:
7
+ r, g, b = color
8
+ a = 255
9
+ else:
10
+ r, g, b, a = color
11
+ return r / 255.0, g / 255.0, b / 255.0, a / 255.0
12
+
13
+
14
+ class Shapes:
15
+ def __init__(self, eng):
16
+ self._eng = eng
17
+
18
+ def filled_rect(self, x, y, w, h, color) -> None:
19
+ r, g, b, a = _normalize_color(color)
20
+ _n.pz_draw_filled_rect(self._eng, x, y, w, h, r, g, b, a)
21
+
22
+ def rect(self, x, y, w, h, color, line_width: int = 1) -> None:
23
+ r, g, b, a = _normalize_color(color)
24
+ _n.pz_draw_rect(self._eng, x, y, w, h, r, g, b, a, line_width)
pixzig/sprite.py ADDED
@@ -0,0 +1,47 @@
1
+ """Sprites. Create via `PixzigApp.load_sprite(texture_name)`."""
2
+ import ctypes
3
+
4
+ from . import _native as _n
5
+
6
+
7
+ class Sprite:
8
+ def __init__(self, handle):
9
+ self._handle = handle
10
+ self._destroyed = False
11
+
12
+ def _check_alive(self) -> None:
13
+ if self._destroyed:
14
+ raise _n.PixzigError("sprite already destroyed")
15
+
16
+ def set_pos(self, x: int, y: int) -> None:
17
+ self._check_alive()
18
+ _n.pz_sprite_set_pos(self._handle, int(x), int(y))
19
+
20
+ @property
21
+ def rect(self):
22
+ self._check_alive()
23
+ x, y, w, h = ctypes.c_float(), ctypes.c_float(), ctypes.c_float(), ctypes.c_float()
24
+ _n.pz_sprite_get_rect(self._handle, ctypes.byref(x), ctypes.byref(y), ctypes.byref(w), ctypes.byref(h))
25
+ return (x.value, y.value, w.value, h.value)
26
+
27
+ @property
28
+ def pos(self):
29
+ x, y, _, _ = self.rect
30
+ return (x, y)
31
+
32
+ @property
33
+ def width(self) -> float:
34
+ return self.rect[2]
35
+
36
+ @property
37
+ def height(self) -> float:
38
+ return self.rect[3]
39
+
40
+ def draw(self) -> None:
41
+ self._check_alive()
42
+ _n.pz_sprite_draw(self._handle)
43
+
44
+ def destroy(self) -> None:
45
+ if not self._destroyed:
46
+ _n.pz_sprite_destroy(self._handle)
47
+ self._destroyed = True
pixzig/text.py ADDED
@@ -0,0 +1,16 @@
1
+ """TrueType text rendering."""
2
+ from . import _native as _n
3
+
4
+
5
+ class Text:
6
+ def __init__(self, eng):
7
+ self._eng = eng
8
+
9
+ def load_font(self, name: str, ttf_path: str, size: float) -> None:
10
+ _n.check(_n.pz_load_font(self._eng, name.encode("utf-8"), ttf_path.encode("utf-8"), float(size)) == 0)
11
+
12
+ def set_font(self, name: str) -> None:
13
+ _n.check(_n.pz_set_default_font(self._eng, name.encode("utf-8")) == 0)
14
+
15
+ def draw(self, text: str, x: int, y: int) -> None:
16
+ _n.pz_draw_string(self._eng, text.encode("utf-8"), int(x), int(y))
pixzig/tilemap.py ADDED
@@ -0,0 +1,52 @@
1
+ """Tiled map loading and chunked rendering. Load a map with
2
+ `PixzigApp.load_tilemap(name, path)`, then create a renderer for it with
3
+ `PixzigApp.create_tilemap_renderer(map_name, texture_name)`.
4
+ """
5
+ import ctypes
6
+
7
+ from . import _native as _n
8
+ from .camera import Camera
9
+
10
+
11
+ class TileMapRenderer:
12
+ def __init__(self, handle):
13
+ self._handle = handle
14
+ self._destroyed = False
15
+
16
+ def _check_alive(self) -> None:
17
+ if self._destroyed:
18
+ raise _n.PixzigError("tilemap renderer already destroyed")
19
+
20
+ def pixel_size(self, layer_index: int):
21
+ """Returns the (width, height) of `layer_index` in pixels, useful for
22
+ setting a camera's bounds to the map's extents."""
23
+ self._check_alive()
24
+ w, h = ctypes.c_float(), ctypes.c_float()
25
+ _n.pz_tilemap_pixel_size(self._handle, int(layer_index), ctypes.byref(w), ctypes.byref(h))
26
+ return (w.value, h.value)
27
+
28
+ def render(self, camera: Camera) -> None:
29
+ self._check_alive()
30
+ _n.pz_tilemap_render(self._handle, camera._handle)
31
+
32
+ def render_below(self, camera: Camera, z: float) -> None:
33
+ """Renders layers with z < `z_threshold`, for background layers."""
34
+ self._check_alive()
35
+ _n.pz_tilemap_render_below(self._handle, camera._handle, float(z))
36
+
37
+ def render_above(self, camera: Camera, z: float) -> None:
38
+ """Renders layers with z >= `z_threshold`, for foreground layers."""
39
+ self._check_alive()
40
+ _n.pz_tilemap_render_above(self._handle, camera._handle, float(z))
41
+
42
+ def check_reload(self) -> bool:
43
+ """Call once per frame; reloads the renderer if the underlying map
44
+ file changed on disk (debug builds only). Returns whether it reloaded,
45
+ so callers know to re-read `pixel_size` for camera bounds."""
46
+ self._check_alive()
47
+ return bool(_n.pz_tilemap_check_reload(self._handle))
48
+
49
+ def destroy(self) -> None:
50
+ if not self._destroyed:
51
+ _n.pz_tilemap_renderer_destroy(self._handle)
52
+ self._destroyed = True
@@ -0,0 +1,6 @@
1
+ Metadata-Version: 2.4
2
+ Name: pixzig
3
+ Version: 0.13.0
4
+ Summary: Python bindings for the pixzig game engine
5
+ License-Expression: MIT
6
+ Requires-Python: >=3.9
@@ -0,0 +1,17 @@
1
+ pixzig/__init__.py,sha256=XY9bJzerb1dwcLhrpj5RVVhtgA15zJg5VUOVKIUh8-Y,277
2
+ pixzig/_native.py,sha256=uBXdcac3oXU8vDs5n4A71aNEYtiRnILZs89G_7MIV5w,9581
3
+ pixzig/action.py,sha256=uegG3NRUi1DdrEQgx6PMRgwvzpztH3GvnOiOxaIuXJg,4963
4
+ pixzig/app.py,sha256=8fRP-f0_SMMKLHMprgv9Ippu_WhDAlvT3Pe1zqM7Px4,6072
5
+ pixzig/camera.py,sha256=J34vDN8dA8cA-KUvnFk43xnlISXQ-U59mFovvw6t_LU,1404
6
+ pixzig/constants.py,sha256=nDiRSlr21PCyS-ie8cuNh6qeM-cE9n_3yxEsDagdGHc,1987
7
+ pixzig/input.py,sha256=wbznD_02b2tU9gmV4rblnzWkvMvewlxzUAzZ6NlvS6w,1774
8
+ pixzig/manifest.py,sha256=sCg1GxtBHsGbpjCErXYHtfbjuWuUKnStzKTGvsoGw8c,1627
9
+ pixzig/pixzig_ffi.dll,sha256=PuH-l_QdYftRMraWZ7quj9CSSsNusJziwKK8rWPdi7U,3640832
10
+ pixzig/shapes.py,sha256=5ks7wK-_Vw_--C_1_6Xv0KBX1kNXvg2k-WPBFzTHGLY,711
11
+ pixzig/sprite.py,sha256=8tdk6vw2jWdFYmytAIVTjaKfutPpd42elOQUU69-izU,1275
12
+ pixzig/text.py,sha256=SLrRMEgSOjZcuCC9pCt7KLzS1rMsx7bO6NHDlQw8nuU,564
13
+ pixzig/tilemap.py,sha256=4xFX69SQX4adMw9Lc6E-qJYeKWyxznLJAoQOfLnHm6U,2042
14
+ pixzig-0.13.0.dist-info/METADATA,sha256=itYbplXv8DjKhefaZoLOiwfFlgdVisTcqhPpZMwaM4Q,150
15
+ pixzig-0.13.0.dist-info/WHEEL,sha256=3I5VVWZdsFlU417aCS2bMRuerS8wsfvBq99MyatBV3A,97
16
+ pixzig-0.13.0.dist-info/top_level.txt,sha256=jToTQIaL9xiFlr6Kfo3zKe2aH2-aCB3slYhdqedGiNI,7
17
+ pixzig-0.13.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-win_amd64
5
+
@@ -0,0 +1 @@
1
+ pixzig