Compare commits

...

12 Commits

Author SHA1 Message Date
038b876b15 change Player.move 2025-02-02 01:33:15 +08:00
705b5c746a change get_collision_surface 2025-02-02 01:11:44 +08:00
1a210382ae modify world generation rule 2025-02-01 12:30:21 +08:00
40fa44f936 fix gravity simulation bug 2025-02-01 11:21:20 +08:00
3ef79886ec change a lot 2025-02-01 03:52:39 +08:00
6670715bf2 fix collision detection issue 2025-01-31 21:17:10 +08:00
3cb2bf4b74 fix issue with block placement 2025-01-31 20:14:48 +08:00
e2cdd2e4e5 change font of prompt text 2025-01-31 20:07:57 +08:00
d7f5e990fd implement block class 2025-01-31 20:06:47 +08:00
64c710aae7 update comments 2025-01-31 19:55:42 +08:00
823158a8eb refactor code structure 2025-01-31 19:35:09 +08:00
d74c866caf change readme 2025-01-31 19:29:00 +08:00
6 changed files with 314 additions and 95 deletions

View File

@@ -6,6 +6,12 @@
git pull https://cantyonion.site/git/cantyonion/LXCsGame.git git pull https://cantyonion.site/git/cantyonion/LXCsGame.git
cd LXCsGame cd LXCsGame
python -m venv .venv python -m venv .venv
# for Windows user
.venv\Script\active.bat
# for Linux user
source .venv/bin/active
pip install pygame pip install pygame
python main.py python main.py
``` ```

134
main.py
View File

