Compare commits
44
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
03a7fcb5b3 | ||
|
|
88104f6703 | ||
|
|
b72cf37400 | ||
|
|
0159569c68 | ||
|
|
2896919bb9 | ||
|
|
a180a5f123 | ||
|
|
5240227053 | ||
|
|
7ecdc5dc5a | ||
|
|
87d5696ddb | ||
|
|
d7c16c80cd | ||
|
|
877395c054 | ||
|
|
eed6713e55 | ||
|
|
75a8525985 | ||
|
|
b864a5e82e | ||
|
|
9c6dd12fd7 | ||
|
|
338af57951 | ||
|
|
b8b95377ce | ||
|
|
e71c302c23 | ||
|
|
887bef6035 | ||
|
|
1ff35871dc | ||
|
|
a50b746ee1 | ||
|
|
0d5063838d | ||
|
|
2c3e4f5e37 | ||
|
|
c1984518d7 | ||
|
|
009c591054 | ||
|
|
6050bca933 | ||
|
|
eefb8da9c6 | ||
|
|
805704f0e5 | ||
|
|
90f734158b | ||
|
|
81b48d6b58 | ||
|
|
f76bbc6442 | ||
|
|
e5c575e5fd | ||
|
|
0148138147 | ||
|
|
5ca95505f2 | ||
|
|
9b03f6906e | ||
|
|
2d0cea6ec3 | ||
|
|
97b870d4bf | ||
|
|
7202813cd3 | ||
|
|
15f734b1d5 | ||
|
|
ca7b23fd2b | ||
|
|
9c8bb2162f | ||
|
|
2f7261f59c | ||
|
|
8fba77d1c0 | ||
|
|
5c91fce9fd |
No files matched your search
@@ -1 +1,36 @@
|
|||||||
### 项目1文档
|
### 项目技术方案
|
||||||
|
- 游戏引擎:pygame
|
||||||
|
- 游戏设计:自行实现多场景
|
||||||
|
|
||||||
|
### 项目结构
|
||||||
|
```
|
||||||
|
project
|
||||||
|
start.py # 入口,包含初始化和主循环
|
||||||
|
data\ # 资源
|
||||||
|
src\ # 代码
|
||||||
|
config\ # 基础配置
|
||||||
|
game_config.py # 游戏配置
|
||||||
|
scenes.py # 场景类字典,用于多场景切换
|
||||||
|
scenes\ # 游戏场景
|
||||||
|
base_scene.py # 场景基类,被其他场景继承,含画布、计时器、下一场景、事件接收、每帧更新、切换场景、退出等参数方法
|
||||||
|
main_menu_scene.py # 主菜单场景,实现了按键组合、按键顺序、鼠标事件、文本显示、雨天特效等功能
|
||||||
|
game_scene.py # 游戏场景,实现了方向移动、碰撞判断等功能
|
||||||
|
empty_scene.py # 空场景,测试或复制用
|
||||||
|
```
|
||||||
|
### 运行
|
||||||
|
安装依赖
|
||||||
|
```
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
运行
|
||||||
|
```
|
||||||
|
python start.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### 执行逻辑
|
||||||
|
1. start.py中执行Game() # 开始游戏
|
||||||
|
2. 类执行初始化__init__() # 读取game_config.py中的配置,初始化窗口画布等,初始化首个场景MainMenuScene并记录到self.scene
|
||||||
|
3. 初始化函数调用run()开始主循环 # 循环中获取事件并发送给场景的handle_event()方法用于接收玩家输入
|
||||||
|
4. 主循环中调用self.scene.update()方法 # 每帧更新场景中实现的游戏逻辑
|
||||||
|
5. 场景切换通过改变self.scene实现 # 主循环中读取场景类的next_scene参数,为None不切换,否则切换为src\config\scenes.py中类实例化对象
|
||||||
|
6. 退出游戏通过设置running为False实现 # 主循环中读取场景类的next_scene参数,为"exit"时退出
|
||||||
Binary file not shown.
File renamed without changes.
+5
-1
@@ -6,7 +6,10 @@
|
|||||||
# Datetime: 2026/7/25 下午10:44
|
# Datetime: 2026/7/25 下午10:44
|
||||||
# Description:
|
# Description:
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
import os
|
||||||
|
|
||||||
import pygame
|
import pygame
|
||||||
|
from src.config.game_config import data_path
|
||||||
|
|
||||||
# 初始化pygame
|
# 初始化pygame
|
||||||
pygame.init()
|
pygame.init()
|
||||||
@@ -22,7 +25,8 @@ screen = pygame.display.set_mode(size)
|
|||||||
pygame.display.set_caption("New_game")
|
pygame.display.set_caption("New_game")
|
||||||
|
|
||||||
# 播放并循环音乐
|
# 播放并循环音乐
|
||||||
pygame.mixer_music.load(r'D:\python_project\Game_project\file\vespidaze-upbeat-rpg-battle-460971.mp3')
|
data_path = data_path()
|
||||||
|
pygame.mixer_music.load(os.path.join(data_path, "vespidaze-upbeat-rpg-battle-460971.mp3"))
|
||||||
pygame.mixer_music.play(-1)
|
pygame.mixer_music.play(-1)
|
||||||
|
|
||||||
# 状态值
|
# 状态值
|
||||||
|
|||||||
Binary file not shown.
@@ -0,0 +1,6 @@
|
|||||||
|
class Color:
|
||||||
|
black = (0, 0, 0)
|
||||||
|
white = (255, 255, 255)
|
||||||
|
a = (255, 0, 0)
|
||||||
|
b = (0, 255, 0)
|
||||||
|
c = (0, 0, 255)
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
class GameConfig:
|
||||||
|
game_name = "Test Game"
|
||||||
|
# window_x = 1920
|
||||||
|
# window_y = 1080
|
||||||
|
window_x = 0
|
||||||
|
window_y = 0
|
||||||
|
bg_color = (0, 0, 0)
|
||||||
|
|
||||||
|
def data_path():
|
||||||
|
file_path = os.path.abspath(__file__)
|
||||||
|
project_path = os.path.dirname(os.path.dirname(os.path.dirname(file_path)))
|
||||||
|
data_path = os.path.join(project_path, "data")
|
||||||
|
return data_path
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print(data_path())
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
from src.scenes.config_scene import ConfigScene
|
||||||
|
from src.scenes.empty_scene import EmptyScene
|
||||||
|
from src.scenes.main_menu_scene import MainMenuScene
|
||||||
|
from src.scenes.game_scene import GameScene
|
||||||
|
from src.scenes.space_scene import SpaceScene
|
||||||
|
|
||||||
|
|
||||||
|
scene_dict = {
|
||||||
|
"MainMenuScene": MainMenuScene,
|
||||||
|
"GameScene": GameScene,
|
||||||
|
"EmptyScene": EmptyScene,
|
||||||
|
"ConfigScene": ConfigScene,
|
||||||
|
"SpaceScene": SpaceScene,
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# ProjectName: Project1
|
||||||
|
# FileName: galaxy.py
|
||||||
|
# Author: chic_robot
|
||||||
|
# Datetime: 2026/8/25 23:45
|
||||||
|
# Description:
|
||||||
|
# 命名规范:文件名全小写+下划线,类名大驼峰,方法和变量小写+下划线连接,
|
||||||
|
# 常量大写,变量和常量用名词,方法用动词
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
import math
|
||||||
|
import random
|
||||||
|
|
||||||
|
from src.config.game_config import GameConfig
|
||||||
|
|
||||||
|
|
||||||
|
class Plant:
|
||||||
|
"""
|
||||||
|
星球
|
||||||
|
"""
|
||||||
|
def __init__(self, x=None, y=None, center_x=None, center_y=None, angle=0.0, r=1, v=1, size=5):
|
||||||
|
self.x = x # 星球坐标
|
||||||
|
self.y = y
|
||||||
|
self.center_x = center_x # 星球环绕中心
|
||||||
|
self.center_y = center_y
|
||||||
|
self.angle = angle # 星球绕行所处角度
|
||||||
|
self.r = r # 星球绕行半径
|
||||||
|
self.v = v # 绕行速度
|
||||||
|
self.size = size # 星球半径
|
||||||
|
|
||||||
|
# 如果没有输入中心坐标则默认为屏幕中心,xy分别判断
|
||||||
|
if not self.center_x:
|
||||||
|
self.center_x = GameConfig.window_x/2
|
||||||
|
if not self.center_y:
|
||||||
|
self.center_y = GameConfig.window_y/2
|
||||||
|
|
||||||
|
# 如果没有传入星球坐标则根据绕行角度更新
|
||||||
|
if not self.x or not self.y:
|
||||||
|
self._update_position_by_angle()
|
||||||
|
|
||||||
|
def _update_position_by_angle(self):
|
||||||
|
"""
|
||||||
|
根据角度与绕行圆心计算坐标
|
||||||
|
"""
|
||||||
|
self.x = self.center_x + self.r * math.cos(self.angle)
|
||||||
|
self.y = self.center_y + self.r * math.sin(self.angle)
|
||||||
|
|
||||||
|
|
||||||
|
class Galaxy:
|
||||||
|
"""
|
||||||
|
星系
|
||||||
|
"""
|
||||||
|
def __init__(self, name, plants_num=0, x=None, y=None):
|
||||||
|
self.name = name # 星系名称
|
||||||
|
self.plants = [] # 星系下行星
|
||||||
|
self.x = x # 星系中心坐标
|
||||||
|
self.y = y
|
||||||
|
|
||||||
|
# 如果没有传入中心坐标则默认为屏幕中心
|
||||||
|
if not x:
|
||||||
|
self.x = GameConfig.window_x/2
|
||||||
|
if not y:
|
||||||
|
self.y = GameConfig.window_y/2
|
||||||
|
self.init_plants(plants_num)
|
||||||
|
|
||||||
|
def init_plants(self, plants_num, center_x=None, center_y=None):
|
||||||
|
"""
|
||||||
|
初始化生成行星
|
||||||
|
"""
|
||||||
|
for i in range(plants_num):
|
||||||
|
r = (i+1) * 50
|
||||||
|
angle = random.uniform(0, 2 * math.pi)
|
||||||
|
plant = Plant(center_x=center_x, center_y=center_y, r=r, angle=angle)
|
||||||
|
self.plants.append(plant)
|
||||||
|
|
||||||
|
# def init_plants(self, plants_num):
|
||||||
|
# for i in range(plants_num):
|
||||||
|
# x = random.randint(0, GameConfig.window_x)
|
||||||
|
# y = random.randint(0, GameConfig.window_y)
|
||||||
|
# plant = Plant(x, y)
|
||||||
|
# self.plants.append(plant)
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import math
|
||||||
|
|
||||||
|
import pygame
|
||||||
|
|
||||||
|
from src.config.color import Color
|
||||||
|
|
||||||
|
|
||||||
|
class Player:
|
||||||
|
def __init__(self, screen):
|
||||||
|
self.screen = screen
|
||||||
|
screen_rect = self.screen.get_rect()
|
||||||
|
self.x = screen_rect.centerx
|
||||||
|
self.y = screen_rect.centery
|
||||||
|
self.v = 0.0
|
||||||
|
self.base_speed = 3.0
|
||||||
|
self.run_multiplier = 2.5
|
||||||
|
self.is_running = False
|
||||||
|
|
||||||
|
def move(self, dx, dy, dt):
|
||||||
|
"""
|
||||||
|
归一化移动,避免斜向移动过快
|
||||||
|
"""
|
||||||
|
magnitude = math.hypot(dx, dy)
|
||||||
|
if magnitude > 0:
|
||||||
|
dx /= magnitude
|
||||||
|
dy /= magnitude
|
||||||
|
speed = self.base_speed
|
||||||
|
if self.is_running:
|
||||||
|
speed *= self.run_multiplier
|
||||||
|
|
||||||
|
# 应用速度和时间差 (dt),实现帧率无关的移动
|
||||||
|
dt /= 1000.0 # 注意:这里 dt 是以秒为单位的浮点数
|
||||||
|
self.x += dx * speed * dt * 60 # 乘以 60 是为了兼容 60FPS 下的体感速度
|
||||||
|
self.y += dy * speed * dt * 60
|
||||||
|
|
||||||
|
def handle_input(self, keys, dt):
|
||||||
|
drx = 0
|
||||||
|
dry = 0
|
||||||
|
if keys[pygame.K_LEFT] or keys[pygame.K_a]:
|
||||||
|
drx -= 1
|
||||||
|
if keys[pygame.K_RIGHT] or keys[pygame.K_d]:
|
||||||
|
drx += 1
|
||||||
|
if keys[pygame.K_UP] or keys[pygame.K_w]:
|
||||||
|
dry -= 1
|
||||||
|
if keys[pygame.K_DOWN] or keys[pygame.K_s]:
|
||||||
|
dry += 1
|
||||||
|
# 按下shift冲刺
|
||||||
|
if keys[pygame.K_LSHIFT]:
|
||||||
|
self.is_running = True
|
||||||
|
else:
|
||||||
|
self.is_running = False
|
||||||
|
# 移动
|
||||||
|
self.move(drx, dry, dt)
|
||||||
|
|
||||||
|
def show(self):
|
||||||
|
pygame.draw.circle(self.screen, Color.white, (int(self.x), int(self.y)), 10)
|
||||||
|
|
||||||
|
def update(self, keys, dt):
|
||||||
|
self.handle_input(keys, dt)
|
||||||
|
self.show()
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import pygame
|
||||||
|
|
||||||
|
class BaseScene:
|
||||||
|
def __init__(self, screen: pygame.Surface):
|
||||||
|
"""
|
||||||
|
初始化场景,接收并保存画布、计时器
|
||||||
|
"""
|
||||||
|
self.screen = screen
|
||||||
|
self.next_scene = None
|
||||||
|
|
||||||
|
def update(self, dt=0):
|
||||||
|
"""
|
||||||
|
场景的每帧更新逻辑
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def handle_event(self, events):
|
||||||
|
"""
|
||||||
|
接收主循环传来的事件并处理
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def switch_to_scene(self, scene_name):
|
||||||
|
"""
|
||||||
|
切换场景
|
||||||
|
"""
|
||||||
|
self.next_scene = scene_name
|
||||||
|
|
||||||
|
def exit(self):
|
||||||
|
"""
|
||||||
|
退出游戏
|
||||||
|
"""
|
||||||
|
self.next_scene = "exit"
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import pygame
|
||||||
|
|
||||||
|
from src.scenes.base_scene import BaseScene
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigScene(BaseScene):
|
||||||
|
name = "ConfigScene" # 场景名称
|
||||||
|
|
||||||
|
def __init__(self, screen):
|
||||||
|
super().__init__(screen)
|
||||||
|
|
||||||
|
def update(self, dt=0):
|
||||||
|
"""
|
||||||
|
场景每帧更新逻辑
|
||||||
|
"""
|
||||||
|
|
||||||
|
def handle_event(self, event):
|
||||||
|
"""
|
||||||
|
接收主循环传来的事件并处理
|
||||||
|
"""
|
||||||
|
if event.type == pygame.KEYDOWN:
|
||||||
|
if event.key == pygame.K_ESCAPE:
|
||||||
|
self.switch_to_scene("MainMenuScene")
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
from src.scenes.base_scene import BaseScene
|
||||||
|
|
||||||
|
|
||||||
|
class EmptyScene(BaseScene):
|
||||||
|
name = "EmptyScene" # 场景名称
|
||||||
|
|
||||||
|
def __init__(self, screen):
|
||||||
|
super().__init__(screen)
|
||||||
|
|
||||||
|
def update(self, dt=0):
|
||||||
|
"""
|
||||||
|
场景每帧更新逻辑
|
||||||
|
"""
|
||||||
|
|
||||||
|
def handle_event(self, event):
|
||||||
|
"""
|
||||||
|
接收主循环传来的事件并处理
|
||||||
|
"""
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
import pygame
|
||||||
|
|
||||||
|
from src.config.game_config import data_path, GameConfig
|
||||||
|
from src.scenes.base_scene import BaseScene
|
||||||
|
|
||||||
|
|
||||||
|
class GameScene(BaseScene):
|
||||||
|
name = "GameScene"
|
||||||
|
def __init__(self, screen):
|
||||||
|
super().__init__(screen)
|
||||||
|
# 初始化音乐
|
||||||
|
pygame.mixer.init()
|
||||||
|
# 播放并循环音乐
|
||||||
|
pygame.mixer_music.load(os.path.join(data_path(), "vespidaze-upbeat-rpg-battle-460971.mp3"))
|
||||||
|
pygame.mixer_music.play(-1)
|
||||||
|
|
||||||
|
self.back_status = False
|
||||||
|
|
||||||
|
# 状态值
|
||||||
|
self.dirty_x = 400
|
||||||
|
self.dirty_y = 300
|
||||||
|
|
||||||
|
self.Left_status = False
|
||||||
|
self.right_status = False
|
||||||
|
self.up_status = False
|
||||||
|
self.down_status = False
|
||||||
|
|
||||||
|
# 颜色
|
||||||
|
self.WHITE = (255, 255, 255)
|
||||||
|
self.RED = (255, 0, 0)
|
||||||
|
self.BLUE = (0, 0, 255)
|
||||||
|
|
||||||
|
def update(self, dt=0):
|
||||||
|
if self.back_status:
|
||||||
|
pygame.mixer_music.stop()
|
||||||
|
self.switch_to_scene("MainMenuScene")
|
||||||
|
if self.Left_status and self.dirty_x >= 0:
|
||||||
|
self.dirty_x -= 1
|
||||||
|
if self.right_status and self.dirty_x <= GameConfig.window_x - 20:
|
||||||
|
self.dirty_x += 1
|
||||||
|
if self.up_status and self.dirty_y >= 0:
|
||||||
|
self.dirty_y -= 1
|
||||||
|
if self.down_status and self.dirty_y <= GameConfig.window_y - 30:
|
||||||
|
self.dirty_y += 1
|
||||||
|
# 填充窗口颜色
|
||||||
|
self.screen.fill((255, 255, 255))
|
||||||
|
|
||||||
|
# 绘制矩形(移动体和固定体)
|
||||||
|
dirty = pygame.draw.rect(self.screen, self.BLUE, [self.dirty_x, self.dirty_y, 20, 30])
|
||||||
|
enemy = pygame.draw.rect(self.screen, self.RED, [40, 60, 10, 10])
|
||||||
|
|
||||||
|
# 碰撞监测
|
||||||
|
if dirty.colliderect(enemy):
|
||||||
|
print("die")
|
||||||
|
self.dirty_x = 400
|
||||||
|
self.dirty_y = 300
|
||||||
|
dirty = pygame.draw.rect(self.screen, self.BLUE, [self.dirty_x, self.dirty_y, 20, 30])
|
||||||
|
|
||||||
|
def handle_event(self, event):
|
||||||
|
# 按下按键
|
||||||
|
if event.type == pygame.KEYDOWN:
|
||||||
|
if event.key == pygame.K_ESCAPE:
|
||||||
|
self.back_status = True
|
||||||
|
elif event.key == pygame.K_LEFT or event.key == pygame.K_a:
|
||||||
|
self.Left_status = True
|
||||||
|
elif event.key == pygame.K_RIGHT or event.key == pygame.K_d:
|
||||||
|
self.right_status = True
|
||||||
|
elif event.key == pygame.K_UP or event.key == pygame.K_w:
|
||||||
|
self.up_status = True
|
||||||
|
elif event.key == pygame.K_DOWN or event.key == pygame.K_s:
|
||||||
|
self.down_status = True
|
||||||
|
# 松开按键
|
||||||
|
elif event.type == pygame.KEYUP:
|
||||||
|
if event.key == pygame.K_LEFT or event.key == pygame.K_a:
|
||||||
|
self.Left_status = False
|
||||||
|
elif event.key == pygame.K_RIGHT or event.key == pygame.K_d:
|
||||||
|
self.right_status = False
|
||||||
|
elif event.key == pygame.K_UP or event.key == pygame.K_w:
|
||||||
|
self.up_status = False
|
||||||
|
elif event.key == pygame.K_DOWN or event.key == pygame.K_s:
|
||||||
|
self.down_status = False
|
||||||
@@ -0,0 +1,300 @@
|
|||||||
|
import os
|
||||||
|
import random
|
||||||
|
|
||||||
|
import pygame
|
||||||
|
from src.config.color import Color
|
||||||
|
from src.config.game_config import data_path, GameConfig
|
||||||
|
from src.scenes.base_scene import BaseScene
|
||||||
|
|
||||||
|
|
||||||
|
class MainMenuScene(BaseScene):
|
||||||
|
name = "MainMenuScene"
|
||||||
|
"""
|
||||||
|
主菜单场景,显示提示信息,展示雨天特效
|
||||||
|
"""
|
||||||
|
def __init__(self, screen):
|
||||||
|
"""
|
||||||
|
初始化函数,初始化按钮按下状态、雨滴列表
|
||||||
|
"""
|
||||||
|
super().__init__(screen)
|
||||||
|
self.button_status = False
|
||||||
|
self.mouse_status = None
|
||||||
|
self.mouse_last_status = None
|
||||||
|
self.debug_status = True
|
||||||
|
self.secret_keys = ["w","w","s","s","a","d","a","d"] # 上上下下左右左右
|
||||||
|
self.secret_key_compare_num = 0
|
||||||
|
self.select_id = 0
|
||||||
|
self.up_status = False
|
||||||
|
self.up_last_status = False
|
||||||
|
self.down_status = False
|
||||||
|
self.down_last_status = False
|
||||||
|
|
||||||
|
self.mouse_on_option = False
|
||||||
|
self.keyboard_ensure_status = False
|
||||||
|
|
||||||
|
# 设置字体
|
||||||
|
font_file = os.path.join(data_path(),"Dymon-ShouXieTi.otf")
|
||||||
|
self.font = pygame.font.Font(font_file,32)
|
||||||
|
self.text_rect_list = []
|
||||||
|
self.__init_text_list()
|
||||||
|
|
||||||
|
# 雨滴列表
|
||||||
|
self.rain_max = 50
|
||||||
|
self.rain_list = []
|
||||||
|
# 雨滴生命周期
|
||||||
|
self.rain_live_max = 10
|
||||||
|
# 初始化雨滴
|
||||||
|
self.init_rain(self.rain_max)
|
||||||
|
|
||||||
|
# 鼠标位置
|
||||||
|
self.mouse_pos = (0, 0)
|
||||||
|
|
||||||
|
def update(self, dt=0):
|
||||||
|
# 按键按下则切换场景
|
||||||
|
if self.button_status:
|
||||||
|
self.switch_to_scene("GameScene")
|
||||||
|
if self.secret_key_compare_num == len(self.secret_keys):
|
||||||
|
print("我去,是通关秘籍!")
|
||||||
|
self.switch_to_scene("EmptyScene")
|
||||||
|
|
||||||
|
if self.__up():
|
||||||
|
self.select_id -= 1
|
||||||
|
if self.__down():
|
||||||
|
self.select_id += 1
|
||||||
|
self.select_id = self.select_id % len(self.text_rect_list)
|
||||||
|
# 显示选项文本
|
||||||
|
self.__show_ui()
|
||||||
|
|
||||||
|
# 处理鼠标点击
|
||||||
|
self.handle_mouse_click()
|
||||||
|
|
||||||
|
# 处理空格或enter
|
||||||
|
self.handle_keyboard()
|
||||||
|
|
||||||
|
# 雨滴坠落
|
||||||
|
self.rain_fall(dt)
|
||||||
|
|
||||||
|
# 更新鼠标位置
|
||||||
|
self.mouse_pos = pygame.mouse.get_pos()
|
||||||
|
# 更新当前帧鼠标状态
|
||||||
|
self.mouse_last_status = self.mouse_status
|
||||||
|
# 更新当前帧上下按键状态
|
||||||
|
self.up_last_status = self.up_status
|
||||||
|
self.down_last_status = self.down_status
|
||||||
|
|
||||||
|
# 调试文本
|
||||||
|
# self.__show(self.up_status)
|
||||||
|
# self.__show(self.up_last_status, (0, 20))
|
||||||
|
|
||||||
|
def handle_event(self, event):
|
||||||
|
"""
|
||||||
|
接收主循环传来的事件并处理
|
||||||
|
"""
|
||||||
|
if event.type == pygame.KEYDOWN:
|
||||||
|
keys = pygame.key.get_pressed() # 获取所有按键状态,用于组合按键判断
|
||||||
|
if event.key == pygame.K_ESCAPE:
|
||||||
|
self.exit() # 退出游戏
|
||||||
|
if keys[pygame.K_z] and keys[pygame.K_x]: # 同时按下Z和X
|
||||||
|
self.button_status = True
|
||||||
|
# 处理空格和enter
|
||||||
|
if event.key == pygame.K_SPACE or event.key == pygame.K_RETURN:
|
||||||
|
self.keyboard_ensure_status = True
|
||||||
|
else:
|
||||||
|
self.keyboard_ensure_status = False
|
||||||
|
|
||||||
|
# 判断是否按顺序按下秘籍
|
||||||
|
if event.unicode.lower() == self.secret_keys[self.secret_key_compare_num]:
|
||||||
|
self.secret_key_compare_num += 1
|
||||||
|
else:
|
||||||
|
self.secret_key_compare_num = 0
|
||||||
|
|
||||||
|
# 处理上下键
|
||||||
|
if event.key == pygame.K_UP or event.key == pygame.K_w:
|
||||||
|
self.up_status = True
|
||||||
|
if event.key == pygame.K_DOWN or event.key == pygame.K_s:
|
||||||
|
self.down_status = True
|
||||||
|
|
||||||
|
if event.type == pygame.KEYUP:
|
||||||
|
# 处理上下键
|
||||||
|
if event.key == pygame.K_UP or event.key == pygame.K_w:
|
||||||
|
self.up_status = False
|
||||||
|
if event.key == pygame.K_DOWN or event.key == pygame.K_s:
|
||||||
|
self.down_status = False
|
||||||
|
|
||||||
|
# 鼠标事件
|
||||||
|
if event.type == pygame.MOUSEBUTTONDOWN:
|
||||||
|
self.mouse_status = "down"
|
||||||
|
elif event.type == pygame.MOUSEBUTTONUP:
|
||||||
|
self.mouse_status = "up"
|
||||||
|
elif event.type == pygame.MOUSEMOTION:
|
||||||
|
self.mouse_status = "move"
|
||||||
|
|
||||||
|
def __click(self):
|
||||||
|
"""
|
||||||
|
判断是否点击鼠标:鼠标抬起时,上一帧的状态如果是按下或者移动则判断为点击
|
||||||
|
"""
|
||||||
|
if self.mouse_status == "up":
|
||||||
|
if self.mouse_last_status in ["down", "move"]:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def __up(self):
|
||||||
|
"""
|
||||||
|
判断是否按下上键:按下时,上一帧的状态是未按下则判断为按下
|
||||||
|
"""
|
||||||
|
if self.up_status:
|
||||||
|
if not self.up_last_status:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
def __down(self):
|
||||||
|
"""
|
||||||
|
判断是否按下下键:按下时,上一帧的状态是未按下则判断为按下
|
||||||
|
"""
|
||||||
|
if self.down_status:
|
||||||
|
if not self.down_last_status:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def __show(self, info, dest=(0, 0)):
|
||||||
|
"""
|
||||||
|
显示调试文本
|
||||||
|
"""
|
||||||
|
if not self.debug_status:
|
||||||
|
return
|
||||||
|
if not isinstance(info, str):
|
||||||
|
info = str(info)
|
||||||
|
info_text = self.font.render(info, True, Color.white)
|
||||||
|
self.screen.blit(info_text, dest)
|
||||||
|
|
||||||
|
def handle_mouse_click(self):
|
||||||
|
"""
|
||||||
|
处理鼠标点击
|
||||||
|
"""
|
||||||
|
if self.__click() and self.mouse_on_option:
|
||||||
|
# 触发选项处理
|
||||||
|
self.__handle_option()
|
||||||
|
|
||||||
|
def handle_keyboard(self):
|
||||||
|
if self.keyboard_ensure_status:
|
||||||
|
# 触发选项处理
|
||||||
|
self.__handle_option()
|
||||||
|
|
||||||
|
def __rain_list_append(self, status=0):
|
||||||
|
"""
|
||||||
|
向列表中添加一个雨滴,默认状态为0
|
||||||
|
"""
|
||||||
|
x = random.randint(0, int(GameConfig.window_x))
|
||||||
|
y = random.randint(0, int(GameConfig.window_y))
|
||||||
|
rain = {
|
||||||
|
"dest": (x, y),
|
||||||
|
"timer": 0,
|
||||||
|
"status": status
|
||||||
|
}
|
||||||
|
self.rain_list.append(rain)
|
||||||
|
|
||||||
|
def __init_text_list(self):
|
||||||
|
# 文本
|
||||||
|
text_list = [
|
||||||
|
"开始游戏",
|
||||||
|
"测试场景",
|
||||||
|
"设置",
|
||||||
|
"退出游戏",
|
||||||
|
]
|
||||||
|
# 转换为文本框
|
||||||
|
for text_str in text_list:
|
||||||
|
self.text_rect_list.append(self.font.render(text_str, True, Color.white))
|
||||||
|
|
||||||
|
def __handle_option(self):
|
||||||
|
"""
|
||||||
|
处理选项,根据id执行
|
||||||
|
"""
|
||||||
|
if self.select_id is None:
|
||||||
|
return
|
||||||
|
elif self.select_id == 0:
|
||||||
|
self.switch_to_scene("GameScene")
|
||||||
|
elif self.select_id == 1:
|
||||||
|
self.switch_to_scene("SpaceScene")
|
||||||
|
elif self.select_id == 2:
|
||||||
|
self.switch_to_scene("ConfigScene")
|
||||||
|
elif self.select_id == len(self.text_rect_list) - 1:
|
||||||
|
self.exit()
|
||||||
|
|
||||||
|
def __show_ui(self):
|
||||||
|
"""
|
||||||
|
显示所有选项
|
||||||
|
"""
|
||||||
|
screen_rect = self.screen.get_rect() # 获取屏幕矩形
|
||||||
|
self.mouse_on_option = False # 未选中选项时重置状态
|
||||||
|
count = 0
|
||||||
|
for text in self.text_rect_list:
|
||||||
|
text_rect = text.get_rect() # 获取文本矩形
|
||||||
|
text_rect.centerx = screen_rect.centerx # 将文本居中到屏幕
|
||||||
|
text_rect.y = screen_rect.y + count * 2 * text_rect.height + screen_rect.centerx/2
|
||||||
|
self.screen.blit(text, text_rect.topleft)
|
||||||
|
# 是否高亮显示
|
||||||
|
if text_rect.y < self.mouse_pos[1] < text_rect.y + text_rect.height \
|
||||||
|
and text_rect.x < self.mouse_pos[0] < text_rect.x + text_rect.width:
|
||||||
|
pygame.draw.rect(self.screen, Color.white, text_rect, 1)
|
||||||
|
# 更新选中id
|
||||||
|
self.select_id = count
|
||||||
|
# 更新鼠标状态
|
||||||
|
self.mouse_on_option = True
|
||||||
|
count += 1
|
||||||
|
|
||||||
|
# 显示键盘选中
|
||||||
|
if self.select_id is not None:
|
||||||
|
text_rect = self.text_rect_list[self.select_id].get_rect()
|
||||||
|
text_rect.centerx = screen_rect.centerx # 将文本居中到屏幕
|
||||||
|
text_rect.y = screen_rect.y + self.select_id * 2 * text_rect.height + screen_rect.centerx/2
|
||||||
|
pygame.draw.rect(self.screen, Color.white, text_rect, 1)
|
||||||
|
|
||||||
|
|
||||||
|
def init_rain(self, max_num):
|
||||||
|
"""
|
||||||
|
初始化雨滴列表,保存雨滴落点的坐标
|
||||||
|
"""
|
||||||
|
for i in range(max_num):
|
||||||
|
status = random.randint(1, self.rain_live_max)
|
||||||
|
self.__rain_list_append(status)
|
||||||
|
|
||||||
|
|
||||||
|
def rain_fall(self, dt):
|
||||||
|
"""
|
||||||
|
展示雨滴画面
|
||||||
|
"""
|
||||||
|
for rain in self.rain_list:
|
||||||
|
|
||||||
|
# 当状态在区间1时绘制雨滴线条
|
||||||
|
if rain["status"] <= self.rain_live_max - 3:
|
||||||
|
# 设计高度
|
||||||
|
rain_high = 10 * self.rain_live_max * 2
|
||||||
|
# 起点位置
|
||||||
|
# x坐标与最后落点x一致
|
||||||
|
x = rain["dest"][0]
|
||||||
|
# y坐标为落点位置向上移动设计高度后,加上生命周期落下的位置
|
||||||
|
y = (rain["dest"][1] - rain_high) + (rain["status"] * self.rain_live_max) * 2
|
||||||
|
y2 = y + 20
|
||||||
|
line_start_point = (x, y)
|
||||||
|
line_end_point = (x, y2)
|
||||||
|
pygame.draw.line(self.screen, Color.white, line_start_point, line_end_point, 1)
|
||||||
|
# 状态在区间2时绘制涟漪
|
||||||
|
elif self.rain_live_max - 6 < rain["status"]:
|
||||||
|
point_width = (rain["status"] - (self.rain_live_max - 3)) * 2
|
||||||
|
pygame.draw.circle(self.screen, Color.white, rain["dest"], point_width, 1)
|
||||||
|
# # 椭圆
|
||||||
|
# rain_point_rect = [rain["dest"][0], rain["dest"][1], rain["status"], rain["status"]]
|
||||||
|
# pygame.draw.ellipse(self.screen, Color.white, rain_point_rect, 1)
|
||||||
|
|
||||||
|
# 雨滴状态每d_status毫秒变化一次
|
||||||
|
d_status = 1000/30
|
||||||
|
if rain["timer"] > d_status:
|
||||||
|
rain["status"] += 1
|
||||||
|
rain["timer"] -= d_status # 不设为0,避免累计时间误差
|
||||||
|
|
||||||
|
# 生命周期走完后销毁并添加新雨滴
|
||||||
|
if rain["status"] > self.rain_live_max:
|
||||||
|
self.rain_list.remove(rain)
|
||||||
|
self.__rain_list_append()
|
||||||
|
|
||||||
|
# timer累计每帧时间
|
||||||
|
rain["timer"] += dt
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
import math
|
||||||
|
import os
|
||||||
|
|
||||||
|
import pygame
|
||||||
|
|
||||||
|
from src.config.color import Color
|
||||||
|
from src.config.game_config import data_path, GameConfig
|
||||||
|
from src.entity.galaxy import Galaxy
|
||||||
|
from src.entity.player import Player
|
||||||
|
from src.scenes.base_scene import BaseScene
|
||||||
|
|
||||||
|
|
||||||
|
class SpaceScene(BaseScene):
|
||||||
|
name = "SpaceScene" # 场景名称
|
||||||
|
|
||||||
|
def __init__(self, screen):
|
||||||
|
super().__init__(screen)
|
||||||
|
|
||||||
|
# 实例化玩家
|
||||||
|
self.player = Player(screen)
|
||||||
|
|
||||||
|
# 初始化星系行星
|
||||||
|
sun = Galaxy("sun", 7)
|
||||||
|
self.plants = sun.plants
|
||||||
|
|
||||||
|
# 设置字体
|
||||||
|
font_file = os.path.join(data_path(), "Dymon-ShouXieTi.otf")
|
||||||
|
self.font = pygame.font.Font(font_file, 32)
|
||||||
|
|
||||||
|
def update(self, dt=0):
|
||||||
|
"""
|
||||||
|
场景每帧更新逻辑
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 玩家更新,接收键盘输入与每帧间隔
|
||||||
|
self.player.update(pygame.key.get_pressed(), dt)
|
||||||
|
|
||||||
|
# 行星位置更新
|
||||||
|
self._plants_update()
|
||||||
|
|
||||||
|
self.__show("加速:" + str(self.player.is_running))
|
||||||
|
self.__show("加速:" + str(self.player.is_running))
|
||||||
|
|
||||||
|
def handle_event(self, event):
|
||||||
|
"""
|
||||||
|
接收主循环传来的事件并处理
|
||||||
|
"""
|
||||||
|
if event.type == pygame.KEYDOWN:
|
||||||
|
if event.key == pygame.K_ESCAPE:
|
||||||
|
self.switch_to_scene("MainMenuScene")
|
||||||
|
|
||||||
|
def __show(self, info, dest=(0, 0)):
|
||||||
|
"""
|
||||||
|
显示调试文本
|
||||||
|
"""
|
||||||
|
if not isinstance(info, str):
|
||||||
|
info = str(info)
|
||||||
|
info_text = self.font.render(info, True, Color.white)
|
||||||
|
self.screen.blit(info_text, dest)
|
||||||
|
|
||||||
|
def _plants_update(self):
|
||||||
|
for plant in self.plants:
|
||||||
|
dx = plant.x - GameConfig.window_x/2
|
||||||
|
dy = plant.y - GameConfig.window_y/2
|
||||||
|
r = math.sqrt(dx**2 + dy**2)
|
||||||
|
info = f"({round(plant.x,2)}, {round(plant.y,2)}), {round(r,2)}"
|
||||||
|
|
||||||
|
font_file = os.path.join(data_path(), "Dymon-ShouXieTi.otf")
|
||||||
|
pos_font = pygame.font.Font(font_file, 16)
|
||||||
|
text = pos_font.render(info, True, Color.white)
|
||||||
|
self.screen.blit(text, (plant.x, plant.y))
|
||||||
|
self._show_plant(plant)
|
||||||
|
self._show_track(plant)
|
||||||
|
|
||||||
|
def _show_plant(self, plant):
|
||||||
|
pygame.draw.circle(self.screen, Color.white, (plant.x, plant.y), plant.size)
|
||||||
|
|
||||||
|
def _show_track(self, plant):
|
||||||
|
pygame.draw.circle(self.screen, Color.white, (plant.center_x, plant.center_y), plant.r, 1)
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
import pygame
|
||||||
|
|
||||||
|
from src.config.game_config import GameConfig
|
||||||
|
from src.config.scenes import scene_dict
|
||||||
|
|
||||||
|
|
||||||
|
class Game:
|
||||||
|
def __init__(self):
|
||||||
|
# 初始化
|
||||||
|
pygame.init()
|
||||||
|
|
||||||
|
# 显示输入法候选框
|
||||||
|
os.environ["SDL_IME_SHOW_UI"] = "1"
|
||||||
|
|
||||||
|
# 如果设置中屏幕为0,0则获取屏幕大小
|
||||||
|
display_info = pygame.display.Info()
|
||||||
|
if not GameConfig.window_x:
|
||||||
|
GameConfig.window_x = display_info.current_w
|
||||||
|
if not GameConfig.window_y:
|
||||||
|
GameConfig.window_y = display_info.current_h
|
||||||
|
|
||||||
|
# 设置窗口大小
|
||||||
|
size = (GameConfig.window_x, GameConfig.window_y)
|
||||||
|
self.screen = pygame.display.set_mode(size, pygame.NOFRAME)
|
||||||
|
|
||||||
|
# 避免每次启动需要切换输入法
|
||||||
|
pygame.key.stop_text_input()
|
||||||
|
|
||||||
|
# 设置计时器
|
||||||
|
self.clock = pygame.time.Clock()
|
||||||
|
|
||||||
|
# 场景字典
|
||||||
|
self.game_scenes = scene_dict
|
||||||
|
# 当前场景
|
||||||
|
self.scene = self.game_scenes["MainMenuScene"](self.screen)
|
||||||
|
|
||||||
|
# 运行循环
|
||||||
|
self.running = True
|
||||||
|
self.run()
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
while self.running:
|
||||||
|
# 更新每帧时间
|
||||||
|
dt = self.clock.tick()
|
||||||
|
# 事件处理
|
||||||
|
for event in pygame.event.get(): # 这里已经获取了事件,事件队列被清空,在场景的update中再次获取将没有事件
|
||||||
|
if event.type == pygame.QUIT:
|
||||||
|
self.running = False
|
||||||
|
self.scene.handle_event(event) # 当前场景接收事件
|
||||||
|
|
||||||
|
# 检查是否切换场景
|
||||||
|
if self.scene.next_scene is not None:
|
||||||
|
if self.scene.next_scene in self.game_scenes.keys():
|
||||||
|
scene_class = self.game_scenes[self.scene.next_scene]
|
||||||
|
self.scene = scene_class(self.screen)
|
||||||
|
|
||||||
|
# 清屏
|
||||||
|
self.screen.fill(GameConfig.bg_color)
|
||||||
|
|
||||||
|
# 场景每帧更新逻辑运行
|
||||||
|
self.scene.update(dt)
|
||||||
|
# 当场景的下一场景为exit时退出游戏
|
||||||
|
if self.scene.next_scene == "exit":
|
||||||
|
self.running = False
|
||||||
|
|
||||||
|
# 更新显示
|
||||||
|
pygame.display.update()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
Game()
|
||||||
Reference in new issue
Block a user