Compare commits
10 Commits
ee42ccc8f3
...
f8b61d6edd
Author | SHA1 | Date | |
---|---|---|---|
f8b61d6edd | |||
f58f3d0f86 | |||
2941d9a39c | |||
f2545d27e9 | |||
79905b3546 | |||
f7e3698cb1 | |||
28b5a476bf | |||
eb305add94 | |||
70256a1547 | |||
7c73d5436d |
@ -12,11 +12,11 @@ pub fn build(b: *std.Build) void {
|
||||
.optimize = optimize,
|
||||
});
|
||||
|
||||
raylib.addTo(b, exe, target, optimize, .{});
|
||||
|
||||
const git_submodule_run = b.addSystemCommand(&.{"git", "submodule", "update", "--init", "--recursive"});
|
||||
exe.step.dependOn(&git_submodule_run.step);
|
||||
|
||||
raylib.addTo(b, exe, target, optimize, .{});
|
||||
|
||||
b.installArtifact(exe);
|
||||
|
||||
const run_cmd = b.addRunArtifact(exe);
|
||||
|
@ -2,21 +2,106 @@ const rl = @import("raylib");
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const assert = std.debug.assert;
|
||||
const PlayerInput = @import("player-input.zig");
|
||||
const UI = @import("./ui.zig");
|
||||
const PixelPerfect = @import("./pixel-perfect.zig");
|
||||
const UIBox = @import("./ui-box.zig");
|
||||
|
||||
const friction = 0.99;
|
||||
const walkForce = 4000;
|
||||
const maxSpeed = 200;
|
||||
const handDistance = 50;
|
||||
const death_ball_size: f32 = 13;
|
||||
const playerSize = 20;
|
||||
const virtualWidth = 350;
|
||||
const virtualHeight = 350;
|
||||
|
||||
const safeRadius = 500;
|
||||
const spawnAreaWidth = 400;
|
||||
const arenaRadius = safeRadius + spawnAreaWidth;
|
||||
|
||||
const shadow_offset = rl.Vector2.one().scale(8);
|
||||
|
||||
const enemyFriction = friction;
|
||||
|
||||
const Enemy = struct {
|
||||
position: rl.Vector2,
|
||||
health: f32,
|
||||
color: rl.Color,
|
||||
size: f32,
|
||||
acceleration: f32,
|
||||
maxSpeed: f32,
|
||||
|
||||
invincibility: f32 = 0,
|
||||
stunned: f32 = 0,
|
||||
velocity: rl.Vector2 = .{ .x = 0, .y = 0 },
|
||||
|
||||
fn initWeak(position: rl.Vector2) Enemy {
|
||||
return Enemy{
|
||||
.position = position,
|
||||
.health = 1.0,
|
||||
.color = rl.YELLOW,
|
||||
.size = 10.0,
|
||||
.acceleration = 5000.0,
|
||||
.maxSpeed = 200,
|
||||
};
|
||||
}
|
||||
|
||||
fn initNormal(position: rl.Vector2) Enemy {
|
||||
return Enemy{
|
||||
.position = position,
|
||||
.health = 2.0,
|
||||
.color = rl.ORANGE,
|
||||
.size = 20.0,
|
||||
.acceleration = 4000,
|
||||
.maxSpeed = 100,
|
||||
};
|
||||
}
|
||||
|
||||
fn initStrong(position: rl.Vector2) Enemy {
|
||||
return Enemy{
|
||||
.position = position,
|
||||
.health = 5.0,
|
||||
.color = rl.GOLD,
|
||||
.size = 50.0,
|
||||
.acceleration = 3000,
|
||||
.maxSpeed = 50,
|
||||
};
|
||||
}
|
||||
|
||||
fn vulnerable(self: Enemy) bool {
|
||||
return self.invincibility == 0;
|
||||
}
|
||||
|
||||
fn alive(self: Enemy) bool {
|
||||
return self.health > 0;
|
||||
}
|
||||
};
|
||||
|
||||
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 }
|
||||
handDirection: rl.Vector2 = rl.Vector2{ .x = 1, .y = 0 },
|
||||
|
||||
input: PlayerInput = PlayerInput.init(),
|
||||
|
||||
health: u32 = 3,
|
||||
maxHealth: u32 = 3,
|
||||
dead: bool = false,
|
||||
invincibility: f32 = 0,
|
||||
|
||||
fn getHandPosition(self: *const Player) rl.Vector2 {
|
||||
return self.position.add(self.handDirection.scale(handDistance));
|
||||
}
|
||||
|
||||
fn alive(self: Player) bool {
|
||||
return self.health > 0;
|
||||
}
|
||||
|
||||
fn vulnerable(self: Player) bool {
|
||||
return self.invincibility == 0;
|
||||
}
|
||||
};
|
||||
|
||||
const Rope = struct {
|
||||
@ -100,10 +185,20 @@ const Rope = struct {
|
||||
const offset = offsets.get(i);
|
||||
vel.* = vel.add(acc.scale(dt));
|
||||
vel.* = vel.scale(1 - self.friction);
|
||||
pos.* = pos.add(vel.scale(dt*10000));
|
||||
|
||||
const amplifier = @as(f32, @floatFromInt(i-1))/@as(f32, @floatFromInt(node_count-2));
|
||||
pos.* = pos.add(vel.scale(dt*10000 + amplifier*5));
|
||||
pos.* = pos.add(offset.scale(0.01));
|
||||
}
|
||||
}
|
||||
|
||||
fn lastNodePosition(self: *Rope) rl.Vector2 {
|
||||
return self.nodePositions.get(self.nodePositions.len-1);
|
||||
}
|
||||
|
||||
fn lastNodeVelocity(self: *Rope) rl.Vector2 {
|
||||
return self.nodeVelocities.get(self.nodeVelocities.len-1);
|
||||
}
|
||||
};
|
||||
|
||||
allocator: Allocator,
|
||||
@ -112,23 +207,35 @@ prng: std.rand.DefaultPrng,
|
||||
enemyTimer: f32 = 0,
|
||||
spawnedEnemiesCount: u32 = 0,
|
||||
killCount: u32 = 0,
|
||||
timePassed: f32 = 0,
|
||||
enemies: std.ArrayList(Enemy),
|
||||
player: Player,
|
||||
rope: Rope,
|
||||
ui: UI,
|
||||
should_close: bool = false,
|
||||
paused: bool = false,
|
||||
pixel_effect: PixelPerfect,
|
||||
|
||||
camera: rl.Camera2D,
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub fn init(allocator: Allocator) Self {
|
||||
var playerPosition = rl.Vector2{ .x = 400, .y = 400 };
|
||||
var playerPosition = rl.Vector2{ .x = 0, .y = 0 };
|
||||
var ropeSize: f32 = 200;
|
||||
var handPosition = playerPosition.add(rl.Vector2{ .x = handDistance, .y = 0});
|
||||
var player = Player{ .position = playerPosition };
|
||||
player.input.activate();
|
||||
|
||||
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)
|
||||
.player = player,
|
||||
.rope = Rope.init(handPosition, handPosition.add(.{ .x = ropeSize, .y = 0.002 }), 10),
|
||||
.camera = .{ .target = playerPosition },
|
||||
.ui = UI.init(),
|
||||
.pixel_effect = PixelPerfect.init(virtualWidth, virtualHeight)
|
||||
};
|
||||
}
|
||||
|
||||
@ -137,58 +244,33 @@ pub fn reset(self: *Self) void {
|
||||
self.* = Self.init(self.allocator);
|
||||
}
|
||||
|
||||
pub fn deinit(self: Self) void {
|
||||
pub fn deinit(self: *Self) void {
|
||||
self.player.input.deactivate();
|
||||
self.enemies.deinit();
|
||||
self.pixel_effect.deinit();
|
||||
}
|
||||
|
||||
pub fn tick(self: *Self) !void {
|
||||
rl.ClearBackground(rl.BLACK);
|
||||
|
||||
fn updatePlayer(self: *Self) void {
|
||||
const dt = rl.GetFrameTime();
|
||||
var rng = self.prng.random();
|
||||
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)
|
||||
});
|
||||
player.invincibility = @max(0, player.invincibility - dt);
|
||||
player.acceleration = rl.Vector2.zero();
|
||||
|
||||
if (player.alive()) {
|
||||
player.handDirection = player.input.getHandPosition();
|
||||
|
||||
const moveDir = player.input.getWalkDirection();
|
||||
player.acceleration = moveDir;
|
||||
player.acceleration = rl.Vector2Scale(player.acceleration, walkForce);
|
||||
}
|
||||
|
||||
var inputDx: f32 = 0;
|
||||
var inputDy: f32 = 0;
|
||||
if (rl.IsKeyDown(rl.KeyboardKey.KEY_W)) {
|
||||
inputDy -= 1;
|
||||
if (player.position.length() > arenaRadius) {
|
||||
const push_strength = std.math.pow(f32, player.position.length() - arenaRadius + 10, 2);
|
||||
const push_direction = player.position.normalize().neg();
|
||||
player.acceleration = player.acceleration.add(push_direction.scale(push_strength));
|
||||
}
|
||||
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 gamepad: i32 = 0;
|
||||
if (rl.IsGamepadAvailable(gamepad)) {
|
||||
inputDx += rl.GetGamepadAxisMovement(gamepad, rl.GamepadAxis.GAMEPAD_AXIS_LEFT_X);
|
||||
inputDy += rl.GetGamepadAxisMovement(gamepad, rl.GamepadAxis.GAMEPAD_AXIS_LEFT_Y);
|
||||
}
|
||||
|
||||
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);
|
||||
@ -196,38 +278,272 @@ pub fn tick(self: *Self) !void {
|
||||
|
||||
player.position = rl.Vector2Add(player.position, rl.Vector2Scale(player.velocity, dt));
|
||||
|
||||
const enemySize: f32 = 20;
|
||||
const deathBallSize: f32 = 10;
|
||||
const playerSize = 20;
|
||||
const handPosition = player.getHandPosition();
|
||||
|
||||
const enemySpeed = 100;
|
||||
for (enemies.items) |*enemy| {
|
||||
const toPlayer = player.position.sub(enemy.position);
|
||||
if (toPlayer.length() <= playerSize + enemySize) {
|
||||
self.reset();
|
||||
return;
|
||||
}
|
||||
const rope_root_node: rl.Vector2 = rope.nodePositions.get(0);
|
||||
rope.nodePositions.set(0, rope_root_node.lerp(handPosition, 0.25));
|
||||
|
||||
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
|
||||
);
|
||||
rope.update(dt);
|
||||
}
|
||||
|
||||
fn updateEnemies(self: *Self) !void {
|
||||
var rng = self.prng.random();
|
||||
const dt = rl.GetFrameTime();
|
||||
var enemies = &self.enemies;
|
||||
var player = &self.player;
|
||||
var rope = &self.rope;
|
||||
|
||||
self.enemyTimer -= dt;
|
||||
if (self.enemyTimer <= 0 and player.alive()) {
|
||||
self.enemyTimer = 0.5 + rng.float(f32) * 2;
|
||||
self.spawnedEnemiesCount += 1;
|
||||
|
||||
const spawn_distance = rng.floatNorm(f32) * spawnAreaWidth/8 + spawnAreaWidth/2 + safeRadius;
|
||||
const spawn_angle = rng.float(f32) * 2 * rl.PI;
|
||||
const enemy_position = (rl.Vector2{ .x = spawn_distance, .y = 0 }).rotate(spawn_angle);
|
||||
try enemies.append(Enemy.initStrong(enemy_position));
|
||||
}
|
||||
|
||||
const handPosition = player.position.add(player.handDirection.scale(handDistance));
|
||||
const deathBallPosition = rope.lastNodePosition();
|
||||
|
||||
const enemyCountText = try std.fmt.allocPrintZ(allocator, "{d}", .{self.spawnedEnemiesCount});
|
||||
defer allocator.free(enemyCountText);
|
||||
rl.DrawText(enemyCountText, 10, 10, 24, rl.RED);
|
||||
for (enemies.items) |*enemy| {
|
||||
const toPlayer = player.position.sub(enemy.position);
|
||||
if (player.vulnerable() and toPlayer.length() <= playerSize + enemy.size) {
|
||||
self.damagePlayer();
|
||||
}
|
||||
|
||||
const killCountText = try std.fmt.allocPrintZ(allocator, "{d}", .{self.killCount});
|
||||
defer allocator.free(killCountText);
|
||||
rl.DrawText(killCountText, 10, 30, 24, rl.GREEN);
|
||||
if (enemy.vulnerable() and player.alive() and rl.Vector2Distance(enemy.position, deathBallPosition) < enemy.size + death_ball_size) {
|
||||
self.damageEnemy(enemy);
|
||||
}
|
||||
|
||||
var acceleration = rl.Vector2.zero();
|
||||
if (enemy.stunned == 0) {
|
||||
const directionToPlayer = toPlayer.normalize();
|
||||
acceleration = acceleration.add(directionToPlayer);
|
||||
}
|
||||
|
||||
var enemyPushForce = rl.Vector2.zero();
|
||||
for (enemies.items) |*otherEnemy| {
|
||||
if (otherEnemy == enemy) continue;
|
||||
|
||||
const difference = enemy.position.sub(otherEnemy.position);
|
||||
const enemyDistance = difference.length();
|
||||
if (otherEnemy.size + enemy.size > enemyDistance*1.2) {
|
||||
enemyPushForce = enemyPushForce.add(difference.normalize());
|
||||
}
|
||||
}
|
||||
|
||||
acceleration = acceleration.add(enemyPushForce);
|
||||
|
||||
enemy.velocity = enemy.velocity.add(acceleration.scale(enemy.acceleration * dt));
|
||||
enemy.velocity = rl.Vector2ClampValue(enemy.velocity, 0, enemy.maxSpeed);
|
||||
enemy.velocity = rl.Vector2Scale(enemy.velocity, std.math.pow(f32, (1 - enemyFriction), dt));
|
||||
enemy.position = enemy.position.add(enemy.velocity.scale(dt));
|
||||
|
||||
enemy.invincibility = @max(0, enemy.invincibility - dt);
|
||||
enemy.stunned = @max(0, enemy.stunned - dt);
|
||||
}
|
||||
|
||||
{ // Remove dead enemies
|
||||
var i: usize = 0;
|
||||
while (i < enemies.items.len) {
|
||||
const enemy = &enemies.items[i];
|
||||
|
||||
if (enemy.health == 0) {
|
||||
self.killCount += 1;
|
||||
_ = enemies.swapRemove(i);
|
||||
continue;
|
||||
}
|
||||
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn damagePlayer(self: *Self) void {
|
||||
const player = &self.player;
|
||||
|
||||
if (player.alive()) {
|
||||
player.health -= 1;
|
||||
player.invincibility = 0.8;
|
||||
}
|
||||
|
||||
if (!player.alive()) {
|
||||
player.input.deactivate();
|
||||
}
|
||||
}
|
||||
|
||||
fn damageEnemy(self: *Self, enemy: *Enemy) void {
|
||||
const deathBallVelocity = self.rope.lastNodeVelocity();
|
||||
|
||||
if (enemy.alive()) {
|
||||
enemy.health -= 1;
|
||||
}
|
||||
|
||||
enemy.invincibility = 0.75;
|
||||
enemy.stunned = 0.9;
|
||||
enemy.velocity = deathBallVelocity.scale(20000);
|
||||
}
|
||||
|
||||
fn getScreenSize() rl.Vector2 {
|
||||
return rl.Vector2{
|
||||
.x = @floatFromInt(rl.GetScreenWidth()),
|
||||
.y = @floatFromInt(rl.GetScreenHeight())
|
||||
};
|
||||
}
|
||||
|
||||
fn allocDrawText(
|
||||
allocator: Allocator,
|
||||
comptime fmt: []const u8,
|
||||
fmt_args: anytype,
|
||||
x: i32,
|
||||
y: i32,
|
||||
font_size: i32,
|
||||
color: rl.Color,
|
||||
) !void {
|
||||
const text = try std.fmt.allocPrintZ(allocator, fmt, fmt_args);
|
||||
defer allocator.free(text);
|
||||
rl.DrawText(text, x, y, font_size, color);
|
||||
}
|
||||
|
||||
fn tickUI(self: *Self) !void {
|
||||
self.ui.mouse_position = self.pixel_effect.getMousePosition();
|
||||
self.ui.mouse_delta = self.pixel_effect.getMouseDelta();
|
||||
|
||||
self.ui.begin();
|
||||
defer self.ui.end();
|
||||
|
||||
var allocator = self.allocator;
|
||||
const screen_box = UIBox.init(0 ,0, virtualWidth, virtualHeight);
|
||||
const font = rl.GetFontDefault();
|
||||
|
||||
// try allocDrawText(allocator, "{d}", .{self.spawnedEnemiesCount}, 10, 10, 12, rl.RED);
|
||||
// try allocDrawText(allocator, "{d}", .{self.killCount}, 10, 30, 12, rl.GREEN);
|
||||
|
||||
const minutes_text = try std.fmt.allocPrint(allocator, "{d:.0}", .{self.timePassed/60});
|
||||
defer allocator.free(minutes_text);
|
||||
|
||||
const seconds_text = try std.fmt.allocPrint(allocator, "{d:.3}", .{@mod(self.timePassed, 60)});
|
||||
defer allocator.free(seconds_text);
|
||||
|
||||
const time_passed_text = try std.mem.concatWithSentinel(allocator, u8, &.{ minutes_text, ":", seconds_text }, 0);
|
||||
defer allocator.free(time_passed_text);
|
||||
|
||||
const minutes_text_z = try std.mem.concatWithSentinel(allocator, u8, &.{ minutes_text }, 0);
|
||||
defer allocator.free(minutes_text_z);
|
||||
|
||||
const font_size = 20;
|
||||
const time_passed_width: f32 = @floatFromInt(rl.MeasureText(minutes_text_z, font_size) + rl.MeasureText(":000", font_size));
|
||||
const time_passed_pos = screen_box.center_top().add(.{ .x = -time_passed_width/2, .y = 10 });
|
||||
rl.DrawTextEx(
|
||||
font,
|
||||
time_passed_text,
|
||||
time_passed_pos,
|
||||
font_size,
|
||||
font_size/10,
|
||||
rl.GREEN
|
||||
);
|
||||
|
||||
if (!self.player.alive()) {
|
||||
const modal_size = rl.Vector2{ .x = 200, .y = 200 };
|
||||
const modal = screen_box.box(
|
||||
screen_box.center() - modal_size.x/2,
|
||||
screen_box.middle() - modal_size.y/2,
|
||||
modal_size.x,
|
||||
modal_size.y
|
||||
);
|
||||
const content = modal.margin(10);
|
||||
|
||||
rl.DrawRectangleRec(modal.rect(), rl.RAYWHITE);
|
||||
UI.drawTextAligned(
|
||||
font,
|
||||
"You died!",
|
||||
content.center_top().add(.{ .x = 0, .y = 30 }),
|
||||
30,
|
||||
rl.BLACK
|
||||
);
|
||||
|
||||
try allocDrawText(
|
||||
allocator,
|
||||
"Kills: {d}", .{self.killCount},
|
||||
@intFromFloat(content.left() + 10),
|
||||
@intFromFloat(content.top() + 60),
|
||||
30,
|
||||
rl.BLACK
|
||||
);
|
||||
|
||||
if (self.ui.button("Restart?", content.left(), content.bottom() - 70, content.width, 30)) {
|
||||
self.reset();
|
||||
}
|
||||
|
||||
if (self.ui.button("Exit?", content.left(), content.bottom() - 30, content.width, 30)) {
|
||||
self.should_close = true;
|
||||
}
|
||||
} else if (self.paused) {
|
||||
const modal_size = rl.Vector2{ .x = 200, .y = 200 };
|
||||
const modal = screen_box.box(
|
||||
screen_box.center() - modal_size.x/2,
|
||||
screen_box.middle() - modal_size.y/2,
|
||||
modal_size.x,
|
||||
modal_size.y
|
||||
);
|
||||
const content = modal.margin(10);
|
||||
|
||||
rl.DrawRectangleRec(modal.rect(), rl.RAYWHITE);
|
||||
UI.drawTextAligned(
|
||||
font,
|
||||
"Paused",
|
||||
content.center_top().add(.{ .x = 0, .y = 30 }),
|
||||
30,
|
||||
rl.BLACK
|
||||
);
|
||||
|
||||
if (self.ui.button("Continue?", content.left(), content.top() + 70, content.width, 30)) {
|
||||
self.togglePaused();
|
||||
}
|
||||
|
||||
if (self.ui.button("Exit?", content.left(), content.top() + 120, content.width, 30)) {
|
||||
self.should_close = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn togglePaused(self: *Self) void {
|
||||
self.paused = !self.paused;
|
||||
if (self.paused) {
|
||||
self.player.input.deactivate();
|
||||
} else {
|
||||
self.player.input.activate();
|
||||
}
|
||||
}
|
||||
|
||||
fn drawPlayer(self: *Self) void {
|
||||
var player = &self.player;
|
||||
const handPosition = player.getHandPosition();
|
||||
|
||||
const healthWidth = 7;
|
||||
const healthPercent = @as(f32, @floatFromInt(player.health)) / @as(f32, @floatFromInt(player.maxHealth));
|
||||
var healthColor = rl.GREEN;
|
||||
if (player.invincibility > 0 and @rem(player.invincibility, 0.2) < 0.1) {
|
||||
healthColor = rl.RED;
|
||||
}
|
||||
rl.DrawCircleSector(
|
||||
player.position,
|
||||
playerSize + healthWidth,
|
||||
0,
|
||||
360 * healthPercent,
|
||||
0,
|
||||
healthColor,
|
||||
);
|
||||
for (0..(player.health+1)) |i| {
|
||||
const percent = @as(f32, @floatFromInt(i)) / @as(f32, @floatFromInt(player.maxHealth));
|
||||
rl.DrawLineEx(
|
||||
player.position,
|
||||
player.position.add((rl.Vector2{ .x = playerSize + healthWidth, .y = 0 }).rotate(percent * 2 * rl.PI)),
|
||||
5,
|
||||
rl.BLACK
|
||||
);
|
||||
}
|
||||
rl.DrawCircle(
|
||||
@intFromFloat(player.position.x),
|
||||
@intFromFloat(player.position.y),
|
||||
@ -240,26 +556,54 @@ pub fn tick(self: *Self) !void {
|
||||
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
|
||||
);
|
||||
fn rgba(r: u8, g: u8, b: u8, a: u8) rl.Color {
|
||||
return rl.Color{ .r = r, .g = g, .b = b, .a = a };
|
||||
}
|
||||
|
||||
rope.nodePositions.set(0, handPosition);
|
||||
fn rgb(r: u8, g: u8, b: u8) rl.Color {
|
||||
return rgba(r, g, b, 255);
|
||||
}
|
||||
|
||||
rope.update(dt);
|
||||
fn drawWorld(self: *Self) void {
|
||||
rl.rlSetLineWidth(1);
|
||||
var enemies = &self.enemies;
|
||||
var rope = &self.rope;
|
||||
const death_ball_position = rope.lastNodePosition();
|
||||
|
||||
const rope_color = rl.PURPLE;
|
||||
rl.ClearBackground(rl.BLACK);
|
||||
rl.DrawCircle(0, 0, arenaRadius, rl.BROWN);
|
||||
rl.DrawCircle(0, 0, 5, rl.GOLD);
|
||||
|
||||
rl.DrawCircleLines(0, 0, safeRadius, rl.WHITE);
|
||||
rl.DrawCircleLines(0, 0, safeRadius + spawnAreaWidth, rl.WHITE);
|
||||
|
||||
for (enemies.items) |*enemy| {
|
||||
rl.DrawCircleV(
|
||||
enemy.position.add(shadow_offset),
|
||||
enemy.size,
|
||||
rgba(0, 0, 0, 100)
|
||||
);
|
||||
}
|
||||
|
||||
for (enemies.items) |*enemy| {
|
||||
var color = enemy.color;
|
||||
if (enemy.invincibility > 0 and @rem(enemy.invincibility, 0.2) < 0.1) {
|
||||
color = rl.LIGHTGRAY;
|
||||
}
|
||||
|
||||
rl.DrawCircleV(
|
||||
enemy.position,
|
||||
enemy.size,
|
||||
color
|
||||
);
|
||||
}
|
||||
|
||||
self.drawPlayer();
|
||||
|
||||
rl.rlDrawRenderBatchActive();
|
||||
rl.rlSetLineWidth(3);
|
||||
for (0..(rope.nodePositions.len-1)) |i| {
|
||||
var node1: rl.Vector2 = rope.nodePositions.get(i);
|
||||
var node2: rl.Vector2 = rope.nodePositions.get(i+1);
|
||||
@ -268,39 +612,66 @@ pub fn tick(self: *Self) !void {
|
||||
@intFromFloat(node1.y),
|
||||
@intFromFloat(node2.x),
|
||||
@intFromFloat(node2.y),
|
||||
rope_color
|
||||
rgb(106, 7, 17)
|
||||
);
|
||||
}
|
||||
rl.rlDrawRenderBatchActive();
|
||||
rl.rlSetLineWidth(1);
|
||||
|
||||
for (rope.nodePositions.slice()) |node| {
|
||||
rl.DrawCircle(
|
||||
@intFromFloat(node.x),
|
||||
@intFromFloat(node.y),
|
||||
5,
|
||||
rope_color
|
||||
6,
|
||||
rgb(110, 10, 20)
|
||||
);
|
||||
}
|
||||
|
||||
const deathBallPosition = rope.nodePositions.get(rope.nodePositions.len-1);
|
||||
|
||||
const death_ball_color = rgb(239, 48, 67);
|
||||
rl.DrawCircle(
|
||||
@intFromFloat(deathBallPosition.x),
|
||||
@intFromFloat(deathBallPosition.y),
|
||||
deathBallSize,
|
||||
rl.RED
|
||||
@intFromFloat(death_ball_position.x),
|
||||
@intFromFloat(death_ball_position.y),
|
||||
death_ball_size,
|
||||
death_ball_color,
|
||||
);
|
||||
|
||||
{
|
||||
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;
|
||||
}
|
||||
const spikes_count = 5;
|
||||
const spike_size = 8;
|
||||
for (0..spikes_count) |i| {
|
||||
const percent = @as(f32, @floatFromInt(i)) / spikes_count;
|
||||
const extent = rl.Vector2.fromAngle(percent * 2 * rl.PI).scale(death_ball_size + spike_size);
|
||||
rl.DrawLineV(death_ball_position.add(extent), death_ball_position.sub(extent), death_ball_color);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tick(self: *Self) !void {
|
||||
const dt = rl.GetFrameTime();
|
||||
|
||||
if (self.player.input.isPausePressed() and self.player.alive()) {
|
||||
self.togglePaused();
|
||||
}
|
||||
|
||||
if (!self.paused) {
|
||||
self.camera.offset.x = virtualWidth/2;
|
||||
self.camera.offset.y = virtualHeight/2;
|
||||
self.camera.zoom = 0.4;
|
||||
self.camera.target = rl.Vector2Lerp(self.camera.target, self.player.position, 10 * dt);
|
||||
|
||||
self.updatePlayer();
|
||||
try self.updateEnemies();
|
||||
|
||||
if (self.player.alive()) {
|
||||
self.timePassed += rl.GetFrameTime();
|
||||
}
|
||||
}
|
||||
|
||||
self.pixel_effect.begin();
|
||||
rl.BeginMode2D(self.camera);
|
||||
self.drawWorld();
|
||||
rl.EndMode2D();
|
||||
self.pixel_effect.end();
|
||||
|
||||
self.pixel_effect.beginTransform();
|
||||
try self.tickUI();
|
||||
self.pixel_effect.endTransform();
|
||||
}
|
||||
|
@ -9,13 +9,17 @@ pub fn main() !void {
|
||||
defer _ = gpa.deinit();
|
||||
|
||||
rl.SetTargetFPS(60);
|
||||
rl.InitWindow(800, 800, "Step kill");
|
||||
rl.SetConfigFlags(rl.ConfigFlags{
|
||||
.FLAG_WINDOW_RESIZABLE = true,
|
||||
});
|
||||
rl.InitWindow(1200, 1200, "Step kill");
|
||||
rl.SetExitKey(.KEY_NULL);
|
||||
defer rl.CloseWindow();
|
||||
|
||||
var scene = MainScene.init(allocator);
|
||||
defer scene.deinit();
|
||||
|
||||
while (!rl.WindowShouldClose()) {
|
||||
while (!rl.WindowShouldClose() and !scene.should_close) {
|
||||
rl.BeginDrawing();
|
||||
defer rl.EndDrawing();
|
||||
|
||||
|
102
src/pixel-perfect.zig
Normal file
102
src/pixel-perfect.zig
Normal file
@ -0,0 +1,102 @@
|
||||
const rl = @import("raylib");
|
||||
|
||||
target: rl.RenderTexture2D,
|
||||
|
||||
pub fn init(width: u32, height: u32) @This() {
|
||||
return @This(){
|
||||
.target = rl.LoadRenderTexture(@intCast(width), @intCast(height))
|
||||
};
|
||||
}
|
||||
|
||||
fn getScale(self: @This()) f32 {
|
||||
const screen_width: f32 = @floatFromInt(rl.GetScreenWidth());
|
||||
const screen_height: f32 = @floatFromInt(rl.GetScreenHeight());
|
||||
const virtual_width: f32 = @floatFromInt(self.target.texture.width);
|
||||
const virtual_height: f32 = @floatFromInt(self.target.texture.height);
|
||||
|
||||
const width_scale = screen_width / virtual_width;
|
||||
const height_scale = screen_height / virtual_height;
|
||||
const scale = @min(width_scale, height_scale);
|
||||
|
||||
return scale;
|
||||
}
|
||||
|
||||
fn getOffset(self: @This()) rl.Vector2 {
|
||||
const screen_width: f32 = @floatFromInt(rl.GetScreenWidth());
|
||||
const screen_height: f32 = @floatFromInt(rl.GetScreenHeight());
|
||||
const virtual_width: f32 = @floatFromInt(self.target.texture.width);
|
||||
const virtual_height: f32 = @floatFromInt(self.target.texture.height);
|
||||
|
||||
const scale = self.getScale();
|
||||
|
||||
return rl.Vector2{
|
||||
.x = (screen_width - virtual_width*scale)/2,
|
||||
.y = (screen_height - virtual_height*scale)/2
|
||||
};
|
||||
}
|
||||
|
||||
pub fn toWorldSpace(self: *@This(), pos: rl.Vector2) rl.Vector2 {
|
||||
const offset = self.getOffset();
|
||||
const scale = self.getScale();
|
||||
|
||||
return rl.Vector2{
|
||||
.x = (pos.x - offset.x) / scale,
|
||||
.y = (pos.y - offset.y) / scale
|
||||
};
|
||||
}
|
||||
|
||||
pub fn getMousePosition(self: *@This()) rl.Vector2 {
|
||||
return self.toWorldSpace(rl.GetMousePosition());
|
||||
}
|
||||
|
||||
pub fn getMouseDelta(self: *@This()) rl.Vector2 {
|
||||
const scale = self.getScale();
|
||||
return rl.GetMouseDelta().scale(scale);
|
||||
}
|
||||
|
||||
pub fn deinit(self: @This()) void {
|
||||
rl.UnloadRenderTexture(self.target);
|
||||
}
|
||||
|
||||
pub fn begin(self: *@This()) void {
|
||||
rl.ClearBackground(rl.BLACK);
|
||||
rl.BeginTextureMode(self.target);
|
||||
}
|
||||
|
||||
pub fn end(self: *@This()) void {
|
||||
rl.EndTextureMode();
|
||||
|
||||
const offset = self.getOffset();
|
||||
const scale = self.getScale();
|
||||
|
||||
const virtual_width: f32 = @floatFromInt(self.target.texture.width);
|
||||
const virtual_height: f32 = @floatFromInt(self.target.texture.height);
|
||||
|
||||
const target_source = rl.Rectangle{
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
.width = virtual_width,
|
||||
.height = -virtual_height,
|
||||
};
|
||||
const target_dest = rl.Rectangle{
|
||||
.x = offset.x,
|
||||
.y = offset.y,
|
||||
.width = virtual_width*scale,
|
||||
.height = virtual_height*scale,
|
||||
};
|
||||
rl.DrawTexturePro(self.target.texture, target_source, target_dest, rl.Vector2.zero(), 0, rl.WHITE);
|
||||
}
|
||||
|
||||
pub fn beginTransform(self: *@This()) void {
|
||||
const offset = self.getOffset();
|
||||
const scale = self.getScale();
|
||||
|
||||
rl.rlPushMatrix();
|
||||
rl.rlTranslatef(offset.x, offset.y, 0);
|
||||
rl.rlScalef(scale, scale, 1);
|
||||
}
|
||||
|
||||
pub fn endTransform(self: *@This()) void {
|
||||
rl.rlPopMatrix();
|
||||
_ = self;
|
||||
}
|
129
src/player-input.zig
Normal file
129
src/player-input.zig
Normal file
@ -0,0 +1,129 @@
|
||||
const rl = @import("raylib");
|
||||
const std = @import("std");
|
||||
|
||||
gamepad: i32 = 0,
|
||||
active: bool = false,
|
||||
|
||||
last_hand_position: rl.Vector2 = rl.Vector2.zero(),
|
||||
mouse_hand_position: rl.Vector2 = rl.Vector2.zero(),
|
||||
|
||||
const mouseHandRadius = 100.0;
|
||||
|
||||
pub fn init() @This() {
|
||||
return @This(){};
|
||||
}
|
||||
|
||||
fn clampVector(vec: rl.Vector2) rl.Vector2 {
|
||||
if (vec.length2() > 1) {
|
||||
return vec.normalize();
|
||||
}
|
||||
|
||||
return vec;
|
||||
}
|
||||
|
||||
fn getKeyboardWalkDirection() rl.Vector2 {
|
||||
var dx: f32 = 0;
|
||||
var dy: f32 = 0;
|
||||
if (rl.IsKeyDown(rl.KeyboardKey.KEY_W)) {
|
||||
dy -= 1;
|
||||
}
|
||||
if (rl.IsKeyDown(rl.KeyboardKey.KEY_S)) {
|
||||
dy += 1;
|
||||
}
|
||||
if (rl.IsKeyDown(rl.KeyboardKey.KEY_A)) {
|
||||
dx -= 1;
|
||||
}
|
||||
if (rl.IsKeyDown(rl.KeyboardKey.KEY_D)) {
|
||||
dx += 1;
|
||||
}
|
||||
|
||||
return (rl.Vector2{ .x = dx, .y = dy }).normalize();
|
||||
}
|
||||
|
||||
fn getGamepadWalkDirection(gamepad: i32) ?rl.Vector2 {
|
||||
if (!rl.IsGamepadAvailable(gamepad)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const x = rl.GetGamepadAxisMovement(gamepad, .GAMEPAD_AXIS_LEFT_X);
|
||||
const y = rl.GetGamepadAxisMovement(gamepad, .GAMEPAD_AXIS_LEFT_Y);
|
||||
|
||||
if (@fabs(x) < 0.001 and @fabs(y) < 0.001) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return clampVector(.{ .x = x, .y = y });
|
||||
}
|
||||
|
||||
pub fn getWalkDirection(self: *@This()) rl.Vector2 {
|
||||
if (!self.active) {
|
||||
return rl.Vector2.zero();
|
||||
}
|
||||
|
||||
var walk_direction = getKeyboardWalkDirection();
|
||||
if (getGamepadWalkDirection(self.gamepad)) |dir| {
|
||||
walk_direction = dir;
|
||||
}
|
||||
|
||||
return walk_direction;
|
||||
}
|
||||
|
||||
fn getGamepadHandPosition(gamepad: i32) ?rl.Vector2 {
|
||||
if (!rl.IsGamepadAvailable(gamepad)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const x = rl.GetGamepadAxisMovement(gamepad, .GAMEPAD_AXIS_RIGHT_X);
|
||||
const y = rl.GetGamepadAxisMovement(gamepad, .GAMEPAD_AXIS_RIGHT_Y);
|
||||
|
||||
if (@fabs(x) < 0.001 and @fabs(y) < 0.001) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return clampVector(.{ .x = x, .y = y });
|
||||
}
|
||||
|
||||
fn getMouseHandPosition(self: *@This()) rl.Vector2 {
|
||||
const mouse_delta = rl.GetMouseDelta();
|
||||
|
||||
self.mouse_hand_position = clampVector(self.mouse_hand_position.add(mouse_delta.scale(1.0/mouseHandRadius)));
|
||||
return self.mouse_hand_position;
|
||||
}
|
||||
|
||||
pub fn getHandPosition(self: *@This()) rl.Vector2 {
|
||||
if (!self.active) {
|
||||
return self.last_hand_position;
|
||||
}
|
||||
|
||||
var hand_position = self.getMouseHandPosition();
|
||||
if (getGamepadHandPosition(self.gamepad)) |pos| {
|
||||
hand_position = pos;
|
||||
}
|
||||
|
||||
self.last_hand_position = hand_position;
|
||||
return hand_position;
|
||||
}
|
||||
|
||||
pub fn isPausePressed(self: *@This()) bool {
|
||||
if (rl.IsKeyPressed(rl.KeyboardKey.KEY_ESCAPE)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (rl.IsGamepadAvailable(self.gamepad) and rl.IsGamepadButtonPressed(self.gamepad, .GAMEPAD_BUTTON_MIDDLE_RIGHT)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn activate(self: *@This()) void {
|
||||
if (self.active) return;
|
||||
rl.DisableCursor();
|
||||
self.active = true;
|
||||
}
|
||||
|
||||
pub fn deactivate(self: *@This()) void {
|
||||
if (!self.active) return;
|
||||
rl.EnableCursor();
|
||||
self.active = false;
|
||||
}
|
65
src/ui-box.zig
Normal file
65
src/ui-box.zig
Normal file
@ -0,0 +1,65 @@
|
||||
const rl = @import("raylib");
|
||||
|
||||
x: f32,
|
||||
y: f32,
|
||||
width: f32,
|
||||
height: f32,
|
||||
|
||||
pub fn init(x: f32, y: f32, width: f32, height: f32) @This() {
|
||||
return @This(){
|
||||
.x = x,
|
||||
.y = y,
|
||||
.width = width,
|
||||
.height = height,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn initScreen() @This() {
|
||||
const width: f32 = @floatFromInt(rl.GetScreenWidth());
|
||||
const height: f32 = @floatFromInt(rl.GetScreenHeight());
|
||||
return @This().init(0, 0, width, height);
|
||||
}
|
||||
|
||||
pub fn box(self: @This(), x: f32, y: f32, width: f32, height: f32) @This() {
|
||||
return @This().init(self.x + x, self.y + y, width, height);
|
||||
}
|
||||
|
||||
pub fn rect(self: @This()) rl.Rectangle {
|
||||
return rl.Rectangle{
|
||||
.x = self.x,
|
||||
.y = self.y,
|
||||
.width = self.width,
|
||||
.height = self.height,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn left(self: @This()) f32 {
|
||||
return self.x;
|
||||
}
|
||||
pub fn center(self: @This()) f32 {
|
||||
return self.x + self.width/2;
|
||||
}
|
||||
pub fn right(self: @This()) f32 {
|
||||
return self.x + self.width;
|
||||
}
|
||||
|
||||
pub fn top(self: @This()) f32 {
|
||||
return self.y;
|
||||
}
|
||||
pub fn middle(self: @This()) f32 {
|
||||
return self.y + self.height/2;
|
||||
}
|
||||
pub fn bottom(self: @This()) f32 {
|
||||
return self.y + self.height;
|
||||
}
|
||||
|
||||
pub fn center_top(self: @This()) rl.Vector2 {
|
||||
return rl.Vector2{ .x = self.center(), .y = self.top() };
|
||||
}
|
||||
pub fn center_middle(self: @This()) rl.Vector {
|
||||
return rl.Vector2{ .x = self.center(), .y = self.middle() };
|
||||
}
|
||||
|
||||
pub fn margin(self: @This(), amount: f32) @This() {
|
||||
return @This().init(self.x + amount, self.y + amount, self.width - 2*amount, self.height - 2*amount);
|
||||
}
|
124
src/ui.zig
Normal file
124
src/ui.zig
Normal file
@ -0,0 +1,124 @@
|
||||
const rl = @import("raylib");
|
||||
const std = @import("std");
|
||||
|
||||
const gamepad: i32 = 0;
|
||||
|
||||
selected_button: i32 = -1,
|
||||
last_button_idx: u32 = 0,
|
||||
button_count: u32 = 0,
|
||||
|
||||
was_mouse_hot: bool = false,
|
||||
is_down: bool = false,
|
||||
is_released: bool = false,
|
||||
first_render: bool = true,
|
||||
|
||||
mouse_position: rl.Vector2 = rl.Vector2.zero(),
|
||||
mouse_delta: rl.Vector2 = rl.Vector2.zero(),
|
||||
|
||||
pub fn init() @This() {
|
||||
return @This(){};
|
||||
}
|
||||
|
||||
fn didMouseMove(self: *@This()) bool {
|
||||
return (self.mouse_delta.x != 0 or self.mouse_delta.y != 0) and !rl.IsCursorHidden();
|
||||
}
|
||||
|
||||
fn isMouseInBounds(self: *@This(), x: f32, y: f32, width: f32, height: f32) bool {
|
||||
const pos = self.mouse_position;
|
||||
return (x <= pos.x and pos.x <= x + width) and (y <= pos.y and pos.y <= y + height);
|
||||
}
|
||||
|
||||
pub fn button(self: *@This(), text: [:0]const u8, x: f32, y: f32, width: f32, height: f32) bool {
|
||||
const button_idx = self.last_button_idx;
|
||||
self.last_button_idx += 1;
|
||||
self.button_count += 1;
|
||||
|
||||
if (self.didMouseMove() and self.isMouseInBounds(x, y, width, height)) {
|
||||
self.selected_button = @intCast(button_idx);
|
||||
self.was_mouse_hot = true;
|
||||
}
|
||||
|
||||
var is_hot = self.selected_button == @as(i32, @intCast(button_idx));
|
||||
|
||||
const rect = rl.Rectangle{
|
||||
.x = x,
|
||||
.y = y,
|
||||
.width = width,
|
||||
.height = height,
|
||||
};
|
||||
if (is_hot and self.is_down) {
|
||||
rl.DrawRectangleRec(rect, rl.RED);
|
||||
} else if (is_hot) {
|
||||
rl.DrawRectangleRec(rect, rl.ORANGE);
|
||||
} else {
|
||||
rl.DrawRectangleRec(rect, rl.GREEN);
|
||||
}
|
||||
|
||||
drawTextAligned(rl.GetFontDefault(), text, .{ .x = x + width/2, .y = y + height/2 }, height * 0.8, rl.BLACK);
|
||||
|
||||
return is_hot and self.is_released;
|
||||
}
|
||||
|
||||
pub fn begin(self: *@This()) void {
|
||||
if (self.first_render and self.button_count > 0) {
|
||||
if (rl.IsGamepadAvailable(gamepad)) {
|
||||
self.selected_button = 0;
|
||||
}
|
||||
self.first_render = false;
|
||||
}
|
||||
|
||||
self.last_button_idx = 0;
|
||||
|
||||
self.was_mouse_hot = false;
|
||||
self.is_down = false;
|
||||
self.is_released = false;
|
||||
|
||||
if (rl.IsGamepadAvailable(gamepad)) {
|
||||
const btn = rl.GamepadButton.GAMEPAD_BUTTON_RIGHT_FACE_DOWN;
|
||||
self.is_down = self.is_down or rl.IsGamepadButtonDown(gamepad, btn);
|
||||
self.is_released = self.is_released or rl.IsGamepadButtonReleased(gamepad, btn);
|
||||
|
||||
if (self.button_count > 0) {
|
||||
const pressed_down = rl.IsGamepadButtonPressed(gamepad, .GAMEPAD_BUTTON_LEFT_FACE_DOWN);
|
||||
const pressed_up = rl.IsGamepadButtonPressed(gamepad, .GAMEPAD_BUTTON_LEFT_FACE_UP);
|
||||
if ((pressed_up or pressed_down) and self.selected_button == -1) {
|
||||
self.selected_button = 0;
|
||||
} else {
|
||||
if (pressed_down) {
|
||||
self.selected_button = @mod(self.selected_button + 1, @as(i32, @intCast(self.button_count)));
|
||||
}
|
||||
if (pressed_up) {
|
||||
self.selected_button = @mod(self.selected_button - 1, @as(i32, @intCast(self.button_count)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!rl.IsCursorHidden()) {
|
||||
const btn = rl.MouseButton.MOUSE_BUTTON_LEFT;
|
||||
self.is_down = self.is_down or rl.IsMouseButtonDown(btn);
|
||||
self.is_released = self.is_released or rl.IsMouseButtonReleased(btn);
|
||||
}
|
||||
|
||||
self.button_count = 0;
|
||||
}
|
||||
|
||||
pub fn end(self: *@This()) void {
|
||||
if (!self.was_mouse_hot and self.didMouseMove()) {
|
||||
self.selected_button = -1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn drawTextAligned(
|
||||
font: rl.Font,
|
||||
text: [*:0]const u8,
|
||||
position: rl.Vector2,
|
||||
font_size: f32,
|
||||
tint: rl.Color,
|
||||
) void {
|
||||
const default_font_size: f32 = 10;
|
||||
const spacing: f32 = @max(font_size/default_font_size, 1);
|
||||
const text_size = rl.MeasureTextEx(font, text, font_size, spacing);
|
||||
rl.DrawTextEx(font, text, position.sub(text_size.scale(0.5)), font_size, spacing, tint);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user