diff options
Diffstat (limited to 'examples')
| -rw-r--r-- | examples/games/zen_craft/assets/dirt.png | bin | 0 -> 3831 bytes | |||
| -rw-r--r-- | examples/games/zen_craft/assets/grass.png | bin | 0 -> 4052 bytes | |||
| -rw-r--r-- | examples/games/zen_craft/assets/grass_side.png | bin | 0 -> 4052 bytes | |||
| -rw-r--r-- | examples/games/zen_craft/assets/moon.png | bin | 0 -> 313 bytes | |||
| -rw-r--r-- | examples/games/zen_craft/assets/stone.png | bin | 0 -> 3254 bytes | |||
| -rw-r--r-- | examples/games/zen_craft/assets/sun.png | bin | 0 -> 266 bytes | |||
| -rw-r--r-- | examples/games/zen_craft/main.zc | 523 |
7 files changed, 523 insertions, 0 deletions
diff --git a/examples/games/zen_craft/assets/dirt.png b/examples/games/zen_craft/assets/dirt.png Binary files differnew file mode 100644 index 0000000..1e94b29 --- /dev/null +++ b/examples/games/zen_craft/assets/dirt.png diff --git a/examples/games/zen_craft/assets/grass.png b/examples/games/zen_craft/assets/grass.png Binary files differnew file mode 100644 index 0000000..178ae60 --- /dev/null +++ b/examples/games/zen_craft/assets/grass.png diff --git a/examples/games/zen_craft/assets/grass_side.png b/examples/games/zen_craft/assets/grass_side.png Binary files differnew file mode 100644 index 0000000..178ae60 --- /dev/null +++ b/examples/games/zen_craft/assets/grass_side.png diff --git a/examples/games/zen_craft/assets/moon.png b/examples/games/zen_craft/assets/moon.png Binary files differnew file mode 100644 index 0000000..e884c4e --- /dev/null +++ b/examples/games/zen_craft/assets/moon.png diff --git a/examples/games/zen_craft/assets/stone.png b/examples/games/zen_craft/assets/stone.png Binary files differnew file mode 100644 index 0000000..4db9a2d --- /dev/null +++ b/examples/games/zen_craft/assets/stone.png diff --git a/examples/games/zen_craft/assets/sun.png b/examples/games/zen_craft/assets/sun.png Binary files differnew file mode 100644 index 0000000..288f766 --- /dev/null +++ b/examples/games/zen_craft/assets/sun.png diff --git a/examples/games/zen_craft/main.zc b/examples/games/zen_craft/main.zc new file mode 100644 index 0000000..7f86c19 --- /dev/null +++ b/examples/games/zen_craft/main.zc @@ -0,0 +1,523 @@ +//> link: -lraylib -lm + +import "raylib.h" as rl; +import "math.h"; + +const SCREEN_WIDTH = 800; +const SCREEN_HEIGHT = 450; +const TITLE = "Zen Craft"; + +// Block Types (Enum) +const BLOCK_AIR = 0; +const BLOCK_DIRT = 1; +const BLOCK_GRASS = 2; +const BLOCK_STONE = 3; + +// Physics Constants +const GRAVITY = 18.0; +const JUMP_FORCE = 8.0; +const MOVE_SPEED = 5.0; + +// ** World / Chunk ** + +struct Chunk { + blocks: int[16][16][16]; // x, y, z + x: int; + z: int; +} + +extern fn GenImagePerlinNoise(width: int, height: int, offsetX: int, offsetY: int, scale: float) -> rl::Image; + +impl Chunk { + static fn new(x: int, z: int) -> Chunk { + var c: Chunk; + c.x = x; + c.z = z; + + // Generate Heightmap + var heights: int[16][16]; + + raw { + Image noise = GenImagePerlinNoise(16, 16, x * 16, z * 16, 0.5f); + + // Use LoadImageColors to safely get pixel data regardless of format + Color* pixels = LoadImageColors(noise); + + for (int i = 0; i < 16; i++) { + for (int j = 0; j < 16; j++) { + // pixels is row-major: y * width + x + // we want x=i, z=j. z is y in image. + Color col = pixels[j * 16 + i]; + int val = col.r; // Grayscale, so r=g=b + + // Map 0..255 to height 3..15 + int h = 3 + (val / 20); + if (h > 15) h = 15; + heights[i][j] = h; + } + } + UnloadImageColors(pixels); + UnloadImage(noise); + } + + for cx in 0..16 { + for cz in 0..16 { + var h = heights[cx][cz]; + for cy in 0..16 { + if cy < h - 1 { + c.blocks[cx][cy][cz] = BLOCK_STONE; // Stone base + } else if cy < h { + c.blocks[cx][cy][cz] = BLOCK_DIRT; // Dirt layer + } else if cy == h { + c.blocks[cx][cy][cz] = BLOCK_GRASS; // Grass top + } else { + c.blocks[cx][cy][cz] = BLOCK_AIR; + } + } + } + } + return c; + } + + fn draw(self) { + for x in 0..16 { + for y in 0..16 { + for z in 0..16 { + var btype = self.blocks[x][y][z]; + if btype != BLOCK_AIR { + var _world_x = (float)(self.x * 16 + x); + var _world_y = (float)y; + var _world_z = (float)(self.z * 16 + z); + match btype { + BLOCK_DIRT => rl::DrawModel(model_dirt, rl::Vector3{x: _world_x + 0.5, y: _world_y + 0.5, z: _world_z + 0.5}, 1.0, rl::WHITE), + BLOCK_STONE => rl::DrawModel(model_stone, rl::Vector3{x: _world_x + 0.5, y: _world_y + 0.5, z: _world_z + 0.5}, 1.0, rl::WHITE), + BLOCK_GRASS => rl::DrawModel(model_grass, rl::Vector3{x: _world_x + 0.5, y: _world_y + 0.5, z: _world_z + 0.5}, 1.0, rl::WHITE), + _ => rl::DrawCube(rl::Vector3{x: _world_x + 0.5, y: _world_y + 0.5, z: _world_z + 0.5}, 1.0, 1.0, 1.0, rl::PINK) + } + } + } + } + } + } +} + +// Global helpers for multi-chunk operations +fn get_block(chunks: Chunk*, num_chunks: int, x: int, y: int, z: int) -> int { + // Check bounds (0 to 32 for x and z, 0 to 16 for y) + if y < 0 || y >= 16 { return BLOCK_AIR; } + if x < 0 || x >= 32 { return BLOCK_AIR; } + if z < 0 || z >= 32 { return BLOCK_AIR; } + + // Determine chunk coordinates + var cx = x / 16; + var cz = z / 16; + + // Determine local block coordinates + var lx = x % 16; + var lz = z % 16; + + // Find the right chunk + for i in 0..num_chunks { + if chunks[i].x == cx && chunks[i].z == cz { + return chunks[i].blocks[lx][y][lz]; + } + } + + return BLOCK_AIR; +} + +fn set_block(chunks: Chunk*, num_chunks: int, x: int, y: int, z: int, type: int) { + if y < 0 || y >= 16 { return; } + + var cx = x / 16; + var cz = z / 16; + var lx = x % 16; + var lz = z % 16; + + for i in 0..num_chunks { + if chunks[i].x == cx && chunks[i].z == cz { + chunks[i].blocks[lx][y][lz] = type; + return; + } + } +} + +struct RaycastHit { + hit: int; + x: int; + y: int; + z: int; + face_x: int; + face_y: int; + face_z: int; +} + +fn raycast(chunks: Chunk*, num_chunks: int, ox: float, oy: float, oz: float, dx: float, dy: float, dz: float, max_dist: float) -> RaycastHit { + var r: RaycastHit; + r.hit = 0; + + var x = ox; + var y = oy; + var z = oz; + + var step = 0.05; + var dist = 0.0; + + var last_x = (int)floor(x); + var last_y = (int)floor(y); + var last_z = (int)floor(z); + + while dist < max_dist { + x += dx * step; + y += dy * step; + z += dz * step; + dist += step; + + var ix = (int)floor(x); + var iy = (int)floor(y); + var iz = (int)floor(z); + + if get_block(chunks, num_chunks, ix, iy, iz) != BLOCK_AIR { + r.hit = 1; + r.x = ix; + r.y = iy; + r.z = iz; + + // Determine face + r.face_x = 0; + r.face_y = 0; + r.face_z = 0; + + if ix > last_x { r.face_x = -1; } + else if ix < last_x { r.face_x = 1; } + else if iy > last_y { r.face_y = -1; } + else if iy < last_y { r.face_y = 1; } + else if iz > last_z { r.face_z = -1; } + else if iz < last_z { r.face_z = 1; } + + return r; + } + + last_x = ix; + last_y = iy; + last_z = iz; + } + + return r; +} + +// Raylib Externs +extern fn DrawCubeTexture(texture: rl::Texture2D, position: rl::Vector3, width: float, height: float, length: float, color: rl::Color); + +// Textures & Models +var model_dirt: rl::Model; +var model_grass: rl::Model; +var model_stone: rl::Model; +var tex_sun: rl::Texture2D; +var tex_moon: rl::Texture2D; + +fn load_texture_from_memory(_data: char*, _len: int) -> rl::Texture2D { + var tex: rl::Texture2D; + raw { + Image img = LoadImageFromMemory(".png", (unsigned char*)_data, _len); + tex = LoadTextureFromImage(img); + UnloadImage(img); + } + return tex; +} + +fn init_textures() { + var dirt_data = embed "examples/games/zen_craft/assets/dirt.png"; + var grass_data = embed "examples/games/zen_craft/assets/grass_side.png"; + var stone_data = embed "examples/games/zen_craft/assets/stone.png"; + var sun_data = embed "examples/games/zen_craft/assets/sun.png"; + var moon_data = embed "examples/games/zen_craft/assets/moon.png"; + + var _tex_dirt = load_texture_from_memory(dirt_data.data, dirt_data.len); + var _tex_grass = load_texture_from_memory(grass_data.data, grass_data.len); + var _tex_stone = load_texture_from_memory(stone_data.data, stone_data.len); + tex_sun = load_texture_from_memory(sun_data.data, sun_data.len); + tex_moon = load_texture_from_memory(moon_data.data, moon_data.len); + + // Create Models + var mesh = rl::GenMeshCube(1.0, 1.0, 1.0); + model_dirt = rl::LoadModelFromMesh(mesh); + model_grass = rl::LoadModelFromMesh(mesh); + model_stone = rl::LoadModelFromMesh(mesh); + + raw { + model_dirt.materials[0].maps[MATERIAL_MAP_DIFFUSE].texture = _tex_dirt; + model_grass.materials[0].maps[MATERIAL_MAP_DIFFUSE].texture = _tex_grass; + model_stone.materials[0].maps[MATERIAL_MAP_DIFFUSE].texture = _tex_stone; + } +} + +fn draw_sky(camera: rl::Camera3D) { + // Disable Depth Mask to draw sky behind everything + rl::BeginMode3D(camera); + // Simple Fixed Sun Position or billboard + var sun_pos = rl::Vector3{x: camera.position.x + 20.0, y: camera.position.y + 40.0, z: camera.position.z + 20.0}; + + raw { + DrawBillboard(camera, tex_sun, sun_pos, 10.0f, WHITE); + } + rl::EndMode3D(); +} + +struct Player { + x: float; + y: float; + z: float; + vx: float; + vy: float; + vz: float; + yaw: float; + pitch: float; + camera: rl::Camera3D; +} + +fn check_collision(chunks: Chunk*, num_chunks: int, x: float, y: float, z: float, w: float, h: float) -> int { + var min_x = (int)floor(x - w/2.0); + var max_x = (int)floor(x + w/2.0); + var min_y = (int)floor(y); + var max_y = (int)floor(y + h); + var min_z = (int)floor(z - w/2.0); + var max_z = (int)floor(z + w/2.0); + + for ix in min_x..max_x+1 { + for iy in min_y..max_y+1 { + for iz in min_z..max_z+1 { + if get_block(chunks, num_chunks, ix, iy, iz) != BLOCK_AIR { + return 1; + } + } + } + } + return 0; +} + +impl Player { + static fn new(x: float, y: float, z: float) -> Player { + var p: Player; + p.x = x; + p.y = y; + p.z = z; + p.vx = 0.0; + p.vy = 0.0; + p.vz = 0.0; + p.yaw = 0.0; + p.pitch = 0.0; + + p.camera = rl::Camera3D{ + position: rl::Vector3{x: x, y: y, z: z}, + target: rl::Vector3{x: x+1.0, y: y, z: z}, + up: rl::Vector3{x: 0.0, y: 1.0, z: 0.0}, + fovy: 60.0, + projection: rl::CAMERA_PERSPECTIVE + }; + return p; + } + + fn update(self, chunks: Chunk*, num_chunks: int, dt: float) { + // Mouse look + var mouse_delta = rl::GetMouseDelta(); + self.yaw += mouse_delta.x * 0.003; + self.pitch += mouse_delta.y * 0.003; + + if self.pitch > 1.5 { self.pitch = 1.5; } + if self.pitch < -1.5 { self.pitch = -1.5; } + + // Movement input + var dx = 0.0; + var dz = 0.0; + if rl::IsKeyDown(rl::KEY_W) { dx += 1.0; } + if rl::IsKeyDown(rl::KEY_S) { dx -= 1.0; } + if rl::IsKeyDown(rl::KEY_A) { dz -= 1.0; } + if rl::IsKeyDown(rl::KEY_D) { dz += 1.0; } + + var fx = cos(self.yaw); + var fz = sin(self.yaw); + var rx = -sin(self.yaw); + var rz = cos(self.yaw); + + var move_x = fx * dx + rx * dz; + var move_z = fz * dx + rz * dz; + + var mlen = sqrt(move_x*move_x + move_z*move_z); + if mlen > 0.001 { + move_x /= mlen; + move_z /= mlen; + self.vx = move_x * MOVE_SPEED; + self.vz = move_z * MOVE_SPEED; + } else { + self.vx = 0.0; + self.vz = 0.0; + } + + // Gravity and jump + self.vy -= (float)GRAVITY * dt; + + if rl::IsKeyPressed(rl::KEY_SPACE) { + // Only jump if on ground (check slightly below) + if check_collision(chunks, num_chunks, self.x, self.y - 0.1, self.z, 0.6, 1.8) { + self.vy = JUMP_FORCE; + } + } + + // Physics integration (Separate Axes) + var box_w = 0.6; + var box_h = 1.8; + + // X axis + self.x += self.vx * dt; + if check_collision(chunks, num_chunks, self.x, self.y, self.z, box_w, box_h) { + self.x -= self.vx * dt; // Revert + self.vx = 0.0; + } + + // Z axis + self.z += self.vz * dt; + if check_collision(chunks, num_chunks, self.x, self.y, self.z, box_w, box_h) { + self.z -= self.vz * dt; // Revert + self.vz = 0.0; + } + + // Y axis + self.y += self.vy * dt; + if check_collision(chunks, num_chunks, self.x, self.y, self.z, box_w, box_h) { + self.y -= self.vy * dt; // Revert + self.vy = 0.0; + } + + // Check World Floor (Void Safety) + if self.y < -10.0 { + self.y = 20.0; + self.vy = 0.0; + } + + // Sync Camera + var view_x = cos(self.yaw) * cos(self.pitch); + var view_z = sin(self.yaw) * cos(self.pitch); + var view_y = -sin(self.pitch); + + self.camera.position.x = self.x; + self.camera.position.y = self.y + 1.6; + self.camera.position.z = self.z; + + self.camera.target.x = self.camera.position.x + view_x; + self.camera.target.y = self.camera.position.y + view_y; + self.camera.target.z = self.camera.position.z + view_z; + } +} + +fn main() { + rl::InitWindow(SCREEN_WIDTH, SCREEN_HEIGHT, TITLE); + rl::SetWindowState(rl::FLAG_WINDOW_RESIZABLE); + defer rl::CloseWindow(); + + rl::SetTargetFPS(60); + + // Load Assets + init_textures(); + + // Initialize Player + var p = Player::new(10.0, 10.0, 10.0); + var selected_block = BLOCK_STONE; + + // Generate Chunks + var chunks: Chunk[4]; + chunks[0] = Chunk::new(0, 0); + chunks[1] = Chunk::new(1, 0); + chunks[2] = Chunk::new(0, 1); + chunks[3] = Chunk::new(1, 1); + + rl::DisableCursor(); // Capture mouse + + while !rl::WindowShouldClose() { + var dt: float = rl::GetFrameTime(); + (&p).update(&chunks[0], 4, dt); + + // Block selection input + if rl::IsKeyPressed(rl::KEY_ONE) { selected_block = BLOCK_DIRT; } + if rl::IsKeyPressed(rl::KEY_TWO) { selected_block = BLOCK_GRASS; } + if rl::IsKeyPressed(rl::KEY_THREE) { selected_block = BLOCK_STONE; } + + rl::BeginDrawing(); + // Sky blue background + rl::ClearBackground(rl::Color{r: 135, g: 206, b: 235, a: 255}); + + draw_sky(p.camera); + + rl::BeginMode3D(p.camera); + + // Render chunks + for i in 0..4 { + var c: Chunk* = &chunks[i]; + c.draw(); + } + + // Block interaction + var screen_center = rl::Vector2{ + x: (float)rl::GetScreenWidth() / 2.0, + y: (float)rl::GetScreenHeight() / 2.0 + }; + var pick_ray = rl::GetScreenToWorldRay(screen_center, p.camera); + + var hit = raycast(&chunks[0], 4, pick_ray.position.x, pick_ray.position.y, pick_ray.position.z, + pick_ray.direction.x, pick_ray.direction.y, pick_ray.direction.z, 5.0); + + if hit.hit != 0 { + // Draw selection + var hx = (float)hit.x; + var hy = (float)hit.y; + var hz = (float)hit.z; + + // Draw slight offset wireframe + rl::DrawCubeWires(rl::Vector3{x: hx + 0.5, y: hy + 0.5, z: hz + 0.5}, 1.01, 1.01, 1.01, rl::BLACK); + + // Input handling + if rl::IsMouseButtonPressed(rl::MOUSE_BUTTON_LEFT) { + set_block(&chunks[0], 4, hit.x, hit.y, hit.z, BLOCK_AIR); + } + else if rl::IsMouseButtonPressed(rl::MOUSE_BUTTON_RIGHT) { + var nx = hit.x + hit.face_x; + var ny = hit.y + hit.face_y; + var nz = hit.z + hit.face_z; + + // Don't place inside player (Simple check) + var dist_x = p.x - ((float)nx + 0.5); + var dist_y = p.y + 1.6 - ((float)ny + 0.5); // Eye pos vs block center + var dist_z = p.z - ((float)nz + 0.5); + if (dist_x*dist_x + dist_y*dist_y + dist_z*dist_z) > 2.0 { + set_block(&chunks[0], 4, nx, ny, nz, selected_block); + } + } + } + + rl::EndMode3D(); + + // Draw HUD + var sw = rl::GetScreenWidth(); + var sh = rl::GetScreenHeight(); + rl::DrawRectangle(sw/2 - 5, sh/2 - 1, 10, 2, rl::RED); // Horizontal + rl::DrawRectangle(sw/2 - 1, sh/2 - 5, 2, 10, rl::RED); // Vertical + + rl::DrawText("Chicken Jockey", 10, 10, 20, rl::DARKGRAY); + rl::DrawText("WASD+Mouse: Move | Click: Break/Place", 10, 30, 20, rl::DARKGRAY); + rl::DrawText("1: Dirt | 2: Grass | 3: Stone", 10, 50, 20, rl::DARKGRAY); + + // Show selected + rl::DrawText("Selected:", 10, 70, 20, rl::BLACK); + + match selected_block { + BLOCK_DIRT => rl::DrawText("DIRT", 110, 70, 20, rl::BROWN), + BLOCK_GRASS => rl::DrawText("GRASS", 110, 70, 20, rl::GREEN), + BLOCK_STONE => rl::DrawText("STONE", 110, 70, 20, rl::GRAY), + _ => {} + } + + rl::DrawFPS(10, 100); + + rl::EndDrawing(); + } +}
\ No newline at end of file |
