CS 3 (Spring 2025) Project 06: Frogger (Game)

Your task this week will be implementing assets that can be attached to bodies.

Setup

Before we start, register for the project using the registration tool.

Engine Changes

For the last time, our engine library has been updated with new functionality (but not as much as previous weeks). As always, we’ll go over the changes that were made to the engine. These changes are especially relevant if you plan on using any sort of collisions in your final game. We encourage you to follow along by reading the relevant files as you read the guide.

forces.h

For last week’s space invaders demo, you might’ve noticed that with how destructive collisions were set up, we had to manually recount the number of invaders left every tick. Wouldn’t it have been nicer if we kept track of the number of invaders in state_t, and we decremented it every time a destructive collision occurred?

We’ve added collision_handler_t and create_collision abstractions this week which would solve that problem. collision_handler_t describes the format of a generic collision handler that takes two bodies, the axis of collision (more on that later!), an optional auxiliary info variable, and a force constant. aux is given to provide any useful information to the collision handler. If we were to remake our space invaders demo from last week, for example, aux could be the state_t of the game that contains the number of remaining invaders.

create_collision is how you register the custom collision handler. It acts very similar to the create_x force functions and uses scene_add_force_creator internally to register a collision force creator along with the collision handler that you pass in. You can’t see the implementation of the collision force creator, but all you need to know is that it checks if a collision has occurred between the two bodies. Only then will it call the collision_handler_t that you passed in.

create_destructive_collision’s functionality remains the same, but internally, we’ve refactored it to call create_collision, which made our code much simpler and flexible. This is another example of abstraction at work. As an exercise, what do you think the collision_handler_t for create_destructive_collision would be?

We’ve also added a create_physics_collision function that also uses our create_collision abstraction to register inelastic and elastic collisions between bodies. Read the documentation for more information!

body.h

We’ve added a new body_reset function for you that resets the force and impulse of the body.

Engine

Collision Resolution

Last week, you implemented find_collision() to detect collisions between two convex polygons. This week, you will help implement collision resolution, which changes the velocities of the bodies based on the physics of the collision. The good news is that we’ve already implemented the physics collision for you; all you need to do is make a small update to your collision code from last week.

Finding the collision axis

When two bodies collide in 3D space, there is a contact plane tangent to both bodies at the point of collision. The collision creates a “normal force” perpendicular to the contact plane. In 2D space, there is an analogous contact line tangent to the bodies. Just as in 3D physics, the applied force is perpendicular to the contact line. We refer to the unit vector perpendicular to the contact line as the “collision axis.” (There are actually two perpendicular directions; we will choose the one that points from the first body towards the second.) In the example below, the black line is the contact line and the red vector is the collision axis (from the green body towards the blue body).

collision

When bodies are polygons rather than continuous shapes, there is no longer a tangent line, but we can approximate a collision axis.

Since you implemented the Separating Axis method last week, the collision axis is simply the axis onto which the shapes’ projections have the least overlap.

Collision Axis

First, take a look at collision.h. Since we care about the separating axis now, find_collision now returns a collision_info_t struct that contains whether or not the bodies collided in addition to the axis.

We’ve already rewritten find_collision to use the new return type of compare_collision. Take a look at it to see how compare_collision is supposed to interact with it.

Notice the double “overlap” variables. These variables represent the minimum overlap calculated by the two collision functions. We’ll pass in their addresses to compare_collision so that the function can update their values every time a new minimum overlap is found. If a collision has occurred, we’ll compare the minimum overlap from both calls and return the separating axis that corresponds to the smaller of the two.

Next, copy and paste your implementation of get_max_min_projections and compare_collision from last week into collision.c, taking care not to overwrite the collision_info_t return type. Here are the changes that you will have to make:

Game

This week, you will be adding the ability to attach bodies to assets so that images can be rendered on top of bodies. After implementing the library, you will fill in parts of the game to implement a basic version of Frogger:

Small Asset Engine Changes

We’ve decided to modify asset.c this week. Last week for the meme generator, we had to manage a list of assets in the state. It’s reasonable to assume that each demo from here on out will maintain a list of assets, so we’ve decided to follow a similar design pattern to what we did in asset_cache: store a global list of assets in asset.c.

If you click into asset.c, you’ll see that we’ve declared a static ASSET_LIST variable at the top. We’ve modified asset_init to initialize ASSET_LIST if it hasn’t already been initialized.

Since we’re using this list and no longer maintain our own, we no longer have to return asset_t’s in our asset_make_x functions: we can just directly add them to our internal list! As a result, the function signatures of asset_make_image and asset_make_text have changed from asset_t * to void. You first task will be to update the code to reflect this change.

Because of this change, we’ve also added and implemented two more functions for you: asset_reset_asset_list and asset_get_asset_list. You’ll use these two functions to interact with the asset list from the demo.

Attaching Bodies to Images

To render images over bodies, there are two design choices that we can make: attaching bodies to images, or attaching images to bodies. In this implementation, we’ll go with the former. Among other things, we’ll have to refactor less of our library, and we can preserve our pattern of storing assets in a list and rendering them in emscripten_main.

Asset Library Overvieew

We’ll begin by taking a look at asset.c and asset.h. In asset.c, we’ve provided the new image_asset_t struct for you: the only thing we’ve added is a body field.

If you open up asset.h, you’ll notice that we’ve added a new function: asset_make_image_with_body. It allows the user to initialize an image asset using the filepath and body. Note that the function doesn’t need to take a bounding box anymore, because the image will be rendered over the body, whose bounding box may change after every tick.

