Platformer I'm making in raylib

[programming:gamedev, programming:C]; Last updated 2026-07-15


Note: this is kinda out of date, i was just trying to fix a bug

So recently I learned about this game engine-type thing, raylib. It's pretty cool; it basically gives you a lot of the most basic things for games and stuff, like ... drawing a rectangle or image on the screen, and checking whether two rectangles overlap.

After getting bored with another game (in Godot), I decided to try making something in it. Turns out it's like really hard. So here are my notes on how I structured my program, mainly because I'm trying to fix a bug and I'm hoping this will help.

The main loop

Ok so in main.c there's a loop that loops while (!WindowShouldClose()). WindowShouldClose() is true when Escape or the Close button on the window is pressed. This loop then has two parts — the part that gets everything ready for drawing on the screen, and the part that ... draws on the screen. This is the basic setup in all of the raylib examples.

First, the loop checks if frozen is true (or well 1). This is true when dialogue is going on. Dialogue is all shown during the drawing part for the level, so when frozen is true it just stops any of the other stuff from being updated. (in the function variables())

If frozen is false (or 0), the like moving around stuff happens. This happens in the function variables(), which calls two functions — spieler_bewegen() (move player[m. technicially]) in player.c, and physics() in physics.c.

Moving the player

The two functions work together to move the player — spielerbewegen() calculates the velocity (in both x and y directions) of the player based on where they are and what inputs the user inputs. Then, physics()sets the x and y of the player based on this.

As to the player themselves, they are a struct with a Rectangle (raylib type) named rect defining where they are, and a Vector2 (also raylib type iirc??) named velocity defining how they will move in pixels per second.

A lot of the structure of this is just taken from some godot code I had because it seemed like it worked.

spielerbewegen()

First, spielerbewegen sets a file-wide variable to the frametime/delta. This is for animation purposes.

The next part checks whether the player is on the floor, using a function defined in level.c. If they are not, the constant GRAVITY defined in game.h is added to their y velocity. Otherwise, their y velocity is reset to 0.

The next if/else checks whether the player should jump. If inputs[2] is pressed down, and on_floor returns 1, they jump and their y velocity is changed to the constant JUMPVELOCITY.

how on_floor() works!

First, I had to decide how I would define "on the floor". I decided that the best choice would be if the player is right on top of a platform but not colliding with it.

Each level is made up of an array of platforms (a struct with a Rectangle named rect and a Color named colour), so this function loops through them and checks each seperately.

This is checked in two parts. First, the y-value is checked. If the player is on the floor, the y-value of the bottom of their rectangle should be equal to the y-value of the top of the platform. The x/y coordinates in raylib are of the upper left corner, so this is if ((rect->y + rect->h) == (check>y)).

After that, the x-values are checked. I decided the easiest way to do this would be to use the built-in function in raylib to check if two rectangles are overlapping, and then just make the rectangles have the same y and height. So, this basically tests if two lines are overlapping. If they are, then the x and y both match up, and the function returns 1. Otherwise, it keeps looping. If it reaches the end of the array and none of them match, then the player is not on the floor, and it returns 0.

After this, the program sets the variable direction to 1 for right, -1 for left, or 0 for neither. It does this by looking at what keys are pressed. Then, player.velocity.x is set to SPEED * direction, and that's the end of the function. (It returns 1 in case I want to add errors later.)

physics()

The physics() function does multiple things, but its main thing is that it runs the move_player() function in the same file. This is the function that translates the velocity of the player into its new x/y coordinates.

First, it figures out how far the player actually has to move. This is done by literally just delta*velocity for both x/y. Then this is rounded away from 0, so that the player never moves 0 pixels.

The way the function actually moves the player while making sure that they don't just go into a platform is simple. The program moves them in smaller steps; if the player has collided into a platform, they then are rewinded one step. After this, the program just moves them one pixel in both x/y until they collide with a wall so that it doesn't end with them 1 pixel above the ground and unable to jump.

