Introduction to CUGL

The purpose of this assignment is to force you to become familiar with CUGL. We have found that so many people wait until the last minute to get CUGL up and running, and as a result, the games in 4152 feel like they are a little bit behind those in 3152. The purpose of these new assignments is to prevent this from happening.

The good new is that, if you took 3152, then this assignment should look familiar to you. It has the same ship and background as ShipDemo, the first lab from that course. But instead of an enemy ship, we now have asteroids. Asteroids collide with the ship and cause it to lose health. Your job is to add some photon torpedoes so that the ship can defend itself.

Asteroids

As a reminder, this assignment is graded in one of two ways. If you are a 4152 student, this is graded entirely on effort (did you legitimately make an attempt), and that effort will go towards you participation grade. If you are a 5152 student, you will get a more detailed numerical score.

Finally a word about scope. CUGL is a cross-platform library that can build for many different devices. But different devices have different input schemes (e.g. mobile devices do not support keyboard controls). To keep things simple for this first assignment, we will be focusing on desktop platforms only. That means you should be working in either XCode (for macOS) or Visual Studio (for Windows). Furthermore, because there are very subtle differences between the two IDEs, you need to tell us which one you used for development. This will aid with grading.

Table of Contents


Useful References

As with the first lab in 3152, a large part of this assignment will be reading documentation and learning where to look for information. We have kept this assignment simple, so you do not need to learn any of the third party libraries like box2d. But the following links are important for this assignment.

CUGL Class API

The CUGL C++ classes are the primary classes that you will use to write your game. These classes, plus a little Box2D and maybe some OpenGL should be enough for your to develop any kind of (2D mobile) game you want this semester. The API above is a Javadoc-style web page generated from the class headers.

C++ API

This website is a good collection of API documentation and tutorials. It is less official than cppreference.com, but contains a lot more information. It is my goto when I need to look up something about a standard library class.

SDL API

This API consists of all of the functions and structs (classes without methods) provided by SDL. For the most part you should not need to program in SDL. We have wrapped the most important features into CUGL classes for you. If you are an Android developer, it does help two know the JNI extensions. But since this assignment is desktop only, this is not relevant here.


Project Files

Download: ShipLab.zip

You should download the project for this assignment. This is a self-contained project just like all of the ones available from the demos page. You can program in XCode, Visual Studio, or Android Studio. For this assignment, you will only be using XCode or Visual Studio. See the engine documentation for how to build and run on your desired platform.

Running the ShipLab

Technically, this game will run (i.e. not crash) on all platforms. However, it will not actually do anything on mobile devices just yet. That is because the InputController only supports keyboard controls right now. While you are welcome to add these controls later, that is not the focus of this assignment.

The desktop, on the other hand, provides you with a little more control. As you can see you have you ship in the center and asteroids passing by on the screen. While asteroids will pass through each other, they will collide with the ship. And when they collide with the ship, they damage the ship, as you can see from the health meter. We know this is a bit unfair, but this is how the original Asteroids worked.

Right now the only thing the ship can do to defend itself is move. You move with the arrow keys. Left and right will turn the ship causing it to bank. Up arrow will thrust forward while down arrow will thrust backward. There is no friction in space, so the ship will not slow down on its only. The only way to slow the ship down is to provide a thrust in the opposite direction. While this can make the ship hard to control at times, this is once again how things worked in the original Asteroids.

Finally, you will notice that this game supports wrap-around. When either the ship or the asteroid goes off one edge, it comes back around the opposite edge. This has important ramifications for how objects are moved, drawn, and collided with.

Your goal in this assignemnt is to add photons to defend the ship. This includes

  • Making a fresh C++ class for the photons
  • Drawing the photons on screen
  • Deleting photons when they go too far
  • Writing code to detect photon-asteroid collisions
  • Breaking up asteroids or deleting them on a collision

Project Structure

We will talk about the exact architecture of CUGL in a future lecture. But as we have not had this lecture yet, we will go over the basics.

The Root Classes

