24) { $height = 24; } if (world_is_river($x, $z)) { $height -= 5; if ($height < 1) { $height = 1; } elseif ($height > WATER_LEVEL - 1) { $height = WATER_LEVEL - 1; } } return $height; } function world_is_steep(int $x, int $z, int $height): bool { $n1 = world_height_at($x + 1, $z); $n2 = world_height_at($x - 1, $z); $n3 = world_height_at($x, $z + 1); $n4 = world_height_at($x, $z - 1); return $height - min($n1, $n2, $n3, $n4) >= 2; } function world_has_tree(int $x, int $z): bool { if (world_is_river($x, $z)) { return false; } if (($x % 13) !== 0 || ($z % 13) !== 0) { return false; } return demo_hash2($x, $z) > 0.72; } function world_plant_type_at(int $x, int $z): int { if (world_is_river($x, $z)) { return 0; } $flowerNoise = demo_noise2($x + 93, -$z - 17, 0.17); if ($flowerNoise > 0.46) { $pick = (int) floor(demo_hash2($x, $z) * 6.0); if ($pick === 0) { return BLOCK_YELLOW_FLOWER; } if ($pick === 1) { return BLOCK_RED_FLOWER; } if ($pick === 2) { return BLOCK_PURPLE_FLOWER; } if ($pick === 3) { return BLOCK_SUN_FLOWER; } if ($pick === 4) { return BLOCK_WHITE_FLOWER; } return BLOCK_BLUE_FLOWER; } $grassNoise = demo_noise2(-$x, $z, 0.31); if ($grassNoise > 0.24 || demo_hash2($x + 11, $z - 19) > 0.70) { return BLOCK_TALL_GRASS; } return 0; } function world_has_cloud(int $x, int $y, int $z): bool { if ($y < CLOUD_MIN_Y || $y > CLOUD_MAX_Y) { return false; } $layer = $y - CLOUD_MIN_Y; $shape = demo_noise2($x + 701, $z - 223, 0.045); $detail = demo_noise2($x * 2 + 17, $z * 2 - 31, 0.12) * 0.25; $threshold = $layer === 1 ? 0.46 : 0.58; return $shape + $detail > $threshold; } function block_type_at(int $x, int $y, int $z): int { if ($y < 0 || $y > MAX_Y) { return 0; } if (world_has_cloud($x, $y, $z)) { return BLOCK_CLOUD; } $height = world_height_at($x, $z); $river = world_is_river($x, $z); if ($river && $y === WATER_LEVEL) { return BLOCK_WATER; } if ($y <= $height) { if ($y === $height) { if ($river || $height <= WATER_LEVEL) { return BLOCK_SAND; } if ($height >= 20) { return BLOCK_SNOW; } if (world_is_steep($x, $z, $height)) { return BLOCK_STONE; } return BLOCK_GRASS; } if ($height >= 17 && $y >= $height - 3) { return BLOCK_STONE; } return $y >= $height - 2 ? BLOCK_DIRT : BLOCK_STONE; } if ($y === $height + 1) { return world_plant_type_at($x, $z); } for ($tx = $x - 3; $tx <= $x + 3; $tx++) { for ($tz = $z - 3; $tz <= $z + 3; $tz++) { if (!world_has_tree($tx, $tz)) { continue; } $base = world_height_at($tx, $tz) + 1; if ($x === $tx && $z === $tz && $y >= $base && $y <= $base + 5) { return BLOCK_WOOD; } $dx = $x - $tx; $dz = $z - $tz; $dy = $y - ($base + 4); if ($y >= $base + 2 && $y <= $base + 6 && ($dx * $dx + $dz * $dz + $dy * $dy) <= 10) { return BLOCK_LEAVES; } } } return 0; } function floor_chunk(int $value): int { if ($value >= 0) { return intdiv($value, CHUNK_SIZE); } return -intdiv(-$value + CHUNK_SIZE - 1, CHUNK_SIZE); } function chunk_key(int $chunkX, int $chunkZ): string { return (string) $chunkX . ':' . (string) $chunkZ; } function sort_pending_chunks(array $pendingChunks, int $centerChunkX, int $centerChunkZ): array { uasort($pendingChunks, static function (array $a, array $b) use ($centerChunkX, $centerChunkZ): int { $adx = (int) $a[0] - $centerChunkX; $adz = (int) $a[1] - $centerChunkZ; $bdx = (int) $b[0] - $centerChunkX; $bdz = (int) $b[1] - $centerChunkZ; $da = $adx * $adx + $adz * $adz; $db = $bdx * $bdx + $bdz * $bdz; return $da <=> $db; }); return $pendingChunks; } function generate_chunk(int $chunkX, int $chunkZ): int { $startX = $chunkX * CHUNK_SIZE; $startZ = $chunkZ * CHUNK_SIZE; $endX = $startX + CHUNK_SIZE - 1; $endZ = $startZ + CHUNK_SIZE - 1; $count = 0; craft_begin_chunk($chunkX, $chunkZ); for ($x = $startX; $x <= $endX; $x++) { if ($x < -WORLD_RADIUS || $x > WORLD_RADIUS) { continue; } for ($z = $startZ; $z <= $endZ; $z++) { if ($z < -WORLD_RADIUS || $z > WORLD_RADIUS) { continue; } for ($y = 0; $y <= MAX_Y; $y++) { $type = block_type_at($x, $y, $z); if ($type !== 0) { craft_set_chunk_block($x, $y, $z, $type); $count++; } } } } craft_commit_chunk($chunkX, $chunkZ); return $count; } function enqueue_visible_chunks(int $centerChunkX, int $centerChunkZ, array $loadedChunks, array $pendingChunks): array { $needed = []; $queued = 0; for ($cx = $centerChunkX - RENDER_CHUNK_RADIUS; $cx <= $centerChunkX + RENDER_CHUNK_RADIUS; $cx++) { for ($cz = $centerChunkZ - RENDER_CHUNK_RADIUS; $cz <= $centerChunkZ + RENDER_CHUNK_RADIUS; $cz++) { $chunkKey = chunk_key($cx, $cz); $needed[$chunkKey] = 1; if (!isset($loadedChunks[$chunkKey]) && !isset($pendingChunks[$chunkKey])) { $pendingChunks[$chunkKey] = [$cx, $cz]; $queued++; } } } return $queued > 0 ? sort_pending_chunks($pendingChunks, $centerChunkX, $centerChunkZ) : $pendingChunks; } function unload_far_chunks(int $centerChunkX, int $centerChunkZ, array $loadedChunks): array { $removed = 0; foreach ($loadedChunks as $loadedKey => $chunk) { $dx = abs((int) $chunk[0] - $centerChunkX); $dz = abs((int) $chunk[1] - $centerChunkZ); if ($dx > KEEP_CHUNK_RADIUS || $dz > KEEP_CHUNK_RADIUS) { craft_remove_chunk((int) $chunk[0], (int) $chunk[1]); unset($loadedChunks[$loadedKey]); $removed++; } } return $loadedChunks; } function process_chunk_queue(array $loadedChunks, array $pendingChunks, int $maxChunks): array { $loaded = 0; foreach ($pendingChunks as $pendingKey => $chunk) { generate_chunk((int) $chunk[0], (int) $chunk[1]); $loadedChunks[$pendingKey] = [(int) $chunk[0], (int) $chunk[1]]; unset($pendingChunks[$pendingKey]); $loaded++; if ($loaded >= $maxChunks) { break; } } return [$loadedChunks, $pendingChunks]; } function main(): void { if (!craft_init('TypePHP Minecraft Demo - Craft/OpenGL', 1280, 720, 'examples/minecraft-demo/textures/texture.png')) { echo "OpenGL 初始化失败\n"; return; } craft_set_sky_texture('examples/minecraft-demo/textures/sky.png'); $x = 8.0; $z = 18.0; $y = (float) world_height_at((int) $x, (int) $z) + 2.4; $yaw = -2.55; $pitch = 0.35; $last = craft_get_time(); $escapeWasDown = false; $centerChunkX = floor_chunk((int) floor($x)); $centerChunkZ = floor_chunk((int) floor($z)); $loadedChunks = []; $pendingChunks = []; craft_begin_world(); $pendingChunks = enqueue_visible_chunks($centerChunkX, $centerChunkZ, $loadedChunks, $pendingChunks); $initialTotal = count($pendingChunks); $initialDone = 0; craft_render_loading($initialDone, $initialTotal); while (count($pendingChunks) > 0) { craft_poll_events(); $before = count($pendingChunks); $queueState = process_chunk_queue($loadedChunks, $pendingChunks, 1); $loadedChunks = $queueState[0]; $pendingChunks = $queueState[1]; $initialDone += $before - count($pendingChunks); craft_render_loading($initialDone, $initialTotal); } while (!craft_should_close()) { $now = craft_get_time(); $dt = max(0.001, min(0.05, $now - $last)); $last = $now; craft_poll_events(); $escapeDown = craft_key_pressed(KEY_ESCAPE); if ($escapeDown && !$escapeWasDown) { if (craft_confirm_exit()) { break; } $last = craft_get_time(); } $escapeWasDown = $escapeDown; $yaw -= craft_mouse_delta_x() * 0.0022; $pitch += craft_mouse_delta_y() * 0.0022; $pitch = max(-1.45, min(1.45, $pitch)); $speed = craft_key_pressed(KEY_SHIFT) ? 14.0 : 7.0; $forwardX = -sin($yaw); $forwardZ = -cos($yaw); $rightX = cos($yaw); $rightZ = -sin($yaw); if (craft_key_pressed(KEY_W)) { $x += $forwardX * $speed * $dt; $z += $forwardZ * $speed * $dt; } if (craft_key_pressed(KEY_S)) { $x -= $forwardX * $speed * $dt; $z -= $forwardZ * $speed * $dt; } if (craft_key_pressed(KEY_D)) { $x += $rightX * $speed * $dt; $z += $rightZ * $speed * $dt; } if (craft_key_pressed(KEY_A)) { $x -= $rightX * $speed * $dt; $z -= $rightZ * $speed * $dt; } if (craft_key_pressed(KEY_SPACE)) { $y += $speed * $dt; } if (craft_key_pressed(KEY_SHIFT)) { $y -= $speed * $dt * 0.6; } $currentChunkX = floor_chunk((int) floor($x)); $currentChunkZ = floor_chunk((int) floor($z)); if ($currentChunkX !== $centerChunkX || $currentChunkZ !== $centerChunkZ) { $centerChunkX = $currentChunkX; $centerChunkZ = $currentChunkZ; $pendingChunks = enqueue_visible_chunks($centerChunkX, $centerChunkZ, $loadedChunks, $pendingChunks); $loadedChunks = unload_far_chunks($centerChunkX, $centerChunkZ, $loadedChunks); $last = craft_get_time(); } $queueState = process_chunk_queue($loadedChunks, $pendingChunks, CHUNKS_PER_FRAME); $loadedChunks = $queueState[0]; $pendingChunks = $queueState[1]; craft_set_camera($x, $y, $z, $yaw, $pitch); craft_render_frame(); craft_sleep(1); } craft_shutdown(); }