The amount of steps and how large they are is done so that neither x nor y is less than 1 pixel. This is done with a bunch of if statements.

  1. If both the x distance and the y distance are 0, there are... 0 steps and all steps are 0 pixels, because uh. they don't have to be moved at all.
  2. If only x is 0, the amount of steps is |y| (since negative steps wouldn't work), and each step for y is distance.y/steps_phys (steps physics, idk why i think i was worried steps would conflict with something). This is so that if distance.y is negative, stepy is still negative.
  3. If only y is 0, it's the same thing, but with x instead of y. so steps_phys = abs(distance.x); stepx = distance.x/steps_phys;
  4. If neither is 0, but the absolute value of x is greater than that of y, steps_phys becomes the absolute value of distance.y. This is so that stepy isn't less than 1/-1. Then, stepy becomes distance.y/steps_phys, so -1 if distance.y is negative, or 1 if it's positive. stepx becomes ceil(distance.x/steps_phys, or literally just the amount needed to accomplish the distance within the steps given.
  5. If the absolute value of y is greater, it's literally the same as the last one, just swap "x" and "y".

After figuring out the steps and how large they are, there is a for loop that loops for each step and adds stepx to position.x and stepy to position.y. After that, it checks if the player is in the ground. If this is true, it subtracts stepx from position.x and stepy from position.y, and enters another loop.

This second loop uses the same variable (i) as the first one, so it only continues for as many loops as the first one had left. It literally just adds or subtracts 1 (based on whether stepx or stepy was positive or negative) from position.x and position.y until the player goes into a wall, where it then moves back 1 in both directions. The idea of this loop is so that the player doesn't end up just floating a couple pixels off of any platform. Then, the function is ended.

The rest of physics() is very simple. All it does is check if the player is off the right side of the screen. If so, it changes to the next level, sets frozen to 1 in order to allow dialogue to play, and moves the player up to the top left of the screen. Also, it breaks the game somehow. When I added the frozen=1it started closing the window on reaching it. This doesn't happen when main() does this, so I have no idea why it happens here. (that's also partially why I'm writing this, I hope i can figure it out...)

Drawing on the screen

The next part of the main loop is the part that draws on the screen. This is begun with BeginDrawing() and ended with EndDrawing().

There are two parts here as well: bühnezeichnen() (draw "stage", idk why i called it that i think i used too much scratch), which draws the level + the dialogue; and spielerzeichnen() (draw player), which ... draws the player.

Drawing the level

First, the function just loops through every platform in the current level, drawing them on the screen. It's literally that easy.

How the level data is structured

The data for each level is stored in level_data.h in 3 big arrays. First, there's an array with all the dialogue and one with all the platforms, and then there's another array that links those together. (along with giving the level a name)

The array of dialogue is an array of structs.The dialogue struct consists of a string for the character speaking it's name, the filename of an image of said character, and what they are saying; additionally, there is a Color (raylib type i think) for the colour the character's name should be shown as.

The array of platforms is also an array of structs. The platform struct consists of a Rectangle defining where on the screen the platform is and a Color defining what colour it should be displayed in.

The array linking these is called stages, and it is also an array of structs. The stage struct has first a string for the name of the level. Then, it has a pointer to the first struct platform in the platforms array used in the level, and an int (platform_count) for how many platforms are in it (all the platforms must be consecutive in the array). The part for the dialogue works the same way, with a pointer to the first one and the amount of dialogues.

Then, it checks if frozen is 1, aka if dialogue is currently going on. If this is true, it runs drawdialogue(), which draws the dialogue on the screen.

How drawdialogue() works

drawdialogue is pretty simple. It basically just draws everything in order. (some of the numbers are a bit confusing though).

  1. DrawRectangle(0, DLGY, 640, DLGHEIGHT, RAYWHITE); — this just draws a rectangle spanning the whole width of the screen from DLGY to the bottom of the screen. (DLGHEIGHT is the distance between DLGY and the bottom of the screen)
  2. drawcharname(dlg->charaname, dlg->characolour) — This is also really easy. It just draws text with the proper colour and font size CHARANAMETEXTSIZE DLGPADDING pixels away from the top left of the dialogue box.
  3. drawcharimage(dlg->image); — this scales the image to fit under the name and make the width of the character info part equal to CHARADISPLAYWIDTH, so it has a width and height of CHARADISPLAYWIDTH - DLGPADDING * 2. But first, it checks whether the file is the same as the last time it ran, in which case it just reuses the texture from last time.
  4. drawdialogueline(dlg->line); — Right now, this only draws the text at the correct spot, but I want to add a way to break lines later.

maybe ill add an image here later to show how the numbers work later idk

After that, it adds 1 to current_dialogue, and checks if its new value is equal to the amount of dialogues in the stage. If this is true (aka there is no more dialogue to play), it sets current__dialogue to 0 for the next stage, and then sets frozen to 0 so that the level can be played.

Drawing the player

Drawing the player is pretty easy. Basically, it just ... draws the image onto the screen at the correct x and y. However, there is also animation partially implemented.

Right now, it doesn't actually draw the correct frame, instead drawing the static version, but the way it works is adding delta to time_since_switch each time and then checking if that is greater than or equal to FRAMELENGTH. Then, it's supposed to draw the correct frame from the spritesheet.

At the end

This repeats until the user either closes the window or presses Escape. Then, the program... ends. like what else would it do idk

uh that's all ty for reading (this is really mainly for my own reference tbh), maybe ill update this when i update the project

<back to all pages

<back to home