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_IMAGE
andASSET_TEXT
. -
obj
: This will be a pointer to the actual asset that we are storing (e.g.SDL_Texture
for images andTTF_Font
for 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_CACHE
represents the single, “global”asset_cache_t
that 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_SIZE
is the font size that allTTF_Font
s 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_Texture
ANDTTF_Font
objects. 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_image
functions 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_t
struct that contains information about the actual meme. At the top of the file, we’ve defined a constant arrayMEME_TEMPLATES
that 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_cache
works 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_memes
to change the meme via user input. - We’ve given you a
get_dimensions_for_text
function 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!