add pixel art effect

This commit is contained in:
Rokas Puzonas 2024-02-18 22:37:43 +02:00
parent f2545d27e9
commit 2941d9a39c
5 changed files with 206 additions and 86 deletions

View File

@ -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();
}

View File

@ -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();

102
src/pixel-perfect.zig Normal file
View 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;
}

65
src/ui-box.zig Normal file
View 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);
}

View File

@ -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);
}