From be6be4390bb8cb58e98ce146ffd89e4b258aab74 Mon Sep 17 00:00:00 2001 From: Rokas Puzonas Date: Sun, 11 Feb 2024 00:41:35 +0200 Subject: [PATCH] create mace prototype --- src/main-scene.zig | 301 +++++++++++++++++++++++++++++++++++++++++++++ src/main.zig | 21 ++-- 2 files changed, 314 insertions(+), 8 deletions(-) create mode 100644 src/main-scene.zig diff --git a/src/main-scene.zig b/src/main-scene.zig new file mode 100644 index 0000000..901cbb1 --- /dev/null +++ b/src/main-scene.zig @@ -0,0 +1,301 @@ +const rl = @import("raylib"); +const std = @import("std"); +const Allocator = std.mem.Allocator; +const assert = std.debug.assert; + +const friction = 0.99; +const walkForce = 4000; +const maxSpeed = 200; +const handDistance = 50; + +const Enemy = struct { + position: rl.Vector2, +}; + +const Player = struct { + position: rl.Vector2, + velocity: rl.Vector2 = rl.Vector2.zero(), + acceleration: rl.Vector2 = rl.Vector2.zero(), + handDirection: rl.Vector2 = rl.Vector2{ .x = 1, .y = 0 } +}; + +const Rope = struct { + const maxNodes = 64; + const NodeArray = std.BoundedArray(rl.Vector2, maxNodes); + + nodePositions: NodeArray, + nodeVelocities: NodeArray, + segmentLength: f32, + springForce: f32 = 10, + + leftoverTime: f32 = 0, + friction: f32 = 0.0005, + + fn init(from: rl.Vector2, to: rl.Vector2, nodeCount: u32) Rope { + assert(nodeCount <= maxNodes); + var nodePositions = NodeArray.init(nodeCount) catch unreachable; + + const diff = to.sub(from); + const segmentLength = diff.length() / @as(f32, @floatFromInt(nodeCount)); + const segmentStep = diff.normalize().scale(segmentLength); + + nodePositions.set(0, from); + for (1..nodeCount) |i| { + const nextNode = nodePositions.get(i-1).add(segmentStep); + nodePositions.set(i, nextNode); + } + + var nodeVelocities = NodeArray.init(nodeCount) catch unreachable; + for (nodeVelocities.slice()) |*vel| { + vel.* = rl.Vector2.zero(); + } + + return Rope{ + .nodePositions = nodePositions, + .nodeVelocities = nodeVelocities, + .segmentLength = segmentLength + }; + } + + fn update(self: *Rope, dt: f32) void { + const timestep = 1.0/(60.0 * 100); + self.leftoverTime += dt; + while (self.leftoverTime >= timestep) : (self.leftoverTime -= timestep) { + self.update_step(timestep); + } + } + + fn update_step(self: *Rope, dt: f32) void { + var offsets = NodeArray.init(self.nodePositions.len) catch unreachable; + for (offsets.slice()) |*offset| { + offset.* = rl.Vector2.zero(); + } + + var accelerations = NodeArray.init(self.nodePositions.len) catch unreachable; + for (accelerations.slice()) |*acc| { + acc.* = rl.Vector2.zero(); + } + + const node_count = self.nodePositions.len; + for (1..node_count) |i| { + const node1: rl.Vector2 = self.nodePositions.get(i-1); + const node2: rl.Vector2 = self.nodePositions.get(i); + const node_diff = node2.sub(node1); + const node_dir = node_diff.normalize(); + + const rope_force = node_diff.length() - self.segmentLength; + const d = node_dir.scale(rope_force); + + offsets.set(i-1, offsets.get(i-1).add(d.scale(0.5))); + offsets.set(i , offsets.get(i ).add(d.scale(-0.5))); + + accelerations.set(i-1, accelerations.get(i-1).add(d)); + accelerations.set(i , accelerations.get(i ).add(d.scale(-1))); + } + + for (1..node_count) |i| { + const pos = &self.nodePositions.slice()[i]; + const vel = &self.nodeVelocities.slice()[i]; + const acc = accelerations.get(i); + const offset = offsets.get(i); + vel.* = vel.add(acc.scale(dt)); + vel.* = vel.scale(1 - self.friction); + pos.* = pos.add(vel.scale(dt*10000)); + pos.* = pos.add(offset.scale(0.01)); + } + } +}; + +allocator: Allocator, +prng: std.rand.DefaultPrng, + +enemyTimer: f32 = 0, +spawnedEnemiesCount: u32 = 0, +killCount: u32 = 0, +enemies: std.ArrayList(Enemy), +player: Player, +rope: Rope, + +const Self = @This(); + +pub fn init(allocator: Allocator) Self { + var playerPosition = rl.Vector2{ .x = 400, .y = 400 }; + var ropeSize: f32 = 200; + var handPosition = playerPosition.add(rl.Vector2{ .x = handDistance, .y = 0}); + + return Self { + .allocator = allocator, + .prng = std.rand.DefaultPrng.init(@bitCast(std.time.timestamp())), + .enemies = std.ArrayList(Enemy).init(allocator), + .player = .{ .position = playerPosition }, + .rope = Rope.init(handPosition, handPosition.add(.{ .x = ropeSize, .y = 0.002 }), 10) + }; +} + +pub fn reset(self: *Self) void { + self.deinit(); + self.* = Self.init(self.allocator); +} + +pub fn deinit(self: Self) void { + self.enemies.deinit(); +} + +pub fn tick(self: *Self) !void { + rl.ClearBackground(rl.BLACK); + + const dt = rl.GetFrameTime(); + var rng = self.prng.random(); + _ = rng; + var player = &self.player; + var enemies = &self.enemies; + var rope = &self.rope; + var allocator = self.allocator; + + self.enemyTimer -= dt; + // if (self.enemyTimer <= 0) { + // self.enemyTimer = 0.5 + rng.float(f32) * 2; + // self.spawnedEnemiesCount += 1; + // try enemies.append(Enemy{ + // .position = rl.Vector2.randomOnUnitCircle(rng).scale(100) + // }); + // } + + var inputDx: f32 = 0; + var inputDy: f32 = 0; + if (rl.IsKeyDown(rl.KeyboardKey.KEY_W)) { + inputDy -= 1; + } + if (rl.IsKeyDown(rl.KeyboardKey.KEY_S)) { + inputDy += 1; + } + if (rl.IsKeyDown(rl.KeyboardKey.KEY_A)) { + inputDx -= 1; + } + if (rl.IsKeyDown(rl.KeyboardKey.KEY_D)) { + inputDx += 1; + } + + var input_dir = rl.Vector2{ .x = inputDx, .y = inputDy }; + input_dir = rl.Vector2Normalize(input_dir); + if (input_dir.x != 0 or input_dir.y != 0) { + player.handDirection = input_dir; + } + + player.acceleration = input_dir; + player.acceleration = rl.Vector2Scale(player.acceleration, walkForce); + + player.velocity = rl.Vector2Add(player.velocity, rl.Vector2Scale(player.acceleration, dt)); + player.velocity = rl.Vector2ClampValue(player.velocity, 0, maxSpeed); + player.velocity = rl.Vector2Scale(player.velocity, std.math.pow(f32, (1 - friction), dt)); + + player.position = rl.Vector2Add(player.position, rl.Vector2Scale(player.velocity, dt)); + + const enemySize: f32 = 20; + const deathBallSize: f32 = 10; + const playerSize = 20; + + const enemySpeed = 100; + for (enemies.items) |*enemy| { + const toPlayer = player.position.sub(enemy.position); + if (toPlayer.length() <= playerSize + enemySize) { + self.reset(); + return; + } + + const directionToPlayer = toPlayer.normalize(); + enemy.position = enemy.position.add(directionToPlayer.scale(enemySpeed * dt)); + rl.DrawCircle( + @intFromFloat(enemy.position.x), + @intFromFloat(enemy.position.y), + enemySize, + rl.RED + ); + } + + const handPosition = player.position.add(player.handDirection.scale(handDistance)); + + const enemyCountText = try std.fmt.allocPrintZ(allocator, "{d}", .{self.spawnedEnemiesCount}); + defer allocator.free(enemyCountText); + rl.DrawText(enemyCountText, 10, 10, 24, rl.RED); + + const killCountText = try std.fmt.allocPrintZ(allocator, "{d}", .{self.killCount}); + defer allocator.free(killCountText); + rl.DrawText(killCountText, 10, 30, 24, rl.GREEN); + + rl.DrawCircle( + @intFromFloat(player.position.x), + @intFromFloat(player.position.y), + playerSize, + rl.RAYWHITE + ); + rl.DrawCircle( + @intFromFloat(handPosition.x), + @intFromFloat(handPosition.y), + 5, + rl.RAYWHITE + ); + rl.DrawLine( + @intFromFloat(player.position.x), + @intFromFloat(player.position.y), + @intFromFloat(player.position.x + player.velocity.x), + @intFromFloat(player.position.y + player.velocity.y), + rl.GREEN + ); + + rl.DrawCircle( + @intFromFloat(player.position.x + input_dir.x * maxSpeed), + @intFromFloat(player.position.y + input_dir.y * maxSpeed), + 5, + rl.GREEN + ); + + rope.nodePositions.set(0, handPosition); + + rope.update(dt); + + const rope_color = rl.PURPLE; + for (0..(rope.nodePositions.len-1)) |i| { + var node1: rl.Vector2 = rope.nodePositions.get(i); + var node2: rl.Vector2 = rope.nodePositions.get(i+1); + rl.DrawLine( + @intFromFloat(node1.x), + @intFromFloat(node1.y), + @intFromFloat(node2.x), + @intFromFloat(node2.y), + rope_color + ); + } + + for (rope.nodePositions.slice()) |node| { + rl.DrawCircle( + @intFromFloat(node.x), + @intFromFloat(node.y), + 5, + rope_color + ); + } + + const deathBallPosition = rope.nodePositions.get(rope.nodePositions.len-1); + + rl.DrawCircle( + @intFromFloat(deathBallPosition.x), + @intFromFloat(deathBallPosition.y), + deathBallSize, + rl.RED + ); + + { + var i: usize = 0; + while (i < enemies.items.len) { + const enemy = &enemies.items[i]; + const distanceToDeathBall = enemy.position.sub(deathBallPosition).length(); + if (distanceToDeathBall < enemySize + deathBallSize) { + self.killCount += 1; + _ = enemies.swapRemove(i); + continue; + } + i += 1; + } + } +} diff --git a/src/main.zig b/src/main.zig index 2378d9b..9ed0a0b 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,19 +1,24 @@ const rl = @import("raylib"); +const std = @import("std"); + +const MainScene = @import("main-scene.zig"); + +pub fn main() !void { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + var allocator = gpa.allocator(); + defer _ = gpa.deinit(); -pub fn main() void { - rl.SetConfigFlags(rl.ConfigFlags{ .FLAG_WINDOW_RESIZABLE = true }); - rl.InitWindow(800, 800, "hello world!"); rl.SetTargetFPS(60); - + rl.InitWindow(800, 800, "Step kill"); defer rl.CloseWindow(); + var scene = MainScene.init(allocator); + defer scene.deinit(); + while (!rl.WindowShouldClose()) { rl.BeginDrawing(); defer rl.EndDrawing(); - rl.ClearBackground(rl.BLACK); - rl.DrawFPS(10, 10); - - rl.DrawText("hello world!", 100, 100, 20, rl.YELLOW); + try scene.tick(); } }