The file main.cpp is the main entry point for this project. For those of you from the introductory class, it is the analogue of DesktopLauncher. It sets the name of your application and the scren size. Since you do not need to change either of those, you will not be making any changes to this file. As far as we are concerned, the main entry point is the root class ShipApp.

Th class ShipApp is a subclass of Application. It has methods for starting for starting the game, running the animation loop, and shutting the game down. However, it does not do the real work of running the game. It only creates three classes, as shown in the dependency graph below.

root-architecture

An arrow from one class to another means that the first class makes a reference to the second class in its code. Throughout this assignment you will note that there are no cycles in the dependency graph. Remember from 3152 that this helps keep the code manageable (though this assignment still has a lot of room for improvement).

The classes GameScene and LoadingScene are examples of game modes, which we mentioned in the Mechanics Revisited lecture. A mode is just any self-contained way of interacting with the game. A mode can be a menu screen, an inventory screen, combat screen, or whatever. In this game are two modes are the loading screen (LoadingScreen) and the game (GameScreen). Each of these is a subclass of Scene2.

The third class is the SpriteBatch, and those of you from 3152 should remember what that is. This is exactly the same concept. It allows you to batch sprites together in a single 2d mesh and sends everything to the graphics card in bulk. This class is actually part of CUGL, so there is no custom code for this batch in this assignment.

Once again, those of you who took 3152 will remember that we wrapped SpriteBatch in another class called a GameCanvas. The purpose of the GameCanvas was to include important information like the screen size and the orthographic projection. In CUGL, all of that information is actually part of the Scene2 classes. That is because of how scene graphs work. While you will not work with scene graphs in this assignment, they will become incredibly important later.

The Game Scene

All of the other classes are associated with GameScene as their root class. That is, only GameScene is responsible for instantiating the other classes in the game, and not ShipApp or main. As with 3152, this code follows a classic Model-View-Controller pattern.

scene-architecture

The illustration above shows the dependency graph for the remaining classes in package shipdemo. We summarize the important classes as follows:

GameScene

As far as this assignment is concerned, GameScene is the true root class. It initializes the game, creating instances of all other classes. It also manages the game-loop via the update and render methods. If you add new objects to your game, you will need to modify this class.

There is an interesting side-note about the render method. The parent class Scene2 already has a render method and can handle drawing automatically. That is because the scene has an associated scene graph, and CUGL sprite batches know how to draw these. But in this case we have overridden the render method in GameScene. That is because we are no using scene graphs. Instead, we are drawing directly to the sprite batch in a way that students from the introductory class should be familiar with.

InputController

InputController is a subcontroller for managing player input. It converts the input from low-level commands to something semantically meaningful (e.g. the game verbs). This is an incredibly important class for CUGL, because different devices support different forms of input. Some devices have keyboards while others don’t. Similarly, some devices have touch screens while others don’t. The purpose of this class is to abstract these issues away from the rest of the application.

Right now, this class only supports keyboard commands, and hence only works on the desktop. Mobile support is beyond the scope of this assignment. Indeed, you should never need to modify this class.

CollisionController

This is a subcontroller for handling ship-to-asteroid collisions. Eventually you will extend this to include ship-to-photon collisions as well. Collisions and physics should always be in a dedicate controller. A model class (like Ship) should never manage its own collisions, since collisions are a binary relationship between (potentially) multiple classes.

Because this is one of the classes that you need to modify, we should talk about its structure. You will notice that it has a init method in addition to the constructor. Indeed, the constructor does not do anything, init does all of the work. Why is that?

Notice that we do not use a pointer to reference the collision. That is because this is an non-dynamic object. We talk about this in the C++ classes lecture. The object was not created with new. So exactly when was it created? Since it is a field of GameScene, it was created exactly the moment GameScene was created.
And GameScene is a non-dynamic field of ShipLab, it was created the instance that ShipLab was created. And ShipLab is a stack-based object created on the first line of main.cpp. So in other words, this object was created at the very beginning.

