mirror of
https://github.com/Not-Nik/raylib-zig.git
synced 2025-09-08 19:47:28 +00:00
Core 2D Camera Platformer Example (#222)
* Core 2D Camera Platformer Example * Use decl literals for enums in platformer example
This commit is contained in:
parent
8d5a7d382c
commit
dfe22275cf
@ -137,6 +137,11 @@ pub fn build(b: *std.Build) !void {
|
||||
.path = "examples/core/2d_camera.zig",
|
||||
.desc = "Shows the functionality of a 2D camera",
|
||||
},
|
||||
.{
|
||||
.name = "2d_camera_platformer",
|
||||
.path = "examples/core/2d_camera_platformer.zig",
|
||||
.desc = "2D camera platformer",
|
||||
},
|
||||
.{
|
||||
.name = "3d_camera_first_person",
|
||||
.path = "examples/core/3d_camera_first_person.zig",
|
||||
|
334
examples/core/2d_camera_platformer.zig
Normal file
334
examples/core/2d_camera_platformer.zig
Normal file
@ -0,0 +1,334 @@
|
||||
// raylib-zig (c) Axel Magnuson 2025
|
||||
//
|
||||
// This is a fairly close 1-1 copy of the original example from raylib, and
|
||||
// thus might not represent completely idiomatic or clean zig.
|
||||
|
||||
const rl = @import("raylib");
|
||||
const rm = @import("raymath");
|
||||
|
||||
const Rect = rl.Rectangle;
|
||||
const Vec2 = rl.Vector2;
|
||||
const Color = rl.Color;
|
||||
const Camera2D = rl.Camera2D;
|
||||
|
||||
const CameraUpdater = *const fn (
|
||||
camera: *Camera2D,
|
||||
player: *Player,
|
||||
env_items: []EnvItem,
|
||||
delta: f32,
|
||||
width: i32,
|
||||
height: i32,
|
||||
) void;
|
||||
|
||||
const G: i32 = 400;
|
||||
const PLAYER_JUMP_SPD: f32 = 350;
|
||||
const PLAYER_HOR_SPD: f32 = 200;
|
||||
|
||||
const Player = struct {
|
||||
can_jump: bool,
|
||||
speed: f32,
|
||||
position: rl.Vector2,
|
||||
};
|
||||
|
||||
const EnvItem = struct {
|
||||
blocking: bool,
|
||||
rect: rl.Rectangle,
|
||||
color: rl.Color,
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------------
|
||||
// Program main entry point
|
||||
//------------------------------------------------------------------------------------
|
||||
pub fn main() anyerror!void {
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
const screen_width = 800;
|
||||
const screen_height = 450;
|
||||
|
||||
rl.initWindow(screen_width, screen_height, "raylib [core] example - 2d camera");
|
||||
defer rl.closeWindow(); // Close window and OpenGL context
|
||||
|
||||
var player: Player = .{ .can_jump = false, .speed = 0, .position = Vec2.init(400, 280) };
|
||||
var env_items = [_]EnvItem{
|
||||
.{ .rect = Rect.init(0, 0, 1000, 400), .blocking = false, .color = .light_gray },
|
||||
.{ .rect = Rect.init(0, 400, 1000, 200), .blocking = true, .color = .gray },
|
||||
.{ .rect = Rect.init(300, 200, 400, 10), .blocking = true, .color = .gray },
|
||||
.{ .rect = Rect.init(250, 300, 100, 10), .blocking = true, .color = .gray },
|
||||
.{ .rect = Rect.init(650, 300, 100, 10), .blocking = true, .color = .gray },
|
||||
};
|
||||
|
||||
var camera: rl.Camera2D = .{
|
||||
.target = player.position,
|
||||
.offset = Vec2.init(screen_width / 2, screen_height / 2),
|
||||
.rotation = 0,
|
||||
.zoom = 1,
|
||||
};
|
||||
|
||||
// store pointers to the multiple functions that could be used to update the camera
|
||||
const camera_updaters = [_]CameraUpdater{
|
||||
updateCameraCenter,
|
||||
updatecameraCenterInsideMap,
|
||||
updateCameraCenterSmoothFollow,
|
||||
updateCameraEvenOutOnLanding,
|
||||
updateCameraPlayerBoundsPush,
|
||||
};
|
||||
|
||||
var camera_option: usize = 0;
|
||||
|
||||
const camera_descriptions = [_][:0]const u8{
|
||||
"Follow player center",
|
||||
"Follow player center, but clamp to map edges",
|
||||
"Follow player center; smoothed",
|
||||
"Follow player center horizontally; update player center vertically after landing",
|
||||
"Player push camera on getting too close to screen edge",
|
||||
};
|
||||
|
||||
rl.setTargetFPS(60); // Set our game to run at 60 frames per second
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
// Main game loop
|
||||
while (!rl.windowShouldClose()) {
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
const delta_time = rl.getFrameTime();
|
||||
|
||||
updatePlayer(&player, &env_items, delta_time);
|
||||
|
||||
camera.zoom += rl.getMouseWheelMove() * 0.05;
|
||||
if (camera.zoom > 3) camera.zoom = 3;
|
||||
if (camera.zoom < 0.25) camera.zoom = 0.25;
|
||||
|
||||
// input: reset
|
||||
if (rl.isKeyPressed(.r)) {
|
||||
camera.zoom = 1;
|
||||
player.position = Vec2.init(400, 280);
|
||||
}
|
||||
|
||||
// input: cycle camera mode
|
||||
if (rl.isKeyPressed(.c)) {
|
||||
camera_option = (camera_option + 1) % camera_updaters.len;
|
||||
}
|
||||
|
||||
// call update camera by pointer
|
||||
camera_updaters[camera_option](
|
||||
&camera,
|
||||
&player,
|
||||
&env_items,
|
||||
delta_time,
|
||||
screen_width,
|
||||
screen_height,
|
||||
);
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
{
|
||||
rl.beginDrawing();
|
||||
defer rl.endDrawing();
|
||||
|
||||
rl.clearBackground(.light_gray);
|
||||
|
||||
{
|
||||
rl.beginMode2D(camera);
|
||||
defer rl.endMode2D();
|
||||
|
||||
for (env_items) |env_item| {
|
||||
rl.drawRectangleRec(env_item.rect, env_item.color);
|
||||
}
|
||||
|
||||
const player_rect = Rect.init(player.position.x - 20, player.position.y - 40, 40, 40);
|
||||
rl.drawRectangleRec(player_rect, .red);
|
||||
rl.drawCircleV(player.position, 5, .gold);
|
||||
}
|
||||
|
||||
rl.drawText("Controls:", 20, 20, 10, .black);
|
||||
rl.drawText("- Right/Left to move", 40, 40, 10, .dark_gray);
|
||||
// todo: controls text
|
||||
rl.drawText("- Current camera mode:", 20, 120, 10, .black);
|
||||
rl.drawText(camera_descriptions[camera_option], 40, 140, 10, .dark_gray);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------
|
||||
// Player update function
|
||||
//------------------------------------------------------------------------------------
|
||||
|
||||
fn updatePlayer(player: *Player, env_items: []EnvItem, delta: f32) void {
|
||||
if (rl.isKeyDown(.left)) player.position.x -= PLAYER_HOR_SPD * delta;
|
||||
if (rl.isKeyDown(.right)) player.position.x += PLAYER_HOR_SPD * delta;
|
||||
if (rl.isKeyDown(.space) and player.can_jump) {
|
||||
player.speed = -PLAYER_JUMP_SPD;
|
||||
player.can_jump = false;
|
||||
}
|
||||
|
||||
var hit_obstacle = false;
|
||||
for (env_items) |ei| {
|
||||
var p: *Vec2 = &player.position;
|
||||
if (ei.blocking and
|
||||
ei.rect.x <= p.x and
|
||||
ei.rect.x + ei.rect.width >= p.x and
|
||||
ei.rect.y >= p.y and
|
||||
ei.rect.y <= p.y + player.speed * delta)
|
||||
{
|
||||
hit_obstacle = true;
|
||||
player.speed = 0;
|
||||
p.y = ei.rect.y;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hit_obstacle) {
|
||||
player.position.y += player.speed * delta;
|
||||
player.speed += G * delta;
|
||||
player.can_jump = false;
|
||||
} else player.can_jump = true;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------
|
||||
// Selectable camera update functions
|
||||
//------------------------------------------------------------------------------------
|
||||
|
||||
// Follow player center
|
||||
fn updateCameraCenter(
|
||||
camera: *Camera2D,
|
||||
player: *Player,
|
||||
_: []EnvItem,
|
||||
_: f32,
|
||||
width: i32,
|
||||
height: i32,
|
||||
) void {
|
||||
const widthf: f32 = @floatFromInt(width);
|
||||
const heightf: f32 = @floatFromInt(height);
|
||||
camera.offset = Vec2.init(widthf / 2, heightf / 2);
|
||||
camera.target = player.position;
|
||||
}
|
||||
|
||||
// Follow player center, but clamp to map edges
|
||||
fn updatecameraCenterInsideMap(
|
||||
camera: *Camera2D,
|
||||
player: *Player,
|
||||
env_items: []EnvItem,
|
||||
_: f32,
|
||||
width: i32,
|
||||
height: i32,
|
||||
) void {
|
||||
const widthf: f32 = @floatFromInt(width);
|
||||
const heightf: f32 = @floatFromInt(height);
|
||||
camera.offset = Vec2.init(widthf / 2, heightf / 2);
|
||||
camera.target = player.position;
|
||||
|
||||
var min_x: f32 = 1000;
|
||||
var min_y: f32 = 1000;
|
||||
var max_x: f32 = -1000;
|
||||
var max_y: f32 = -1000;
|
||||
|
||||
for (env_items) |ei| {
|
||||
min_x = @min(ei.rect.x, min_x);
|
||||
min_y = @min(ei.rect.y, min_y);
|
||||
max_x = @max(ei.rect.x + ei.rect.width, max_x);
|
||||
max_y = @max(ei.rect.y + ei.rect.height, max_y);
|
||||
}
|
||||
|
||||
const max = rl.getWorldToScreen2D(Vec2.init(max_x, max_y), camera.*);
|
||||
const min = rl.getWorldToScreen2D(Vec2.init(min_x, min_y), camera.*);
|
||||
|
||||
if (max.x < widthf) camera.offset.x = widthf - (max.x - widthf / 2);
|
||||
if (max.y < heightf) camera.offset.y = heightf - (max.y - heightf / 2);
|
||||
if (min.x > 0) camera.offset.x = widthf / 2 - min.x;
|
||||
if (min.y > 0) camera.offset.y = heightf / 2 - min.y;
|
||||
}
|
||||
|
||||
// Follow player center; smoothed
|
||||
fn updateCameraCenterSmoothFollow(
|
||||
camera: *Camera2D,
|
||||
player: *Player,
|
||||
_: []EnvItem,
|
||||
delta: f32,
|
||||
width: i32,
|
||||
height: i32,
|
||||
) void {
|
||||
const min_speed = 30;
|
||||
const min_effect_length = 10;
|
||||
const fraction_speed = 0.8;
|
||||
|
||||
const widthf: f32 = @floatFromInt(width);
|
||||
const heightf: f32 = @floatFromInt(height);
|
||||
|
||||
camera.offset = Vec2.init(widthf / 2, heightf / 2);
|
||||
const diff = player.position.subtract(camera.target);
|
||||
const length = diff.length();
|
||||
|
||||
if (length > min_effect_length) {
|
||||
const speed = @max(fraction_speed * length, min_speed);
|
||||
camera.target = camera.target.add(diff.scale(speed * delta / length));
|
||||
}
|
||||
}
|
||||
|
||||
var evening_out: bool = false;
|
||||
var even_out_target: f32 = 0;
|
||||
|
||||
// Follow player center horizontally; update player center vertically after landing
|
||||
fn updateCameraEvenOutOnLanding(
|
||||
camera: *Camera2D,
|
||||
player: *Player,
|
||||
_: []EnvItem,
|
||||
delta: f32,
|
||||
width: i32,
|
||||
height: i32,
|
||||
) void {
|
||||
const even_out_speed = 700;
|
||||
|
||||
const widthf: f32 = @floatFromInt(width);
|
||||
const heightf: f32 = @floatFromInt(height);
|
||||
|
||||
camera.offset = Vec2.init(widthf / 2, heightf / 2);
|
||||
camera.target.x = player.position.x;
|
||||
|
||||
if (evening_out) {
|
||||
if (even_out_target > camera.target.y) {
|
||||
camera.target.y += even_out_speed * delta;
|
||||
if (camera.target.y > even_out_target) {
|
||||
camera.target.y = even_out_target;
|
||||
evening_out = false;
|
||||
}
|
||||
} else {
|
||||
camera.target.y -= even_out_speed * delta;
|
||||
if (camera.target.y < even_out_target) {
|
||||
camera.target.y = even_out_target;
|
||||
evening_out = false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (player.can_jump and player.speed == 0 and player.position.y != camera.target.y) {
|
||||
evening_out = true;
|
||||
even_out_target = player.position.y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Player push camera on getting too close to screen edge
|
||||
fn updateCameraPlayerBoundsPush(
|
||||
camera: *Camera2D,
|
||||
player: *Player,
|
||||
_: []EnvItem,
|
||||
_: f32,
|
||||
width: i32,
|
||||
height: i32,
|
||||
) void {
|
||||
const bbox = Vec2.init(0.2, 0.2);
|
||||
|
||||
const widthf: f32 = @floatFromInt(width);
|
||||
const heightf: f32 = @floatFromInt(height);
|
||||
|
||||
const bbox_world_min = rl.getScreenToWorld2D(Vec2.init((1 - bbox.x) * 0.5 * widthf, (1 - bbox.y) * 0.5 * heightf), camera.*);
|
||||
const bbox_world_max = rl.getScreenToWorld2D(Vec2.init((1 + bbox.x) * 0.5 * widthf, (1 + bbox.y) * 0.5 * heightf), camera.*);
|
||||
camera.offset = Vec2.init((1 - bbox.x) * 0.5 * widthf, (1 - bbox.y) * 0.5 * heightf);
|
||||
|
||||
if (player.position.x < bbox_world_min.x) camera.target.x = player.position.x;
|
||||
if (player.position.y < bbox_world_min.y) camera.target.y = player.position.y;
|
||||
if (player.position.x > bbox_world_max.x) camera.target.x = bbox_world_min.x + (player.position.x - bbox_world_max.x);
|
||||
if (player.position.y > bbox_world_max.y) camera.target.y = bbox_world_min.y + (player.position.y - bbox_world_max.y);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user