Setup
Before we start, register for the project using the registration tool.
Introduction
This week, you will be implementing a library to help manage the assets (like fonts, images, etc.) that we use in our games. So far, we’ve managed assets by storing them in our state_t struct. However, as our game grows, we will need a more organized way to create, store, and use assets. In this project, you will implement a library that will do exactly that! Finally, to test that it works, you will then use it to finish the implementation of a C meme generator using our new asset library.
Engine Changes
This week, there are no engine library changes to go over because you’ll be extending
the library yourself! However, you will need to copy over your collision.c file
from last week’s project into this week’s project in order for the game to work.
You may find this project to be a bit of a step up in difficulty, but don’t worry! We have written a lot of boilerplate code for you already and included pseudocode and examples in the code and documentation.
Overview
One of the pitfalls of storing all our individual assets in our state_t struct is having to find the variable in state_t that corresponds to the filepath of the asset we want to use. This is cumbersome for the programmer and error-prone. Instead, what if we had a struct like a cache, or store of assets, that could retrieve the asset for us using just the filepath? You will be implementing such a struct in asset_cache.c.
asset_cache_t
To implement the functionality stated above, we will manage our assets using a map that is built on top of our list_t struct! While our time complexity won’t be as good as a hash table, it will be more than sufficient for our purposes due to the small number of assets we will be using.
To implement key-value pairs, we will use an entry_t struct (provided in asset_cache.c) with the following fields:
-
filepath: This will be the filepath to the asset and serve as the “key” of our map. Note theconst char *type! -
type: This will be the “type” of asset that we are storing, represented as an enum that is defined inasset.h. You can take a look at it if you want, but all you need to know is that the only defined types areASSET_IMAGEandASSET_TEXT. -
obj: This will be a pointer to the actual asset that we are storing (e.g.SDL_Texturefor images andTTF_Fontfor fonts) and serve as the “value” of our map. Why do you think we are using avoid *type here?
asset_cache_init and asset_cache_destroy have been implemented for you. Before you take a look at them, here are a few things to keep in mind:
-
ASSET_CACHErepresents the single, “global”asset_cache_tthat we will be using. Since we only need a single instance of the cache to store all our assets throughout the lifetime of our games, we can declare it as a global variable in our file. -
FONT_SIZEis the font size that allTTF_Fonts will be rendered with. Since we can change the size of text by changing the size of the surface that it’s being rendered on, we will use this constant to simplify rendering text.
Now you’ll be implementing the rest of the functions in asset_cache.c!
Before you do, keep in mind that a function in sdl_wrapper.c that we’ve provided to you will be
helpful when implementing asset_cache_obj_get_or_create.
Task 1.
Implement the asset_cache_free_entry and asset_cache_obj_get_or_create functions in library/asset_cache.c.
Take a look at the documentation in library/asset_cache.h and the hints in library/asset_cache.c to understand what each function should do.
asset_t
Having an asset_cache_t is great, and now we can access assets by just using the filepath! However, we still have a few problems:
- We have to manage different
SDL_TextureANDTTF_Fontobjects. With each, we also have to store asset-specific information, like the text color for text assets. - Each asset also shares similar information like the bounding box (size of the render surface).
It would be much easier to abstract these details away and use a single struct that contains all the information we need to render an asset. That way, all we need to do is to initialize an asset with its appropriate arguments and render it. This is where the asset_t struct comes in!
The asset_t struct will contain a type field of the asset (corresponding to the same type field that we used in the entry_t struct!) and the bounding_box that stores the dimensions and location of the asset when it’s rendered. However, since each asset also requires type-specific fields, we will implement two other asset structs that “inherit” from asset_t using the C inheritance pattern. These structs have been defined for you in the asset.c file.
We’ve already written asset_init and asset_destroy for you. Note how asset_destroy is
effectively just free. Why is this the case? Make sure you know why before moving
on to the next task.
Task 2.
Implement the asset_make_text and asset_make_imagefunctions in library/asset.c.
Like before, take a look at the documentation in library/asset.h and the comments in library/asset.c to understand what each function should do.
Now, we’ll tie everything all together by implementing the asset_render function. As
written in the hints, you’ll need to use functions defined in sdl_wrapper
to render the asset onto the screen. We’ve already provided you with a function to render
images. This means that you may need to write your own function to render text
onto the screen.
However, you might have already written this function before in one of the previous projects–if you have, feel free to copy it over into this project and use it! If you didn’t, use the function we gave you as a model to write it.
Also keep in mind that you might’ve hard-coded the color of the text before. However, we’ve defined our text assets such that we want to give the user the flexibility of using any color they want.
The slight issue with this is that our library uses the color_t type, but the
SDL/TTF libraries use the SDL_Color type. As a result, you’ll have to convert the
color_t to an SDL_Color when you render text. Feel free to make a helper function for this.
Task 3.
Implement asset_render in library/asset.c.
Like before, take a look at the documentation in library/asset.h and the comments in library/asset.c. You
may also have to write a function to help you render text onto the screen–make sure you
think carefully about which file this function should go in.
Game
Now, it’s time to put it all together! We have given you starter code for the meme generator project in game.c.
After you finish writing one function, you’ll end up with a C meme generator that can rotate between memes by pressing the spacebar:
Before you start, take a look through game.c. We want you to focus on a few things:
- We’ve defined a
meme_tstruct that contains information about the actual meme. At the top of the file, we’ve defined a constant arrayMEME_TEMPLATESthat contains the info for each meme we will be using. - To render the memes onto our screen, we manage lists of assets that correspond
to the memes we want to display. This is the general pattern that we’ll use with our asset
engine: the demo/game keeps track of assets and uses
asset_render.asset_cacheworks behind the scenes (since it’s being called inasset.c), and the user shouldn’t need to interact with it outside of usingasset_cache_init(called inemscripten_init) andasset_cache_destroy(called inemscripten_free). Keep in mind that the order of how the assets are rendered matters here! - We’ve defined a key handler
cycle_memesto change the meme via user input. - We’ve given you a
get_dimensions_for_textfunction that should be helpful to figure out the width and the height of the bounding box for your captions.
The only thing you need to implement is the generate_memes function. We’ve included documentation about what it does, but also take a look at where generate_memes is being called and what emscripten_main is calling to get a general sense of how it’s being used. Additionally,
we’ve implemented emscripten_free for you. Notice how short it is–make sure your implementation of generate_memes
makes sense given how emscripten_free is written!
Task 4.
Implement the generate_memes function in game.c to complete the meme generator. Run
make game to make sure it works before submitting.
Note that the meme generator can still work without the asset/asset_cache library being
written correctly (e.g. since emscripten_free is not actually being called). Since
there are no auto tests to check if your library is written correctly,
course staff may manually check your implementation, and its correctness could impact a significant portion of your grade.
Now you’ve created a meme generator that is much more encapsulated, abstracted, and easy to use
than a version that would store all the assets in its state.
This will pay huge dividends for your game later down the line. Great work!