More important, it was created before we actually knew what our window size was. All mobile devices are different, so you do not know your window size until the game starts up. And that window size is what we need to initialize CollisionController (to support wrap-around). So we need to initialize CollisionController after the object is actually created. This is quite common in games, and indeed is the primary programming pattern for Objective-C, the “native” programming language for iOS. This is the reason why almost all CUGL classes have constructors that do nothing, but init methods that do the proper work of a constructor. The only exceptions are math classes (vectors, matrices) which are designed to be fast and lightweight.

Of course all of this could be avoided if we just used a pointer and delayed allocating CollisionController until we needed it. But this is unnecessary, and doing it this way allows us to eliminate pointer overhead. This is very common with controllers. With that said, you should feel free to dynamically allocate controllers in your game project if this makes sense.

As one last bit of warning, remember that non-dynamic objects use a period instead of an arrow to reference their fields and methods. This can trip you up a bit in this assignment as you will be combining objects that use pointers with those that do not.

Ship

This class represents a ship. It has spatial information like position, velocity, and rotation. It is a fairly lightweight model. Many of the methods that you would expect for a Ship object to have are actually managed by the controllers.

This model class is different from the collision controller in that we dynamically allocate it. But you will notice that we do not use the keyword new. We do not use raw pointers in this class if we can help it. The use of new leads to memory leaks and memory leaks are very, very bad.

Instead, if you dynamically allocate an object, you should use a shared pointer. Let’s look at this line of code:

_ship = std::make_shared<Ship>(getSize()/2, _constants->get("ship"));

The type of _ship is std::shared_ptr<Ship>. For most intents and purposes is works the same as a Ship*. You can pass it around by reference, you can dereference it, and you access fields and methods with an arrow. In addition, the std::make_shared<Ship>() is a replacement for new Ship(). The arguments work exactly the same and it calls exactly the same constructor. The only difference is the return type.

The reason to use a shared pointer is because of what happens when you set it to nullptr. Unlike raw pointers, shared pointers support garbage collection and C++ will delete them immediately. We talk about this in the C++ memory lecture. The performance hit for using a shared pointer over a raw pointer is so slight that in only matters in the most high performance calculations (e.g. things that CUGL does in the background). So you should always use a shared pointer in place of new

AsteroidSet

This class is a set that stores all the active asteroids. While the asteroids are themselves represented as shared pointers (because they are dynamically allocated as they are created), the set itself is not. The set contains information common to all asteroids, such as their mass, the amount of damage they cause, and the texture used to draw them.

The asteroid set also has two sets: a pending and a current. That is because we will want to add new asteroids to the set at the same time we are currently looping over the asteroid set. It should be pretty obvious why it is a bad idea to add things to a set while we are looping over it (though deleting from such a set is okay). The pending set allows us to queue up added asteroids, and add them to the system when it is safe to do so.

The other thing to notice about asteroid code (particularly in CollisionController) is that we make heavy usage of C++ iterators. You should look online for tutorials on exactly how they work. They return a (raw) pointer to a memory location to an object. That means if you are using an iterator on a set of smart pointers, you have to dereference the iterator once to get the smart pointer, and a second time to get the fields or methods of the smart pointer.

Why do we do this? First of all, because iterators are super fast. But more importantly, it makes deleting items in a loop much, much easier. And that is something that we do a lot in games.

Other Features

That is an overview of the classes we have provided. However there are some CUGL and C++ features you should be familiar with.

Asset Management

Those of you from the introductory course may remember that we added an extension to LibGDX to support asset loading by JSON files. You specified all of the assets in a JSON file and the asset loader automatically loaded them for you. This asset loader was filled by the loading screen, and all assets where accessed by their JSON key.

This extension was actually copied from CUGL, where we first implemented this idea. If you look at the assets folder you will see several JSON files. The file loading.json is to bootstrap the game with minimal assets for the loading screen. The file assets.json defines all the assets used by the game (including some we have not used just yet). And the file constants.json is an extra set of constants for defining how ships and asteroids work. Read the descriptions of those classes to see what happens when you modify this file.

SpriteBatch Drawing

The sprite batch is the primary graphics pipeline for 2d games. While CUGL supports 3d graphics (and we have had 3d games in the past), you are much more on your own there. We have not provided a preconstructed 3d graphics pipeline (though we will talk about this in a later lecture).

