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 +13 -0
- pixzig/_native.py +221 -0
- pixzig/action.py +120 -0
- pixzig/app.py +157 -0
- pixzig/camera.py +47 -0
- pixzig/constants.py +133 -0
- pixzig/input.py +61 -0
- pixzig/manifest.py +46 -0
- pixzig/pixzig_ffi.dll +0 -0
- pixzig/shapes.py +24 -0
- pixzig/sprite.py +47 -0
- pixzig/text.py +16 -0
- pixzig/tilemap.py +52 -0
- pixzig-0.13.0.dist-info/METADATA +6 -0
- pixzig-0.13.0.dist-info/RECORD +17 -0
- pixzig-0.13.0.dist-info/WHEEL +5 -0
- pixzig-0.13.0.dist-info/top_level.txt +1 -0
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,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 @@
|
|
|
1
|
+
pixzig
|