gamekit2d 0.3.2__tar.gz → 0.3.3__tar.gz
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.
- {gamekit2d-0.3.2/gamekit2d.egg-info → gamekit2d-0.3.3}/PKG-INFO +2 -2
- {gamekit2d-0.3.2 → gamekit2d-0.3.3}/README.md +1 -1
- {gamekit2d-0.3.2 → gamekit2d-0.3.3}/gamekit/__init__.py +1 -1
- {gamekit2d-0.3.2 → gamekit2d-0.3.3}/gamekit/core/game.py +94 -29
- {gamekit2d-0.3.2 → gamekit2d-0.3.3}/gamekit/render/backend.py +17 -15
- {gamekit2d-0.3.2 → gamekit2d-0.3.3}/gamekit/render/window.py +41 -35
- {gamekit2d-0.3.2 → gamekit2d-0.3.3}/gamekit/sprites/sprite.py +4 -1
- {gamekit2d-0.3.2 → gamekit2d-0.3.3/gamekit2d.egg-info}/PKG-INFO +2 -2
- {gamekit2d-0.3.2 → gamekit2d-0.3.3}/pyproject.toml +1 -1
- {gamekit2d-0.3.2 → gamekit2d-0.3.3}/LICENSE +0 -0
- {gamekit2d-0.3.2 → gamekit2d-0.3.3}/MANIFEST.in +0 -0
- {gamekit2d-0.3.2 → gamekit2d-0.3.3}/TUTORIAL.html +0 -0
- {gamekit2d-0.3.2 → gamekit2d-0.3.3}/TUTORIAL.md +0 -0
- {gamekit2d-0.3.2 → gamekit2d-0.3.3}/TUTORIAL_zh.md +0 -0
- {gamekit2d-0.3.2 → gamekit2d-0.3.3}/examples/01_hello.py +0 -0
- {gamekit2d-0.3.2 → gamekit2d-0.3.3}/examples/02_pong.py +0 -0
- {gamekit2d-0.3.2 → gamekit2d-0.3.3}/examples/03_breakout.py +0 -0
- {gamekit2d-0.3.2 → gamekit2d-0.3.3}/examples/04_sprite_image.py +0 -0
- {gamekit2d-0.3.2 → gamekit2d-0.3.3}/examples/05_space_shooter.py +0 -0
- {gamekit2d-0.3.2 → gamekit2d-0.3.3}/examples/assets/coin1.png +0 -0
- {gamekit2d-0.3.2 → gamekit2d-0.3.3}/examples/assets/coin2.png +0 -0
- {gamekit2d-0.3.2 → gamekit2d-0.3.3}/examples/assets/player.png +0 -0
- {gamekit2d-0.3.2 → gamekit2d-0.3.3}/examples/assets/star.png +0 -0
- {gamekit2d-0.3.2 → gamekit2d-0.3.3}/examples/make_assets.py +0 -0
- {gamekit2d-0.3.2 → gamekit2d-0.3.3}/gamekit/audio/__init__.py +0 -0
- {gamekit2d-0.3.2 → gamekit2d-0.3.3}/gamekit/audio/sound.py +0 -0
- {gamekit2d-0.3.2 → gamekit2d-0.3.3}/gamekit/core/__init__.py +0 -0
- {gamekit2d-0.3.2 → gamekit2d-0.3.3}/gamekit/core/keys.py +0 -0
- {gamekit2d-0.3.2 → gamekit2d-0.3.3}/gamekit/core/scene.py +0 -0
- {gamekit2d-0.3.2 → gamekit2d-0.3.3}/gamekit/fx/__init__.py +0 -0
- {gamekit2d-0.3.2 → gamekit2d-0.3.3}/gamekit/fx/draw.py +0 -0
- {gamekit2d-0.3.2 → gamekit2d-0.3.3}/gamekit/fx/particles.py +0 -0
- {gamekit2d-0.3.2 → gamekit2d-0.3.3}/gamekit/physics/__init__.py +0 -0
- {gamekit2d-0.3.2 → gamekit2d-0.3.3}/gamekit/physics/collision.py +0 -0
- {gamekit2d-0.3.2 → gamekit2d-0.3.3}/gamekit/render/__init__.py +0 -0
- {gamekit2d-0.3.2 → gamekit2d-0.3.3}/gamekit/render/surface.py +0 -0
- {gamekit2d-0.3.2 → gamekit2d-0.3.3}/gamekit/render/win32.py +0 -0
- {gamekit2d-0.3.2 → gamekit2d-0.3.3}/gamekit/sprites/__init__.py +0 -0
- {gamekit2d-0.3.2 → gamekit2d-0.3.3}/gamekit/sprites/animation.py +0 -0
- {gamekit2d-0.3.2 → gamekit2d-0.3.3}/gamekit/sprites/group.py +0 -0
- {gamekit2d-0.3.2 → gamekit2d-0.3.3}/gamekit/sprites/image.py +0 -0
- {gamekit2d-0.3.2 → gamekit2d-0.3.3}/gamekit/ui/__init__.py +0 -0
- {gamekit2d-0.3.2 → gamekit2d-0.3.3}/gamekit/ui/text.py +0 -0
- {gamekit2d-0.3.2 → gamekit2d-0.3.3}/gamekit/ui/widgets.py +0 -0
- {gamekit2d-0.3.2 → gamekit2d-0.3.3}/gamekit/utils/__init__.py +0 -0
- {gamekit2d-0.3.2 → gamekit2d-0.3.3}/gamekit/utils/color.py +0 -0
- {gamekit2d-0.3.2 → gamekit2d-0.3.3}/gamekit/utils/vector.py +0 -0
- {gamekit2d-0.3.2 → gamekit2d-0.3.3}/gamekit2d.egg-info/SOURCES.txt +0 -0
- {gamekit2d-0.3.2 → gamekit2d-0.3.3}/gamekit2d.egg-info/dependency_links.txt +0 -0
- {gamekit2d-0.3.2 → gamekit2d-0.3.3}/gamekit2d.egg-info/top_level.txt +0 -0
- {gamekit2d-0.3.2 → gamekit2d-0.3.3}/setup.cfg +0 -0
- {gamekit2d-0.3.2 → gamekit2d-0.3.3}/tests/run_selftest.py +0 -0
- {gamekit2d-0.3.2 → gamekit2d-0.3.3}/tests/test_collision.py +0 -0
- {gamekit2d-0.3.2 → gamekit2d-0.3.3}/tests/test_draw.py +0 -0
- {gamekit2d-0.3.2 → gamekit2d-0.3.3}/tests/test_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gamekit2d
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.3
|
|
4
4
|
Summary: A tiny zero-dependency 2D game library (standard library only, self-made GDI renderer + tkinter + winsound). A simpler pygame.
|
|
5
5
|
Author: gamekit contributors
|
|
6
6
|
License-Expression: MIT
|
|
@@ -82,7 +82,7 @@ def wave(dt):
|
|
|
82
82
|
|
|
83
83
|
`Group` 是一组精灵的轻量容器:整组移动 / 隐藏 / 删除,不用自己写循环。精灵仍由 Game 统一渲染,Group 只做批量管理。
|
|
84
84
|
|
|
85
|
-
`Game(..., dirty=True)`
|
|
85
|
+
`Game(..., dirty=True)` 开启**脏矩形局部更新**:不整屏清屏,只把上一帧真正移动过的精灵旧位置刷回背景色。配合 `game.sprite(..., static=True)` 标记静态元素(墙壁、砖块、UI 装饰)——它们只在第一帧画进后台缓冲,之后只要没被移动精灵压过就不再重画。实测"400 静态 + 8 动态"场景比每帧全画快 **1.46 倍**。大量精灵同时移动时仍用默认 `dirty=False`。仅 GDI 后端有效。
|
|
86
86
|
|
|
87
87
|
---
|
|
88
88
|
|
|
@@ -58,7 +58,7 @@ def wave(dt):
|
|
|
58
58
|
|
|
59
59
|
`Group` 是一组精灵的轻量容器:整组移动 / 隐藏 / 删除,不用自己写循环。精灵仍由 Game 统一渲染,Group 只做批量管理。
|
|
60
60
|
|
|
61
|
-
`Game(..., dirty=True)`
|
|
61
|
+
`Game(..., dirty=True)` 开启**脏矩形局部更新**:不整屏清屏,只把上一帧真正移动过的精灵旧位置刷回背景色。配合 `game.sprite(..., static=True)` 标记静态元素(墙壁、砖块、UI 装饰)——它们只在第一帧画进后台缓冲,之后只要没被移动精灵压过就不再重画。实测"400 静态 + 8 动态"场景比每帧全画快 **1.46 倍**。大量精灵同时移动时仍用默认 `dirty=False`。仅 GDI 后端有效。
|
|
62
62
|
|
|
63
63
|
---
|
|
64
64
|
|
|
@@ -46,7 +46,7 @@ from .utils.color import (Color, to_color, mix, random_color,
|
|
|
46
46
|
LIGHT_GRAY, DARK_GRAY, NAVY)
|
|
47
47
|
from .utils.vector import Vec2
|
|
48
48
|
|
|
49
|
-
__version__ = "0.3.
|
|
49
|
+
__version__ = "0.3.3"
|
|
50
50
|
__all__ = [
|
|
51
51
|
"Game", "Scene", "Key", "normalize",
|
|
52
52
|
"Sprite", "Group", "Animation",
|
|
@@ -63,6 +63,7 @@ class Game:
|
|
|
63
63
|
self._dirty = bool(dirty) and (self.backend == "GdiRenderer")
|
|
64
64
|
self._last_bboxes = {}
|
|
65
65
|
self._moved = {}
|
|
66
|
+
self._dirty_first = True # 第一帧必须全画(静态精灵首次入缓冲)
|
|
66
67
|
|
|
67
68
|
# ---- 全局物理 ----
|
|
68
69
|
self.gravity = 0.0 # 像素/秒^2,作用于 gravity_scale>0 的精灵
|
|
@@ -188,15 +189,27 @@ class Game:
|
|
|
188
189
|
r = self._renderer
|
|
189
190
|
|
|
190
191
|
if self._dirty:
|
|
191
|
-
|
|
192
|
-
r.begin_frame(clear=False)
|
|
193
|
-
if self._moved:
|
|
194
|
-
r.recover_background(list(self._moved.values()))
|
|
195
|
-
self._moved = {}
|
|
192
|
+
self._render_dirty(r)
|
|
196
193
|
else:
|
|
197
194
|
r.begin_frame(clear=True)
|
|
195
|
+
self._draw_all(r)
|
|
198
196
|
|
|
199
|
-
|
|
197
|
+
for t in list(self._texts):
|
|
198
|
+
if not t._removed:
|
|
199
|
+
t._draw(r)
|
|
200
|
+
|
|
201
|
+
for u in list(self._ui_elements):
|
|
202
|
+
if not u._removed:
|
|
203
|
+
u._draw(r)
|
|
204
|
+
|
|
205
|
+
for ps in list(self._particles):
|
|
206
|
+
if not ps._removed:
|
|
207
|
+
ps._draw(r)
|
|
208
|
+
|
|
209
|
+
r.end_frame()
|
|
210
|
+
|
|
211
|
+
def _collect_drawables(self):
|
|
212
|
+
"""收集当前帧需要绘制的精灵/图形,按 layer 排序。"""
|
|
200
213
|
drawables = []
|
|
201
214
|
for s in self._sprites:
|
|
202
215
|
if not s._removed and s.visible:
|
|
@@ -204,36 +217,84 @@ class Game:
|
|
|
204
217
|
for sh in self._shapes:
|
|
205
218
|
if not sh._removed and sh.visible:
|
|
206
219
|
drawables.append((sh.layer, sh))
|
|
220
|
+
drawables.sort(key=lambda x: x[0])
|
|
221
|
+
return drawables
|
|
222
|
+
|
|
223
|
+
def _draw_all(self, r):
|
|
224
|
+
"""整屏清屏模式:画全部精灵/图形。"""
|
|
225
|
+
for _, obj in self._collect_drawables():
|
|
226
|
+
obj._draw(r)
|
|
227
|
+
|
|
228
|
+
@staticmethod
|
|
229
|
+
def _bbox_intersects(l, t, r, b, rect):
|
|
230
|
+
rl, rt, rr, rb = rect
|
|
231
|
+
return not (r < rl or l > rr or b < rt or t > rb)
|
|
232
|
+
|
|
233
|
+
def _render_dirty(self, r):
|
|
234
|
+
"""脏矩形模式:不清整屏,静态精灵只画一次,动态精灵旧位置用背景恢复。"""
|
|
235
|
+
r.begin_frame(clear=False)
|
|
236
|
+
dirty = list(self._moved.values()) # 上一帧动态精灵的旧位置
|
|
237
|
+
if dirty:
|
|
238
|
+
r.recover_background(dirty)
|
|
239
|
+
|
|
240
|
+
statics = []
|
|
241
|
+
dynamics = []
|
|
207
242
|
seen = set()
|
|
208
|
-
for
|
|
243
|
+
for s in self._sprites:
|
|
244
|
+
if s._removed or not s.visible:
|
|
245
|
+
continue
|
|
246
|
+
if getattr(s, "static", False):
|
|
247
|
+
statics.append((s.layer, s))
|
|
248
|
+
else:
|
|
249
|
+
dynamics.append((s.layer, s))
|
|
250
|
+
|
|
251
|
+
if self._dirty_first:
|
|
252
|
+
# 第一帧:所有精灵都要画进后台缓冲(包括静态的)
|
|
253
|
+
to_draw = [(sh.layer, sh) for sh in self._shapes if not sh._removed and sh.visible]
|
|
254
|
+
to_draw.extend(statics)
|
|
255
|
+
to_draw.extend(dynamics)
|
|
256
|
+
to_draw.sort(key=lambda x: x[0])
|
|
257
|
+
for _, obj in to_draw:
|
|
258
|
+
obj._draw(r)
|
|
259
|
+
if hasattr(obj, "left"):
|
|
260
|
+
seen.add(id(obj))
|
|
261
|
+
self._dirty_first = False
|
|
262
|
+
# 记录所有动态精灵 bbox(静态精灵不进 _last_bboxes,
|
|
263
|
+
# 它们位置不变,以后靠"与脏区相交才重画"管理)
|
|
264
|
+
for _, s in dynamics:
|
|
265
|
+
self._last_bboxes[id(s)] = (int(s.left), int(s.top),
|
|
266
|
+
int(s.right), int(s.bottom))
|
|
267
|
+
return
|
|
268
|
+
|
|
269
|
+
# 后续帧:静态精灵只有 bbox 与脏区相交时才重画(被动态精灵压过)
|
|
270
|
+
redraw_statics = []
|
|
271
|
+
for layer, s in statics:
|
|
272
|
+
sl = int(s.left); st = int(s.top); sr = int(s.right); sb = int(s.bottom)
|
|
273
|
+
if any(self._bbox_intersects(sl, st, sr, sb, d) for d in dirty):
|
|
274
|
+
redraw_statics.append((layer, s))
|
|
275
|
+
|
|
276
|
+
to_draw = redraw_statics + dynamics
|
|
277
|
+
# shapes 每帧重画(简单起见,不做静态优化)
|
|
278
|
+
for sh in self._shapes:
|
|
279
|
+
if not sh._removed and sh.visible:
|
|
280
|
+
to_draw.append((sh.layer, sh))
|
|
281
|
+
to_draw.sort(key=lambda x: x[0])
|
|
282
|
+
|
|
283
|
+
self._moved = {}
|
|
284
|
+
for _, obj in to_draw:
|
|
209
285
|
obj._draw(r)
|
|
210
|
-
if
|
|
286
|
+
if hasattr(obj, "left") and not getattr(obj, "static", False):
|
|
211
287
|
bid = id(obj)
|
|
212
288
|
seen.add(bid)
|
|
213
289
|
nb = (int(obj.left), int(obj.top),
|
|
214
290
|
int(obj.right), int(obj.bottom))
|
|
215
291
|
old = self._last_bboxes.get(bid)
|
|
216
292
|
if old is not None and old != nb:
|
|
217
|
-
self._moved[bid] = old
|
|
293
|
+
self._moved[bid] = old
|
|
218
294
|
self._last_bboxes[bid] = nb
|
|
219
|
-
#
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
self._last_bboxes.pop(bid, None)
|
|
223
|
-
|
|
224
|
-
for t in list(self._texts):
|
|
225
|
-
if not t._removed:
|
|
226
|
-
t._draw(r)
|
|
227
|
-
|
|
228
|
-
for u in list(self._ui_elements):
|
|
229
|
-
if not u._removed:
|
|
230
|
-
u._draw(r)
|
|
231
|
-
|
|
232
|
-
for ps in list(self._particles):
|
|
233
|
-
if not ps._removed:
|
|
234
|
-
ps._draw(r)
|
|
235
|
-
|
|
236
|
-
r.end_frame()
|
|
295
|
+
# 清理已删除动态精灵的 bbox 记录
|
|
296
|
+
for bid in [b for b in self._last_bboxes if b not in seen]:
|
|
297
|
+
self._last_bboxes.pop(bid, None)
|
|
237
298
|
|
|
238
299
|
# ==================================================================
|
|
239
300
|
# 定时器
|
|
@@ -269,15 +330,19 @@ class Game:
|
|
|
269
330
|
# 对象工厂
|
|
270
331
|
# ==================================================================
|
|
271
332
|
def sprite(self, image=None, x=0, y=0, width=None, height=None,
|
|
272
|
-
color=None, shape="rect", tag=None, layer=0):
|
|
333
|
+
color=None, shape="rect", tag=None, layer=0, static=False):
|
|
273
334
|
"""创建并返回一个精灵。
|
|
274
335
|
|
|
275
336
|
``game.sprite("player.png", x=100, y=100)``
|
|
276
337
|
``game.sprite(color="red", x=200, y=200, width=40, height=40, shape="circle")``
|
|
338
|
+
|
|
339
|
+
``static=True`` 标记静态精灵:dirty 模式下只在第一帧绘制,
|
|
340
|
+
之后不再重画(被移动精灵压过时会自动重画那一块)。
|
|
277
341
|
"""
|
|
278
342
|
from ..sprites.sprite import Sprite
|
|
279
343
|
s = Sprite(self, image=image, x=x, y=y, width=width, height=height,
|
|
280
|
-
color=color, shape=shape, tag=tag, layer=layer
|
|
344
|
+
color=color, shape=shape, tag=tag, layer=layer,
|
|
345
|
+
static=static)
|
|
281
346
|
if s not in self._sprites:
|
|
282
347
|
self._sprites.append(s)
|
|
283
348
|
return s
|
|
@@ -327,6 +327,9 @@ def _vk_to_keysym(vk):
|
|
|
327
327
|
# 矩形 Surface 缓存上限:超过后回退直接 GDI 绘制,防 GDI 对象耗尽
|
|
328
328
|
_RECT_CACHE_MAX = 256
|
|
329
329
|
|
|
330
|
+
# 进程级共享的隐藏 tk root:PhotoImage 解码需要 Tk 默认 root
|
|
331
|
+
_shared_decode_root = None
|
|
332
|
+
|
|
330
333
|
|
|
331
334
|
class GdiRenderer(Renderer):
|
|
332
335
|
"""基于自研 GDI 内核的后端(立即模式)。
|
|
@@ -348,15 +351,18 @@ class GdiRenderer(Renderer):
|
|
|
348
351
|
self._fonts = {} # (family, size, bold, italic) -> HFONT
|
|
349
352
|
self._image_cache = {} # id(photo) -> Surface(图像精灵像素缓存)
|
|
350
353
|
self._rect_cache = {} # (w,h,color[,outline]) -> Surface(矩形精灵缓存)
|
|
351
|
-
|
|
352
|
-
#
|
|
353
|
-
#
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
354
|
+
# 隐藏 tk root 是进程级共享单例:PhotoImage 解码需要 Tk 默认 root,
|
|
355
|
+
# 但 Tcl 一次只能有一个解释器。多个 Game 复用同一个 root,
|
|
356
|
+
# 不在每个后端关闭时 destroy(进程退出时一起回收)。
|
|
357
|
+
global _shared_decode_root
|
|
358
|
+
if _shared_decode_root is None:
|
|
359
|
+
try:
|
|
360
|
+
import tkinter as tk
|
|
361
|
+
_shared_decode_root = tk.Tk()
|
|
362
|
+
_shared_decode_root.withdraw()
|
|
363
|
+
except Exception:
|
|
364
|
+
_shared_decode_root = None
|
|
365
|
+
self._decode_root = _shared_decode_root
|
|
360
366
|
|
|
361
367
|
# 输入状态
|
|
362
368
|
self._keys_held = set()
|
|
@@ -401,12 +407,8 @@ class GdiRenderer(Renderer):
|
|
|
401
407
|
pass
|
|
402
408
|
self._rect_cache.clear()
|
|
403
409
|
self._window.close()
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
self._decode_root.destroy()
|
|
407
|
-
except Exception:
|
|
408
|
-
pass
|
|
409
|
-
self._decode_root = None
|
|
410
|
+
# decode_root 是进程级共享单例,不在此处 destroy
|
|
411
|
+
self._decode_root = None
|
|
410
412
|
|
|
411
413
|
# ---- 每帧 ----
|
|
412
414
|
def pump(self):
|
|
@@ -19,6 +19,32 @@ _CLASS_NAME = "GameKitRenderWindow"
|
|
|
19
19
|
|
|
20
20
|
# 所有活着的 Window:hwnd -> Window,供 wndproc 回调(滚轮等)反查实例
|
|
21
21
|
_WINDOWS = {}
|
|
22
|
+
# 窗口类只注册一次(重复 RegisterClassW 会让类句柄和 WNDPROC 闭包累积)
|
|
23
|
+
_class_registered = False
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@win32.WNDPROC
|
|
27
|
+
def _wndproc(hwnd, msg, wparam, lparam):
|
|
28
|
+
"""模块级窗口过程:不闭包,所有 GameKit 窗口共享。"""
|
|
29
|
+
if msg == WM_MOUSEWHEEL:
|
|
30
|
+
win = _WINDOWS.get(hwnd)
|
|
31
|
+
if win is not None:
|
|
32
|
+
delta = (wparam >> 16) & 0xFFFF
|
|
33
|
+
if delta >= 0x8000:
|
|
34
|
+
delta -= 0x10000
|
|
35
|
+
win._wheel_delta += delta
|
|
36
|
+
return 0
|
|
37
|
+
if msg == WM_CLOSE:
|
|
38
|
+
win32.user32.DestroyWindow(hwnd)
|
|
39
|
+
return 0
|
|
40
|
+
if msg == WM_DESTROY:
|
|
41
|
+
# 注意:这里不要 PostQuitMessage(0)。
|
|
42
|
+
# PostQuitMessage 会往线程消息队列塞 WM_QUIT,
|
|
43
|
+
# 若同进程随后再用 tkinter(Tcl 事件循环),残留的
|
|
44
|
+
# WM_QUIT 会让其悄悄退出。关闭检测改由 poll_events 的
|
|
45
|
+
# IsWindow 完成。
|
|
46
|
+
return 0
|
|
47
|
+
return win32.user32.DefWindowProcW(hwnd, msg, wparam, lparam)
|
|
22
48
|
|
|
23
49
|
|
|
24
50
|
class Window:
|
|
@@ -51,41 +77,21 @@ class Window:
|
|
|
51
77
|
# ------------------------------------------------------------ 创建
|
|
52
78
|
|
|
53
79
|
def _register_class(self):
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
if
|
|
68
|
-
|
|
69
|
-
# PostQuitMessage 会往线程消息队列塞 WM_QUIT,
|
|
70
|
-
# 若同进程随后再用 tkinter(Tcl 事件循环),残留的
|
|
71
|
-
# WM_QUIT 会让其悄悄退出。关闭检测改由 poll_events 的
|
|
72
|
-
# IsWindow 完成。
|
|
73
|
-
return 0
|
|
74
|
-
return win32.user32.DefWindowProcW(hwnd, msg, wparam, lparam)
|
|
75
|
-
|
|
76
|
-
self._wndproc = wndproc # 保持引用
|
|
77
|
-
wc = WNDCLASSW()
|
|
78
|
-
wc.style = 0
|
|
79
|
-
wc.lpfnWndProc = wndproc
|
|
80
|
-
wc.cbClsExtra = 0
|
|
81
|
-
wc.cbWndExtra = 0
|
|
82
|
-
wc.hInstance = win32.hinstance()
|
|
83
|
-
wc.hIcon = None
|
|
84
|
-
wc.hCursor = None
|
|
85
|
-
wc.hbrBackground = win32.gdi32.GetStockObject(win32.COLOR_WINDOW)
|
|
86
|
-
wc.lpszMenuName = None
|
|
87
|
-
wc.lpszClassName = _CLASS_NAME
|
|
88
|
-
win32.user32.RegisterClassW(byref(wc))
|
|
80
|
+
global _class_registered
|
|
81
|
+
if not _class_registered:
|
|
82
|
+
wc = WNDCLASSW()
|
|
83
|
+
wc.style = 0
|
|
84
|
+
wc.lpfnWndProc = _wndproc
|
|
85
|
+
wc.cbClsExtra = 0
|
|
86
|
+
wc.cbWndExtra = 0
|
|
87
|
+
wc.hInstance = win32.hinstance()
|
|
88
|
+
wc.hIcon = None
|
|
89
|
+
wc.hCursor = None
|
|
90
|
+
wc.hbrBackground = win32.gdi32.GetStockObject(win32.COLOR_WINDOW)
|
|
91
|
+
wc.lpszMenuName = None
|
|
92
|
+
wc.lpszClassName = _CLASS_NAME
|
|
93
|
+
if win32.user32.RegisterClassW(byref(wc)):
|
|
94
|
+
_class_registered = True
|
|
89
95
|
|
|
90
96
|
def _create_window(self, title):
|
|
91
97
|
style = win32.WS_OVERLAPPEDWINDOW | win32.WS_VISIBLE
|
|
@@ -23,7 +23,7 @@ class Sprite:
|
|
|
23
23
|
"""
|
|
24
24
|
|
|
25
25
|
def __init__(self, game, image=None, x=0, y=0, width=None, height=None,
|
|
26
|
-
color=None, shape="rect", tag=None, layer=0):
|
|
26
|
+
color=None, shape="rect", tag=None, layer=0, static=False):
|
|
27
27
|
self.game = game
|
|
28
28
|
|
|
29
29
|
# ---- 位置与速度 ----
|
|
@@ -40,6 +40,9 @@ class Sprite:
|
|
|
40
40
|
self.shape = shape if shape in ("rect", "circle") else "rect"
|
|
41
41
|
self.layer = layer
|
|
42
42
|
self.tag = tag
|
|
43
|
+
# static=True:标记为静态精灵。dirty 模式下它只在第一帧绘制,
|
|
44
|
+
# 之后只要没被动态精灵压过就不再重画(像素留在后台缓冲里)。
|
|
45
|
+
self.static = bool(static)
|
|
43
46
|
self._image_path = None
|
|
44
47
|
self._photo = None
|
|
45
48
|
self._flip_h = False
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gamekit2d
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.3
|
|
4
4
|
Summary: A tiny zero-dependency 2D game library (standard library only, self-made GDI renderer + tkinter + winsound). A simpler pygame.
|
|
5
5
|
Author: gamekit contributors
|
|
6
6
|
License-Expression: MIT
|
|
@@ -82,7 +82,7 @@ def wave(dt):
|
|
|
82
82
|
|
|
83
83
|
`Group` 是一组精灵的轻量容器:整组移动 / 隐藏 / 删除,不用自己写循环。精灵仍由 Game 统一渲染,Group 只做批量管理。
|
|
84
84
|
|
|
85
|
-
`Game(..., dirty=True)`
|
|
85
|
+
`Game(..., dirty=True)` 开启**脏矩形局部更新**:不整屏清屏,只把上一帧真正移动过的精灵旧位置刷回背景色。配合 `game.sprite(..., static=True)` 标记静态元素(墙壁、砖块、UI 装饰)——它们只在第一帧画进后台缓冲,之后只要没被移动精灵压过就不再重画。实测"400 静态 + 8 动态"场景比每帧全画快 **1.46 倍**。大量精灵同时移动时仍用默认 `dirty=False`。仅 GDI 后端有效。
|
|
86
86
|
|
|
87
87
|
---
|
|
88
88
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "gamekit2d"
|
|
7
|
-
version = "0.3.
|
|
7
|
+
version = "0.3.3"
|
|
8
8
|
description = "A tiny zero-dependency 2D game library (standard library only, self-made GDI renderer + tkinter + winsound). A simpler pygame."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|