Introduction
This week, you will add buttons to the asset library that your teammate implemented last week! These will hopefully help with any GUIs that your team decides to incorporate into your game in the latter half of the course. To test that it works, we will once again be improving on the meme generator from previous weeks by adding next/back and play/pause buttons to look like the following example:
Important Changes
We have changed get_keycode and sdl_render_scene in the wrapper this week, so
be sure not to overwrite any of those changes when copying and pasting code from
previous weeks over.
Overview
As you saw last week, implementing images and text as assets makes it much easier to intialize, manage, and use them. Thus, it makes sense to treat buttons as assets too. We’ll start by making the necessary changes in asset_cache.c.
If you haven’t already, talk with your teammate who implemented the asset cache (game component) last week to gain an understanding of how it works.
asset_cache_t
First, copy and paste your implementation for asset_cache_obj_get_or_create and asset_cache_free_entry from last week. Remember to copy any helper functions over as well (if your team had any).
Next, implement asset_cache_register_button. This is similar to the “create” part of asset_cache_obj_get_or_create, only that we’re creating buttons and never retrieving them outside of asset_cache. To stay consistent with how asset_cache functions, we’ll need to wrap the button in an entry_t type. We can do this by setting the conveniently typed obj field to the button and filepath to NULL. Remember that all buttons should also be added to ASSET_CACHE!
Now that the filepath of some entry_t structs are NULL, some of our functions may break. Check to make sure that you’re not assuming filepath is always non-NULL; if you are, rewrite those functions accordingly.
Note on Ownership
When buttons are registered, they are added to ASSET_CACHE, whose contents are ultimately freed when asset_cache_destroy is called. In other words, asset_cache.c has ownership over all buttons. Thus, buttons
should not be manually freed in game.c or anywhere else.
Consequently, we’ll add a case for the ASSET_BUTTON type in asset_cache_free_entry. Remember that you should
only be freeing what obj points to.
Finally, implement asset_cache_handle_buttons. The documentation in asset_cache.h tells you what to do. The function is simple to implement but very powerful: calling it activates all button handlers that need to be called. Don’t worry about how asset_on_button_click is implemented for now; we’ll come to that later.
Task 0.
Implement the asset_cache_register_button and asset_cache_handle_buttons functions and modify
asset_cache_free_entry in asset_cache.c.
sdl_wrapper
Now, we’ll add asset_cache_handle_buttons in sdl_wrapper. More specifically, we’ll add it in sdl_is_done, which is where we detect if the mouse has been clicked. Every time it does, we want to run asset_cache_handle_buttons to run any button handlers appropriately.
After you’ve done so, take a look at emscripten.c and look at the game loop. Pay special attention to the fact that we run sdl_is_done after emscripten_main runs. This will be very important in the next step.
Task 1.
Call the asset_cache_handle_buttons function in sdl_wrapper.c.
asset_t
Like images and text, we’ve defined a button_asset_t struct in asset.c using the C inheritance pattern. Here’s a quick breakdown of some new fields:
-
image_assetandtext_asset- Buttons will have the option of rendering images and text. Note how we’re using the encapsulatedimage_asset_tandtext_asset_tstructs-that significantly simplifies ourbutton_asset_ttype. -
handler- This is the function that should run whenever the button is clicked. It has abutton_handler_ttype that we’ve defined for you inasset.h. Currently, it only takes in the game state, but feel free to add any parameters as you see fit later on.
We’ll come back to is_rendered later.
First, copy and paste your implementations of asset_make_image, asset_make_text, and asset_render from last week.
Note: You may be wondering if we have to change asset_destroy for buttons to free the image and text assets that
they point to. This is a design decision: We’ve decided to code our library so that the user who
initializes the button asset has ownership over the image and text assets that it points to. The reason
for this is because it’s common for buttons to share the same image (like the “Next” and “Back” buttons in the game),
and it’s
nice to be able to reuse the same image asset rather than creating duplicates for every button. In this case,
freeing the image and text assets for every button would lead to double-free errors.
Your first task is to implement asset_make_button. Make sure to use the asset_init function that we’ve defined for you to reduce code duplication. It should be pretty similar to the two functions above it.
Next, implement the ASSET_BUTTON case in asset_render. Here are a few things to keep in mind:
- Be careful about code duplication here. Think about what fields we included in
button_asset_t. - Order matters for rendering the image and text!
Finally, we’ll implement the asset_on_button_click function. First, take a look at the documentation for it in asset.h. Here is a breakdown of the general steps you should take:
- Check if the location of the mouse is within the bounding box of the button (We recommend writing a helper function).
- If we’ve reached this step, we can run the button handler.
Task 2.
Implement the asset_make_button, asset_render, and asset_on_button_click functions in asset.c.
Like before, take a look at the documentation in asset.h to understand what each function should do.
This implementation works great most of the time, but now we have a problem. Let’s say we intialized a game with a start button in the middle of the page. Once it’s pressed, the game starts, and the user is free to use their mouse/keyboard. What happens if they clicked in the middle of the page again? Based on what we wrote here, the start button handler would be activated, which is definitely not what we want to do.
How can we can prevent this problem? We know we only want to activate a button handler when it’s clicked
and it’s rendered onto the screen. That’s where the is_rendered field comes in.
Here’s the general logic for how we’ll use it: every time the button is rendered, we update the field
appropriately. Then, after every game loop, we reset is_rendered for every button.
The first fix is easy: we can modify asset_render so that is_rendered is updated appropriately.
For the second fix, we’ll have to modify the asset_on_button_click, which checks if the button handler should be run.
We’ll add the following steps all above the code that we wrote previously.
- Check if
buttonis rendered. If it’s not, we canreturn. - Reset the
is_renderedfield. Why reset it here? Look at whereasset_on_button_clickis being called, and compare it to whereasset_renderis called. How do we know thatasset_on_button_clickwill always run after all calls ofasset_render? (Hint: it relates to the earlier section about the game loop!)
Task 3.
Modify the asset_render and asset_on_button_click functions in asset.c.
Game
Now we can put everything together! We’ve given you starter code for the meme generator project in game.c. We’ve set up all of the button handlers for you, along with the button information using a button_info_t struct. You will have to copy and paste your team’s implementation of generate_memes from last week.
The only thing you will need to implement is the create_button_from_info function. Before you do, take a look at the rest of the file to get a general sense of how the function is being used. Trust us; it will make a LOT more sense if you do this before implementing create_button_from_info.
Finally, here are some notes about this game.c file that may be helpful:
- Note that we’re not actually free’ing any of the buttons in
game.c. Since they’re being registered and stored in the asset cache, they will all be freed whenasset_cache_destroyis run. -
emscripten_mainreturns a bool now. This is more relevant for thedemostudent, but it essentially signifies if the game has ended. - Buttons don’t have to include both an image and text.
Task 3.
Implement the create_button_from_info function in game.c to complete the meme generator.
Open-ended Questions
Task 4. When you are done, your group should meet and complete the questions here.
