From 2941d9a39c10d7fd8b1cbbf693b0572672f8dddd Mon Sep 17 00:00:00 2001 From: Rokas Puzonas Date: Sun, 18 Feb 2024 22:37:43 +0200 Subject: [PATCH] add pixel art effect --- src/main-scene.zig | 101 ++++++++++------------------------------- src/main.zig | 3 ++ src/pixel-perfect.zig | 102 ++++++++++++++++++++++++++++++++++++++++++ src/ui-box.zig | 65 +++++++++++++++++++++++++++ src/ui.zig | 21 +++++---- 5 files changed, 206 insertions(+), 86 deletions(-) create mode 100644 src/pixel-perfect.zig create mode 100644 src/ui-box.zig diff --git a/src/main-scene.zig b/src/main-scene.zig index e690384..9502f52 100644 --- a/src/main-scene.zig +++ b/src/main-scene.zig @@ -4,6 +4,8 @@ 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; @@ -11,6 +13,8 @@ const maxSpeed = 200; const handDistance = 50; const deathBallSize: f32 = 10; const playerSize = 20; +const virtualWidth = 300; +const virtualHeight = 300; const enemyFriction = friction; @@ -204,6 +208,7 @@ rope: Rope, ui: UI, should_close: bool = false, paused: bool = false, +pixel_effect: PixelPerfect, camera: rl.Camera2D, @@ -223,7 +228,8 @@ pub fn init(allocator: Allocator) Self { .player = player, .rope = Rope.init(handPosition, handPosition.add(.{ .x = ropeSize, .y = 0.002 }), 10), .camera = .{ .target = playerPosition }, - .ui = UI.init() + .ui = UI.init(), + .pixel_effect = PixelPerfect.init(virtualWidth, virtualHeight) }; } @@ -235,6 +241,7 @@ pub fn reset(self: *Self) void { pub fn deinit(self: *Self) void { self.player.input.deactivate(); self.enemies.deinit(); + self.pixel_effect.deinit(); } fn tickPlayer(self: *Self) void { @@ -384,82 +391,19 @@ fn allocDrawText( rl.DrawText(text, x, y, font_size, color); } -const UIBox = struct { - x: f32, - y: f32, - width: f32, - height: f32, - - fn init(x: f32, y: f32, width: f32, height: f32) UIBox { - return UIBox{ - .x = x, - .y = y, - .width = width, - .height = height, - }; - } - - fn initScreen() UIBox { - const width: f32 = @floatFromInt(rl.GetScreenWidth()); - const height: f32 = @floatFromInt(rl.GetScreenHeight()); - return UIBox.init(0, 0, width, height); - } - - fn box(self: UIBox, x: f32, y: f32, width: f32, height: f32) UIBox { - return UIBox.init(self.x + x, self.y + y, width, height); - } - - fn rect(self: UIBox) rl.Rectangle { - return rl.Rectangle{ - .x = self.x, - .y = self.y, - .width = self.width, - .height = self.height, - }; - } - - fn left(self: UIBox) f32 { - return self.x; - } - fn center(self: UIBox) f32 { - return self.x + self.width/2; - } - fn right(self: UIBox) f32 { - return self.x + self.width; - } - - fn top(self: UIBox) f32 { - return self.y; - } - fn middle(self: UIBox) f32 { - return self.y + self.height/2; - } - fn bottom(self: UIBox) f32 { - return self.y + self.height; - } - - fn center_top(self: UIBox) rl.Vector2 { - return rl.Vector2{ .x = self.center(), .y = self.top() }; - } - fn center_middle(self: UIBox) rl.Vector { - return rl.Vector2{ .x = self.center(), .y = self.middle() }; - } - - fn margin(self: UIBox, amount: f32) UIBox { - return UIBox.init(self.x + amount, self.y + amount, self.width - 2*amount, self.height - 2*amount); - } -}; - 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.initScreen(); + const screen_box = UIBox.init(0 ,0, virtualWidth, virtualHeight); const font = rl.GetFontDefault(); - try allocDrawText(allocator, "{d}", .{self.spawnedEnemiesCount}, 10, 10, 24, rl.RED); - try allocDrawText(allocator, "{d}", .{self.killCount}, 10, 30, 24, rl.GREEN); + 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); @@ -473,7 +417,7 @@ fn tickUI(self: *Self) !void { const minutes_text_z = try std.mem.concatWithSentinel(allocator, u8, &.{ minutes_text }, 0); defer allocator.free(minutes_text_z); - const font_size = 48; + const font_size = 12; 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 = 30 }); rl.DrawTextEx( @@ -481,7 +425,7 @@ fn tickUI(self: *Self) !void { time_passed_text, time_passed_pos, font_size, - 4.8, + font_size/10, rl.GREEN ); @@ -590,13 +534,11 @@ fn drawPlayer(self: *Self) void { } pub fn tick(self: *Self) !void { - rl.ClearBackground(rl.BROWN); - - const screenSize = getScreenSize(); const dt = rl.GetFrameTime(); - self.camera.offset.x = screenSize.x/2; - self.camera.offset.y = screenSize.y/2; + 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); var enemies = &self.enemies; @@ -617,6 +559,8 @@ pub fn tick(self: *Self) !void { const deathBallPosition = rope.nodePositions.get(rope.nodePositions.len-1); + self.pixel_effect.begin(); + rl.ClearBackground(rl.BROWN); rl.BeginMode2D(self.camera); { rl.DrawCircle(0, 0, 5, rl.GOLD); @@ -667,6 +611,9 @@ pub fn tick(self: *Self) !void { ); } rl.EndMode2D(); + self.pixel_effect.end(); + self.pixel_effect.beginTransform(); try self.tickUI(); + self.pixel_effect.endTransform(); } diff --git a/src/main.zig b/src/main.zig index 5d832d1..87f0f2c 100644 --- a/src/main.zig +++ b/src/main.zig @@ -9,6 +9,9 @@ pub fn main() !void { defer _ = gpa.deinit(); rl.SetTargetFPS(60); + rl.SetConfigFlags(rl.ConfigFlags{ + .FLAG_WINDOW_RESIZABLE = true, + }); rl.InitWindow(1200, 1200, "Step kill"); rl.SetExitKey(.KEY_NULL); defer rl.CloseWindow(); diff --git a/src/pixel-perfect.zig b/src/pixel-perfect.zig new file mode 100644 index 0000000..9510d31 --- /dev/null +++ b/src/pixel-perfect.zig @@ -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; +} diff --git a/src/ui-box.zig b/src/ui-box.zig new file mode 100644 index 0000000..de00308 --- /dev/null +++ b/src/ui-box.zig @@ -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); +} diff --git a/src/ui.zig b/src/ui.zig index 7535d60..6d820eb 100644 --- a/src/ui.zig +++ b/src/ui.zig @@ -12,13 +12,20 @@ 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() bool { - const mouse_delta = rl.GetMouseDelta(); - return (mouse_delta.x != 0 or mouse_delta.y != 0) and !rl.IsCursorHidden(); +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 { @@ -26,7 +33,7 @@ pub fn button(self: *@This(), text: [:0]const u8, x: f32, y: f32, width: f32, he self.last_button_idx += 1; self.button_count += 1; - if (didMouseMove() and isMouseInBounds(x, y, width, height)) { + if (self.didMouseMove() and self.isMouseInBounds(x, y, width, height)) { self.selected_button = @intCast(button_idx); self.was_mouse_hot = true; } @@ -97,7 +104,7 @@ pub fn begin(self: *@This()) void { } pub fn end(self: *@This()) void { - if (!self.was_mouse_hot and didMouseMove()) { + if (!self.was_mouse_hot and self.didMouseMove()) { self.selected_button = -1; } } @@ -115,7 +122,3 @@ pub fn drawTextAligned( rl.DrawTextEx(font, text, position.sub(text_size.scale(0.5)), font_size, spacing, tint); } -fn isMouseInBounds(x: f32, y: f32, width: f32, height: f32) bool { - const pos = rl.GetMousePosition(); - return (x <= pos.x and pos.x <= x + width) and (y <= pos.y and pos.y <= y + height); -}