# đŸ—ī¸ Architettura del Motore di Gioco Procedurale (Stile Minecraft) ## 🌍 GENERAZIONE DEL MONDO ### Sistema di Noise Multi-Livello ```python import noise import numpy as np class WorldGenerator: def __init__(self, seed): self.seed = seed self.octaves = 6 self.persistence = 0.5 self.lacunarity = 2.0 def generate_height_map(self, chunk_x, chunk_z): height_map = np.zeros((16, 16)) for x in range(16): for z in range(16): world_x = chunk_x * 16 + x world_z = chunk_z * 16 + z continental = noise.pnoise2( world_x / 1000.0, world_z / 1000.0, octaves=2, persistence=0.5, base=self.seed ) erosion = noise.pnoise2( world_x / 300.0, world_z / 300.0, octaves=4, persistence=0.6, base=self.seed + 1 ) peaks_valleys = noise.pnoise2( world_x / 150.0, world_z / 150.0, octaves=6, persistence=0.7, base=self.seed + 2 ) height = 64 + continental * 40 + erosion * 20 + peaks_valleys * 30 height_map[x][z] = int(np.clip(height, 0, 319)) return height_map def get_temperature(self, x, z): temp = noise.pnoise2( x / 500.0, z / 500.0, octaves=3, base=self.seed + 10 ) return (temp + 1) / 2 def get_humidity(self, x, z): humidity = noise.pnoise2( x / 400.0, z / 400.0, octaves=3, base=self.seed + 20 ) return (humidity + 1) / 2 def determine_biome(self, x, z, y): temp = self.get_temperature(x, z) humidity = self.get_humidity(x, z) if y < 62: return 'ocean' elif temp < 0.3: return 'snowy_tundra' elif temp > 0.8 and humidity < 0.3: return 'desert' elif humidity > 0.7 and temp > 0.6: return 'jungle' elif temp > 0.5 and humidity < 0.4: return 'savanna' elif y > 100: return 'mountains' else: return 'plains' def generate_caves_3d(self, x, y, z): cave_noise = noise.pnoise3( x / 50.0, y / 30.0, z / 50.0, octaves=3, base=self.seed + 100 ) worm_cave = noise.pnoise3( x / 80.0, y / 80.0, z / 80.0, octaves=2, base=self.seed + 200 ) return cave_noise > 0.6 or worm_cave > 0.7 def generate_chunk_terrain(self, chunk_x, chunk_z): chunk = np.zeros((16, 320, 16), dtype=np.uint16) height_map = self.generate_height_map(chunk_x, chunk_z) for x in range(16): for z in range(16): world_x = chunk_x * 16 + x world_z = chunk_z * 16 + z surface_height = int(height_map[x][z]) biome = self.determine_biome(world_x, world_z, surface_height) for y in range(320): if y == 0: chunk[x][y][z] = BEDROCK elif y < surface_height - 4: if self.generate_caves_3d(world_x, y, world_z): chunk[x][y][z] = AIR else: chunk[x][y][z] = STONE elif y < surface_height - 1: chunk[x][y][z] = DIRT elif y == surface_height: if biome == 'desert': chunk[x][y][z] = SAND elif biome == 'ocean' and y < 62: chunk[x][y][z] = WATER else: chunk[x][y][z] = GRASS elif y <= 62: chunk[x][y][z] = WATER else: chunk[x][y][z] = AIR return chunk, height_map ``` ### Strutture Procedurali ```python class StructureGenerator: def __init__(self, seed): self.seed = seed self.village_spacing = 32 self.stronghold_count = 128 def get_structure_reference(self, chunk_x, chunk_z, structure_type): np.random.seed(hash((chunk_x, chunk_z, structure_type, self.seed))) region_x = chunk_x // self.village_spacing region_z = chunk_z // self.village_spacing structure_x = region_x * self.village_spacing + np.random.randint(0, self.village_spacing) structure_z = region_z * self.village_spacing + np.random.randint(0, self.village_spacing) if chunk_x == structure_x and chunk_z == structure_z: if np.random.random() < 0.3: return (structure_x, structure_z) return None def generate_village(self, chunk, start_x, start_z, height_map): buildings = [ {'type': 'house', 'size': (5, 5), 'offset': (0, 0)}, {'type': 'farm', 'size': (9, 9), 'offset': (7, 0)}, {'type': 'well', 'size': (3, 3), 'offset': (3, 7)} ] for building in buildings: self.place_building(chunk, start_x + building['offset'][0], start_z + building['offset'][1], building['type'], building['size'], height_map) def generate_mineshaft(self, chunk, x, y, z): direction = np.random.choice(['x', 'z']) length = np.random.randint(10, 30) for i in range(length): if direction == 'x': chunk[x + i][y][z] = AIR chunk[x + i][y + 1][z] = AIR if i % 2 == 0: chunk[x + i][y][z - 1] = FENCE chunk[x + i][y][z + 1] = FENCE else: chunk[x][y][z + i] = AIR chunk[x][y + 1][z + i] = AIR ``` ## đŸ“Ļ CHUNK SYSTEM ```python class Chunk: def __init__(self, x, z, world): self.x = x self.z = z self.world = world self.blocks = np.zeros((16, 320, 16), dtype=np.uint16) self.light_sky = np.full((16, 320, 16), 15, dtype=np.uint8) self.light_block = np.zeros((16, 320, 16), dtype=np.uint8) self.biomes = np.zeros((16, 320, 16), dtype=np.uint8) self.entities = [] self.tile_entities = {} self.state = 'empty' self.mesh_dirty = True self.vbo = None def generate_terrain(self, generator): self.blocks, height_map = generator.generate_chunk_terrain(self.x, self.z) self.state = 'terrain' self.calculate_sky_light() def populate(self, generator): if self.state != 'terrain': return for x in range(16): for z in range(16): world_x = self.x * 16 + x world_z = self.z * 16 + z for y in range(319, 0, -1): if self.blocks[x][y][z] == GRASS: biome = generator.determine_biome(world_x, world_z, y) if biome == 'plains' and np.random.random() < 0.1: self.place_tree(x, y + 1, z) elif biome == 'desert' and np.random.random() < 0.05: self.place_cactus(x, y + 1, z) break structure_ref = generator.get_structure_reference(self.x, self.z, 'village') if structure_ref: generator.generate_village(self.blocks, 0, 0, None) self.state = 'populated' def tick(self, delta_time): for x in range(16): for z in range(16): rx = np.random.randint(0, 16) rz = np.random.randint(0, 16) ry = np.random.randint(0, 320) block = self.blocks[rx][ry][rz] if block == GRASS: if self.blocks[rx][ry + 1][rz] == AIR and np.random.random() < 0.001: self.blocks[rx][ry + 1][rz] = TALL_GRASS elif block == SAPLING: if np.random.random() < 0.05: self.place_tree(rx, ry, rz) elif block == WATER: self.update_fluid(rx, ry, rz, WATER) for entity in self.entities: entity.tick(delta_time) def place_tree(self, x, y, z): trunk_height = np.random.randint(4, 7) for i in range(trunk_height): if y + i < 320: self.blocks[x][y + i][z] = LOG leaf_y = y + trunk_height for dx in range(-2, 3): for dz in range(-2, 3): for dy in range(-2, 1): lx, ly, lz = x + dx, leaf_y + dy, z + dz if 0 <= lx < 16 and 0 <= ly < 320 and 0 <= lz < 16: if abs(dx) + abs(dz) <= 2: if self.blocks[lx][ly][lz] == AIR: self.blocks[lx][ly][lz] = LEAVES def update_fluid(self, x, y, z, fluid_type): if y > 0 and self.blocks[x][y - 1][z] == AIR: self.blocks[x][y - 1][z] = fluid_type else: for dx, dz in [(-1, 0), (1, 0), (0, -1), (0, 1)]: nx, nz = x + dx, z + dz if 0 <= nx < 16 and 0 <= nz < 16: if self.blocks[nx][y][nz] == AIR: self.blocks[nx][y][nz] = fluid_type def calculate_sky_light(self): for x in range(16): for z in range(16): light_level = 15 for y in range(319, -1, -1): block = self.blocks[x][y][z] if block != AIR: light_level = max(0, light_level - BLOCK_OPACITY[block]) self.light_sky[x][y][z] = light_level class ChunkManager: def __init__(self, world, generator): self.world = world self.generator = generator self.chunks = {} self.loading_queue = [] def get_chunk(self, chunk_x, chunk_z): key = (chunk_x, chunk_z) if key not in self.chunks: return None return self.chunks[key] def load_chunk(self, chunk_x, chunk_z): key = (chunk_x, chunk_z) if key in self.chunks: return self.chunks[key] chunk = Chunk(chunk_x, chunk_z, self.world) chunk.generate_terrain(self.generator) chunk.populate(self.generator) self.chunks[key] = chunk return chunk def unload_chunk(self, chunk_x, chunk_z): key = (chunk_x, chunk_z) if key in self.chunks: del self.chunks[key] def update_loaded_chunks(self, player_pos, render_distance, simulation_distance): player_chunk_x = int(player_pos[0]) // 16 player_chunk_z = int(player_pos[2]) // 16 chunks_to_load = [] for dx in range(-render_distance, render_distance + 1): for dz in range(-render_distance, render_distance + 1): chunk_x = player_chunk_x + dx chunk_z = player_chunk_z + dz if (chunk_x, chunk_z) not in self.chunks: chunks_to_load.append((chunk_x, chunk_z)) for chunk_x, chunk_z in chunks_to_load: self.load_chunk(chunk_x, chunk_z) chunks_to_unload = [] for (chunk_x, chunk_z) in self.chunks.keys(): dx = abs(chunk_x - player_chunk_x) dz = abs(chunk_z - player_chunk_z) if dx > render_distance or dz > render_distance: chunks_to_unload.append((chunk_x, chunk_z)) for chunk_x, chunk_z in chunks_to_unload: self.unload_chunk(chunk_x, chunk_z) for (chunk_x, chunk_z), chunk in self.chunks.items(): dx = abs(chunk_x - player_chunk_x) dz = abs(chunk_z - player_chunk_z) if dx <= simulation_distance and dz <= simulation_distance: chunk.tick(0.05) ``` ## 🧱 BLOCCHI E FISICA ```python BLOCK_PROPERTIES = { AIR: {'solid': False, 'transparent': True, 'gravity': False, 'light_emission': 0}, STONE: {'solid': True, 'transparent': False, 'gravity': False, 'light_emission': 0}, DIRT: {'solid': True, 'transparent': False, 'gravity': False, 'light_emission': 0}, GRASS: {'solid': True, 'transparent': False, 'gravity': False, 'light_emission': 0}, SAND: {'solid': True, 'transparent': False, 'gravity': True, 'light_emission': 0}, GRAVEL: {'solid': True, 'transparent': False, 'gravity': True, 'light_emission': 0}, WATER: {'solid': False, 'transparent': True, 'gravity': False, 'light_emission': 0, 'fluid': True, 'viscosity': 0.8}, LAVA: {'solid': False, 'transparent': True, 'gravity': False, 'light_emission': 15, 'fluid': True, 'viscosity': 0.3}, GLOWSTONE: {'solid': True, 'transparent': False, 'gravity': False, 'light_emission': 15}, TORCH: {'solid': False, 'transparent': True, 'gravity': False, 'light_emission': 14}, } class BlockPhysics: def __init__(self, world): self.world = world self.falling_blocks = [] def tick(self): self.update_gravity_blocks() self.update_fluids() def update_gravity_blocks(self): chunks_to_check = list(self.world.chunk_manager.chunks.values()) for chunk in chunks_to_check: for x in range(16): for z in range(16): for y in range(319, 0, -1): block = chunk.blocks[x][y][z] if BLOCK_PROPERTIES.get(block, {}).get('gravity', False): block_below = chunk.blocks[x][y - 1][z] if block_below == AIR or BLOCK_PROPERTIES.get(block_below, {}).get('fluid', False): world_x = chunk.x * 16 + x world_z = chunk.z * 16 + z self.spawn_falling_block(world_x, y, world_z, block) chunk.blocks[x][y][z] = AIR def spawn_falling_block(self, x, y, z, block_type): entity = FallingBlockEntity(x + 0.5, y, z + 0.5, block_type) self.falling_blocks.append(entity) def update_fluids(self): for chunk in self.world.chunk_manager.chunks.values(): updates = [] for x in range(16): for z in range(16): for y in range(320): block = chunk.blocks[x][y][z] if BLOCK_PROPERTIES.get(block, {}).get('fluid', False): updates.append((x, y, z, block)) for x, y, z, fluid in updates: if y > 0 and chunk.blocks[x][y - 1][z] == AIR: chunk.blocks[x][y - 1][z] = fluid else: for dx, dz in [(-1, 0), (1, 0), (0, -1), (0, 1)]: nx, nz = x + dx, z + dz if 0 <= nx < 16 and 0 <= nz < 16: if chunk.blocks[nx][y][nz] == AIR: if np.random.random() < 0.3: chunk.blocks[nx][y][nz] = fluid class RedstoneSimulator: def __init__(self, world): self.world = world self.power_sources = {} self.wire_network = {} def update(self): for pos, power in list(self.power_sources.items()): self.propagate_power(pos, power) def propagate_power(self, pos, power): x, y, z = pos visited = set() queue = [(x, y, z, power)] while queue: cx, cy, cz, current_power = queue.pop(0) if (cx, cy, cz) in visited: continue visited.add((cx, cy, cz)) if current_power <= 0: continue block = self.world.get_block(cx, cy, cz) if block == REDSTONE_WIRE: for dx, dy, dz in [(1,0,0), (-1,0,0), (0,0,1), (0,0,-1)]: nx, ny, nz = cx + dx, cy + dy, cz + dz next_block = self.world.get_block(nx, ny, nz) if next_block == REDSTONE_WIRE: queue.append((nx, ny, nz, current_power - 1)) elif self.is_redstone_component(next_block): self.activate_component(nx, ny, nz, current_power) def is_redstone_component(self, block): return block in [PISTON, DISPENSER, DOOR, LAMP] def activate_component(self, x, y, z, power): block = self.world.get_block(x, y, z) if block == PISTON: self.extend_piston(x, y, z) elif block == DISPENSER: self.trigger_dispenser(x, y, z) ``` ## đŸ•šī¸ MOVIMENTO DEL PLAYER ```python class Player: def __init__(self, x, y, z): self.position = np.array([x, y, z], dtype=float) self.velocity = np.array([0.0, 0.0, 0.0]) self.acceleration = np.array([0.0, 0.0, 0.0]) self.yaw = 0.0 self.pitch = 0.0 self.on_ground = False self.in_water = False self.sprinting = False self.flying = False self.bounding_box = AABB( self.position[0] - 0.3, self.position[1], self.position[2] - 0.3, self.position[0] + 0.3, self.position[1] + 1.8, self.position[2] + 0.3 ) self.move_speed = 4.317 self.sprint_multiplier = 1.3 self.jump_velocity = 0.42 self.gravity = -0.08 self.drag = 0.91 self.water_drag = 0.8 def update(self, dt, input_vector, world): forward = input_vector['forward'] strafe = input_vector['strafe'] jump = input_vector['jump'] sprint = input_vector['sprint'] self.sprinting = sprint and forward > 0 move_direction = self.calculate_move_direction(forward, strafe) self.check_environment(world) if self.flying: self.update_flying(move_direction, jump, dt) else: self.update_walking(move_direction, jump, dt) self.apply_physics(dt) self.resolve_collisions(world) self.position += self.velocity self.update_bounding_box() def calculate_move_direction(self, forward, strafe): yaw_rad = np.radians(self.yaw) move_x = -np.sin(yaw_rad) * forward + np.cos(yaw_rad) * strafe move_z = np.cos(yaw_rad) * forward + np.sin(yaw_rad) * strafe direction = np.array([move_x, 0, move_z]) length = np.linalg.norm(direction) if length > 0: direction = direction / length return direction def update_walking(self, move_direction, jump, dt): speed = self.move_speed if self.sprinting: speed *= self.sprint_multiplier if self.in_water: speed *= 0.5 self.acceleration[0] = move_direction[0] * speed * 0.02 self.acceleration[2] = move_direction[2] * speed * 0.02 if jump: self.velocity[1] = 0.04 else: self.acceleration[0] = move_direction[0] * speed * 0.1 self.acceleration[2] = move_direction[2] * speed * 0.1 if jump and self.on_ground: self.velocity[1] = self.jump_velocity self.on_ground = False def update_flying(self, move_direction, jump, dt): pitch_rad = np.radians(self.pitch) yaw_rad = np.radians(self.yaw) fly_forward = np.array([ -np.sin(yaw_rad) * np.cos(pitch_rad), -np.sin(pitch_rad), np.cos(yaw_rad) * np.cos(pitch_rad) ]) fly_speed = 10.0 self.velocity = fly_forward * fly_speed * move_direction[2] if jump: self.velocity[1] += fly_speed * 0.5 def apply_physics(self, dt): if not self.flying: self.velocity[1] += self.gravity self.velocity += self.acceleration drag = self.water_drag if self.in_water else self.drag self.velocity[0] *= drag self.velocity[2] *= drag if abs(self.velocity[0]) < 0.001: self.velocity[0] = 0 if abs(self.velocity[2]) < 0.001: self.velocity[2] = 0 self.velocity[1] = np.clip(self.velocity[1], -3.92, 10.0) self.acceleration = np.array([0.0, 0.0, 0.0]) def resolve_collisions(self, world): future_box = AABB( self.position[0] + self.velocity[0] - 0.3, self.position[1] + self.velocity[1], self.position[2] + self.velocity[2] - 0.3, self.position[0] + self.velocity[0] + 0.3, self.position[1] + self.velocity[1] + 1.8, self.position[2] + self.velocity[2] + 0.3 ) colliding_blocks = world.get_colliding_blocks(future_box) for block_box in colliding_blocks: if self.velocity[1] < 0 and future_box.min_y < block_box.max_y: self.velocity[1] = 0 self.position[1] = block_box.max_y self.on_ground = True elif self.velocity[1] > 0 and future_box.max_y > block_box.min_y: self.velocity[1] = 0 self.position[1] = block_box.min_y - 1.8 if self.velocity[0] != 0: if future_box.max_x > block_box.min_x and future_box.min_x < block_box.max_x: self.velocity[0] = 0 if self.velocity[2] != 0: if future_box.max_z > block_box.min_z and future_box.min_z < block_box.max_z: self.velocity[2] = 0 def check_environment(self, world): feet_block = world.get_block(int(self.position[0]), int(self.position[1]), int(self.position[2])) head_block = world.get_block(int(self.position[0]), int(self.position[1] + 1), int(self.position[2])) self.in_water = (feet_block == WATER or head_block == WATER) def update_bounding_box(self): self.bounding_box = AABB( self.position[0] - 0.3, self.position[1], self.position[2] - 0.3, self.position[0] + 0.3, self.position[1] + 1.8, self.position[2] + 0.3 ) class AABB: def __init__(self, min_x, min_y, min_z, max_x, max_y, max_z): self.min_x = min_x self.min_y = min_y self.min_z = min_z self.max_x = max_x self.max_y = max_y self.max_z = max_z def intersects(self, other): return (self.min_x < other.max_x and self.max_x > other.min_x and self.min_y < other.max_y and self.max_y > other.min_y and self.min_z < other.max_z and self.max_z > other.min_z) ``` ## 🤖 IA E MOB ```python class Entity: def __init__(self, x, y, z): self.position = np.array([x, y, z], dtype=float) self.velocity = np.array([0.0, 0.0, 0.0]) self.health = 20 self.max_health = 20 self.bounding_box = None def tick(self, dt): pass class Mob(Entity): def __init__(self, x, y, z, mob_type): super().__init__(x, y, z) self.mob_type = mob_type self.ai = self.create_ai() self.target = None self.path = [] def create_ai(self): if self.mob_type == 'zombie': return ZombieAI(self) elif self.mob_type == 'skeleton': return SkeletonAI(self) elif self.mob_type == 'cow': return PassiveAI(self) def tick(self, dt): self.ai.update(dt) if self.path: self.follow_path(dt) self.apply_gravity(dt) self.position += self.velocity class BehaviorTree: def __init__(self, root): self.root = root def tick(self): return self.root.execute() class Selector: def __init__(self,