如何在类似《我的世界》的2D游戏中在块上生成树或其他结构



我正在尝试创建一个类似2D Minecraft的游戏,但遇到了一个问题。我正试图在地形的顶部生成树木。地形是用单纯形噪声生成的,它被分成8x8块,如果玩家每次移动都有必要,游戏会生成新的块。我试着通过在地形顶部随机选择一个位置来生成树,并将其上方的块设置为我想要的块,然后我遇到了另一个问题。这些树可能会分成相邻的几块。我试图通过将树中其他块中的部分存储在字典中,并在生成其他块时从字典中生成它们来解决这个问题,但还有另一个问题。有时,与包含大部分树的块相邻的块已经生成,所以当生成具有树的块时,我无法覆盖它。。。我对如何让它发挥作用有点困惑。

这是用于生成新块的代码,其中参数x和y是要生成的块的位置:

def generate_chunk(x, y):
chunk_data = {}
for y_pos in range(CHUNK_SIZE):
for x_pos in range(CHUNK_SIZE):
block = (x * CHUNK_SIZE + x_pos, y * CHUNK_SIZE + y_pos)
block_name = ""
height = int(noise.noise2d(block[0]*0.1, 0)*5)
if block[1] == 5-height:
block_name = "grass_block"
elif 5-height < block[1] < 10-height:
block_name = "dirt"
elif block[1] >= 10-height:
block_name = "stone"
if block_name != "":
chunk_data[block] = block_name
return chunk_data

这是一个主循环,玩家附近的区块被生成,并在玩家离开时被临时删除和保存:

running = True
while running:
dt = clock.tick(FPS) / 16
pygame.display.set_caption(f"2D Minecraft | FPS: {int(clock.get_fps())}")
for event in pygame.event.get():
if event.type == QUIT:
running = False
rendered_chunks = []
for y in range(int(HEIGHT/(CHUNK_SIZE*BLOCK_SIZE)+2)):
for x in range(int(WIDTH/(CHUNK_SIZE*BLOCK_SIZE)+2)):
chunk = (
x - 1 + int(round(camera.pos.x / (CHUNK_SIZE * BLOCK_SIZE))), 
y - 1 + int(round(camera.pos.y / (CHUNK_SIZE * BLOCK_SIZE)))
)
rendered_chunks.append(chunk)
if chunk not in chunks:
chunks[chunk] = Chunk(chunk)
unrendered_chunks = []
for y in range(int(HEIGHT/(CHUNK_SIZE*BLOCK_SIZE)+4)):
for x in range(int(WIDTH/(CHUNK_SIZE*BLOCK_SIZE)+4)):
chunk = (
x - 2 + int(round(camera.pos.x / (CHUNK_SIZE * BLOCK_SIZE))), 
y - 2 + int(round(camera.pos.y / (CHUNK_SIZE * BLOCK_SIZE)))
)
try: chunks[chunk]
except: pass
else:
if chunk not in rendered_chunks:
unrendered_chunks.append(chunk)
for chunk in unrendered_chunks:
for block in chunks[chunk].block_data:
if block in blocks:
blocks[block].kill()
del blocks[block]
camera.update()
player.update()
screen.fill((135, 206, 250))
for chunk in rendered_chunks:
chunks[chunk].render()
player.draw(screen)
pygame.display.flip()

以下是Block类和Chunk类:

class Block(pygame.sprite.Sprite):
def __init__(self, chunk, pos, name):
pygame.sprite.Sprite.__init__(self)
blocks[tuple(pos)] = self
self.name = name
self.chunk = chunk
self.coords = vec(pos)
self.pos = self.coords * BLOCK_SIZE
self.image = block_textures[self.name]
self.rect = self.image.get_rect()
def update(self):
self.rect.topleft = self.pos - camera.pos
def draw(self, screen):
screen.blit(self.image, self.rect.topleft)
class Chunk(object):
def __init__(self, pos):
self.pos = pos
self.block_data = generate_chunk(pos[0], pos[1])
for block in self.block_data:
blocks[block] = Block(self, block, self.block_data[block])
def render(self):
if self.pos in rendered_chunks:
for block in self.block_data:
try: blocks[block]
except:
blocks[block] = Block(self, block, self.block_data[block])
blocks[block].update()
blocks[block].draw(screen)
pygame.draw.rect(screen, (255, 255, 0), (self.pos[0]*CHUNK_SIZE*BLOCK_SIZE-camera.pos[0], self.pos[1]*CHUNK_SIZE*BLOCK_SIZE-camera.pos[1], CHUNK_SIZE*BLOCK_SIZE, CHUNK_SIZE*BLOCK_SIZE), width=1)

