diff --git a/main.py b/main.py index 2bf0b21..e31cdef 100644 --- a/main.py +++ b/main.py @@ -1,6 +1,6 @@ import pygame -from module import WIDTH, HEIGHT, BLOCK_SIZE, PLAYER_SPEED, GRAVITY, COLORS +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 @@ -31,6 +31,7 @@ while running: player.put_block(bx, by) elif event.button == 3: # 右键拆除方块 player.put_block(bx, by, 'delete') + player.on_ground = False # 不完美解决方法,挖方块时更新状态 elif event.type == pygame.KEYDOWN: if event.key == pygame.K_1: player.select_block(GrassBlock) @@ -49,19 +50,15 @@ while running: player.move(PLAYER_SPEED, 0) if keys[pygame.K_w]: player.jump() + if keys[pygame.K_F3]: + OPTIONS["debug"] = True + if keys[pygame.K_F4]: + OPTIONS["debug"] = False # 物理模拟 - 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 + if not player.on_ground: + player.velocity = GRAVITY + player.move(0, player.velocity) # 绘制世界 screen.fill(COLORS["air"]) @@ -74,13 +71,22 @@ while running: pygame.draw.rect(screen, block.color, rect) # 绘制玩家 - pygame.draw.circle(screen, COLORS["player"], (player.x, player.y), 10) + player.draw(screen) # 显示提示文字 font = pygame.font.SysFont('KaiTi', 24) text = font.render(f"Selected: {player.selected_block.id} (1-4切换方块)", True, (255, 255, 255)) 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() clock.tick(60) diff --git a/module/__init__.py b/module/__init__.py index 0d10b2f..6d0d58a 100644 --- a/module/__init__.py +++ b/module/__init__.py @@ -13,6 +13,11 @@ COLORS = { # 游戏参数 BLOCK_SIZE = 20 -GRAVITY = 0.5 +GRAVITY = 3 PLAYER_SPEED = 5 -JUMP_FORCE = -12 \ No newline at end of file +JUMP_FORCE = -36 +PLAYER_SIZE = 20 + +OPTIONS = { + "debug": False +} diff --git a/module/block.py b/module/block.py index 5b67e58..97db6c6 100644 --- a/module/block.py +++ b/module/block.py @@ -1,4 +1,10 @@ -from module import COLORS +from module import COLORS, BLOCK_SIZE, PLAYER_SIZE +from typing import TYPE_CHECKING + +# 解决循环导入 +if TYPE_CHECKING: + from module.player import Player + class Block: id = '' @@ -11,11 +17,45 @@ class Block: self.x = x self.y = y - def get_screen_x(self): - return self.x * 20 + def get_screen_x(self) -> int: + return self.x * BLOCK_SIZE - def get_screen_y(self): - return self.y * 20 + 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 + + def get_collision_surface(self) -> tuple[int]: + # 获得方块表面界限(碰撞面) + # 上、右、下、左 + real_x = self.x * BLOCK_SIZE + BLOCK_SIZE / 2 + real_y = self.y * BLOCK_SIZE + BLOCK_SIZE / 2 + + top = real_y - BLOCK_SIZE / 2 + bottom = real_y + BLOCK_SIZE / 2 + left = real_x - BLOCK_SIZE / 2 + right = real_x + BLOCK_SIZE / 2 + 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): diff --git a/module/player.py b/module/player.py index 656cca4..c0d9c79 100644 --- a/module/player.py +++ b/module/player.py @@ -1,6 +1,8 @@ from typing import Type, Literal -from module import WIDTH, HEIGHT, JUMP_FORCE +import pygame + +from module import WIDTH, HEIGHT, JUMP_FORCE, COLORS, PLAYER_SIZE, OPTIONS, BLOCK_SIZE from module.block import AirBlock, Block @@ -14,37 +16,56 @@ class Player: self.selected_block = AirBlock self.world = world - def move(self, dx, dy): - world_position = {'x': int(self.x) // 20, 'y': int(self.y) // 20} + def move(self, dx: float, dy: float): + # 仅需检测玩家当前区块的上下左右的方块是否有碰撞体积 + block_x_idx = int(self.x) // BLOCK_SIZE + block_y_idx = int(self.y) // BLOCK_SIZE - # TODO:Need to judgment x and y in order not to raise Index Out Of Range Error - top_block = self.world[world_position['x']][world_position['y'] - 1] - button_block = self.world[world_position['x']][world_position['y'] + 1] - left_block = self.world[world_position['x'] - 1][world_position['y']] - right_block = self.world[world_position['x'] + 1][world_position['y']] + 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 - if dx < 0 and left_block.collision: - new_x = left_block.get_screen_x() + 30 - elif dx > 0 and right_block.collision: - new_x = right_block.get_screen_x() - 10 - else: - new_x = self.x + dx - - if dy < 0 and top_block.collision: - new_y = top_block.get_screen_y() + 30 - elif dy > 0 and button_block.collision: - new_y = button_block.get_screen_y() - 10 + # 无碰撞体积时加 dx dy 有碰撞体积时,限制其值 + # 向下移动时且下面方块有碰撞体积,与上表面碰撞 + if dy > 0 and bottom_block is not None and bottom_block.collision: + if bottom_block.check_collision(self): + new_y = bottom_block.get_collision_surface()[0] - 10 + self.on_ground = True + else: + new_y = self.y + dy + # 向上移动时且上面方块有碰撞体积,与下表面碰撞 + elif dy < 0 and top_block is not None and top_block.collision: + if top_block.check_collision(self): + new_y = top_block.get_collision_surface()[2] + 10 + else: + new_y = self.y + dy else: new_y = self.y + dy + # 右移动 + if dx > 0 and right_block is not None and right_block.collision: + if right_block.check_collision(self): + new_x = right_block.get_collision_surface()[3] - 10 + else: + new_x = self.x + dx + # 左移动 + elif dx < 0 and left_block is not None and left_block.collision: + if left_block.check_collision(self): + new_x = left_block.get_collision_surface()[1] + 10 + else: + new_x = self.x + dx + 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.velocity = JUMP_FORCE - self.on_ground = False + self.y += JUMP_FORCE + self.on_ground = False def select_block(self, block: Type[Block]): self.selected_block = block @@ -55,4 +76,15 @@ class Player: elif action == 'delete': self.world[x][y] = AirBlock(x, y) else: - pass \ No newline at end of file + pass + + def draw(self, screen: pygame.Surface): + pygame.draw.circle(screen, COLORS["player"], (self.x, self.y), PLAYER_SIZE // 2) + self.move(0, 0) + # 绘制玩家所在区块颜色 + if OPTIONS['debug']: + block_x = int(self.x) // BLOCK_SIZE * BLOCK_SIZE + block_y = int(self.y) // BLOCK_SIZE * BLOCK_SIZE + + rect = pygame.Rect(block_x, block_y, BLOCK_SIZE, BLOCK_SIZE) + pygame.draw.rect(screen, '#ffd5004d', rect)