You start drawing by calling begin and finish it by calling end. True to its name, all drawing calls are batched until end is called, at which point they are all processed at once. There are a lot of possibly drawing calls supported and you should look at the SpriteBatch documentation to see all of them. For the most part this assignment only needs the ability to draw a texture within a rectangle, like we do with the background image.

However, there are two exceptions. The first is a SpriteSheet. A sprite sheet works like the FilmStrip class from the introductory course. It breaks an image into multiple frames and allows the user to animate these frames. While there is only one asteroid texture, there are multiple asteroid sprite sheets, one for each asteroid. That is because each asteroid could be at a different animation frame. But since they all share the same texture, this class is very lightweight.

In addition, the sprite sheet handles its own drawing. By that we mean you pass the sprite batch to the sprite sheet instead of the other way around. We take this approach when we want to extend the capability of the sprite batch. The sprite batch does enough as it is, and does not need a new method every time we think of a new drawing type.

The other exception is drawing text. As some of you have heard me rant, working with fonts and drawing text is incredibly complicated. But fortunately CUGL handles all of these details for you. There is a class called TextLayout. You assign this class a font and the text to draw, and it creates all of the drawing information for you. You then pass this to the sprite batch to draw. We have done this for the health meter. Note that we change the color to black before drawing. That is because the default color for fonts and all other objects is white in order to support color tinting.

The other thing to keep in mind about a text layout is that it is a layout. You can specify the size of the box and use this to create multiline text. But it also means that you must call layout whenever you change the text. If you do not do this, nothing will be drawn.

Object Allocation

One of the things you learn from this assignment is that sometimes you want a non-dynamic object and sometimes you want a dynamic object. And whenever you want a dynamic object, you typically want a shared pointer. That is the reason for a particular pattern in use by all CUGL classes outside of the math package.

If you want a non-dynamic object, you initialize it by calling init. This allows you to initialize an object long after it is actually created. However, std::make_shared calls the (useless) constructor and not any of the init methods. Thereforce, if you want a dynamically allocated shared pointer, we have provided a static method called alloc. Just like std::make_shared corresponds to a constructor call, each alloc method corresponds to an init with the same name and arguments. For example, to dynamically allocate a new sprite batch, you would call

   std::shared_ptr<SpriteBatch> batch = SpriteBatch::alloc();

Once again, this init-alloc pattern comes from Objective-C, the native language for iOS. But it is in general a nature pattern for programming games. With that said, you are not require to construct your own classes using this pattern. You should use what is most natural.

The const Keyword

If you have never programmed in C++ before, you maybe confused by the word const that appears everywhere. This is type feature that allows you to specify that something cannot be modified. But you don’t just apply it to variables. You can apply it to methods as well.

It is very easy to get yourself in trouble if you do not know how to use const properly. The type system will complain that you have a const violation because you are secretly modifying something you promised that you would not. This is known as “const hell.” The easiest way to prevent you from falling into this trap is not to use const for now. Just drop the keyword.

The most counter-intuitive use of const is with shared pointers. Consider this method

    bool resolveCollision(const std::shared_ptr<Ship>& ship, AsteroidSet& ast);

We do not have a const before the reference to the asteroid set, as we may want to modify the set. But why then do we have a const before the ship? Surely we want to modify that is well. Well, we can modify the ship. The const just says we cannot modify the pointer to point to a different ship; modifying the contents of the ship is just fine.

Why do we do this? If you notice, we also add an & sign to the pointer, which means we are passing the pointer by reference. It might seem redundant to pass a pointer by reference. But what we are saying is that we don’t want this function to copy the shared pointer, as copying a shared pointer asserts ownership, and that adds expensive overhead we do not want. But we also do not want to allow the function to destroy the shared pointer itself, hence the const. Indeed, this approach to shared pointers is considered best practice even outside of CUGL.

IMPORTANT

We discovered in class there is a typo in the code documentation. It says that the ship angle starts in the east and is measured in radians. This is wrong. The ship angle starts facing to the north and is measured in degrees.