@@ -1,78 +1,17 @@
import pygame import pygame
import random
from module import WIDTH, HEIGHT, BLOCK_SIZE, PLAYER_SPEED, GRAVITY, COLORS, OPTIONS
from module.block import GrassBlock, StoneBlock, SandBlock, WaterBlock
from module.player import Player
from module.utils import generate_terrain
# 初始化 # 初始化
pygame.init() pygame.init()
WIDTH, HEIGHT = 800, 600
screen = pygame.display.set_mode((WIDTH, HEIGHT)) screen = pygame.display.set_mode((WIDTH, HEIGHT))
clock = pygame.time.Clock() clock = pygame.time.Clock()
# 颜色定义
COLORS = {
"air": (0, 191, 255),
"grass": (34, 139, 34),
"dirt": (139, 69, 19),
"stone": (128, 128, 128),
"sand": (238, 232, 170),
"water": (0, 0, 255),
"player": (255, 0, 0)
}
# 游戏参数
BLOCK_SIZE = 20
GRAVITY = 0.5
PLAYER_SPEED = 5
JUMP_FORCE = -12
# 生成地形
def generate_terrain():
world = []
ground_level = HEIGHT // BLOCK_SIZE // 2
for x in range(WIDTH // BLOCK_SIZE):
column = []
height = ground_level + random.randint(-3, 3)
for y in range(HEIGHT // BLOCK_SIZE):
if y > height + 2:
column.append("stone")
elif y == height:
column.append("grass")
elif y > height - 3:
column.append("dirt")
else:
column.append("air")
world.append(column)
return world
world = generate_terrain() world = generate_terrain()
player = Player(world)
# 玩家类
class Player:
def __init__(self):
self.x = WIDTH // 2
self.y = HEIGHT // 2
self.velocity = 0
self.on_ground = False
self.selected_block = "grass"
def move(self, dx, dy):
new_x = self.x + dx
new_y = self.y + dy
if 0 <= new_x < WIDTH and 0 <= new_y < HEIGHT:
self.x = new_x
self.y = new_y
def jump(self):
if self.on_ground:
self.velocity = JUMP_FORCE
self.on_ground = False
player = Player()
# 游戏循环 # 游戏循环
running = True running = True
@@ -85,20 +24,22 @@ while running:
mx, my = pygame.mouse.get_pos() mx, my = pygame.mouse.get_pos()
bx = mx // BLOCK_SIZE bx = mx // BLOCK_SIZE
by = my // BLOCK_SIZE by = my // BLOCK_SIZE
block = world[bx][by]
if event.button == 1: # 左键放置方块 if event.button == 1: # 左键放置方块
if world[bx][by] == "air": if block.id == "air":
world[bx][by] = player.selected_block player.put_block(bx, by)
elif event.button == 3: # 右键拆除方块 elif event.button == 3 and block.destroyable: # 右键拆除方块
world[bx][by] = "air" player.put_block(bx, by, 'delete')
elif event.type == pygame.KEYDOWN: elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_1: if event.key == pygame.K_1:
player.selected_block = "grass" player.select_block(GrassBlock)
elif event.key == pygame.K_2: elif event.key == pygame.K_2:
player.selected_block = "stone" player.select_block(StoneBlock)
elif event.key == pygame.K_3: elif event.key == pygame.K_3:
player.selected_block = "sand" player.select_block(SandBlock)
elif event.key == pygame.K_4: elif event.key == pygame.K_4:
player.selected_block = "water" player.select_block(WaterBlock)
# 玩家移动 # 玩家移动
keys = pygame.key.get_pressed() keys = pygame.key.get_pressed()
@@ -108,36 +49,39 @@ while running:
player.move(PLAYER_SPEED, 0) player.move(PLAYER_SPEED, 0)
if keys[pygame.K_w]: if keys[pygame.K_w]:
player.jump() player.jump()
if keys[pygame.K_F3]:
OPTIONS["debug"] = True
if keys[pygame.K_F4]:
OPTIONS["debug"] = False
# 物理模拟 # 物理模拟
player.velocity += GRAVITY if not player.on_ground:
player.move(0, player.velocity) player.velocity = GRAVITY
player.move(0, player.velocity)
# 碰撞检测
px = int(player.x // BLOCK_SIZE)
py = int(player.y // BLOCK_SIZE)
if py < len(world[px]) - 1 and world[px][py + 1] != "air":
player.on_ground = True
player.velocity = 0
else:
player.on_ground = False
# 绘制世界 # 绘制世界
screen.fill(COLORS["air"]) for row in world:
for x in range(len(world)): for block in row:
for y in range(len(world[x])): rect = pygame.Rect(block.x * BLOCK_SIZE, block.y * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE)
if world[x][y] != "air": pygame.draw.rect(screen, block.color, rect)
rect = pygame.Rect(x * BLOCK_SIZE, y * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE)
pygame.draw.rect(screen, COLORS[world[x][y]], rect)
# 绘制玩家 # 绘制玩家
pygame.draw.circle(screen, COLORS["player"], (player.x, player.y), 10) player.draw(screen)
# 显示提示文字 # 显示提示文字
font = pygame.font.SysFont(None, 24) font = pygame.font.SysFont('KaiTi', 24)
text = font.render(f"Selected: {player.selected_block} (1-4切换方块)", True, (255, 255, 255)) text = font.render(f"Selected: {player.selected_block.id} (1-4切换方块)", True, (255, 255, 255))
screen.blit(text, (10, 10)) screen.blit(text, (10, 10))
# Debug Mode
if OPTIONS["debug"]:
debug_text = font.render("调试模式", True, (255, 255, 255))
screen.blit(debug_text, (10, 34))
for row in world:
for block in row:
if block.check_collision(player) and block.id != "air":
pygame.draw.circle(screen, COLORS["player"], block.get_center_vector(), 2)
pygame.display.flip() pygame.display.flip()
clock.tick(60) clock.tick(60)

24
module/__init__.py Normal file
View File

@@ -0,0 +1,24 @@
WIDTH, HEIGHT = 800, 600
# 颜色定义
COLORS = {
"air": (0, 191, 255),
"grass": (34, 139, 34),
"dirt": (139, 69, 19),
"stone": (128, 128, 128),
"sand": (238, 232, 170),
"water": (0, 0, 255),
"bedrock": (0, 0, 0),
"player": (255, 0, 0)
}
# 游戏参数
BLOCK_SIZE = 20
GRAVITY = 3
PLAYER_SPEED = 5
JUMP_FORCE = -36
PLAYER_SIZE = 20
OPTIONS = {
"debug": False
}

113
module/block.py Normal file
View File

@@ -0,0 +1,113 @@
from module import COLORS, BLOCK_SIZE, PLAYER_SIZE
from typing import TYPE_CHECKING
# 解决循环导入
if TYPE_CHECKING:
from module.player import Player
class Block:
id = ''
color = ''
flowable = False # 可流动
fallible = False # 可下坠
collision = True # 可以碰撞
destroyable = True # 可被破坏
def __init__(self, x: int, y: int):
self.x = x
self.y = y
def get_screen_x(self) -> int:
return self.x * BLOCK_SIZE
def get_screen_y(self) -> int:
return self.y * BLOCK_SIZE
def check_collision(self, player: 'Player') -> bool:
# 将玩家的碰撞体积看作为一个正方形,其形心坐标为 (player.x, player.y)
# 方块的坐标,即其的 (x, y) 是在 world 中的下标,需要将其转换为坐标
# 由于 下标*边长 计算得到的是这个方块左上角的坐标,所以要添加偏移量
# 计算得到形心坐标,偏移量是方块大小的一半
real_x = self.x * BLOCK_SIZE + BLOCK_SIZE / 2
real_y = self.y * BLOCK_SIZE + BLOCK_SIZE / 2
# 判断其是否碰撞仅需看其两形心间的距离,如果玩家的形心落在了方块碰撞
# 区域内,则发生碰撞,否则不发生碰撞。方块碰撞检测区域位于同形心坐标
# 边长为 PLAYER_SIZE + BLOCK_SIZE 的矩形区域
# 定界:
left = real_x - (PLAYER_SIZE + BLOCK_SIZE) / 2
right = real_x + (PLAYER_SIZE + BLOCK_SIZE) / 2
top = real_y - (PLAYER_SIZE + BLOCK_SIZE) / 2
bottom = real_y + (PLAYER_SIZE + BLOCK_SIZE) / 2
# 判碰撞:
return left < player.x < right and top < player.y < bottom and self.collision
def get_collision_surface(self) -> tuple[int, int, int, int]:
# 获得方块表面界限(碰撞面)
# 上、右、下、左
top = self.y * BLOCK_SIZE
bottom = top + BLOCK_SIZE
left = self.x * BLOCK_SIZE
right = left + BLOCK_SIZE
return top, right, bottom, left
def get_center_vector(self):
return self.x * BLOCK_SIZE + BLOCK_SIZE / 2, self.y * BLOCK_SIZE + BLOCK_SIZE / 2
class GrassBlock(Block):
id = 'grass'
color = COLORS.get(id)
def __init__(self, x: int, y: int):
super().__init__(x, y)
class DirtBlock(Block):
id = 'dirt'
color = COLORS.get(id)
def __init__(self, x: int, y: int):
super().__init__(x, y)
class SandBlock(Block):
id = 'sand'
color = COLORS.get(id)
flowable = True
def __init__(self, x: int, y: int):
super().__init__(x, y)
class StoneBlock(Block):
id = 'stone'
color = COLORS.get(id)
def __init__(self, x: int, y: int):
super().__init__(x, y)
class AirBlock(Block):
id = 'air'
color = COLORS.get(id)
collision = False
def __init__(self, x: int, y: int):
super().__init__(x, y)
class WaterBlock(Block):
id = 'water'
color = COLORS.get(id)
flowable = True
collision = False
def __init__(self, x: int, y: int):
super().__init__(x, y)
class BedrockBlock(Block):
id = 'bedrock'
color = COLORS.get(id)
destroyable = False

95
module/player.py Normal file
View File

@@ -0,0 +1,95 @@
from typing import Type, Literal
import pygame
from module import WIDTH, HEIGHT, JUMP_FORCE, COLORS, PLAYER_SIZE, OPTIONS, BLOCK_SIZE
from module.block import AirBlock, Block
# 玩家类
class Player:
def __init__(self, world: list[list[Block]]):
self.x = 100
self.y = 100
self.velocity = 0
self.on_ground = False
self.selected_block = AirBlock
self.world = world
def move(self, dx: float, dy: float):
# 仅需检测玩家当前区块的上下左右的方块是否有碰撞体积
block_x_idx, block_y_idx = self.get_pixel_idx()
top_block = self.world[block_x_idx][block_y_idx - 1] if block_y_idx > 0 else None
bottom_block = self.world[block_x_idx][block_y_idx + 1] if block_y_idx < HEIGHT // 20 - 1 else None
left_block = self.world[block_x_idx - 1][block_y_idx] if block_x_idx > 0 else None
right_block = self.world[block_x_idx + 1][block_y_idx] if block_x_idx < WIDTH // 20 - 1 else None
# 无碰撞体积时加 dx dy 有碰撞体积时,限制其值
# 向下移动时且下面方块有碰撞体积,与上表面碰撞
if dy > 0 and bottom_block is not None and bottom_block.check_collision(self):
new_y = bottom_block.get_collision_surface()[0] - 10
# 向上移动时且上面方块有碰撞体积,与下表面碰撞
elif dy < 0 and top_block is not None and top_block.check_collision(self):
new_y = top_block.get_collision_surface()[2] + 10
else:
new_y = self.y + dy
# 右移动
if dx > 0 and right_block is not None and right_block.check_collision(self):
new_x = right_block.get_collision_surface()[3] - 10
# 左移动
elif dx < 0 and left_block is not None and left_block.check_collision(self):
new_x = left_block.get_collision_surface()[1] + 10
else:
new_x = self.x + dx
if 0 <= new_x < WIDTH and 0 <= new_y < HEIGHT:
self.x = new_x
self.y = new_y
def jump(self):
if self.on_ground:
self.y += JUMP_FORCE
self.on_ground = False
def select_block(self, block: Type[Block]):
self.selected_block = block
def put_block(self, x: int, y: int, action: Literal['create', 'delete'] = 'create'):
if action == 'create':
self.world[x][y] = self.selected_block(x, y)
elif action == 'delete':
self.world[x][y] = AirBlock(x, y)
else:
pass
def draw(self, screen: pygame.Surface):
self.move(0, 0)
# 重力检测
block_x_idx, block_y_idx = self.get_pixel_idx()
bottom_block = self.world[block_x_idx][block_y_idx + 1] if block_y_idx < HEIGHT // 20 - 1 else None
if bottom_block and bottom_block.collision and bottom_block.check_collision(self):
self.on_ground = True
else:
self.on_ground = False
pygame.draw.circle(screen, COLORS["player"], (self.x, self.y), PLAYER_SIZE // 2)
if OPTIONS['debug']:
block_x = block_x_idx * BLOCK_SIZE
block_y = block_y_idx * BLOCK_SIZE
# 绘制玩家所在区块颜色
rect = pygame.Rect(block_x, block_y, BLOCK_SIZE, BLOCK_SIZE)
pygame.draw.rect(screen, '#ffd5004d', rect)
# 显示玩家坐标
font = pygame.font.SysFont('KaiTi', 24)
text = font.render(f"x: {self.x}, y: {self.y}", True, (255, 255, 255))
screen.blit(text, (200, 34))
def get_pixel_idx(self) -> tuple[int, int]:
return int(self.x) // BLOCK_SIZE, int(self.y) // BLOCK_SIZE

37
module/utils.py Normal file
View File

@@ -0,0 +1,37 @@
import random
from module import WIDTH, BLOCK_SIZE, HEIGHT
from module.block import StoneBlock, GrassBlock, DirtBlock, AirBlock, Block, BedrockBlock
# 生成地形
def generate_terrain() -> list[list[Block]]:
world = []
ground_level = HEIGHT // BLOCK_SIZE // 2
for x in range(WIDTH // BLOCK_SIZE):
#
# AIR BLOCK
# height ============================= GRASS BLOCK
# 4 DIRT BLOCK
# +5 =============================
# STONE BLOCK
# max ============================= BEDROCK BLOCK
#
column = []
height = ground_level + random.randint(-3, 3)
for y in range(HEIGHT // BLOCK_SIZE):
if y < height:
block = AirBlock(x, y)
elif y == height:
block = GrassBlock(x, y)
elif height < y < height + 5:
block = DirtBlock(x, y)
elif y == HEIGHT // BLOCK_SIZE - 1:
block = BedrockBlock(x, y)
else:
block = StoneBlock(x, y)
column.append(block)
world.append(column)
return world