Since we want the image to be rendered over its body, we’ll need a way of getting the bounding box that corresponds to the body. This leads us to our first task.

Getting the bounding box for a body

Getting the bounding box for a body will be relatively straightforward. We’ll want the bounding box to be the smallest box that can cover the body entirely.

Thus, we’ll iterate through all the points in the body, storing variables for the top left and bottom right corners as vectors. Once we’ve iterated through all the points, we’ll know that the smallest bounding box will have a top left corner at (min_x, max_y) and bottom right corner of (max_x, min_y). Then, we can use those points to calculate the fields of the SDL_Rect that we return. Remember that .x and .y refer to the top left point, and .w and .h can be calculated from the difference between the maximum and minimum x and y values.

bounding boxes

Since we are using SDL_Rect (and another reason that will become clear later), we should implement the function to get a bounding box for a body in sdl_wrapper.c.

Modifying asset.c

Now we can modify asset.c to render images that contain bodies.

Now that our image assets can contain bodies, we’ll have to modify our asset_make_image functions slightly. We’ll still keep the code that’s currently in the asset_make_image function but just add a line setting the body field to NULL. In asset_make_image_with_body, we’ll set the body field appropriately but pass in an arbitrary SDL_Rect to asset_init.

Now that the user can initialize image assets with bodies, we’ll also have to change asset_render, and more specifically, the case if the asset has type ASSET_IMAGE.

The fix will be small: if the asset’s body field is not NULL, we’ll set the bounding box to the body’s bounding box (which we can get using the function we just wrote). Otherwise, we’ll render the image asset like normal.

game.c

Now we can start putting everything together in game.c.

We’ve given you most of the starter code. While you look through it, take a look at emscripten_main: it iterates through the body_assets list set by asset_get_asset_list() and renders each asset. Your job is to add the image assets in emscripten_init. We’ve defined the filepaths for you at the top of the file. You’ll want to call the new asset_make_image_with_body function.

If you’ve followed everything so far, your images might look a little funky:

bounding boxes

It seems like the images are flipped across the center of the window. When the frog body moves up, the corresponding image moves down.

This is because of a small nuance in our function to get the bounding box for a body. In the SDL library, the y coordinates increase from top to bottom, not bottom to top. This explains why the images are “flipped” horizontally:

bounding boxes

How should we fix this? Take a look at sdl_draw_body function in sdl_wrapper.c and look at how bodies are rendered. As it iterates through all the points in the body, it accounts for the different y axes by using get_window_position and get_window_center to get the appropriate SDL coordinates. It then uses the new list of converted points to render the body onto the screen correctly.

We’ll do the same thing in our function to get a body’s bounding box. Since we’ve defined it in sdl_wrapper.c, we have access to the get_window_position and get_window_center functions.

Following sdl_draw_body as a guide, we’ll get the window center and use it to convert the top left and bottom right variables to the appropriate SDL coords.

Once you do that, you may also have to fix how we calculate .h for the SDL_Rect that we return since the top left corner now has a smaller SDL y coordinate than the bottom right corner. If .h is negative, the image won’t appear.

If done correctly, the images should overlay the bodies like this:

bounding boxes

However, there’s one problem: the bodies can be seen behind the images. There are two fixes for this:

  1. We can render a background image. Since we are rendering all the images after we render the bodies, the bodies won’t be visible.
  2. We can delete sdl_render_scene() from emscripten_main, since every body in our scene has a corresponding image.

In emscripten_init, replace the “TODO: (4c)...” with code to render the background image and add it to the state. Note that we shouldn’t be using asset_make_image_with_body since there is no body attached to the background image.

Even though we have a background image now, we might as well delete sdl_render_scene from emscripten_main since the bodies won’t be visible anyways.

Note that the code has a tiny bug in it that might be preventing the frog from showing up–make sure to fix it!

Congrats! You’ve implemented a basic version of Frogger.

Removing bodies attached to assets

In this frogger game, we aren’t removing any bodies. However, if we had removed any bodies that were attached to images (which you will most likely be doing for your game), this could lead to problems like memory out of bounds errors.

Why would this happen? The scene is currently responsible for calling force creators, which may mark bodies as removed and then free them all within scene_tick. If we then try to render all the image assets with bodies in the next tick, we’d be accessing memory that has already been free’d.

We briefly discussed the structure of scene_tick in project04, but here it is again for reference:

  1. Goes through all the force creators and applies them onto bodies, potentially marking some bodies for removal.
  2. Goes through all the bodies that are marked for removal, and removes any force creators that are supposed to act on them.
  3. Actually removes and frees the bodies that have been marked for removal.

Before step 3 occurs, we need to somehow go through all the image assets and remove any of the ones whose bodies are about to be removed.

Our proposed solution is the following: every time body_remove is run, body_remove will pass in the body to asset_remove_body, whose job is to find any image assets that contain that body and remove them if they do.

This is another reason why we decided to shift from having the user manage the list of assets to managing them in the asset engine: this way, we can have body_remove interact with ASSET_LIST behind the scenes (which the user can’t do because we’ve only provided an interface for body to them).

The general structure of asset_remove_body will be the following:

Importantly, don’t break out of the loop once this condition occurs–multiple image assets could be pointing to the same body.

Also keep in mind that you don’t have to explcitily call asset_remove_body anywhere–we call this function in our internal implementation of body_remove.

Final Task

Your game repo is presumably empty with the exception of your README.md. Since we’ve just completed our last physics-related project, please copy all your files from this project to your game repo to serve as the foundation for your game.