Instructions

Throughout this assignment you will modify three files

  • SLPhotonSet.cpp and its header
  • SLCollisionController.cpp and its header
  • SLGameScene.cpp and its header

The last two have a lot of code already written, and you can use the code there to help you figure out what to do. The first two files are completely blank, however. It is up to you to learn the C++ to fill them. Fortunately, PhotonSet and AsteroidSet set have a lot in common.

1. Create PhotonSet and Photon

As we hinted, you want to make a photon set and you will use asteroid set to guide how you do it. The photon set and the asteroid set should have the following things in common:

  • PhotonSet is a class with an unordered_set that contains individual photons
  • Photon should be an inner class of PhotonSet
  • PhotonSet should contain shared pointers of Photon objects
  • You should initialize PhotonSet with the photons constants from the JSON file

However, they have the following differences:

  • PhotonSet only needs one set, as we will never add a photon while looping over them.
  • Photon objects do not need a sprite sheet as they are not animated

The class PhotonSet should contain the attributes that all photons have in common:

  • speed: The initial speed of a photon (to multiply by a direction vector)
  • mass: The mass of a photon
  • radius: The radius of a basic photon
  • maxage: The lifetime (in animation frames) of a photon before deletion

All of these are defined in the JSON file except radius. The radius will be defined by the size of the texture (which we will handle in the next step).

The class Photon should have the following attributes:

  • position: The photon position
  • velocity: The photon velocity
  • scale: The drawing scale to vary size
  • age: The current photon age (start at 0)
  • maxage: Another reference to maxage so the photon can compare.

For the most part, this is a simplified version of how asteroids work. The only issue is the age, which we can ignore for now. We do not care if the attributes are public or private – that is up to you.

We will not worry about making new photons just yet. But you should initialize the photon set in GameScene using the contents of the JSON file. This will require a modification to both GameScene and its header (to add a new attribute) . Again, look to the asteroid set for guidance.

Since nothing is drawn yet, you will need to use print statements to debug you code and make sure that this initialization has gone correctly. The function CULog will help you here. This is a cross-platform version of printf that was covered in the C++ basics lecture.

2. Generate Photon objects

You should add a method to spawn new photons to PhotonSet. Unlike asteroid, there is only one type of photon. But when the asteroid is spawned, you will need to know the location, velocity, and angle of the ship.

You will create the photon at the same location as the ship (because the ship is firing it). For the velocity, create a unit vector with the same angle as the ship and multiply this by the default photon speed. However you should also add the ship velocity to this vector so that the ship does not run into its own photons. New photons should start out with an age of 0 and a scale of 1.5.

You will also need to add update methods to the photon set and the photon. The photon set update should loop through the photons, calling the update method for each. Futhermore, if any photon reaches its max age, you should delete the photon. Once again, use the technique for deleting items in a loop.

The update method for an single photon should do four things:

  • Add the velocity to the position
  • Wrap the photon if it goes out of bounds
  • Advance the age by 1
  • Update the scale to be 1.5 - 1.5*age/maxage

Once again, the asteroid set is a valuable reference.

Add calls to the update method to GameScene. You should also add code to fire a photon. Fire a photon only if the user presses the fire key (space) and the ship is ready to fire.

Once again, you are not ready to draw anything. Use CULog to test that photons are successfully being created when you press fire. You should also make sure they are being deleted correctly.

3. Draw the Photons

Now it is time to draw the photons. This is both easier and harder than the draw code in asteroid set. Add a texture attribute to PhotonSet and assign the appropriate texture using the asset manager in GameScene. There is no sprite sheet to worry about since photons are not animated. Individual photons do not need a reference to this texture either. But you should use this texture to set the radius of the photons. The radius is half of the maximum of the width and height of the texture.

When you draw the texture you will need to scale the image to match the photon scale. You should use an Affine2 transform to scale and position the image of each photon. You will want to use the SpriteBatch method that allows you to specify an origin. The origin should be the center of the texture. Otherwise, the sprite batch will put the bottom left corner of the texture at the position you specify.

