qgame 1.0.0__py3-none-any.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.
qgame/__init__.py ADDED
@@ -0,0 +1,57 @@
1
+ try:
2
+ from .core import init, set_settings, Window, Events, Clock, EventType
3
+ from .input import Keys, Keyboard, Mouse, MouseButtons
4
+ from .collision import Collision
5
+ from .audio import Sound, Music
6
+ from .graphics import Image, Draw, Rect
7
+ from .camera import Camera
8
+ from .animation import (
9
+ add_file_path, image_show, animation_start, animation_stop,
10
+ set_animation_frame, set_animation_speed, animation_reverse,
11
+ animation_reverse_back, AnimationStatus
12
+ )
13
+ from .animation_sprite import AnimationSprite, AnimationBackground
14
+ from .ui import TextBox
15
+ from .scene import Scene, SceneManager
16
+ from .spritesheet import Spritesheet
17
+ except ImportError:
18
+ from core import init, set_settings, Window, Events, Clock, EventType
19
+ from input import Keys, Keyboard, Mouse, MouseButtons
20
+ from collision import Collision
21
+ from audio import Sound, Music
22
+ from graphics import Image, Draw, Rect
23
+ from camera import Camera
24
+ from animation import (
25
+ add_file_path, image_show, animation_start, animation_stop,
26
+ set_animation_frame, set_animation_speed, animation_reverse,
27
+ animation_reverse_back, AnimationStatus
28
+ )
29
+ from animation_sprite import AnimationSprite, AnimationBackground
30
+ from ui import TextBox
31
+ from scene import Scene, SceneManager
32
+ from spritesheet import Spritesheet
33
+
34
+ # 接口全局实例映射
35
+ window = Window()
36
+ events = Events()
37
+ eventType = EventType
38
+ draw = Draw()
39
+ keys = Keys()
40
+ keyboard = Keyboard()
41
+ mouse = Mouse()
42
+ mouseButtons = MouseButtons
43
+ collision = Collision()
44
+ scene_manager = SceneManager # 实例化映射 alias 方便小写调用
45
+
46
+ __author__ = "WatermelonCode"
47
+ ___version__ = "1.0.0"
48
+
49
+ __all__ = [
50
+ 'init', 'set_settings', 'window', 'events', 'draw', 'keys', 'keyboard',
51
+ 'mouse', 'mouseButtons', 'collision', 'Clock', 'EventType',
52
+ 'Image', 'Draw', 'Rect', 'Sound', 'Music',
53
+ 'Camera', 'add_file_path', 'image_show', 'animation_start', 'animation_stop',
54
+ 'set_animation_frame', 'set_animation_speed', 'animation_reverse',
55
+ 'animation_reverse_back', 'AnimationStatus', 'AnimationSprite', 'AnimationBackground',
56
+ 'TextBox', 'Scene', 'SceneManager', 'scene_manager', 'Spritesheet'
57
+ ]
qgame/__main__.py ADDED
@@ -0,0 +1,566 @@
1
+ import sys
2
+ import os
3
+
4
+ # ==================== 智能路径自适应寻轨 ====================
5
+ current_dir = os.path.dirname(os.path.abspath(__file__))
6
+ parent_dir = os.path.dirname(current_dir)
7
+
8
+ if parent_dir not in sys.path:
9
+ sys.path.insert(0, parent_dir)
10
+ if os.getcwd() not in sys.path:
11
+ sys.path.append(os.getcwd())
12
+
13
+ import qgame
14
+ # ===========================================================
15
+
16
+ import math
17
+ import random
18
+ import json
19
+
20
+ # 定位包内内置的 examples 物理资源路径
21
+ EXAMPLES_DIR = os.path.join(current_dir, "examples")
22
+ PLAYER_IMG_PATH = os.path.join(EXAMPLES_DIR, "player.png")
23
+ SOUND_WAV_PATH = os.path.join(EXAMPLES_DIR, "sound.wav")
24
+ MUSIC_MP3_PATH = os.path.join(EXAMPLES_DIR, "music.mp3")
25
+
26
+ # ==================== 示例 1:Delta Time 与图像旋转镜像 ====================
27
+ def run_delta_time_demo():
28
+ qgame.init()
29
+ canvas = qgame.set_settings(width=800, height=600, title="Delta Time 与图像旋转镜像")
30
+ clock = qgame.Clock()
31
+
32
+ player_img = None
33
+ if os.path.exists(PLAYER_IMG_PATH):
34
+ player_img = qgame.Image(PLAYER_IMG_PATH)
35
+ else:
36
+ print(f"[警告] 未在 {PLAYER_IMG_PATH} 找到 player.png")
37
+
38
+ rect_x = 100.0
39
+ speed_per_second = 300.0
40
+ running = True
41
+ angle = 0
42
+
43
+ while running:
44
+ dt = clock.tick(60)
45
+ fps = 1.0 / dt if dt > 0 else 0
46
+
47
+ for event in qgame.events.get():
48
+ if event.type == qgame.eventType.QUIT:
49
+ running = False
50
+ elif event.type == qgame.eventType.KEY_DOWN:
51
+ if event.key == qgame.keys.ESCAPE:
52
+ running = False
53
+ if event.key == qgame.keys.F11:
54
+ qgame.window.toggle_fullscreen()
55
+ if event.key == qgame.keys.F and player_img:
56
+ player_img.flip(horizontal=True, vertical=False)
57
+ if event.key == qgame.keys.G and player_img:
58
+ player_img.flip(horizontal=False, vertical=True)
59
+ if event.key == qgame.keys.H and player_img:
60
+ player_img.reset()
61
+ player_img.resize(100, 100)
62
+
63
+ rect_x += speed_per_second * dt
64
+ if rect_x > 800:
65
+ rect_x = -100
66
+
67
+ if player_img:
68
+ player_img.reset()
69
+ player_img.resize(100, 100)
70
+ angle = (angle + 100) % 360
71
+ player_img.rotate(angle)
72
+
73
+ qgame.draw.fill(canvas, (30, 30, 30))
74
+ qgame.draw.rect(canvas, (0, 180, 255), (int(rect_x), 300, 100, 50))
75
+ qgame.draw.text(canvas, "Smooth Speed Box", (int(rect_x), 275), size=12)
76
+
77
+ if player_img:
78
+ qgame.draw.image(canvas, player_img, (350, 100), center=True)
79
+ qgame.draw.text(canvas, "Press [F]: Flip H [G]: Flip V [H]: Reset", (220, 220), size=14)
80
+
81
+ qgame.draw.text(canvas, f"Delta Time: {dt:.4f}s | FPS: {fps:.1f}", (50, 480), size=16, color=(100, 255, 100))
82
+ qgame.window.update()
83
+
84
+ # ==================== 示例 2:裁剪、不透明度、全屏和隐藏鼠标 ====================
85
+ def run_clipping_demo():
86
+ qgame.init()
87
+ canvas = qgame.set_settings(width=800, height=600, title="不透明度与图像裁剪")
88
+ clock = qgame.Clock()
89
+
90
+ player_img = None
91
+ if os.path.exists(PLAYER_IMG_PATH):
92
+ player_img = qgame.Image(PLAYER_IMG_PATH)
93
+
94
+ cursor_visible = True
95
+ opacity_timer = 0.0
96
+ running = True
97
+
98
+ while running:
99
+ dt = clock.tick(60)
100
+ for event in qgame.events.get():
101
+ if event.type == qgame.eventType.QUIT:
102
+ running = False
103
+ elif event.type == qgame.eventType.KEY_DOWN:
104
+ if event.key == qgame.keys.ESCAPE:
105
+ running = False
106
+ if event.key == qgame.keys.F11:
107
+ qgame.window.toggle_fullscreen()
108
+ if event.key == qgame.keys.M:
109
+ cursor_visible = not cursor_visible
110
+ qgame.window.show_cursor(cursor_visible)
111
+
112
+ qgame.draw.fill(canvas, (20, 24, 30))
113
+ if player_img:
114
+ opacity_timer += dt * 2.0
115
+ current_opacity = 0.5 + 0.5 * math.sin(opacity_timer)
116
+ qgame.draw.image(canvas, player_img, (200, 300), center=True, opacity=current_opacity)
117
+
118
+ crop_rect = (0, 0, min(50, player_img.width), min(50, player_img.height))
119
+ qgame.draw.image(canvas, player_img, (600, 300), center=True, src_rect=crop_rect)
120
+
121
+ qgame.draw.text(canvas, "F11: Fullscreen | M: Toggle Cursor | Esc: Back", (50, 50), size=18)
122
+ qgame.window.update()
123
+
124
+ # ==================== 示例 3:场景管理与UI自动销毁 ====================
125
+ class MenuScene(qgame.Scene):
126
+ def on_enter(self, *args, **kwargs):
127
+ super().on_enter()
128
+ self.input_box = self.add_ui(
129
+ qgame.TextBox(200, 180, 400, 150, placeholder="写下你的勇者名字...", multi_line=True)
130
+ )
131
+ self.input_box.set_theme(bg_color=(35, 35, 45), text_color=(255, 255, 0), border_color=(0, 255, 120))
132
+ self.button_rect = (300, 380, 200, 50)
133
+
134
+ def handle_event(self, event):
135
+ if event.type == qgame.eventType.MOUSE_BUTTON_DOWN:
136
+ if event.button == qgame.mouseButtons.LEFT:
137
+ if qgame.collision.check_point_rect(event.pos, self.button_rect):
138
+ name = self.input_box.get_text().strip() or "神秘勇者"
139
+ qgame.scene_manager.switch(PlayScene(), player_name=name)
140
+
141
+ def draw(self, canvas):
142
+ qgame.draw.fill(canvas, (24, 28, 38))
143
+ qgame.draw.text(canvas, "勇者传说 - 主菜单", (280, 100), size=28, color=(255, 255, 255))
144
+
145
+ m_pos = qgame.mouse.get_pos()
146
+ hover = qgame.collision.check_point_rect(m_pos, self.button_rect)
147
+ btn_color = (0, 255, 120) if hover else (0, 180, 90)
148
+
149
+ qgame.draw.rect(canvas, btn_color, self.button_rect, width=0)
150
+ qgame.draw.text(canvas, "开始游戏", (360, 393), size=18, color=(0, 0, 0))
151
+
152
+ class PlayScene(qgame.Scene):
153
+ def on_enter(self, *args, **kwargs):
154
+ super().on_enter()
155
+ self.player_name = kwargs.get("player_name", "神秘人")
156
+ self.px, self.py = 400.0, 300.0
157
+ self.speed = 250.0
158
+
159
+ def handle_event(self, event):
160
+ if event.type == qgame.eventType.KEY_DOWN:
161
+ if event.key == qgame.keys.ESCAPE:
162
+ qgame.scene_manager.switch(MenuScene())
163
+
164
+ def update(self, dt):
165
+ if qgame.keyboard.is_pressed(qgame.keys.W) or qgame.keyboard.is_pressed(qgame.keys.UP):
166
+ self.py -= self.speed * dt
167
+ if qgame.keyboard.is_pressed(qgame.keys.S) or qgame.keyboard.is_pressed(qgame.keys.DOWN):
168
+ self.py += self.speed * dt
169
+ if qgame.keyboard.is_pressed(qgame.keys.A) or qgame.keyboard.is_pressed(qgame.keys.LEFT):
170
+ self.px -= self.speed * dt
171
+ if qgame.keyboard.is_pressed(qgame.keys.D) or qgame.keyboard.is_pressed(qgame.keys.RIGHT):
172
+ self.px += self.speed * dt
173
+
174
+ def draw(self, canvas):
175
+ qgame.draw.fill(canvas, (10, 40, 30))
176
+ qgame.draw.text(canvas, f"当前勇者: {self.player_name}", (20, 20), size=16, color=(255, 255, 255))
177
+ qgame.draw.text(canvas, "操作: WASD/方向键移动,按 Esc 键返回主菜单", (20, 50), size=14, color=(200, 200, 200))
178
+ qgame.draw.rect(canvas, (0, 255, 200), (int(self.px) - 25, int(self.py) - 25, 50, 50))
179
+
180
+ def run_scene_demo():
181
+ qgame.init()
182
+ canvas = qgame.set_settings(width=800, height=600, title="qgame 场景管理系统测试")
183
+ clock = qgame.Clock()
184
+
185
+ qgame.scene_manager.switch(MenuScene())
186
+ running = True
187
+ while running:
188
+ dt = clock.tick(60)
189
+ for event in qgame.events.get():
190
+ if event.type == qgame.eventType.QUIT:
191
+ running = False
192
+ if event.type == qgame.eventType.KEY_DOWN:
193
+ if event.key == qgame.keys.F11:
194
+ qgame.window.toggle_fullscreen()
195
+ qgame.scene_manager.handle_event(event)
196
+
197
+ qgame.scene_manager.update(dt)
198
+ qgame.scene_manager.draw(canvas)
199
+ qgame.window.update()
200
+
201
+ # ==================== 示例 4:高级摄像机追踪测试 ====================
202
+ def run_camera_demo():
203
+ qgame.init()
204
+ canvas = qgame.set_settings(width=800, height=600, title="qgame 2D 摄像机平滑缓冲示范")
205
+ clock = qgame.Clock()
206
+
207
+ WORLD_WIDTH, WORLD_HEIGHT = 2000, 2000
208
+ camera = qgame.Camera(800, 600)
209
+ camera.set_bounds(0, 0, WORLD_WIDTH, WORLD_HEIGHT)
210
+ camera.set_deadzone(120, 100)
211
+
212
+ class Player:
213
+ def __init__(self):
214
+ self.x, self.y = 1000.0, 1000.0
215
+ self.width, self.height = 40, 40
216
+ self.speed = 400.0
217
+
218
+ player = Player()
219
+ camera.follow(player, lerp_speed=4.5)
220
+
221
+ stars = []
222
+ for _ in range(120):
223
+ stars.append((
224
+ random.randint(10, WORLD_WIDTH - 20),
225
+ random.randint(10, WORLD_HEIGHT - 20),
226
+ (random.randint(180, 255), random.randint(180, 255), random.randint(0, 100))
227
+ ))
228
+
229
+ running, show_debug = True, True
230
+ while running:
231
+ dt = clock.tick(60)
232
+ for event in qgame.events.get():
233
+ if event.type == qgame.eventType.QUIT:
234
+ running = False
235
+ elif event.type == qgame.eventType.KEY_DOWN:
236
+ if event.key == qgame.keys.ESCAPE:
237
+ running = False
238
+ elif event.key == qgame.keys.SPACE:
239
+ camera.shake(intensity=18.0, duration=0.4)
240
+ elif event.key == qgame.keys.F3:
241
+ show_debug = not show_debug
242
+ elif event.key == qgame.keys.F11:
243
+ qgame.window.toggle_fullscreen()
244
+
245
+ dx, dy = 0.0, 0.0
246
+ if qgame.keyboard.is_pressed(qgame.keys.W) or qgame.keyboard.is_pressed(qgame.keys.UP):
247
+ dy -= player.speed * dt
248
+ if qgame.keyboard.is_pressed(qgame.keys.S) or qgame.keyboard.is_pressed(qgame.keys.DOWN):
249
+ dy += player.speed * dt
250
+ if qgame.keyboard.is_pressed(qgame.keys.A) or qgame.keyboard.is_pressed(qgame.keys.LEFT):
251
+ dx -= player.speed * dt
252
+ if qgame.keyboard.is_pressed(qgame.keys.D) or qgame.keyboard.is_pressed(qgame.keys.RIGHT):
253
+ dx += player.speed * dt
254
+
255
+ player.x += dx
256
+ player.y += dy
257
+
258
+ old_x, old_y = player.x, player.y
259
+ player.x = max(0.0, min(player.x, WORLD_WIDTH - player.width))
260
+ player.y = max(0.0, min(player.y, WORLD_HEIGHT - player.height))
261
+ if (player.x != old_x or player.y != old_y) and (dx != 0.0 or dy != 0.0):
262
+ camera.shake(intensity=3.0, duration=0.15)
263
+
264
+ camera.update(dt)
265
+
266
+ qgame.draw.fill(canvas, (20, 20, 25))
267
+ map_rect = camera.apply((0, 0, WORLD_WIDTH, WORLD_HEIGHT))
268
+ qgame.draw.rect(canvas, (40, 45, 60), map_rect, width=2)
269
+
270
+ for star in stars:
271
+ screen_pos = camera.apply((star[0], star[1]))
272
+ if 0 <= screen_pos[0] <= 800 and 0 <= screen_pos[1] <= 600:
273
+ qgame.draw.rect(canvas, star[2], (screen_pos[0], screen_pos[1], 8, 8), width=0)
274
+
275
+ p_screen_rect = camera.apply((player.x, player.y, player.width, player.height))
276
+ qgame.draw.rect(canvas, (0, 255, 120), p_screen_rect, width=0)
277
+ qgame.draw.rect(canvas, (255, 255, 255), p_screen_rect, width=2)
278
+
279
+ if show_debug:
280
+ dz_rect = (int(400 - camera.deadzone_width / 2),
281
+ int(300 - camera.deadzone_height / 2),
282
+ int(camera.deadzone_width),
283
+ int(camera.deadzone_height))
284
+ qgame.draw.rect(canvas, (255, 200, 50), dz_rect, width=1)
285
+
286
+ qgame.draw.text(canvas, f"Coordinate: ({int(player.x)}, {int(player.y)})", (20, 20), size=16)
287
+ qgame.draw.text(canvas, "WASD: Move | Space: Shake Screen | Esc: Back", (20, 550), size=14)
288
+ qgame.window.update()
289
+
290
+ # ==================== 示例 5:合图与网格切图测试 ====================
291
+ def run_spritesheet_demo():
292
+ from PySide6.QtGui import QPainter, QColor, QImage
293
+ img_path = "temp_test_spritesheet.png"
294
+ json_path = "temp_test_atlas.json"
295
+
296
+ # 生成测试图
297
+ temp_img = QImage(128, 128, QImage.Format.Format_ARGB32)
298
+ p = QPainter(temp_img)
299
+ p.fillRect(0, 0, 64, 64, QColor(255, 50, 50))
300
+ p.fillRect(64, 0, 64, 64, QColor(50, 255, 50))
301
+ p.fillRect(0, 64, 64, 64, QColor(50, 50, 255))
302
+ p.fillRect(64, 64, 64, 64, QColor(255, 255, 50))
303
+ p.end()
304
+ temp_img.save(img_path)
305
+
306
+ with open(json_path, 'w', encoding='utf-8') as f:
307
+ json.dump({
308
+ "frames": {
309
+ "red_block.png": {"frame": {"x": 0, "y": 0, "w": 64, "h": 64}},
310
+ "green_block.png": {"frame": {"x": 64, "y": 0, "w": 64, "h": 64}},
311
+ "blue_block.png": {"frame": {"x": 0, "y": 64, "w": 64, "h": 64}},
312
+ "yellow_block.png": {"frame": {"x": 64, "y": 64, "w": 64, "h": 64}}
313
+ }
314
+ }, f)
315
+
316
+ qgame.init()
317
+ canvas = qgame.set_settings(width=800, height=600, title="雪碧图与合图解析器演示")
318
+ clock = qgame.Clock()
319
+
320
+ sheet = qgame.Spritesheet(img_path)
321
+ tiles = sheet.parse_grid(tile_width=64, tile_height=64)
322
+ atlas = sheet.parse_atlas(json_path)
323
+
324
+ running = True
325
+ while running:
326
+ clock.tick(60)
327
+ for event in qgame.events.get():
328
+ if event.type == qgame.eventType.QUIT:
329
+ running = False
330
+ if event.type == qgame.eventType.KEY_DOWN and event.key == qgame.keys.ESCAPE:
331
+ running = False
332
+
333
+ qgame.draw.fill(canvas, (30, 30, 30))
334
+ qgame.draw.text(canvas, "Spritesheet / Texture Atlas 成果展示", (50, 40), size=22)
335
+
336
+ qgame.draw.text(canvas, "1. 顺序网格切割渲染 (parse_grid):", (50, 100), size=14, color=(200, 200, 200))
337
+ for idx, tile_image in enumerate(tiles):
338
+ qgame.draw.image(canvas, tile_image, (50 + idx * 80, 130))
339
+
340
+ qgame.draw.text(canvas, "2. 配置文件字典直接提取 (parse_atlas):", (50, 280), size=14, color=(200, 200, 200))
341
+ qgame.draw.image(canvas, atlas["blue_block.png"], (50, 310))
342
+ qgame.draw.image(canvas, atlas["yellow_block.png"], (180, 310))
343
+ qgame.draw.image(canvas, atlas["red_block.png"], (310, 310))
344
+
345
+ qgame.window.update()
346
+
347
+ for p in [img_path, json_path]:
348
+ if os.path.exists(p):
349
+ os.remove(p)
350
+
351
+ # ==================== 示例 6:几何图形与抗锯齿文字 ====================
352
+ def run_geometrics_demo():
353
+ qgame.init()
354
+ canvas = qgame.set_settings(width=800, height=600, title="几何画板功能测试")
355
+ clock = qgame.Clock()
356
+
357
+ circle_radius, pulse_dir = 50, 1
358
+ running = True
359
+ while running:
360
+ clock.tick(60)
361
+ for event in qgame.events.get():
362
+ if event.type == qgame.eventType.QUIT:
363
+ running = False
364
+ elif event.type == qgame.eventType.KEY_DOWN and event.key == qgame.keys.ESCAPE:
365
+ running = False
366
+
367
+ circle_radius += pulse_dir * 1
368
+ if circle_radius >= 120 or circle_radius <= 20:
369
+ pulse_dir *= -1
370
+
371
+ qgame.draw.fill(canvas, (30, 32, 40))
372
+
373
+ qgame.draw.rect(canvas, (240, 80, 80), (50, 50, 150, 100))
374
+ qgame.draw.rect(canvas, (80, 240, 80), (50, 200, 150, 100), width=4)
375
+ qgame.draw.circle(canvas, (80, 120, 240), (400, 250), circle_radius)
376
+ qgame.draw.circle(canvas, (240, 200, 80), (400, 250), 130, width=3)
377
+ qgame.draw.ellipse(canvas, (180, 80, 240), (600, 50, 150, 80))
378
+ qgame.draw.line(canvas, (255, 100, 255), (50, 320), (750, 380), width=4)
379
+
380
+ qgame.draw.text(canvas, "QGame Engine Text Rendering Test", (50, 420), size=24)
381
+ qgame.draw.text(canvas, "Size 28px 中文抗锯齿测试", (50, 500), size=28, color=(255, 200, 50))
382
+ qgame.window.update()
383
+
384
+ # ==================== 示例 7:文本输入与聊天框自适应 ====================
385
+ def run_textbox_demo():
386
+ qgame.init()
387
+ canvas = qgame.set_settings(width=800, height=600, title="UI 控件自适应文本框测试")
388
+ clock = qgame.Clock()
389
+
390
+ chat_board = qgame.TextBox(50, 50, 700, 350, placeholder="聊天频道暂无消息...", multi_line=True)
391
+ chat_board.widget.setReadOnly(True)
392
+ chat_board.set_theme(bg_color=(20, 20, 25, 180), text_color=(200, 230, 200), border_color=(100, 100, 120), font_size=13)
393
+
394
+ chat_input = qgame.TextBox(50, 420, 600, 100, placeholder="输入文本,按回车或点击发送按钮发送...", multi_line=True)
395
+ chat_input.set_focus()
396
+
397
+ btn_rect = (670, 420, 80, 100)
398
+ chat_log = ["【系统提示】: 欢迎测试 QGame 原生聊天框!按 F11 体验完美全屏缩放。"]
399
+
400
+ def post_msg():
401
+ text = chat_input.get_text().strip()
402
+ if text:
403
+ chat_log.append(f"【测试勇者】: {text}")
404
+ chat_input.clear()
405
+ chat_board.set_text("\n".join(chat_log))
406
+ chat_board.widget.moveCursor(chat_board.widget.textCursor().MoveOperation.End)
407
+ chat_input.set_focus()
408
+
409
+ chat_board.set_text("\n".join(chat_log))
410
+ running = True
411
+
412
+ while running:
413
+ clock.tick(60)
414
+ for event in qgame.events.get():
415
+ if event.type == qgame.eventType.QUIT:
416
+ running = False
417
+ elif event.type == qgame.eventType.KEY_DOWN:
418
+ if event.key == qgame.keys.ESCAPE:
419
+ running = False
420
+ elif event.key == qgame.keys.F11:
421
+ qgame.window.toggle_fullscreen()
422
+ elif event.key == qgame.keys.RETURN or event.key == qgame.keys.ENTER:
423
+ post_msg()
424
+ elif event.type == qgame.eventType.MOUSE_BUTTON_DOWN:
425
+ if event.button == qgame.mouseButtons.LEFT:
426
+ if qgame.collision.check_point_rect(event.pos, btn_rect):
427
+ post_msg()
428
+
429
+ qgame.draw.fill(canvas, (30, 35, 45))
430
+ is_hover = qgame.collision.check_point_rect(qgame.mouse.get_pos(), btn_rect)
431
+ qgame.draw.rect(canvas, (90, 160, 255) if is_hover else (70, 130, 230), btn_rect)
432
+ qgame.draw.text(canvas, "发送", (690, 460), size=18, color=(255, 255, 255))
433
+ qgame.window.update()
434
+
435
+ # ==================== 示例 8:音效音乐与碰撞交互测试 ====================
436
+ def run_audio_collision_demo():
437
+ qgame.init()
438
+ canvas = qgame.set_settings(width=800, height=600, title="音效音乐与方块点碰撞检测")
439
+ clock = qgame.Clock()
440
+
441
+ rect_btn = (150, 200, 200, 80)
442
+ square_btn = (480, 190, 150, 100)
443
+
444
+ sound_eff = None
445
+ if os.path.exists(SOUND_WAV_PATH):
446
+ try:
447
+ sound_eff = qgame.Sound(SOUND_WAV_PATH)
448
+ sound_eff.set_volume(0.8)
449
+ except Exception as e:
450
+ print(f"音效载入失败: {e}")
451
+
452
+ bg_music = None
453
+ bg_music_active = False
454
+ if os.path.exists(MUSIC_MP3_PATH):
455
+ try:
456
+ bg_music = qgame.Music(MUSIC_MP3_PATH)
457
+ bg_music.set_volume(2)
458
+ bg_music.play(loop=True)
459
+ bg_music_active = True
460
+ except Exception as e:
461
+ print(f"背景音乐载入失败: {e}")
462
+
463
+ status_msg = "音频测试就绪。将鼠标悬停在按钮上并点击。"
464
+ running = True
465
+
466
+ while running:
467
+ clock.tick(60)
468
+ mouse_pt = qgame.mouse.get_pos()
469
+
470
+ hover_rect = qgame.collision.check_point_rect(mouse_pt, rect_btn)
471
+ hover_square = qgame.collision.check_point_rect(mouse_pt, square_btn)
472
+
473
+ for event in qgame.events.get():
474
+ if event.type == qgame.eventType.QUIT:
475
+ running = False
476
+ elif event.type == qgame.eventType.KEY_DOWN:
477
+ if event.key == qgame.keys.ESCAPE:
478
+ running = False
479
+ if event.key == qgame.keys.F11:
480
+ qgame.window.toggle_fullscreen()
481
+
482
+ elif event.type == qgame.eventType.MOUSE_BUTTON_DOWN:
483
+ if event.button == qgame.mouseButtons.LEFT:
484
+ if qgame.collision.check_point_rect(event.pos, rect_btn):
485
+ status_msg = "播放音效:sound.wav"
486
+ if sound_eff:
487
+ sound_eff.play()
488
+
489
+ elif qgame.collision.check_point_rect(event.pos, square_btn):
490
+ if bg_music:
491
+ if bg_music_active:
492
+ bg_music.pause()
493
+ bg_music_active = False
494
+ status_msg = "BGM 已暂停"
495
+ else:
496
+ bg_music.unpause()
497
+ bg_music_active = True
498
+ status_msg = "BGM 继续播放"
499
+
500
+ qgame.draw.fill(canvas, (30, 32, 40))
501
+
502
+ green_color = (130, 250, 130) if hover_rect else (60, 180, 60)
503
+ blue_color = (130, 180, 250) if hover_square else (60, 110, 200)
504
+
505
+ qgame.draw.rect(canvas, green_color, rect_btn)
506
+ qgame.draw.rect(canvas, blue_color, square_btn)
507
+
508
+ qgame.draw.text(canvas, "Play Sound", (195, 230), size=14, color=(0, 0, 0))
509
+ qgame.draw.text(canvas, "BGM Ctrl\n(Square)", (495, 220), size=12, color=(0, 0, 0))
510
+
511
+ qgame.draw.text(canvas, "Audio & Points Collision Interactive Demo", (50, 60), size=22)
512
+ qgame.draw.text(canvas, f"State: {status_msg}", (50, 420), size=16, color=(255, 220, 100))
513
+ qgame.draw.text(canvas, "Press [ESC] to back.", (50, 530), size=14, color=(150, 150, 150))
514
+
515
+ qgame.window.update()
516
+
517
+ if bg_music:
518
+ bg_music.stop()
519
+
520
+ # ==================== CLI 路由导航中心 ====================
521
+ def main():
522
+ while True:
523
+ print("\n" + "=" * 40)
524
+ print(" QGame Engine 演示包航行器 ")
525
+ print("=" * 40)
526
+ print("1. Delta Time 平滑步进与图像翻转旋转")
527
+ print("2. 视口裁剪、不透明度呼吸与鼠标光标显隐")
528
+ print("3. 多场景无缝管理与场景 UI 销毁测试")
529
+ print("4. 世界轴 2D 摄像机 (平滑跟随、震屏、死区缓冲)")
530
+ print("5. 瓦片网格切图与合图配置文件解析测试")
531
+ print("6. 几何图形混合绘制与高质量抗锯齿文字")
532
+ print("7. 原生 IME 输入框与聊天框自使用缩放测试")
533
+ print("8. 音效音乐加载与方块点碰撞交互测试")
534
+ print("0. 退出测试系统")
535
+ print("=" * 40)
536
+
537
+ try:
538
+ choice = input("请输入您想运行的演示编号并回车 [0-8]: ").strip()
539
+ except (KeyboardInterrupt, SystemExit):
540
+ print("\n已安全退出。")
541
+ break
542
+
543
+ if choice == "1":
544
+ run_delta_time_demo()
545
+ elif choice == "2":
546
+ run_clipping_demo()
547
+ elif choice == "3":
548
+ run_scene_demo()
549
+ elif choice == "4":
550
+ run_camera_demo()
551
+ elif choice == "5":
552
+ run_spritesheet_demo()
553
+ elif choice == "6":
554
+ run_geometrics_demo()
555
+ elif choice == "7":
556
+ run_textbox_demo()
557
+ elif choice == "8":
558
+ run_audio_collision_demo()
559
+ elif choice in ("0", ""):
560
+ print("演示程序关闭。")
561
+ break
562
+ else:
563
+ print("输入无效,请输入 0 到 8 之间的数字。")
564
+
565
+ if __name__ == "__main__":
566
+ main()
qgame/anim_data.py ADDED
@@ -0,0 +1,9 @@
1
+ # 摄像机的运行状态
2
+ camera_position = {"x": 0, "y" : 0}
3
+ camera_size = 1.0
4
+ camera_d = 0 # 0 = 向上, 90 = 向右, 180 = 向下, 270 = 向左
5
+ screen_center = {"x" : 0, "y" : 0}
6
+ minimum = 0.00000000000001
7
+
8
+ # 动画别名存储
9
+ an_alias = []