I've made a python game called 2d minecraft (guess where I got the idea) that has an infinite chunk system. I believe the idea I have is alright but my coding is bad and I have tried to improve it as much as I can before posting here.
Here is the code:
import pygame
# set up variables
title, size = "2d_MC", (1365, 705)
pygame.init()
screen, window_rect, clock = pygame.display.set_mode(size), pygame.Rect((0, 0), size), pygame.time.Clock()
pygame.display.set_caption(title)
pygame.event.set_allowed([pygame.QUIT])
block_size = 64
block_texture_names = ["textures\\void.png",
"textures\\air.png",
"textures\\grass_block.png",
"textures\\dirt.png",
"textures\\stone.png",
"textures\\sandstone.png",
"textures\\sand.png",
"textures\\bedrock.png",
"textures\\oak_log.png",
"textures\\oak_leaves.png",
"textures\\cobblestone.png"]
block_textures = []
def block_texture(texture):
# does the image loading
return pygame.transform.scale(pygame.image.load(texture), (block_size, block_size)).convert_alpha()
def convert(pos, convert_value_x, convert_value_y):
# converts coordinate systems
nx, rx = divmod(pos.x, convert_value_x)
ny, ry = divmod(pos.y, convert_value_y)
return ((nx, ny), (rx, ry))
class chunk():
width, height = 8, 8
def __init__(self, pos):
self.pos = pygame.Vector2(pos)
self.blocks = {}
# sets up the range and blocks
self.range = (range(0, self.width), range(0, self.height))
for x in self.range[0]:
for y in self.range[1]:
self.blocks[(x, y)] = 0
def load(self):
# will be of more use in the future
self.generate()
def generate(self):
# simple generation for now
for x in self.range[0]:
for y in self.range[1]:
x_pos, y_pos = int(self.pos.x)+x, int(self.pos.y)+y
surface = 0
bedrock = 16
soil_amount = 3
if y_pos == surface:
block = 2
elif y_pos > surface and y_pos <= surface + soil_amount:
block = 3
elif y_pos > surface + soil_amount and y_pos < bedrock:
block = 4
elif y_pos == bedrock:
block = 7
elif y_pos > bedrock:
block = 0
else:
block = 1
self.blocks[(x, y)] = block
def blocks_str(self):
# string representation of the blocks in the chunk, for testing purposes
return [self.blocks[(x, y)] for x in self.range[0] for y in self.range[1]]
def save(self):
# will be used in furture
pass
def unload(self):
# will be of more use in the future
for x in self.range[0]:
for y in self.range[1]:
self.blocks[(x, y)] = 0
class world():
def __init__(self, loader_pos, loader_distance):
self.loaded_chunks = {}
self.loader_pos = loader_pos
self.loader_chunk_pos = pygame.Vector2(convert(loader_pos, chunk.width*block_size, chunk.height*block_size)[0])
self.loader_distance = loader_distance
self.rendered = pygame.Surface((1, 1)).convert_alpha()
def handle_chunk_loader(self):
# get chunks needed and unloads and loads acordingly
chunks_needed = [chunk_pos for chunk_pos in self.chunks_to_load(self.loader_chunk_pos)]
chunks_currently_loaded = [chunk_pos for chunk_pos in self.loaded_chunks]
self.load_chunks([chunk_pos for chunk_pos in chunks_needed if chunk_pos not in chunks_currently_loaded])
self.unload_chunks([chunk_pos for chunk_pos in chunks_currently_loaded if chunk_pos not in chunks_needed])
def load_chunks(self, chunks_pos):
for chunk_pos in chunks_pos:
self.loaded_chunks[(chunk_pos.x, chunk_pos.y)] = chunk(chunk_pos)
self.loaded_chunks[(chunk_pos.x, chunk_pos.y)].load()
def unload_chunks(self, chunks_pos):
for chunk_pos in chunks_pos:
chunk = self.loaded_chunks.pop(chunk_pos)
# chunk.save()
chunk.unload()
def get_block(self, pos):
# will be of more use in the future
chunk_pos, block_pos = convert(pos, chunk.width, chunk.height)
try:
return self.loaded_chunks[chunk_pos].blocks[block_pos]
except Exception as e:
return 0
def chunks_to_load(self, loader_pos):
return [pygame.Vector2(chunk_pos_x*chunk.width, chunk_pos_y*chunk.height)
for chunk_pos_x in range(int(loader_pos.x)-self.loader_distance, int(loader_pos.x)+self.loader_distance+1)
for chunk_pos_y in range(int(loader_pos.y)-self.loader_distance, int(loader_pos.y)+self.loader_distance+1)]
def change_pos(self, pos):
self.loader_pos += pos
self.loader_chunk_pos = pygame.Vector2(convert(self.loader_pos, chunk.width*block_size, chunk.height*block_size)[0])
def set_pos(self, pos):
self.loader_pos = pos
self.loader_chunk_pos = pygame.Vector2(convert(pos, chunk.width*block_size, chunk.height*block_size)[0])
def render(self, screen, use_pos, other_offset=0):
for chunk_pos, ch in self.loaded_chunks.items():
chunk_screen_pos = pygame.Vector2(chunk_pos[0], chunk_pos[1])*block_size
for block_pos, block in ch.blocks.items():
screen.blit(block_textures[block], ((chunk_screen_pos + pygame.Vector2(block_pos)*block_size) - self.loader_pos*int(use_pos))+other_offset)
def handle_events():
for e in pygame.event.get():
if e.type == pygame.QUIT:
return 1
keys = pygame.key.get_pressed()
if keys[pygame.K_UP]:
overworld.change_pos(pygame.Vector2(0, -2))
if keys[pygame.K_DOWN]:
overworld.change_pos(pygame.Vector2(0, 2))
if keys[pygame.K_LEFT]:
overworld.change_pos(pygame.Vector2(-2, 0))
if keys[pygame.K_RIGHT]:
overworld.change_pos(pygame.Vector2(2, 0))
return 0
def draw():
screen.fill((255, 255, 255))
overworld.render(screen, True, other_offset=window_rect.center)
#screen.blit(overworld.rendered, (0, 0))
def game_logic():
overworld.handle_chunk_loader()
def run_game():
# set up textures
for texture in block_texture_names:
block_textures.append(block_texture(texture))
while 1:
game_logic()
if handle_events():
break
draw()
pygame.display.update(window_rect)
clock.tick()
print(clock.get_fps())
pygame.quit()
overworld = world(pygame.Vector2(0, 0), 2)
run_game()
It runs at a minimum of 30 fps and maximum of 50 and I would like to get it to 60 at least. I would also not mind reducing the code.