DEV Community

Brandon Harrell
Brandon Harrell

Posted on

Following my passion #2: position vector and learning more Zig

Introduction

Ok, so actually doing some coding for my project on a work night. And boy did I have a busy day at work. I'm in crunch time and I had to debug this really annoying issue with Kafka. Thats a blog for another time lol

Ok moving the square

So yesterday I made a bootstrap Raylib project and it was just drawing a square to the screen. Today the goal was to make the square move. Which I did accomplish

How did I do it?

Well I'm going to admit I'm cheating a little. I'm not entirely new to game development. Well at least this isn't my first attempt. I once tried to write a game in C++ in 2024 during a layoff (layoff didn't last long, and I dropped the project once I got a job). So I do know about position vectors to some extent. But since I'm fairly new to Zig this was going to be an interesting challenge

Interesting stuff about Zig

So Zig reminds me a lot of Go, but there are obviously some big differences. I tried to introduce a little code structure to my project by keeping a Player struct.

const Player = struct{
  .x: usize,
  .y: usize,
  .width: usize,
  .height: usize,
}
Enter fullscreen mode Exit fullscreen mode

This is just to define the position and size of our square on the screen. You'll immediately see and issue if you've worked with Raylib, but I'll get into that.

Like Go or Rust you can also attach methods to your structs. However unlike Go, your constructor goes inside your struct. In Go you typically have your construct to create and initialize the struct. Which is happening here. But in Go you don't usually have the construct as a function receiver.

By convention (pretty sure this isn't imposed by the language), you construct your object via the init method.

So it would look something like this.

const Player = struct{
  .x: usize,
  .y: usize,
  .width: usize,
  .height: usize,
}

pub fn init() Player{
  return Player{
// initializations are here
   }
}
Enter fullscreen mode Exit fullscreen mode

This actually looks pretty similar to Rust. You can optionally do something like const Self = @This(). This allows you to identify your class reference as a Self or anything you choose. Pretty nice for people who come from other languages. I decided not to go with this though.

Next I put a draw function:

    pub fn draw(self: Player) void {
        const origin = rl.Vector2{
            .x = 0,
            .y = 0,
        };

        const rec = rl.Rectangle{
            .x = self.x,
            .y = self.y,
            .height = self.height,
            .width = self.width,
        };

        rl.drawRectanglePro(rec, origin, \@as(f32, @floatFromInt(0)), .white);
    }
Enter fullscreen mode Exit fullscreen mode

Here i define the origin as a 2 dimensional vector. And I take my initialization values from the constructor and I update a rectangle. Last I just draw the triangle. But this is all stuff I did yesterday this is just me organizing it better.

The rl identifier is just an alias to the Raylib library.

Last there is an update I need to call. It looked like this:

    pub fn update(self: Player) void {
        self.x += 1;
    }
Enter fullscreen mode Exit fullscreen mode

Ok discovering the problems

One thing I did to try to center the square in the middle of the screen was this:

   return Player{
            .x = rl.getScreenHeight() / 2,
            .y =  rl.getScreenWidth()/ 2,
            .height = 10,
            .width = 10,
        };
Enter fullscreen mode Exit fullscreen mode

Now this math may be incorrect and I'm sure it is. But the point is that r1.getScreenHeight() returns an i32. But all of the draw square functions from Raylib takes in f32. So I had to convert them.

        return Player{
            .x = @as(f32, @floatFromInt(rl.getScreenHeight())) / 2,
            .y = @as(f32, @floatFromInt(rl.getScreenWidth())) / 2,
            .height = 10,
            .width = 10,
        };
Enter fullscreen mode Exit fullscreen mode

Again not perfect due to the precision values now here. But it makes the compiler happy and I plan to fix all of this later.

The one that was most weird was my update. It couldn't update because it was a constant! Then I remember I have to actually pass the address and update the address. A bit of a quirk, but actually it would have worked the same way in Go. However Go would have made a copy of the struct and updated that. Which wouldn't have been ideal for state reasons. Zig makes everything a constant unless you're explict about it. So easy fix.

    pub fn update(self: *Player) void {
        self.x += 1;
    }
Enter fullscreen mode Exit fullscreen mode

So my code looked like this

     if (rl.isKeyDown(.d)) {
            player.update();
            player.draw();
        }
        rl.beginDrawing();

        defer rl.endDrawing();

        rl.clearBackground(.dark_gray);
        player.draw();
Enter fullscreen mode Exit fullscreen mode

And I ended up with a square that could move if hold down the 'D' key.

What did I learn

I learned more zig than game dev today. Again today there was just some hours I set aside to sit back and play with this. Tomorrow I am going to try to draw some more squares (yay) and see if I can get my little square to sit on top of them. So looks like I need to learn some lessons in collision detection.

Top comments (0)