最小的可复制代码,我认为所有需要的信息都在上面,但以防万一你需要剩下的:

import pygame
from pygame.locals import *
from random import *
from math import *
import json
import os
import opensimplex
FPS = 60
WIDTH, HEIGHT = 1200, 600
SCR_DIM = (WIDTH, HEIGHT)
GRAVITY = 0.5
SLIDE = 0.3
TERMINAL_VEL = 24
BLOCK_SIZE = 64
CHUNK_SIZE = 8
SEED = randint(-2147483648, 2147483647)
pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT), HWSURFACE | DOUBLEBUF)
pygame.display.set_caption("2D Minecraft")
clock = pygame.time.Clock()
mixer = pygame.mixer.init()
vec = pygame.math.Vector2
noise = opensimplex.OpenSimplex(seed=SEED)
seed(SEED)
block_textures = {}
for img in os.listdir("res/textures/blocks/"):
block_textures[img[:-4]] = pygame.image.load("res/textures/blocks/"+img).convert_alpha()
for image in block_textures:
block_textures[image] = pygame.transform.scale(block_textures[image], (BLOCK_SIZE, BLOCK_SIZE))
def intv(vector):
return vec(int(vector.x), int(vector.y))
def inttup(tup):
return (int(tup[0]), int(tup[1]))
def block_collide(ax, ay, width, height, b):
a_rect = pygame.Rect(ax-camera.pos.x, ay-camera.pos.y, width, height)
b_rect = pygame.Rect(b.pos.x-camera.pos.x, b.pos.y-camera.pos.y, BLOCK_SIZE, BLOCK_SIZE)
if a_rect.colliderect(b_rect):
return True
return False
class Camera(pygame.sprite.Sprite):
def __init__(self, master):
self.master = master
self.pos = self.master.size / 2
self.pos = self.master.pos - self.pos - vec(SCR_DIM) / 2 + self.master.size / 2
def update(self):
tick_offset = self.master.pos - self.pos - vec(SCR_DIM) / 2 + self.master.size / 2
if -1 < tick_offset.x < 1:
tick_offset.x = 0
if -1 < tick_offset.y < 1:
tick_offset.y = 0
self.pos += tick_offset / 10
class Player(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.size = vec(0.225*BLOCK_SIZE, 1.8*BLOCK_SIZE)
self.width, self.height = self.size.x, self.size.y
self.start_pos = vec(0, 3) * BLOCK_SIZE
self.pos = vec(self.start_pos)
self.coords = self.pos // BLOCK_SIZE
self.vel = vec(0, 0)
self.max_speed = 5.306
self.jumping_max_speed = 6.6
self.rect = pygame.Rect((0, 0, 0.225*BLOCK_SIZE, 1.8*BLOCK_SIZE))
self.bottom_bar = pygame.Rect((self.rect.x+1, self.rect.bottom), (self.width-2, 1))
self.on_ground = False
def update(self):
keys = pygame.key.get_pressed()
if keys[K_a]:
if self.vel.x > -self.max_speed:
self.vel.x -= SLIDE
elif self.vel.x < 0:
self.vel.x += SLIDE
if keys[K_d]:
if self.vel.x < self.max_speed:
self.vel.x += SLIDE
elif self.vel.x > 0:
self.vel.x -= SLIDE
if keys[K_w] and self.on_ground:
self.vel.y = -9.2
self.vel.x *= 1.1
if self.vel.x > self.jumping_max_speed:
self.vel.x = self.jumping_max_speed
elif self.vel.x < -self.jumping_max_speed:
self.vel.x = -self.jumping_max_speed
if -SLIDE < self.vel.x < SLIDE:
self.vel.x = 0
self.vel.y += GRAVITY
if self.vel.y > TERMINAL_VEL:
self.vel.y = TERMINAL_VEL
self.move()
self.bottom_bar = pygame.Rect((self.rect.left+1, self.rect.bottom), (self.width-2, 1))
for block in blocks:
if self.bottom_bar.colliderect(blocks[block].rect):
self.on_ground = True
break
else:
self.on_ground = False
if self.on_ground:
self.vel.x *= 0.99
self.coords = self.pos // BLOCK_SIZE
self.chunk = self.coords // CHUNK_SIZE
self.rect.topleft = self.pos - camera.pos
def draw(self, screen):
pygame.draw.rect(screen, (0, 0, 0), self.rect)
def move(self):
for y in range(4):
for x in range(3):
try:
block = blocks[(int(self.coords.x-1+x), int(self.coords.y-1+y))]
except:
pass
else:
if self.vel.y < 0:
if block_collide(floor(self.pos.x), floor(self.pos.y+self.vel.y), self.width, self.height, block):
self.pos.y = floor(block.pos.y + BLOCK_SIZE)
self.vel.y = 0
elif self.vel.y >= 0:
if self.vel.x <= 0:
if block_collide(floor(self.pos.x), ceil(self.pos.y+self.vel.y), self.width, self.height, block):
self.pos.y = ceil(block.pos.y - self.height)
self.vel.y = 0
elif self.vel.x > 0:
if block_collide(ceil(self.pos.x), ceil(self.pos.y+self.vel.y), self.width, self.height, block):
self.pos.y = ceil(block.pos.y - self.height)
self.vel.y = 0
if self.vel.x < 0:
if block_collide(floor(self.pos.x+self.vel.x), floor(self.pos.y), self.width, self.height, block):
self.pos.x = floor(block.pos.x + BLOCK_SIZE)
self.vel.x = 0
elif self.vel.x >= 0:
if block_collide(ceil(self.pos.x+self.vel.x), ceil(self.pos.y), self.width, self.height, block):
self.pos.x = ceil(block.pos.x - self.width)
self.vel.x = 0
self.pos += self.vel
class Block(pygame.sprite.Sprite):
def __init__(self, chunk, pos, name):
pygame.sprite.Sprite.__init__(self)
blocks[tuple(pos)] = self
self.name = name
self.chunk = chunk
self.coords = vec(pos)
self.pos = self.coords * BLOCK_SIZE
self.image = block_textures[self.name]
self.rect = self.image.get_rect()
def update(self):
self.rect.topleft = self.pos - camera.pos
def draw(self, screen):
screen.blit(self.image, self.rect.topleft)
class Chunk(object):
def __init__(self, pos):
self.pos = pos
self.block_data = generate_chunk(pos[0], pos[1])
for block in self.block_data:
blocks[block] = Block(self, block, self.block_data[block])
def render(self):
if self.pos in rendered_chunks:
for block in self.block_data:
try: blocks[block]
except:
blocks[block] = Block(self, block, self.block_data[block])
blocks[block].update()
blocks[block].draw(screen)
pygame.draw.rect(screen, (255, 255, 0), (self.pos[0]*CHUNK_SIZE*BLOCK_SIZE-camera.pos[0], self.pos[1]*CHUNK_SIZE*BLOCK_SIZE-camera.pos[1], CHUNK_SIZE*BLOCK_SIZE, CHUNK_SIZE*BLOCK_SIZE), width=1)
def generate_chunk(x, y):
chunk_data = {}
for y_pos in range(CHUNK_SIZE):
for x_pos in range(CHUNK_SIZE):
block = (x * CHUNK_SIZE + x_pos, y * CHUNK_SIZE + y_pos)
block_name = ""
height = int(noise.noise2d(block[0]*0.1, 0)*5)
if block[1] == 5-height:
block_name = "grass_block"
elif 5-height < block[1] < 10-height:
block_name = "dirt"
elif block[1] >= 10-height:
block_name = "stone"
if block_name != "":
chunk_data[block] = block_name
return chunk_data
blocks = {}
chunks = {}
player = Player()
camera = Camera(player)
running = True
while running:
dt = clock.tick(FPS) / 16
pygame.display.set_caption(f"2D Minecraft | FPS: {int(clock.get_fps())}")
for event in pygame.event.get():
if event.type == QUIT:
running = False
rendered_chunks = []
for y in range(int(HEIGHT/(CHUNK_SIZE*BLOCK_SIZE)+2)):
for x in range(int(WIDTH/(CHUNK_SIZE*BLOCK_SIZE)+2)):
chunk = (
x - 1 + int(round(camera.pos.x / (CHUNK_SIZE * BLOCK_SIZE))), 
y - 1 + int(round(camera.pos.y / (CHUNK_SIZE * BLOCK_SIZE)))
)
rendered_chunks.append(chunk)
if chunk not in chunks:
chunks[chunk] = Chunk(chunk)
unrendered_chunks = []
for y in range(int(HEIGHT/(CHUNK_SIZE*BLOCK_SIZE)+4)):
for x in range(int(WIDTH/(CHUNK_SIZE*BLOCK_SIZE)+4)):
chunk = (
x - 2 + int(round(camera.pos.x / (CHUNK_SIZE * BLOCK_SIZE))), 
y - 2 + int(round(camera.pos.y / (CHUNK_SIZE * BLOCK_SIZE)))
)
try: chunks[chunk]
except: pass
else:
if chunk not in rendered_chunks:
unrendered_chunks.append(chunk)
for chunk in unrendered_chunks:
for block in chunks[chunk].block_data:
if block in blocks:
blocks[block].kill()
del blocks[block]
camera.update()
player.update()
screen.fill((135, 206, 250))
for chunk in rendered_chunks:
chunks[chunk].render()
player.draw(screen)
pygame.display.flip()
pygame.quit()
quit()

(顺便说一句,黄线是区块边界(

一般的想法是实现一个结构(例如树(的大小,计算它可以跨越多少块,然后在生成块(x, y)时检查它周围的所有块


TREE_SHAPE = {
(0, 0): "oak_log",
(0, -1): "oak_log",
(0, -2): "oak_log",
(0, -3): "oak_log",
(0, -4): "oak_leaves",
(1, -4): "oak_leaves",
(2, -4): "oak_leaves",
(3, -4): "oak_leaves",
(-1, -4): "oak_leaves",
(-2, -4): "oak_leaves",
(-3, -4): "oak_leaves",
}
MAX_TREE_SIZE = (max(x for x, y in TREE_SHAPE) - min(x for x, y in TREE_SHAPE) + 1,
max(y for x, y in TREE_SHAPE) - min(y for x, y in TREE_SHAPE) + 1)
CHUNKS_TO_CHECK = int(ceil(MAX_TREE_SIZE[0] / CHUNK_SIZE)), int(ceil(MAX_TREE_SIZE[1] / CHUNK_SIZE))

def generate_tree(base):
return {(base[0] + offset[0], base[1] + offset[1]): block for offset, block in TREE_SHAPE.items()}
# Replace everything above with however you want to generate Trees.
# It might be worth factoring that out into a StructureGenerator class.

def get_trees(x, y):
out = []
seed(SEED + x * CHUNK_SIZE + y)  # Make sure this function always produces the same output
for _ in range(CHUNK_SIZE // 8):  # At most one Tree attempt per 4 blocks
block_x = x * CHUNK_SIZE + randrange(0, CHUNK_SIZE)
grass_y = int(5 - noise.noise2d(block_x * 0.1, 0) * 5)  # Same as in generate_chunk
if not 0 <= grass_y - y * CHUNK_SIZE < CHUNK_SIZE:  # Tree spot not in this chunk
continue
out.append(generate_tree((block_x, grass_y)))
return out

def generate_chunk(x, y):
chunk_data = {}
# ... Your old code
for ox in range(-CHUNKS_TO_CHECK[0], CHUNKS_TO_CHECK[0] + 1):
for oy in range(-CHUNKS_TO_CHECK[1], CHUNKS_TO_CHECK[1] + 1):
# For each Chunk around us (and ourself), check which trees there are.
trees = get_trees(x + ox, y + oy)
for tree in trees:
for block, block_name in tree.items():
if 0<=block[0]-x*CHUNK_SIZE<CHUNK_SIZE and 0<=block[0]-x*CHUNK_SIZE<CHUNK_SIZE:
# This block is in this chunk
chunk_data[block] = block_name
return chunk_data

最新更新