Compare commits
6 Commits
3cb2bf4b74
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 038b876b15 | |||
| 705b5c746a | |||
| 1a210382ae | |||
| 40fa44f936 | |||
| 3ef79886ec | |||
| 6670715bf2 |
55
main.py
55
main.py
@@ -1,7 +1,7 @@
|
|||||||
import pygame
|
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 AirBlock, GrassBlock, StoneBlock, SandBlock, WaterBlock
|
from module.block import GrassBlock, StoneBlock, SandBlock, WaterBlock
|
||||||
from module.player import Player
|
from module.player import Player
|
||||||
from module.utils import generate_terrain
|
from module.utils import generate_terrain
|
||||||
|
|
||||||
@@ -11,7 +11,7 @@ screen = pygame.display.set_mode((WIDTH, HEIGHT))
|
|||||||
clock = pygame.time.Clock()
|
clock = pygame.time.Clock()
|
||||||
|
|
||||||
world = generate_terrain()
|
world = generate_terrain()
|
||||||
player = Player()
|
player = Player(world)
|
||||||
|
|
||||||
# 游戏循环
|
# 游戏循环
|
||||||
running = True
|
running = True
|
||||||
@@ -28,18 +28,18 @@ while running:
|
|||||||
block = world[bx][by]
|
block = world[bx][by]
|
||||||
if event.button == 1: # 左键放置方块
|
if event.button == 1: # 左键放置方块
|
||||||
if block.id == "air":
|
if block.id == "air":
|
||||||
world[bx][by] = player.selected_block(bx, by)
|
player.put_block(bx, by)
|
||||||
elif event.button == 3: # 右键拆除方块
|
elif event.button == 3 and block.destroyable: # 右键拆除方块
|
||||||
world[bx][by] = AirBlock(bx, by)
|
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 = GrassBlock
|
player.select_block(GrassBlock)
|
||||||
elif event.key == pygame.K_2:
|
elif event.key == pygame.K_2:
|
||||||
player.selected_block = StoneBlock
|
player.select_block(StoneBlock)
|
||||||
elif event.key == pygame.K_3:
|
elif event.key == pygame.K_3:
|
||||||
player.selected_block = SandBlock
|
player.select_block(SandBlock)
|
||||||
elif event.key == pygame.K_4:
|
elif event.key == pygame.K_4:
|
||||||
player.selected_block = WaterBlock
|
player.select_block(WaterBlock)
|
||||||
|
|
||||||
# 玩家移动
|
# 玩家移动
|
||||||
keys = pygame.key.get_pressed()
|
keys = pygame.key.get_pressed()
|
||||||
@@ -49,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 row in world:
|
||||||
for block in row:
|
for block in row:
|
||||||
if block.id != "air":
|
rect = pygame.Rect(block.x * BLOCK_SIZE, block.y * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE)
|
||||||
rect = pygame.Rect(block.x * BLOCK_SIZE, block.y * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE)
|
pygame.draw.rect(screen, block.color, rect)
|
||||||
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)
|
font = pygame.font.SysFont('KaiTi', 24)
|
||||||
text = font.render(f"Selected: {player.selected_block.id} (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)
|
||||||
|
|
||||||
|
|||||||
@@ -8,11 +8,17 @@ COLORS = {
|
|||||||
"stone": (128, 128, 128),
|
"stone": (128, 128, 128),
|
||||||
"sand": (238, 232, 170),
|
"sand": (238, 232, 170),
|
||||||
"water": (0, 0, 255),
|
"water": (0, 0, 255),
|
||||||
|
"bedrock": (0, 0, 0),
|
||||||
"player": (255, 0, 0)
|
"player": (255, 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
# 游戏参数
|
# 游戏参数
|
||||||
BLOCK_SIZE = 20
|
BLOCK_SIZE = 20
|
||||||
GRAVITY = 0.5
|
GRAVITY = 3
|
||||||
PLAYER_SPEED = 5
|
PLAYER_SPEED = 5
|
||||||
JUMP_FORCE = -12
|
JUMP_FORCE = -36
|
||||||
|
PLAYER_SIZE = 20
|
||||||
|
|
||||||
|
OPTIONS = {
|
||||||
|
"debug": False
|
||||||
|
}
|
||||||
|
|||||||
@@ -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:
|
class Block:
|
||||||
id = ''
|
id = ''
|
||||||
@@ -6,11 +12,49 @@ class Block:
|
|||||||
flowable = False # 可流动
|
flowable = False # 可流动
|
||||||
fallible = False # 可下坠
|
fallible = False # 可下坠
|
||||||
collision = True # 可以碰撞
|
collision = True # 可以碰撞
|
||||||
|
destroyable = True # 可被破坏
|
||||||
|
|
||||||
def __init__(self, x: int, y: int):
|
def __init__(self, x: int, y: int):
|
||||||
self.x = x
|
self.x = x
|
||||||
self.y = y
|
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):
|
class GrassBlock(Block):
|
||||||
id = 'grass'
|
id = 'grass'
|
||||||
@@ -61,4 +105,9 @@ class WaterBlock(Block):
|
|||||||
collision = False
|
collision = False
|
||||||
|
|
||||||
def __init__(self, x: int, y: int):
|
def __init__(self, x: int, y: int):
|
||||||
super().__init__(x, y)
|
super().__init__(x, y)
|
||||||
|
|
||||||
|
class BedrockBlock(Block):
|
||||||
|
id = 'bedrock'
|
||||||
|
color = COLORS.get(id)
|
||||||
|
destroyable = False
|
||||||
@@ -1,24 +1,95 @@
|
|||||||
from module import WIDTH, HEIGHT, JUMP_FORCE
|
from typing import Type, Literal
|
||||||
from module.block import AirBlock
|
|
||||||
|
import pygame
|
||||||
|
|
||||||
|
from module import WIDTH, HEIGHT, JUMP_FORCE, COLORS, PLAYER_SIZE, OPTIONS, BLOCK_SIZE
|
||||||
|
from module.block import AirBlock, Block
|
||||||
|
|
||||||
|
|
||||||
# 玩家类
|
# 玩家类
|
||||||
class Player:
|
class Player:
|
||||||
def __init__(self):
|
def __init__(self, world: list[list[Block]]):
|
||||||
self.x = WIDTH // 2
|
self.x = 100
|
||||||
self.y = HEIGHT // 2
|
self.y = 100
|
||||||
self.velocity = 0
|
self.velocity = 0
|
||||||
self.on_ground = False
|
self.on_ground = False
|
||||||
self.selected_block = AirBlock
|
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
|
||||||
|
|
||||||
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:
|
if 0 <= new_x < WIDTH and 0 <= new_y < HEIGHT:
|
||||||
self.x = new_x
|
self.x = new_x
|
||||||
self.y = new_y
|
self.y = new_y
|
||||||
|
|
||||||
def jump(self):
|
def jump(self):
|
||||||
if self.on_ground:
|
if self.on_ground:
|
||||||
self.velocity = JUMP_FORCE
|
self.y += JUMP_FORCE
|
||||||
self.on_ground = False
|
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
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import random
|
import random
|
||||||
from module import WIDTH, BLOCK_SIZE, HEIGHT
|
from module import WIDTH, BLOCK_SIZE, HEIGHT
|
||||||
from module.block import StoneBlock, GrassBlock, DirtBlock, AirBlock, Block
|
from module.block import StoneBlock, GrassBlock, DirtBlock, AirBlock, Block, BedrockBlock
|
||||||
|
|
||||||
|
|
||||||
# 生成地形
|
# 生成地形
|
||||||
@@ -9,17 +9,29 @@ def generate_terrain() -> list[list[Block]]:
|
|||||||
ground_level = HEIGHT // BLOCK_SIZE // 2
|
ground_level = HEIGHT // BLOCK_SIZE // 2
|
||||||
|
|
||||||
for x in range(WIDTH // BLOCK_SIZE):
|
for x in range(WIDTH // BLOCK_SIZE):
|
||||||
|
#
|
||||||
|
# AIR BLOCK
|
||||||
|
# height ============================= GRASS BLOCK
|
||||||
|
# 4 DIRT BLOCK
|
||||||
|
# +5 =============================
|
||||||
|
# STONE BLOCK
|
||||||
|
# max ============================= BEDROCK BLOCK
|
||||||
|
#
|
||||||
column = []
|
column = []
|
||||||
height = ground_level + random.randint(-3, 3)
|
height = ground_level + random.randint(-3, 3)
|
||||||
for y in range(HEIGHT // BLOCK_SIZE):
|
for y in range(HEIGHT // BLOCK_SIZE):
|
||||||
if y > height + 2:
|
if y < height:
|
||||||
column.append(StoneBlock(x, y))
|
block = AirBlock(x, y)
|
||||||
elif y == height:
|
elif y == height:
|
||||||
column.append(GrassBlock(x, y))
|
block = GrassBlock(x, y)
|
||||||
elif y > height - 3:
|
elif height < y < height + 5:
|
||||||
column.append(DirtBlock(x, y))
|
block = DirtBlock(x, y)
|
||||||
|
elif y == HEIGHT // BLOCK_SIZE - 1:
|
||||||
|
block = BedrockBlock(x, y)
|
||||||
else:
|
else:
|
||||||
column.append(AirBlock(x, y))
|
block = StoneBlock(x, y)
|
||||||
|
column.append(block)
|
||||||
|
|
||||||
world.append(column)
|
world.append(column)
|
||||||
|
|
||||||
return world
|
return world
|
||||||
Reference in New Issue
Block a user