Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Rock Blaster is a top-down Asteroids-like game. The following tutorials introduce a variety of advanced topics. These tutorials continue to advance the concepts and FlatRedBall functionality introduced by Beefball.
Note: RockBlaster contains useful information about how to use the FlatRedBall Editor (also referred to as Glue in these tutorials), but it is a tutorial which was written before many of the current FlatRedBall features were created. Therefore, some of the approaches in this tutorial may not represent the current recommended patterns.
Welcome to the Rock Blaster tutorials. Rock Blaster is a simple top-down game similar to the old Asteroids game. This set of tutorials will cover a variety of Glue topics. If you've been through the Beefball tutorials, this set of tutorials will build on that information and show more Glue functionality. If you haven't yet, we recommend going through the Beefball tutorials first - they cover a lot of the basics that we will use in this game.
The Rock Blaster tutorials will reinforce some of the topics covered in the Beefball tutorials including:
Entity creation
Screen creation
Object creation
Variable creation
File creation and management
Collision
Scoring
Hud
Game Flow
The Rock Blaster tutorials will include the following topics as well:
Handling game graphics (PNGs)
Rotation
Dynamic Entity creation
Game data (score)
And much more!
So let's get started!
The following tutorials will use a set of art from a classic PC game named Tyrian which has been opened by Dan Cook, the original artist on the game. Thanks to Danc for supporting game development!
This tutorial begins with the creation of a new project in FlatRedBall. We'll make a new Desktop GL .NET 6 project.
To create a new project:
Select File -> New Project or click the New Project button in the Quick Actions tab if you do not have a project open already
Enter the name RockBlaster (no spaces) as your project name
Uncheck the Open New Project Wizard option - we will run the game first before running the wizard to make sure everything is set up correctly
Click Create Project!
Your project should now be open in the FlatRedBall Editor:
Press the play button to launch the (empty) game.
Next we'll run the New Project Wizard which will jumpstart the development process by adding common Screens, Entities, and other files to our project. To run the Wizard, click the Run Wizard button in the Quick Actions tab.
Check the Custom (Advanced) option.
Select Start Wizard from Scratch.
We will change a few of the default options. Follow along with these images and make your options match.
Our game will not use Tiled Map files. CloudCollision is only used for platformer games.
Our player will not use default control types like Top-down or Platformer. We will be implementing our own custom controls, so select the None (controls will be added later) option. Our player will rotate, so Circle collision is preferred to Rectangle collision. Also, our player is not a platformer character, so uncheck Add Player vs. cloud collision.
Change Number of levels to create to 1. Uncheck the other options since our game does not have any Tiled Map files.
Leave UI options unchanged. We will use Gum to display game HUD.
Set the Game Resolution to 800x600 and the Game Scale% to 100. Uncheck all other Camera options. Our game will not have a Camera which moves.
Skip the option to Download/Import screens by clicking Next.
Skip the option to add additional objects by clicking Next.
Click Done. Wait a moment and your project will be all set up.
If we run our game now we will see our player which is a white circle.
That was easy! You now have a project that we will use in the following tutorials. Next we will set up the skeleton (the general structure) of our game.
So far we've done a lot of work on our project, but the only visual thing in our game is a white circle. This tutorial will focus on the Player - the Entity which will be directly controlled by our input devices.
Before we start working on our Player entity, keep in mind that entities do not have to be fully-defined all at once. If you are familiar with the genre you that you are creating a game for, or if you have very detailed plans about what you are creating, then you may be able to define your Entities very thoroughly the very first time you work with them. However, if you still have some questions in your head about how the game will play or how objects will behave (this is very common, so don't feel bad if you do) then you may implement some of your Entity before you play the game and possibly move on to a different area of the game. This tutorial will take an iterative approach to defining Entities, as this is more common in real game development. This means we will define some of the Player Entity now, and we'll return to add more in later tutorials.
Next we will add these four ship files to the Player entity. To do this:
Expand the Player Entity in Glue
Drag+drop the four files onto the Files folder in your entity
You should now see the files as part of your Player Entity.
Where are my files located? If you saved the .png files in the project's Content folder, then Glue will reference the .png files from their current location. If you saved the files outside of this folder, then Glue will copy them into a folder specific to the Player Entity. If you ever need to find the files, you can right-click on the files in Glue and select View in explorer.
Now that we have four images in our game, we need a Sprite to display them. To add a Sprite to your Player Entity:
Select the Player entity
Select the Quick Actions tab
Click Add Object to Player
Select the Sprite option
Click OK
Our Player contains multiple files, so we need to tell the Sprite which Texture (.png) to use. To do this:
Select the newly-created SpriteInstance under the Player entity
Select the Variables tab
Use the Texture drop-down to select MainShip1
If we run the game now, we will see our ship displaying the MainShip1 Texture on its Sprite.
So far we have created an simple project called Rock Blaster. Next we will create a skeleton for our game.
When we refer to a game skeleton, we mean an initial setup which contains empty or nearly-empty Screens and Entities. We are not referring to an actual skeleton, but rather the simplified structure that a skeleton implies.
Creating a skeleton is a great exercise because it can quickly get you to think about what your game will contain. You can add Screens and Entities as you think them up because there is no implementation required. As you become more experienced with making games (especially with FlatRedBall) you will find it easier to create skeletons.
The first step is purely conceptual. You can start creating a skeleton with any tool that you find comfortable. You may prefer to use a simple text or spreadsheet document, or perhaps you prefer to write a list on a piece of paper. The point of this step is to create a list of screens and entities which you expect to include in your game. This game will be about flying a space ship, shooting rocks, and attempting to stay alive long enough to earn a high score. The Wizard has already taken care of creating our screens (GameScreen and Level1) so we don't need to do anything with screens for now. We'll create a list of Entities needed for our game:
Player (already created by the Wizard Wizard)
Rock
Bullet
Hud (will be made in Gum)
HealthBar (will be made in Gum)
EndGameUi (will be made in Gum)
If you don't think of all of the Screens and Entities that you'll need, that's okay! The purpose of this isn't to fully define the game, but rather to get you to think about the game from a development perspective - something which you may not do immediately when you think up a game idea. Working through the Screen and Entity list may help you realize things that you may need.
Now that we created a list of things that we need in our game, we will start by adding the entities.
You are modifying your Visual Studio Project All of the work that you do in the FlatRedBall Editor results mainly in code being generated and in your Visual Studio project (.csproj) being modified. Normally you don't need to do anything - when you add things to FRB, they will automatically show up in Visual Studio. However, keep in mind that any changes made in Visual Studio (such as adding new classes) will not be picked up by FlatRedBall until you save your project.
Let's start with the Rock entity:
Select the Quick Actions tab in FlatRedBall
Click Add Entity
Enter the name Rock
Check the Circle option. We check this option to identify that our Rock entity can collide with other objects (such as our Player) and that the collision shape is a circle.
Leave the other defaults checked
Click OK
To add the bullet, repeat the steps above, but this time name the entity Bullet. Otherwise, all options should be the same including Circle collision.
Now you should have two new entities - Bullet and Rock.
You may have noticed that the window for creating new entities has two options checked
Create Factory
Include lists in GameScreen
Both of these options are checked by default because they are used in most games.
The Create Factory option results in FlatRedBall generating a Factory object. Factories are used to simplify the creation of new instances of an entity. They are responsible for adding new instances to their necessary lists, providing events for when new objects are created, and can improve the performance of your game through pooling and better sorting. We will be using Factories in later tutorials when we create bullet Bullet and Rock instances.
The Include lists in GameScreen option results in lists being added automatically to the GameScreen. If you are creating an entity which will collide with other entities, this is almost always handled through lists in GameScreen. We can verify that our GameScreen now has lists by expanding its Objects folder.
Now that we have created two new entities (Rock and Bullet), we have the basis for a game. We'll continue in the next tutorial by working on the Player entity.
As mentioned in the introduction to this set of tutorials, this game will be built using art from Tyrian as . For simplicity these tutorials will provide a subset of the Tyrian art. Modifications have been made only to remove unused parts of various images, to add transparency, and to separate images into separate files for convenience. Otherwise all art is in its original state. If you finish this tutorial and would like to continue developing this game, visit for the full set of Tyrian art. For the ship, save the following four files to any location on your computer - remember where you save them as you will need to be able to find them: We will use four files to support up to four players at the same time.
At this point our Player instance in GameScreen is using a Sprite defined in Glue. However, nothing is happening in our game so far. The next tutorial will add some simple behavior to our Player Entity. --
This tutorial will define the Rock entity and add it to our GameScreen so that we can shoot it. This tutorial will focus specifically on the creation of the Rock entity (adding a Sprite) as well as collision. We will revisit the creation of Rock entities in the next tutorial as well when we handle the creation of Rocks over time.
As you might have guessed, the first step is to add PNGs and a Sprite to the Rock entity. To add the PNGs:
Expand the Rock Entity in Glue
Drag+drop the four files into the Files folder
Now you should have four files in the Rock entity. Like before, the PNG files will be copied into the game project. To add the Sprite to the Rock entity:
Select the Rock entity
Click the Add Object to Rock button in the Quick Actions tab
Select the Sprite type
Click OK
To set the Sprite's Texture:
Select the newly-added SpriteInstance
Select the Variables tab
Use the Texture drop-down to select Rock1
The next question we are faced with is how to handle spawning of the rocks (spawning also means to create an Entity at runtime on a time interval). Our spawn code should have the following criteria:
We should be able to control the difficulty of the game by increasing or reducing the rate at which Rocks spawn
Rocks need to appear at random locations so the player cannot predict their movement
Rocks should not spawn on-screen. This both looks unrealistic, and it can also be frustrating to the player if a Rock suddenly appears on top of the player
Rocks should move in all directions. Our game will require the player to rotate the ship to navigate in every direction, so rocks shouldn't just appear on the top of the screen.
Furthermore, we must also consider where we should put the code for Rock spawning. One option is to put the code in the GameScreen. However, we can keep our code easier to maintain if we separate this logic into its own class. Also, since we will want to have variables to control the initial spawn rate as well as how fast the spawn rate grows, we will create a new Entity to store these variables.
To create the RockSpawner Entity:
Select the Quick Actions tab
Click the Add Entity button
Enter the name RockSpawner
Click OK - we won't need any collision or visuals in this entity
The RockSpawner is different from our other entities - we only need a single RockSpawner instance. To add an instance to the GameScreen:
Select the RockSpawner
Click the Quick Actions tab
Click the Add RockSpawner Instance to GameScreen button
Next we'll add four variables to the RockSpawner. These are:
RocksPerSecond
SpawnRateIncrease
MinVelocity
MaxVelocity
To add RocksPerSecond:
Select the RockSpawner
Click the Variables tab
Click the Add New Variable button
Enter the name RocksPerSecond
Click OK
Repeat the steps above for the other three variables. We'll set some defaults for our variables as well:
RocksPerSecond = 0.2
SpawnRateIncrease = 0.015
MinVelocity = 50
MaxVelocity = 100
Now that we have variables to use for spawning, we can create our spawning logic. The logic will be broken up into two parts:
Deciding if it's time to spawn a rock
Spawning a rock and giving it its initial velocity
First, we'll add the top-level logic to CustomActivity. To do this:
Go to Visual Studio
Open RockSpawner.cs
Modify this file so that it contains the following code
Vector3 not found? If Visual Studio draws the red squiggly line underneath Vector3 and complains that it can't find it, then add the following using statement at the top of the file:
Why do we create so many functions? You may be wondering why we have created so many functions when we implement the logic for our Entities. It would be possible to write all of the code in a single function. However, there are many reasons for breaking your code up into multiple functions. One of the most important is readability. Looking at a long function can be difficult to understand, and also difficult to maintain. Separating your code into multiple functions makes it far more readable and easy to maintain.
At this point we have two functions left to write - a GetRandomRockPosition function and a GetRandomRockVelocity. Each function will require some math and explanation so we will cover each in its own section.
GetRandomRockPosition will give us a position on the edge of the screen. Conceptually the process will be:
Picking a random side on the screen - options are top, right, bottom, and left
Picking a random point on the selected side
Moving the point off-screen so that the Rock will be fully off-screen when it is spawned
To perform these three steps, add the following code to the RockSpawner.cs file:
GetRandomRockVelocity will give us the velocity for the new Rock. This implementation will result in all rocks starting on the edge of the screen and moving towards the center. A more advanced velocity might randomize the angle at which they move so that they don't always pass through the middle; however, this approach will give us sufficiently random movement. The steps to this method are:
Find the center of the screen at Z = 0
Get the direction towards the center of the screen
Normalize the direction, then multiply it by the desired speed to get the final velocity
To perform these steps, add the following method to your RockSpawner.cs:
If you run the game at this point you will notice that the RockSpawner spawns rocks, but spawn rate doesn't increase so the game never gets harder over time. To change this, add the following code to the RockSpawner's CustomActivity:
Now the game is really making progress. If you run the game now you will be able to fly around and fire bullets at rocks. Of course, we haven't put collision in just yet. We'll do that next tutorial.
Download the following four images:
Currently the most obvious missing component in our game is collision - the MainShip can move through Rocks, Bullets pass through Rocks, and Rocks pass through each other. This tutorial covers adding collision to all objects.
All of our entities which need collision (Player, Rock, Bullet) have collision circles which can be seen visibly as white circles. As you may have noticed, the collision shapes are a little too big. We can modify the Radius value on all of our collisions to more accurately match the art. To change the Player collision radius:
Expand the Player entity
Expand the Objects folder
Select the CircleInstance object
Click on the Variables tab
Change the Radius value to 10
Repeat the process above for the following circles:
Bullet CircleInstance Radius = 3
Rock CircleInstance Radius = 6
Now our collision size matches the size of our objects.
You may have noticed that the bullet sprite doesn't quite match the collision radius. We could either offset the collision to match the sprite, or we could offset the sprite to offset the collision. While both approaches will solve the problem, we recommend keeping the collision centered if possible. For larger games this can improve performance. Therefore, we'll adjust the sprite.
Expand the Bullet entity
Select SpriteInstance
Change the Y value to -5
That's much better!
Next we will add logic to our collision. When talking about collision, it's helpful to think about collision relationships. A collision relationship is created between two objects (in this case two lists) and defines what happens when any items in these lists collide. Our game will have three collision relationships:
Bullet vs Rock - destroy the rock and bullet (for now).
Player vs Rock - destroy the player and the rock. Eventually we'll want to handle a game over state.
Rock vs Rock - this is an example of a self-colliding list. When one rock collides against another we want to have them bounce off of each other.
To create a collision relationship, drag+drop one list in the GameScreen onto another list. For example, dragging the BulletList on top of the RockList will create a collision relationship between these two lists.
We can also repeat this process to create a relationship between the PlayerList and RockList.
To create a self-collision, we cannot use the drag+drop method. Instead, we can use the Collision tab:
Select the RockList object under GameScreen
Click the Collision tab
Click the Add button next to the RockList label to add a self-collision
Notice that the Collision tab provides a quick way to view all collision relationships for the currently-selected object. All collision relationships are also organized under the Collision Relationships item in the GameScreen.
So far we've created a few collision relationships. This tells the game that we want something to happen when objects collide, but we haven't yet told Glue what we want to happen. First we'll start with the RockListVsRockList collision relationship. To edit a collision relationship, click it and select the Collision tab. Notice that there are lots of options available to customize collision relationships.
For now we'll focus on the Collision Physics section, which lets us specify how the colliding objects should be moved or whether they should bounce when a collision occurs. We want the objects to bounce, so click the Bounce Collision option. Leave all of the other defaults unchanged.
Now if we run our game, the rocks will bounce off of each other. You may need to let the game run for a while to accumulate enough rocks for a collision to occur.
Often a game needs custom code to be executed whenever a collision occurs. Our game needs to destroy bullets and rocks whenever they collide, and players and rocks must be destroyed whenever a player collides against a rock. Collision events provide the option of running custom code whenever a collision occurs. First, we'll add custom code whenever the bullet and rock collide.
Select the BulletListVsRockList collision relationship
Click the Collision tab
Click the OK button to confirm the defaults for the new event
This will add a new event to the code project. Before we edit it, let's repeat the same process for the PlayerListVsRockList collision relationship:
Select the PlayerListVsRockList collision relationship
Click the Collision tab
Click the Add Event button
Click the OK button to accept the defaults
All events added to GameScreen will be contained in the GameScreen.Events.cs file in our Visual Studio project. You may need to expand the GameScreen.cs file in the Solution Explorer to see this file.
Notice that the methods created provide access to the objects that have collided. For example, OnBulletListVsRockListCollisionOccurred provides access to the Bullet and Rock that have collided, named first and second, respectively. We can modify the code to destroy all objects when they collide. To do so, modify the code in your event file as shown in the following snippet:
This tutorial did a lot to make our game feel like a real game! Now we can shoot rocks and take damage. Of course, once the player dies the game continues to run. We'll improve that behavior in a future tutorial.
When we added a Sprite to the Rock Entity, we added four different images. At this point our game only uses one image (the smallest one). This tutorial will create states for each of the sizes, and will implement Rocks breaking up into smaller rocks.
A state can be thought of as a collection of variables. States are usually created whenever multiple variables must be changed together. For example, we have four images for our rocks. If we change which rock we show (SpriteInstance Texture), we also want to change the collision size (CircleInstance Radius). We will be creating states for the four rock sizes in this tutorial, then add logic to break larger rocks into smaller rocks.
Before we create states, we will decide which variables we want to modify. In this case, the variables we want to modify are not on the Rock entity itself, but instead on *objects inside *the Rock. Therefore we will need to tunnel variables to get access to these variables in our states. First we'll tunnel the Texture variable on our SpriteInstance:
Expand the Rock entity in Glue
Drag+drop the SpriteInstance onto the Variables folder
Select Texture in the Variable dropdown
Click OK
Repeat the process above to tunnel the CircleInstance Radius variable. Now the Rock entity has a variable for the SpriteInstance Texture and CircleInstance radius.
Now we can create our four states. We recommend always placing states in a category - this keeps states organized and is very important for larger games. To add a category:
Expand the Rock object
Right-click on the States folder
Select Add State Category
Enter the name RockSize
Click OK
The easiest way to edit states is to use the State Data tab:
Expand the States folder
Click the RockSize state category
Click the State Data tab. The location of your State Data tab may be different depending on FlatRedBall settings.
Since states usually require setting many variables, FlatRedBall provides a spreadsheet-like view of state data. By default categories do not modify any variables, so we must explicitly include which variables to our RockSize category. The easiest way is to drag+drop the variables that we would like modified onto the RockSize category.
Alternatively, the ... button at the top-right of the State Data tab provides more control over included and excluded variables.
Now we can create four states - one for each size of rock. We will call the states Size1, Size2, Size3, and Size4. Size1 will be the smallest and Size4 will be the largest. Enter the values in the spreadsheet, with the state name being the first column.
New Rocks in Rock Blaster will default to State4 - the largest. Once they are shot they will break down into smaller rocks - much like the original Asteroids game. We can change the default size of Rocks with one line of code. To do this:
Switch to Visual Studio
Open RockSpawner.cs
Find the PerformSpawn function
Add the following line of code after Rock rock = RockFactory.CreateNew();
Currently when a Rock collides with a Bullet the game calls Destroy on the Rock instance. Instead, we will want the Rock to decide whether it should break up into smaller rocks before it is destroyed. First, let's replace the Destroy call with a TakeHit call:
Open GameScreen.Event.cs in Visual Studio
Find the OnBulletListVsRockListCollisionOccurred function. Make sure to modify the bullet vs rock method.
Change Destroy to TakeHit. Your code should look like:
Next we'll create a TakeHit function in the Rock entity. This is a custom function which will create new rocks:
Open Rock.cs
Add the following code:
Finally, we'll create a BreakIntoPieces function in Rock.cs:
Notice that we are using two variables which haven't been defined:
NumberOfRocksToBreakInto
RandomSpeedOnBreak
Although we could have added these variables directly into code, it's much better to escalate these variables to Glue variables so that they can be easily modified at a later time. To do this:
Click the Rock entity
Click the Variables tab
Click Add New Variable
Change the type to int - the default (which we've used on all variables so far) is float
Enter the name NumberOfRocksToBreakInto
Click OK
Repeat the process above, but create a float named RandomSpeedOnBreak Set the values:
NumberOfRocksToBreakInto = 2
RandomSpeedOnBreak = 50
This tutorial will cover how to add multiple players to the GameScreen. Fortunately we have programmed almost everything to support multiple MainShips from the beginning, so this tutorial will not require too much work.
Most games include two requirements for a new player to join the game:
A controller must be connected for the player
The player must perform some action (such as pressing a button in a join screen)
To keep the tutorial shorter than it would otherwise be, we will assume that if a controller is connected, then the player intends to play. If you are developing a game which you intend to distribute to others, you should consider giving the player more freedom such as being able to drop out of a game and to require explicit action to join.
To add additional players we will detect how many controllers are connected, and create additional players as necessary. To do this:
Open GameScreen.cs in Visual Studio
Find the CustomInitialize function
Modify this function as shown in the following code snippet:
Next we'll need to implement AddAdditionalShips. Add the following function to GameScreen.cs:
Now that we're calling SetPlayerIndex, we'll need to make a public function for it
Open Player.cs in Visual Studio
Add the following to Player.cs:
Now that the game is somewhat playable - bullets destroy rocks and the player can lose, we need to add a scoring system and a score display. This tutorial introduces using Gum in a FlatRedBall project. Gum is a general-purpose UI tool which integrates tightly with FlatRedBall. In fact, if you used the Glue Wizard in the first tutorial, then you already have a Gum project in your game - it's just empty.
The Gum tool is packaged with the FRBDK.zip so if you are running FlatRedBall, you already have it downloaded. You can find it in <FRBDK Unzip Location>/Gum/Data/Gum.exe
The easiest way to open Gum is to click the Gum button in Glue.
Gum should open (if you have the file associations set up properly). If you do not have file association set up, you might see a window that looks like the following image:
If you see this dialog, click the Yes button, then navigate to the Gum.exe location which should be at <FRBDK Unzip Location>/Gum/Data/Gum.exe.
If you used the Glue Wizard in the first tutorial, you will also have a Gum screen set up for the GameScreen called GameScreenGum. Note that Glue will automatically create a Gum screen for every Glue screen.
Expand the Standard folder
Drag+drop the Text object onto the GameScreenGum
Adjust the position of the Text to its desired location in the Editor window
That's all there is to it! Gum automatically saves all changes, and Glue (if open) automatically reacts to these file changes, so we can run the game and see the Hello TextInstance right away.
To access the TextInstance which is inside GameScreenGum:
Drag+drop the GameScreenGum onto the GameScreen's Objects folder
Use the Source Name dropdown to select TextInstance
Click OK
We can access the TextInstance in code and change any of its variables. For example, we can change the Text property in CustomActivity. As a test, open GameScreen.cs and add the following code in CustomActivity:
PlayerData
GlobalData
To add PlayerData to your game:
Add a PlayerData class to the DataTypes folder (which should be part of your project automatically)
Press CTRL+SHIFT+S in Visual Studio to save the project. This will notify Glue that the Project has changed, and that it needs to reload it.
Modify PlayerData so the class looks like the following code snippet:
Next add GlobalData to your game:
Add a GlobalData class in your Visual Studio project in the DataTypes folder
Press CTRL+SHIFT+S in Visual Studio to save the project.
Modify GlobalData so the class looks as follows:
Now we have a well-organized, globally accessible location for our score. For simplicity all players will share the same score, so we've only created one PlayerData in GlobalData.
Finally we need to update the Score in GlobalData as well as the Hud whenever a player destroys a rock. To keep things simple we'll award points whenever a rock is shot - whether it has actually been destroyed or whether it has been broken up into smaller pieces. To do this:
Open GameScreen.Event.cs
Find OnBulletListVsRockListCollisionOccurred
Add the lines in the "new code" section so your code looks like:
As Visual Studio will indicate, PointValue is a variable that has not yet been defined. We will need to add a PointValue variable to Rock. To do this:
Select the Rock Entity in Glue
Click the Variables tab
Select int as the Type
Enter the name PointValue
Click OK
Set the newly-created value to 10
We can also change the starting TextInstance.Text value to 0 by adding the following code in GameScreen.cs in the CustomInitialize function:
Also, don't forget to remove the temporary code we wrote earlier which set the textInstance.Text to the current screen time.
Currently our game is playable, but lacks a lot of polish and fun factor needed for a final game. As you may have noticed, if the player ship collides with a rock then the ship is destroyed and the game is over. We'll make the game a little more forgiving by providing a health bar so that the player can survive multiple hits.
Previously we added a score Text object in Gum. This Text object existed in screen space - it was positioned relative to the top-left corner of our screen. Health bars will be different - they will be positioned relative to the Player instance. To create a HealthBar Component in Gum:
Open Gum
Right-click on the Components folder and select Add Component
Enter the name HealthBar and click OK
First we'll adjust main component values to be sized properly and to be positioned according to its center. Set the following values:
XOrigin = Center
YOrigin = Center
Width = 48
Height = 12
Next we'll add a background ColoredRectangle:
Expand the Standard folder in Gum
Drag+drop a ColoredRectangle on the HealthBar component
Set the ColoredRectangle Name to Background
Click the Alignment tab
Click the Center Dock button to have the colored rectangle fill the component
This ColoredRectangle will be the background of our HealthBar which will display if the player takes damage, so we will change its color to red.
Repeat the steps above, except this time:
Set the name to Foreground
Set the color to a light green color
Our HealthBar is almost ready to be used except it doesn't have an easy way to display health percentage. First, we'll modify the Foreground object to use a percentage width:
Select Foreground
Change X Units to Pixels From Left
Change X Origin to Left
Change Width to 100
Change its Width Unit to Percentage of Container
Now the Foreground can have its width changed, and the green bar will show the appropriate percent.
We want to expose this variable so that it can be accessed on the HealthBar itself:
Right-click on the Foreground Width property
Select Expose Variable
Enter the name PercentFull
Entities can contain instances of Gum objects. We will add a HealthBar to our Player, just like we added a Sprite earlier:
Select the Player object in Glue
Click the Quick Actions tab
Click Add Object to Player
Click the Gum object type
Select the HealthBarRuntime option. Note that this is the same name as the HealthBar Component in Gum with the word "Runtime" at the end.
Click OK
If we run the game now, we'll see the HealthBar on top of the Player.
First we'll adjust the Y value of the HealthBar so that it doesn't overlap the ship.
Select the HealthBar component in Gum
Change Y to -28. By default, negative Y moves an object up in Gum.
Next we'll adjust the HealthBar so it doesn't rotate with the ship. At the time of this writing, this cannot be done through Gum, so it must be done in code. To do this:
Open Player.cs in Visual Studio
Add the following lines to CustomInitialize:
Now the HealthBar appears above the player and does not rotate with the player.
Click the Add Event button
If you now play the game, the rocks will be very large:
If you now run your game and shoot the rocks, you will see that they will break up into smaller pieces. Our game is becoming far more playable (and difficult). Next we'll add a HUD and logic for scoring. --
Now each ship will use a different texture so that players will be able to tell each other apart. The next tutorial will improve on the way player death is handled by adding the concept of player health and a health bar. --
For more information on Gum, see the . If you would like more information on how to use Gum, see the .
Gum follows many of the same concepts as Glue, but it is primarily a visual tool. The window on the right provides a , so creating visual layout is usually easier to do in Gum than in Glue. Gum also includes a list of Standard objects which can be used in your project with no setup. We will use a Text instance to display our score. To do this:
At this point our text object says "Hello". Instead, we'd like to display a player's score. To change the Text, we can get a reference to the TextInstance in code or in Glue. Both approaches have their benefits, but for simplicity we will access the TextInstance in Glue. For more information, see the . The TextInstance is defined in the Gum screen, which has the file format .gusx. Our GameScreen already has the Gum screen added - this happened automatically when we used the wizard.
Now that the TextInstance can display a score, we need to actually keep track of the score so we can display it. We need to decide where to keep the score. As outlined in information such as the player's score should not be tied to any Screen or Entity. Rather, it should be stored independently and globally as an object contained in GlobalData. We'll create two classes to store our data:
Now the game includes a Score HUD that updates as the player progresses through the game. The next tutorial will add support for multiple players. --
Now we have our HealthBars in game and ready to be used. The next tutorial will hook up the logic to display the player's health and will modify the collision code so the player takes damage when hitting rocks. --
If you've made it this far through the RockBlaster tutorials, congratulations! We've covered a lot of topics in Glue and FlatRedBall. This last tutorial will clean up the game. All games require some polish and clean up before being finished. Normally this phase of game development takes a lot longer and covers far more than this tutorial, but we'll keep it short to focus on Glue and FlatRedBall features.
All entities with collision currently draw their circles. This is useful for understanding how large the collision area is and to identify bugs, but we need to turn off the shapes before marking the game as finished. To do this:
In Glue, expand the Bullet Entity
Expand the Objects item under Bullet
Select CircleInstance
Select the Variables tab
Uncheck the Visible property
Repeat this process for:
CircleInstance in Player
CircleInstance in Rock
Now our game looks much better without the white circles:
If the user dies currently the game simply sits there and doesn't let the player know what's going on. We can add an end-game UI in Gum to announce that the game has ended.
Open Gum
Drag+drop a Text object from the Standards folder onto GameScreenGum
Rename the Text to GameOverAnnouncement
Change the Text property on GameOverAnnouncement to Game Over. You may need to press the TAB key to apply the changes to the Text property. Pressing the Enter key results in a newline being added to the Text.
Click the Alignment tab and then click the Center Anchor button
Now that we've adjusted the position, we can set GameOverAnnouncement to be invisible by unchecking the Visible property under the Variables tab.
Just like with the score hud, we will access the GameOverAnnouncement in Glue:
Expand GameScreen in Glue
Expand the Files folder
Drag+drop GameScreenGum onto the Objects folder
Use the drop-down to select GameOverAnnouncement
Click OK
Next we'll want to detect if the game is over and show the hud if so. To do this:
Open GameScreen.cs in Visual Studio
Add the following to the CustomActivity in GameScreen.cs:
Next we'll want to implement EndGameActivity. To do this:
Add the following code to GameScreen.cs in the GameScreen class:
Not that we have the accumulation fixed we will need to remove the debugger code. To do this, open Game1.cs and locate the calls to the FlatRedBall.Debug.Debugger and remove those lines.
Way to go! You've just finished the RockBlaster tutorial series. We've covered a lot of features that are commonly used in FlatRedBall games. Of course there's always more you can do to a game, and now that you've come this far we encourage you to experiment with the game. Here's some things to try, some simple, and some more complex:
Modifying the way the bullets fire. Possible ideas include
A machine-gun that fires rapidly when holding the fire button down
Spread fire
Bullets which use different graphics and do area damage when exploding
Make the rocks rotate randomly
Add borders to the edge of the level so the player can't leave the screen
Stop rock spawning when the game has ended
Add a background to the GameScreen
Allow the user to restart the level by pushing a button when the game ends
Create an Explosion entity that shows whenever Rocks or the Player are destroyed
There are endless possibilities. Good luck! <- 13. Destroying Entities -- Back to Tutorials ->
At this point we have a ship which is visible on Screen, but it doesn't do anything. This tutorial will add behavior to our Player Entity so that it can move, turn, and shoot.
For this game, the Player will continuously move forward at a constant speed. The Player object will be turned left and right with the keyboard or Xbox gamepad. Before we begin writing any code we'll add two variables to Player: MovementSpeed and TurningSpeed. To do this:
Select the Player entity in Glue
Select the Variables tab
Click the Add New Variable button
Leave the defaults Create a new variable option and float type
Enter the name MovementSpeed
Click OK
Repeat the steps above to also add a TurningSpeed variable.
Next let's give the variables some default values:
Enter a value of 100 for MovementSpeed. This is the number of pixels the Player will travel in one second.
Enter a value of 3.14 for TurningSpeed. This is the maximum number of radians the Player will rotate in one second.
To apply movement we will need to write some C# code. To do this:
Open the project in Visual Studio (or switch to Visual Studio if you already have it open)
Open Player.cs. This will be in the Entities folder in the Solution Explorer.
Scroll to the CustomActivity method in Player.cs
Modify CustomActivity as shown in the following snippet:
If you now run the game you will see the ship move upward, then eventually move off-screen.
What is "RotationMatrix.Up"?: If you are unfamiliar with the RotationMatrix property, or with matrices in general then you may be wondering about the RotationMatrix.Up variable, and why we're using it. The RotationMatrix property contains information about how an Entity, Sprite, or any other PositionedObject is rotated. The Up property indicates you which way is "up" for the object given its current rotation. This value is in "object space" meaning that if the object rotates, then this value will rotate along with the object. This is especially convenient for this tutorial because this game will have the ships always moving forward. The code above will work regardless of which way the Player is rotated - something which we'll see in the coming sections.
The next step is to assign input logic so the Player can turn. We will add an object to our Player representing the input device. This could be a gampad, keyboard, or any other object. By using the I1DInput interface, we can write the code the same regardless of the actual hardware used to control the Player. Modify Player.cs as shown in the following snippet:
Note that all of the code we have written uses coefficients (MovementSpeed and TurningSpeed) defined in Glue. This means that you can modify these values in the Player Entity at any time if you want to tune how the game feels. For example, if you want the ship to turn faster, increase TurningSpeed to a larger value.
Next we will give the Bullet a PNG file and a Sprite to use. This process is essentially the same as when we added PNG files and a Sprite to our Player Entity so you may find these steps familiar. To add the PNG:
Expand the Bullet entity in Glue
Drag+drop Bullet1.png onto the Files folder in the Bullet
To add a Sprite to the Bullet entity:
Select the Bullet entity in Glue
Click the Quick Actions tab
Click the Add Object to Bullet button
Select the Sprite type
Click OK
Now we can set the Sprite's Texture:
Expand the Bullet Entity's Object folder
Select the newly-created SpriteInstance
Select the Variables tab
Set the Texture drop-down to Bullet1
The next step is to add firing bullets. We'll be using the BulletFactory which we created in an earlier tutorial to create a new bullet and automatically add it to the GameScreen's BulletList. For more information on factories, see the tutorial on this topic. Next we will need to define a bullet speed. To do this:
Click the Bullet entity in Glue
Click the Variables tab
Click the Add New Variable button
Leave the defaults
Set the variable name to MovementSpeed
Set MovementSpeed to 300
Now we can use BulletFactory to create bullets when the player shoots. To do this:
Go to Player.cs in Visual Studio
Modify the Player.cs code so that it contains the following code:
Notice that we are using the RotationMatrix of the MainShip to adjust the initial positions of the bullets, as well as their velocity
If you run the game you should be able to fly, turn, and shoot.
Although we have a long way to go, this is a big milestone for Rock Blaster. You can now see how the game feels for the very first time. Since relevant coefficients are set in Glue, you can change the values to make the game feel differently. Now that we can shoot bullets we'll need something to shoot at. The next tutorial will add Rock entity instances to the GameScreen.
As mentioned in the conclusion of the previous tutorial the game currently has a severe accumulation bug (which some may refer to as a memory leak, although it's not quite the same thing). This bug occurs because the game is continually creating new Entity instances, but is not destroying them. Rather than simply correcting the problem, let's see how to diagnose this problem.
FlatRedBall includes a number of Managers which apply automatic every-frame behavior to the objects that they manage. In the case of our game, managers enable behavior such as the application of velocity, attachments, and rotational velocity. Most FlatRedBall types which have every-frame behavior have an associated manager. Entities (such as Player) are custom classes that are created by Glue and custom code; therefore, the Engine doesn't deal directly with the types that are defined in our game. The base type for all Entities, however, is PositionedObject - a class which is understood by FlatRedBall and is managed by the SpriteManager. If you are interested you can verify this by looking in any Entity's Generated.cs file and searching for this code:
When a PositionedObject is added to the SpriteManager, it is added to the ManagedPositionedObjects enumerable. We can use this to keep track of how many Entities are living, and check for accumulation bugs.
The easiest way to identify if there is an accumulation bug is to write out the number of Entities in the ManagedPositionedObjects enumerable. To do this, add the following to Update in Game1.cs after the call to FlatRedBallServices.Update:
Although it may seem rather obvious where our accumulation is occurring, we will nonetheless walk through the process of identifying what types of Entities are accumulating. The current state of our game makes it very easy to spot this problem because the GameScreen does not exit when the player loses. Therefore, we can simply look at the state of the engine once enough objects have accumulated and the player has died. To track down the types of Entities which are accumulating:
Begin playing the game
Survive for as long as you can. If you have trouble surviving, you can give the Player a higher StartingHealth such as 60. "Cheats" like this are common in game development.
Survive long enough to have a large number of accumulated Entities (such as 300 - maybe less if your computer can't handle that many)
Once you have died, or once you have reached this many Entities, keep the game running and switch focus to Visual Studio.
Place a breakpoint in any function that is called regularly, such as Update in Game1.cs or CustomActivity in GameScreen.cs
The breakpoint should be hit immediately
Add the following to the watch window in Visual Studio:
This means that some bullets are between 20,000 and 30,000 pixels away from the center of the Screen. Not only does this tell us that we have a problem with accumulation, but it also provides a hint of how to solve the problem - we can simply destroy any bullet which is farther away from the center of the Screen than some value. Keep in mind that this method of identifying accumulation bugs depends on context. If you are clearing out your Entities effectively, then you may have Bullets very early in ManagedPositionedObjects. Of course, if that were the case then we would have never noticed an accumulation in the ManagedPositionedObjects Count in the first place.
Let's fix part of the accumulation bug by destroying Bullets when they have moved far enough away from center of the screen. We'll do this simply by getting the absolute value of the X or Y of the bullet. If it's greater than some number (which needs to be big enough to guarantee it's off screen) then we will destroy the bullet. To do this, open GameScreen.cs and add the following code to CustomActivity.cs:
Next implement the RemovalActivity as follows:
Now with this code implemented you can run the game and observe the ManagedPositionedObjects count at the bottom left of the Screen. It should climb much slower than before.
We can correct this by changing the RemovalActivity method to also check for rocks. Add the following after the for-loop shown earlier:
You should notice that the performance of the game will no longer drop significantly when playing for extended periods of time. Furthermore the number of live Entities doesn't climb much above 100. As mentioned earlier, some increase will occur, but this is due to there being more Rocks on screen at the same time.
The last tutorial created a functional HealthBar and added it to the Player entity. This tutorial will add a Health variable to the Player entity and will update the HealthBar so it displays the health.
First we'll define a Health variable that will keep track of how much health each Player has left. At this point you might expect that we will add the Health variable as a Glue variable in the Player Entity. However, the Health variable will be defined in the Player.cs code file rather than in Glue. Let's take a moment and discuss why this variable should not be a Glue variable.
Variables belong in Glue if they are:
Variables which are used as coefficients in code which will never change throughout the life of the project - such as the MovementSpeed on the BulletEntity
Variables which are tunnel to variables on FlatRedBall types - such as the Score variable in the Hud Entity tunneling in to the DisplayText property on a Text object
Variables which need to be modified in Glue States or Events - such as the Texture of the Sprite on the Rock Entity. Notice that this also falls under the category of being a variable tunneling to a FlatRedBall type variable.
Variables which define the starting state of another variable. The "starting state variable" should not change; however, the variable that it sets may change after it is set.
Let's look at each condition and see how it relates to the Health variable:
The Health variable is not a coefficient for logic. It will not be used to control the behavior of any game objects. Furthermore, the Health variable will change as the Player takes damage from rocks.
The Health variable is a new variable which is not directly tied to any FlatRedBall type variable.
We will not modify the Health variable through any states or events - it will only be modified when we detect collision.
The Health value will need a starting value, however it will change throughout the life of the project. However, we will need a starting value for the Health variable.
As indicated above, we will need a variable to define the starting value of the Health variable. To create this variable:
Select the Player entity
Click the Variables tab
Click the Add New Variable button
Set the Type to int
Enter the name StartingHealth
Click OK
Set the value of StartingHealth to 6
Now we can define the Health variable in the Player.cs file. To do this open Player.cs in Visual Studio and add the following code to the Player class:
Next we'll need to set the Health value to the StartingHealth value. To do this, add the following code in CustomInitialize in the Player.cs file:
Now we have a Health value which is functional - in other words it destroys the Player when it reaches 0. Next we'll modify the Health value when the Player collides with a Rock. To do this, open GameScreen.Event.cs and find the OnPlayerListVsRockListCollisionOccurred function. Locate the following line in the deepest if-statement:
Remove this line and replace it with:
We'll also want to remove the following line:
Replace it with:
You can also remove the comment about "Eventually we'll want to do something like..." If you now play the game you'll notice that when the Player collides with a Rock, the rock breaks apart. The Player also takes damage and if 6 rocks are collided with the Player is destroyed. Keep in mind that additional Rocks are created whenever a larger rock is destroyed, so the Player may actually collide with multiple Rocks and appear to be destroyed before 6 collisions have occurred.
Now that we have a Health value we need to update the HealthBar according to the current Health value. To do this, modify the Player Health property as shown in the following code snippet:
If the Player takes damage now. the HealthBar updates to show how much health the player has left.
Our game is becoming more and more playable with each tutorial; however, there is a severe bug present in our game - an accumulation bug occurring due to Entities being dynamically created and not destroyed. If you play the game and survive long enough you will see the frame rate slowly drop. Fortunately this problem is easy to solve. The next tutorial will show how to check for an accumulation of Entities and how to remove them.
Now the GameOver will appear after all ships have died. If you have increased the ship's health you will want to reduce it back to a reasonable number (like 6). This is important for the final state of the game as well as it will help you test the end game logic quicker.
Download the following file to your computer:
Of course a large number of Entities does not necessarily mean that there is an accumulation bug; however, games should have reach a point where the number of Entities on screen level-out. However, as long as you continue to play Rock Blaster the number of Entities that are living gradually increases, indicating this really is an accumulation bug. So we now have verified that there is a problem and we have a way to measure it, but how do we know specifically which entities are causing problems? The next section will explain.
Expand this to see all contained PositionedObjects: The order of objects in the ManagedPositionedObjects enumerable is the order that the objects were created - therefore the oldest objects will appear first in the list. This is convenient for us because (excluding objects which live as long as the Player itself) the oldest objects are the most likely to be leaked objects. If we search the list for Bullet instances we can get a sense of the problem. Bullets should be temporary objects. Furthermore it's clear that the object should not still be alive at a position which is out of the bounds of the screen:
The fix we made above has greatly reduced accumulation, thus improving performance; however the bug isn't completely solved. You'll see that the number of objects continues to climb. Of course, you have to be careful when performing tests like this. RockBlaster is designed to increase the spawn rate of rocks. This means that it's likely that as the game progresses more and more rocks will be visible on screen at once, so we expect to see an increase in the number of Entities as the game progresses. Therefore, you can't always assume that an increasing number of living Entities is a bug. However, in this case, it is - we are creating Rocks and never destroying them (unless they are being shot). We can verify this by looking at the position values of the Rocks in the Watch window (just like we did for Bullets earlier): In my case some of the rocks are so far away from the game screen that they should obviously be removed:
Now that we've fixed our accumulation bug the game is far more playable and will no longer slow down when playing for a long period of time. At this point our game is nearly complete - at least in the scope of this tutorial. The next (and last) tutorial will add some polish to our game and clean up debug code and objects. --