You should also remember to support wrap-around by drawing an additional image whenever a photon crosses an edge of the screen. Again, look at asteroid set for guidance. You need to wrap when the edge of the photon crosses the edge, not just the center. That is why we need to add the radius. Remember that the true radius of a photon is the basic radius times the scale.

When you are done you should be able to visually inspect your work by firing your weapon. It is probably a good idea to test the basic code before you implement wrap around. But once you do have wrap-around, fire at the edge of the screen to verify it works.

4. Add a Sound Effect

We want to give some extra feel to the weapons system with a sound effect. There are two appropriate sounds effects for the photo: “laser” or “fusion”. Pick the one you like best and use AudioEngine to play the sound. There is an example for the sprite-asteroid collision that you can use as a guide.

For the most part, the audio engine is straight forward. The only thing that may be confusing is the key (particularly if you did not take the introductory course). Every sound must have a key. This key uniquely identifies that sound instance so that you cancel it later or make changes. In fact, you can only have one sound at a time with a given key, so playing a sound with the same key will cancel the first one. This is a way to keep from overloading your sound card.

5. Add Collision Code

It is now time to add collision code to support photons. You should make a new method in CollisionController that checks for photon-to-asteroid collisions. Detecting a collision is easy; just look at how we check for ship to asteroid collisions (everything is a circle). The only issue is what to do when a collision happens.

The answer depends on the type of the asteroid. In all cases, both the asteroid and the photon are destroyed. You should remove them from the appropriate set. But if the type of the asteroid is any value greater than 1, you should replace the asteroid with three asteroids of one less type (and hence smaller size). The photon is essentially breaking the asteroid apart.

When you generate the three asteroids, they should all start with the same position as the old asteroid (asteroids do not collide with each other). But they should have three different velocities. The first should have the same speed as the old asteroid, but the direction of the photon. The other two should be 120 degree rotations of this velocity vector. This is shown in the pictures below (but remember the asteroids start in the same position).

collision1 collision2
Collision Aftermath

Remember to call this method in GameScene so that you actually do check for collisions. Finally, you should play a sound effect if there is a photon-asteroid collision. We recommend the “blast” sound effect.

7. End the Game

Congratulations! The ship can now defend itself. But to properly make this into a game, we need to add an ending. The game ends when the ship reaches 0 health or all asteroids are destroyed. The first is a loss and the second is a win.

When the game ends, all animation should halt. And you should display a message on the screen. This message should be centered on the screen and tell the player whether or not they won or lost. To do this, you will use a TextLayout object, just like we used for the health meter.

But here is the catch: fonts are fixed to a certain size. We will talk about why this is in a future lecture. But the current font is too small for a win-loss message. We want it three times the size. However, you are forbidden from loading a font of a larger size. Instead you should scale the text by a factor of 3 before drawing it. We also want you to color the text. It should be green for a win and red for a loss.

If the user wants to play again, they should be able to do so by pressing the R key. In fact, the R key means that the player can reset the game at any time, even while still playing. To properly support this, you need to add (re)initialization code to the reset method in GameScene to clear out any stray photons.

Once you complete this you are done with the assignment.


Submission

Due: Thu, Feb 02 at 11:59 PM

This is reasonable amount of work for a masters student to complete in a week. In fact, if you already know C++, you could probably complete this in a long evening (though do not wait until the night before!). But we think this is an excellent tutorial to show you the basics of the engine. And in our experience you will probably spend more time just getting the engine to compile for the first time, as you learn XCode or Visual Studio.

When submitting the assignment do not submit the entire project. That is too large and CMS will crash under the load. Instead we want you create a zip file containing the files:

  • SLGameScene.h
  • SLGameScene.cpp
  • SLPhotonSet.h
  • SLPhotonSet.cpp
  • SLCollisionController.h
  • SLCollisionController.cpp
  • readme.txt

The file readme.txt should tell us if you worked in Visual Studio or in XCode. That will help us in grading your work. In addition it should describe (very briefly) how you handled each task and where in the code it was handled.

Submit this zip file as lab1code.zip to CMS