Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
FlatRedBall makes platformer game creation easy. This section includes tutorials covering various platformer topics from beginner to expert.
This tutorial provides a set of steps for creating an entity with platformer behavior.
The FlatRedBall Editor provides a quick setup for creating a platformer project. To create a platformer project:
Launch the FlatRedBall Editor
Create a new project
Wait for the project to finish loading
Wait for the Wizard window to appear
Select the Platformer project option
Wait for the wizard to finish processing
Run the game from either Visual Studio or the FlatRedBall Editor
If you are using the wizard as shown above, you can skip this tutorial and the next tutorial and move on to the Movement Values tutorial. If you are interested in how to build a platformer in the editor "from scratch", keep reading.
Although this tutorial is focused on creating a platformer entity, we will first add a GameScreen. Creating a GameScreen first makes it much easier to add an entity after. Most FlatRedBall projects have a GameScreen - it's a standard screen created by the wizard, so it's best to follow this naming convention in your own projects even if you aren't using the wizard.
Note that you may already have a GameScreen in your project. If so, you can skip this section. To add a GameScreen:
Select the Quick Actions
Click the Add Screen/Level button
Leave the default GameScreen name
Check both the Add SolidCollision ShapeCollection and Add CloudCollision ShapeCollection options
Click OK
We will return to the GameScreen in future tutorials, but having one created before we create entities will speed up the process.
The Player entity is our entity that will be controlled and have platformer physics. Regardless of the genre, most FlatRedBall games have a Player entity. It's a convention that your games should follow, just like having a GameScreen.
To create an entity with platformer behavior:
Select the Quick Actions tab in Glue
Click the Add Entity button
Enter Player as the name for the entity
Check the AxisAlignedRectangle checkbox - platformer entities perform collision against their environment
Verify that the ICollidable checkbox is checked - this enables collision which is necessary for platforming physics
Change the Input Movement Type to Platformer
Leave the Tiled options selected to automatically create a list for this new entity in GameScreen
Click OK
This will create a new platformer entity with a rich set of default functionality. We can verify that the entity is marked as a platformer by checking its Entity Input Movement tab to verify that it is marked as a platformer and that it has two movement types:
Ground
Air
Now that we have the Player entity set up with platfomer control values, we can add it to our GameScreen by drag+dropping the Player onto the GameScreen node. We should already have a PlayerList in our GameScreen so the newly-added object will be inside of that list after the drag+drop.
Note that we add a Player to the GameScreen before creating any Levels. We add the Player to the GameScreen so that we have a player in every Level (screens which inherit from GameScreen).
To create a CameraControllingEntity:
Select GameScreen
Select the Quick Actions tab
Click the Add Object to GameScreen button
Find and select Camera Controlling Entity
Click OK
We want to set up the newly created CameraControllingEntityInstance to track our player as it moves around in the map. To do this:
Select CameraControllingEntityInstance under GameScreen/Objects
Select the Variables tab on the right
Set the variable Targets to PlayerList
Set the variable Map to Map
The camera should now be able to track the player. For more information on automatic camera control, see the CameraControllingEntity documentation.
This tutorial series primarily focuses on creating a platformer entity. If you would like to control the camera manually, you can do so by modifying Camera.Main. More information about the Camera object can be found on the Camera documentation page.
Before we can run our game, we also need to add a Level. Typically, the GameScreen includes objects, files, and objects which are common to all screens (such as a Player), while each level includes objects and logic which are level-specific (such as the TMX).
To add a level:
Select the Quick Actions tab
Click the Add Screen/Level button
Leave all defaults and click OK
After clicking OK, another popup appears with options for the level tile map (TMX). Leave all defaults and click OK
Your project should now have a Screen named Level1. This is marked as the startup screen (it has the play icon and appears in the startup dropdown).
If we run our game now, we'll see the entity functional - at least, it seems to fall with gravity. In the next tutorial we'll add collision and controls using our entity.
This tutorial explores how to add double jump to a platformer character. It covers how to add an extra jump (double jump), infinite jumping, or a limited number of jumps in the air.
Double jumping is a feature in many platforms which gives the player more control over player movement. Players can use double jumping to remain in the air for a longer period of time, to reach areas higher than possible with a single jump, and improve horizontal movement precision. Entities which support double jumping require two sets of values - the movement values before double jump and the movement values to apply after double jump. Platformer entities automatically receive a platformer values called Air which are the before double jump variables, so we need to create a new set of values. To do this:
Select your platformer entity (Player)
Expand the drop-down next to the Add Movement Type button
Select Default Air Control option
Change the name of the new values to AfterDoubleJump
Now that we have a set of values for after double jump, we need to tell our game to use these new values for double jump:
Select the platformer entity
Click the Variables tab
Change the After Double Jump value dropdown to the name of the new set of values we just created (AfterDoubleJump)
Finally we need to change the Jump Speed value on the Air movement values to be greater than zero. This is the velocity which will be applied when performing a double jump.
Now the platformer entity (Player) supports double jumping.
We can also support infinite double jumps by either setting the AfterDoubleJump Jump Speed value to greater than zero, or by setting the AfterDoubleJump variable to be Air. This results in the character being able to jump indefinitely which can be useful if implementing swimming or abilities like the flying racoon power-up in Super Mario Bros 3.
As shown above, the platformer entity can support a single double jump by making the AfterDoubleJump variables have a Jump Speed of 0. By increasing the Jump Speed to greater than 0, then the platformer entity can jump infinitely similar to flying or swimming. It is also possible to limit the number of jumps, but this requires custom code.
For this example we will use the two movement types from the previous sections: Air and AfterDoubleJump. To limit the number of jumps, make sure that AfterDoubleJump has a Jump Speed value of 0.
Next we will conditionally assign the Air value in code AirMovement
value in code according to the number of jumps the player has performed since touching the ground.
To do so, open your platformer entity's code file (Player.cs) and modify the code as shown in the following snippet:
Now our platformer entity can jump 5 times, as controlled by a variable in HandleJump.
The following tutorials provide an introduction to working with Platformers in the FlatRedBall Editor.
This walkthrough covers concepts related to creating moving platforms. Moving platforms can be used to transport a player vertically or horizontally, and can provide challenge and variety to a platformer level. When walking on a moving platform, the platformer Player is able to move faster and jump further.
This sample can be downloaded from GitHub: https://github.com/vchelaru/FlatRedBall/tree/NetStandard/Samples/Platformer/MovingPlatformDemo
This walkthrough refers to MovingPlatfomDemo as this demo and the demo.
This walkthrough covers a number of concepts related to moving platforms:
Moving platforms horizontally using acceleration and await calls
Performing platformer collision between the PlayerList and MovingPlatformList
The MovingPlatform entity is used in this demo. It moves itself by setting its XAcceleration value and changing this value on a timer controlled by async calls. This logic is started in MovingPlatform.cs and continues to loop until the MovingPlatform is destroyed.
Notice that the StartMoving function is an async method, and it uses the async functionality supported in FlatRedBall to simplify the looping logic for moving. The logic in StartMoving performs the following:
Set XAcceleration to 30 for 1 second - the platform starts stationary and ends moving 30 units/second to the right
Set XAcceleration to 0 for 2 seconds - the platform will be moving to the right at a constant speed
Set XAcceleration to -30 for 2 seconds - the platform will reverse direction
Set XAcceleration to 0 for 2 seconds - the platform will be moving to the left at a constant speed
Set XAcceleration t0 30 for 1 second - this will result in the platform standing still, but its acceleration will continue on the next loop
This code loops continually until the entity is destroyed. The entity sets this value to true in CustomDestroy which will result in the StartMoving loop eventually ending. Notice that this code uses acceleration values, which ultimately change the MovingPlatform velocity values. Moving platforms must use velocity to move as opposed to directly setting their position values to have an impact on the movement of the player.
The GameScreen contains a list of Players and a list of MovingPlatforms.
The PlayerList collides against the MovingPlatformList using Platformer Solid Collision physics
Note that usually platformer entities like Player collide against TileShapeCollections using Platformer Solid Collision, but this can also be used when colliding against another entity list.
This walkthrough showed how to move a moving platform using acceleration and how to collide the player entity against the moving platform using platformer physics.
This walkthrough covers the concept of doors - objects which can move the player from one area on the map to another. Doors are often used to subdivide single levels, and are used in games like Super Mario World (pipes and doors) and Mega Man X (doors leading to bosses).
The sample project can be downloaded from Github: https://github.com/vchelaru/FlatRedBall/tree/NetStandard/Samples/Platformer/DoorsDemo
This walkthrough refers to the DoorsDemo as this demo and the demo.
This walkthrough covers a number of concepts for using doors:
The map is broken up into three sections which are all part of the same TMX file, but the Camera follows bounds defined by rectangles in the map
Doors are entities which are added to the Tiled map
The collision event between Player and Door instances is used to check if the player is pressing the up direction
When transitioning (the screen is fading in and out), the code uses async/await to wait for the fading animations before allowing the Player to move
The Level1Map file is a TMX which contains three sections. These three sections are drawn just like normal tile maps, but they also contain a layer called CameraBoundsLayer. This layer, which is an Object Layer, contains three rectangles. Object layers with shapes can be used any time a shape is needed for game logic. Other uses besides camera bounds include defining large triggers (such as the goal in a level) or areas to destroy objects which fall off of a level (such as enemies falling into pits).
These three rectangles will be used to mark the bounds of the Camera when the Player is in one of the three areas of the map. These bounds will apply directly to the Camera, so they must not extend beyond tile edges. Every object layer with shapes is automatically loaded into a ShapeCollection contained in the map. The name of the ShapeCollection matches the name of the layer, so it can be retrieved with a First linq call on the map's ShapeCollections object, as shown in GameScreen's CustomInitialize.
The boundsShapeCollection field is used to position the player in the UpdateBoundsForPosition method. This takes a Vector3 defining a point inside the rectangle. The method then looks through all rectangles to find which rectangle contains the argument position, and adjusts the CameraEntityInstance's Map object.
Normally the Map object references an entire MapDrawableBatch (the runtime for TMX files), but in this case we replace it with an AxisAlignedRectangle. In either case, the CameraControllingEntityInstance will respect the bounds of the Map object it is assigned. We can observe this behavior by walking to the edge of the map. Notice that the Camera doesn't move further to the right even though it hasn't reached the edge of the TMX file.
The demo defines a Door entity in Glue which contains a Sprite and AxisAlignedRectangle. The Sprite is used to display the visual, and the AxisAlignedRectangle defines the collision area used to detect if the player can enter the door. The Door instances are created in our map which contains four Door tiles. The standard door tile is used to place doors in the map.
Its Type is set to Door, which results in instances of that tile automatically being replaced with Door entities.
The map contains four doors. Each door in the map has another door which marks where the Player should appear when travelling through the door. These pairs are identified by their names. The demo uses the convention of setting the Name of each pair of doors with the same letter. Specifically, the doors "A 1" and "A 2" are paired together and the doors "B 1" and "B 2" are paired together. This convention could support up to 26 pairs of doors if using only upper case, and more if using lower case and numbers, so it will work for even larger levels.
When collision occurs between a Door and Player and the up direction is pressed, the demo obtains the otherDoor to determine where to place the player. This code is used in the OnPlayerListVsDoorListCollisionOccurred method.
The logic for moving between doors is ultimately driven by the PlayerListVsDoorList collision relationship in Glue. This collision relationship raises an event OnPlayerListVsDoorListCollisionOccurred in GameScreen.Event.cs.
We'll focus primarily on the collision and positioning logic in this section. Initially we check if the player has input enabled and whether the player is pressing up. Checking the InputEnabled property is important otherwise the Player could press Up multiple times during the transition animation and force the animations to play over and over, resulting in confusing behavior. We use the PressedUp property which returns whether the default up input was just pressed. This property is defined in Player.cs.
As mentioned earlier, once inside the if-statement, the demo searches for a matching door which has the same first letter in its name. The player is immediately moved to this position and UpdateBoundsForPosition is called. As shown earlier, this method updates the camera bounds to the rectangle which contains the other door.
The demo also immediately moves the camera to the new bounds through the ApplyTarget method. The ApplyTarget method can optionally lerp (move the Camera smoothly) or move immediately. In this case we move immediately so that the camera doesn't move across the level and through areas inbetween the map sections. Notice that we are moving between two doors when entering a door, but we could also have created a convention which results in players moving to a completely different level. In this case, we would have called MoveToScreen rather than moving the Player to a new position. Such a system would enable games with maps which have multiple areas but which also connect to other maps, such as a dungeon in Final Fantasy 4.
When the player collides with a door and the if-check passes, the player's input is disabled and the transition logic begins. The first part of this transition is the async prefix on the OnPlayerListVsDoorListCollisionOccured method. This enables the method to use the await keyword to delay logic. Without this, the method would need to use more complex code to enable and disable input as the fade animations are playing. The OnPlayerListVsDoorListCollisionOccurred method is effectively split up into three sections, separated by the await calls:
The first section (yellow) happens immediately when the player presses up - the player's input is disabled, the door shows its open graphic, and the fading animation begins playing. Once the animation finishes the second section (green) happens. This section moves the player immediately and adjusts the camera bounds, and opens the other door. This happens while the screen is completely covered by the black overlay, so the player cannot see the immediate movement happen. Once this movement happens, the black overlay fades away. Finally, once the fade happens, the third section (blue) happens. This closes the door and enables the player's movement.
This walkthrough has covered how to add doors to a map enabling the player to move between different sections in the same level.
This page covers all control values available in a platformer entity. It also provides ways to implement common functionality such as ice physics and under-water levels.
If you created your game using the wizard, then the Player entity already has a set of default values. If you manually created your Player entity, then it should also have a set of default control variables.
These values serve as a starting point for platformers - they can be tuned to provide a custom feel to platformer entities. The platformer control values can be viewed and edited by selecting the Player entity and clicking on the Entity Input Movement tab.
This is the maximum speed (maximum absolute horizontal velocity) that the character can move through input. Note that if using Immediate horizontal movement, then this is a hard value - not other forces can modify the character movement. For more information, see the next section. Increasing this value makes the character move more quickly, but doing so can make the game more difficult to control if the value is too large.
This value controls whether the character reaches maximum velocity immediately, or if it takes time for the character to speed up and slow down to the maximum velocity and back to standing still. Using Immediate increases the responsiveness of your game, and allows players to move very accurately. Examples of immediate-movement games include Mega Man and Castlevania.
The Speed Up/Down option results in the platformer entity accelerating to max speed and back down to a standstill over time, instead of immediately. This option creates more natural movement and requires more planning on the player's part (such as building up speed before a big jump). Examples of speed up/down movement include the Super Mario Bros. series and the Donkey Kong Country series.
The Speed Up Time value controls how many seconds are required for the platformer entity to reach max speed. This value is only available if using Speed Up/Down horizontal movement.
Increasing this value makes the character makes the platformer entity feel sluggish. Decreasing this value makes the platformer entity feel more responsive. A value of 0 is identical to using Immediate horizontal movement. A larger speed up time can also be used for different terrains and environments. For example, a larger value can make the ground feel more slippery (if the character is walking on ice). A larger value can also make the character seem heavier, or can be used to simulate under-water movement. A larger Speed Up Time can be used for air movement so that control is less precise when in the air.
The Slow Down Time value controls how many seconds it takes for the platformer entity to decelerate from full-speed to a standstill. A larger value can make the ground seem more slippery, especially when paired with a larger Speed Up Time. Usually Slow Down Time should not be larger than Speed Up Time. If the two values are similar, then this makes the ground feel slippery. The following table shows common combinations of Speed Up Time and Slow Down Time. The values High, Medium, and Low do not have fixed meaning, but a typical setup may include these values:
Low .15 seconds
Medium .4 seconds
High .8 seconds
Of course, you should modify values to achieve the desired movement for your specific game.
Standard Ground Movement
Low or Medium
Low
Ice Ground Movement
High
High
Underwater Ground Movement
High
Medium
Air Movement
Medium
Medium
Heavy Character Ground Movement
High
Low
This value controls the velocity of the platformer entity at the moment when jumping off the ground, or when initiating a double-jump. Larger values allow the character to jump higher. This value is typically larger than Max Speed, but the exact value often requires multiple iterations to get the right feel.
A platformer entity's jump height is also impacted by Gravity, so both Jump Speed and Gravity may need to be modified together. A low jump speed can be used for double-jumps, or for swimming when under water. A large jump speed can be used for characters who can jump higher. The Jump Speed value on Air movement can control whether the character can perform a double jump. By default, this value is 0 which means that the character cannot double-jump. Setting a value greater than 0 means a character can double jump. This topic will be covered in more detail in the following tutorial.
If Hold to Jump Higher is checked, then the player will be able to hold the jump button to cause the platformer character to jump higher. This allows players to perform shorter hops when desired, and longer jumps to clear large obstacles. The larger this value, the longer the player can hold the button to jump higher.
Variable-height jumping can be implemented a number of different ways. The platformer entity implements variable height jumping by turning off gravity while the player is holding the button down. This approach has the following characteristics:
Jumps feel responsive and immediate - the platformer entity does not delay jumping while the player is holding the button down. Instead, jump height is determined by not applying acceleration immediately.
Jump motion may not appear totally fluid, especially in high-gravity games.
The following image shows two jump arcs. The first is the movement of the character when the button is held, the second is without:
Although it may be difficult to see, the entity moves in a straight line on the first part of the first jump, rather than moving in an arc. This is the result of gravity being turned off while the button is held. If we color the first part red, the linear movement is a little easier to see:
The Max Jump Hold Time value sets the maximum amount of time that the player can hold the jump button to extend the platformer entity's jump. A large value gives the player the option to hold the button to jump higher. A small value results in the max and min jump heights not differing by much. A very large value may result in the player appearing to "float" while jumping, so keep this in mind when setting large values (such as over 1 second). A large Max Jump Hold Time may be used with a small Max Jump Speed to create swimming controls.
This value controls whether the platformer entity can press the down arrow + jump to fall down through cloud collision. If this value is false, then cloud collisions can only be jumped up through, but the player cannot fall down through them.
This is the distance to fall when pressing down + jump on a cloud platform before testing cloud collision again. When falling through cloud collision, collision against clouds is temporarily disabled until the user has fallen far enough. Once that has happened, cloud collision is re-enabled. This value should be roughly the thickness of cloud collision objects plus the height of the player collision. For example, if the tile height is 16 and the player's collision height is 32, then the Cloud Platform Thickness value should be set to 48. If a TileShapeCollection's UpdateShapesForCloudCollision method is called, then only half of the shape will be used for collision, so only half of the tile height needs to be considered when determining the Cloud Platform Thickness.
Gravity controls how fast a character accelerates downward when falling. This value is assigned to the PositionedObject.YAcceleration property. Increasing this value makes the entity fall more quickly, while a smaller value makes the entity fall slower, or seem to float.
Max Falling Speed sets a limit to the platformer entity's y velocity. A smaller max falling speed results in the entity falling more slowly, even if Gravity is large. A smaller max falling speed can be used to slow a player's fall down with special abilities such as the raccoon tail in Super Mario Bros 3.
A smaller Max Falling Speed and low Gravity can be used to implement water physics such as the water levels in Donkey Kong Country.
This walkthrough covers concepts related to creating an end-of-level entity which moves the player to the next level and creating a checkpoint which lets the player start at a midpoint in a level after dying.
The sample project can be downloaded from GitHub: https://github.com/vchelaru/FlatRedBall/tree/NetStandard/Samples/Platformer/CheckpointAndLevelEndDemo
This walkthrough refers to CheckpointAndEndLevelDemo as the demo and this demo.
This walkthrough covers a number of concepts for checkpoints and end of level:
Creating instances of Checkpoint and EndOfLevel entities in Tiled on object layers to allow different values on each instance
Storing the current checkpoint name in a static variable so it persists across Screen instances
Spawning and re-spawning the player at checkpoint locations
Collision between the Player and objects controlling spawning (Checkpoint, EndOfLevel, and PitCollision)
This demo includes two levels: Level1 and Level2. Each level has its own TMX file: Level1Map.tmx and Level2Map.TMX. If your project used the platformer plugin then it should have these by default. If your game already has existing levels, you can follow along but you will work in your existing levels rather than Level1 and Level2.
The checkpoints and doors will be added to a Tiled object layer. You must have at least one object layer on each level which should include a checkpoint. For this video we'll use the name GameplayObjectLayer so that it is similar to the standard GameplayLayer.
We will create two entities: Checkpoint and EndOfLevel.
To add a Checkpoint entity:
Right-click on the Entities folder
Select Add Entity
Enter the name Checkpoint
Check the AxisAlignedRectangle option
Click OK
Repeat the process above to create an EndOfLevel entity
Next we'll add a new variable to EndOfLevel:
Expand EndOfLevel
Right-click on Variables
Select Add variable
Select the type string
Enter the name NextLevel
Click OK
Next we need to associate a particular tile with the entity. By doing this, FlatRedBall automatically instantiates the entities whenever it encounters a tile.
To do this:
Open your level in Tiled. Make sure you do not have other levels open as to avoid mixing tilesets
Select the TiledIcons tileset and click on the edit button
Select the tile that you would like to use as a checkpoint, such as the checkered flag
Set the Class to Checkpoint - be sure to match the name of your entity exactly\
Save your tileset
Now you can add instances of the Checkpoint tile to your level in the GameplayObjectLayer. You should provide a descriptive name of the Checkpoint, such as LevelStart. Note that checkpoints can exist at the beginning of a level - this is where the player may spawn when going from one level to another.
To do this:
Select the Checkpoint tile
Select the GameplayObjectLayer
Clck the Add Tile icon to go into tile placement mode
Click on the map to add the Checkpoint tile
All checkpoints must have names so that they can be referenced in code. For this project we assume that every level has at least one checkpoint with the name LevelStart. You can set this name on the newly-created Checkpoint by selecting it and setting its name in Tiled:
Next we'll declare which tile in our tileset should create an EndOfLevel instance. To do this, open up the TiledIcons tileset in edit mode again. Select the icon that looks like a door. You may notice that it already has a Class set, so you can change it from "Door" to "EndOfLevel". As mentioned above, the name must match your entity exactly.
Next, we can add a new property to the tile. This should match the name of our variable in the FRB Editor exactly. To do this:
Select the tile
Click the + icon at the bottom of the properties
Keep the type as string
Enter a property name of NextLevel
Click OK
The variable should appear on the tile.
Repeat the process above to add an EndOfLevel instance to your GameplayObjectLayer.
Once this instance has been placed, its variable can be changed. For example, we can set the variable to Level2.
We want our player to spawn at a checkpoint. We'll create a static variable called LastCheckpointName which indicates the starting Checkpoint.
Initially when the game starts the LastCheckpointName should be set to LevelStart so that the checkpoint that was previously created is used:
The following code shows the logic that does this:
The spawning checkpoint is used to set the player's position. Notice that the player is moved down 8 units after spawning - this accounts for the position of the spawn point being the center of the object, while the player's position is at the bottom (at the player's feet).
Collision between the Player and various objects controls the spawning behavior. As mentioned earlier, the LastCheckpointName variable controls which checkpoint is used to position the Player instance. The CustomInitialize method is called whenever a level is created (or recreated).
We will use the EndOfLevel instances to move the player from one level to the next, and to set the LastCheckpointName.
To do this:
Create a collision relationship between the PlayerList and EndOfLevelList in GameScreen
Add an event to the newly-created collision relationship
Open GameScreen.Event.cs
Add the following code to move to the next level and set the LastCheckpointName:
Notice that the code above assumes that the EndOfLevel instance has a valid NextLevel value. If the EndOfLevel NextLevel property is not set to a valid screen then this code will throw an exception. The code above resets the LastCheckpointName to LevelStart so that the Player spawns at the beginning of the level.
We can set the LastCheckpointName whenever the player collides with a checkpoint - it doesn't have to be only when the player collides with a door. To do this:
Create a collision relationship between PlayerList and CheckpointList
Add an event to the newly-created collision relationship
Open GameScreen.Event.cs
Add the following code:
The OnPlayerVsEndOfLevelCollided resets the LastCheckpointName whenever colliding with a door, so this checkpoint will apply whenever the screen changes. The OnPlayerListVsCheckpointListCollided sets the LastCheckpointName to the name of the collided checkpoint, but this will only apply when the screen is restarted. Typically, this would happen when the player dies.
Player death can be handled in a variety of ways, such as by collision with a TileShapeCollection, or even with a hotkey to test death. Regardless, the way to restart the screen is by calling this.RestartScreen().
For example, if you have a TileShapeCollection named PitCollision, a collision relationship can be created between the PlayerList and PitCollision to restart the screen. This relationship would have an event with the following code in GameScreen.Event.cs:
This code restarts the screen, which results in the entire screen being completely destroyed and recreated. Since CustomInitialize runs again, the Player will be re-positioned according to the LastCheckpointName.
The demo includes two types of checkpoints:
Visible checkpoints - when the player collides with these checkpoints, the checkpoint changes appearance and sets the LastCheckpointName property.
Invisible checkpoints - are used only to spawn the player. In the case of the demo, only at the beginning of the level.
Whether a checkpoint is visible or not is controlled by an exposed Visible property.
Please note that if you are adding the checkpoints to your own custom project, to have the Visible property available you will need to set the ImplementsIVisible in Checkpoint Properties to true and then create a variable via the Expose an existing variable and select Visible. Also, since FlatRedBall purely converts the Tiled objects in the objects layer to instances of a FlatRedBall Entity with the same class, to actually see the flag and the door in your game you will need to add a Sprite object to the Checkpoint and EndOfLevel entities and set them to appropriate images or animation chain files. This subject is explained in detail in following tutorials.
Only Visible Checkpoint instances are considered in the Player vs Checkpoint relationship event.
The Checkpoint is visually composed of two Sprites:
PoleSprite
FlagSprite
By default, the FlagSprite is invisible, but is turned on in the MarkAsChecked function. This provides visual confirmation to the user that the checkpoint has been triggered.
This walkthrough showed how the demo project creates checkpoints which can be used to restart the player mid-level and end of level objects (doors) which can move to the next level.
This tutorial covers how to read input to move a platformer entity. We will also be creating a level to test out our platformer entity. To create a level and collision, we will be using a Tiled level. For more information on working with Tiled, see the Tiled documentation.
Note - if you created your Platformer project using the wizard, feel free to skip this tutorial. This tutorial is only needed if you are manually creating your game.
The previous tutorial created a GameScreen which contains two TileShapeCollections:
SolidCollision
CloudCollision
By default, each is associated with a standard tile from the tileset included in our Level1Map.tmx. However, these collisions do not have any effect on our player since we haven't told the player to collide with them.
To set up collision between our PlayerList and SolidCollision:
Expand GameScreen Objects
Drag+drop the PlayerList onto the SolidCollision to create a relationship
Since our Player is marked as a Platformer entity, the FlatRedBall editor assumes that the PlayerVsSolidCollision relationship should use platformer physics. You can verify that this is the case by selecting the PlayerVsSolidCollision object and clicking on the Collision tab.
Now the player will collide with the level.
By default, the platformer entity already supports a default set of controls. To see this, select the Player entity, then select the Entity Input Movement tab.
By default, the platformer will be controllable with a plugged-in Xbox Gamepad. If no Gamepad is detected, then the entity can be controlled with WASD and Space.
Note - the animation above shows a game that is using the CameraControllingEntity to position the camera in the center of the map. If you are not using the CameraControllingEntity, then you can manually position the camera in your GameScreen by changing the Camera.Main.X
and Camera.Main.Y
variables in CustomInitialize
. Also, note that the Player is inside of the map. You can modify the player's starting X and Y values either in code or by selecting the Player1 object in GameScreen and changing its X and Y values.
If you want to override which input is used to move the player, you can change the controls in code. For example, to change the character to jump with the Enter key and to move with the arrow keys:
Go to GameScreen.cs
Modify the CustomInitialize function to contain the following input assignment code:
The code above added keyboard controls so that the Player can be moved horizontally with the Left and Right arrow keys and jumps using the Enter button.
Now our platformer character reacts to input and collides with the environment. We can change the environment by opening Level1Map at any time and painting new tiles. The next tutorial takes a deeper look at the control values.
The GroundCollidedAgainst property can be used to detect the type of terrain that a platformer entity is standing on. This property, along with the standard ItemsCollidedAgainst property, can be used to perform complex logic in response to collision after all collision for a frame has been resolved. The most common use of the GroundCollidedAgainst property is to assign movement values.
The GroundCollidedAgainst property is only available for gluj version 22 and newer. For more information see the page. New FlatRedBall projects use this version, but some of the samples linked in the Platformer tutorials may not have this value available.
Typically a platformer game will have collision relationships between the Player and various TileShapeCollections. For example, the following screenshot shows collision relationships between the Player and a number of TileShapeCollections.
Whenever the player collides with one of these TileShapeCollections, the GroundCollidedAgainst property will be updated. Some games may have multiple TileShapeCollections represent one type of terrain (such as Ice and IceCloud), so all collision should be resolved before determining the terrain type. In such a situation, the Player entity can have properties for determining the ground type as shown in the following code snippet:
These properties enable the assignment of movement values, such as GroundMovement, in CustomActivity as shown in the following code snippet:
Similar logic could be performed to assign AirMovement and AfterDoubleJump. Consider that the code above requires that the GameScreen has its collision objects exposed publicly. To verify this is the case, select the object in the FlatRedBall Editor and check its HasPublicProperty value.
This set of tutorials covers how to add breakable blocks to your platformer game. Breakable blocks have been popularized in the Super Mario Bros franchise, but are also common in other games such as Castlevania, Metroid, and Mega Man.
Despite being introduced in an early Nintendo Entertainment System (and arcade) game, the feature of being able to break blocks requires advanced FlatRedBall collision functionality. We will be covering the following FlatRedBall topics:
Creating entities in Tiled
Using TileShapeCollections to adjust Entity RepositionDirections
Dynamic destruction of entities
Combining manual collision calls with CollisionRelationships
Manually calling CollisionRelationship
This walkthrough looks at the FlatRedBall Multiplayer Platformer project and explains the important details of creating a (local) multiplayer game similar to games like Contra III.
We will be referring to the MultiplayerPlatformerDemo as this demo and the demo throughout this walkthrough.
Local multiplayer games provide a variety of ways to join. Older games on the Super Nintendo assumed that if you selected two players, you would use the first and second controller without checking that the controllers were actually connected. More modern games, especially on the PC, check the connected status of controllers and allow players to join and leave. Joining and leaving can be performed on a dedicated page, or can be performed dynamically allowing players to join and leave mid-game. For simplicity, this demo only allows players to join and leave in a dedicated screen. Conceptually, the process for joining has the following steps:
A page displays UI to tell the user which controllers are connected. Players can connect controllers and press buttons to join.
The values for which players have joined must be stored somewhere which is accessible by both the GameScreen and the screen for joining/leaving. These values cannot be instance values on a screen.
The GameScreen must inspect these values and create Player instances (using a Factory) for every joined player. The correct gamepad must be assigned.
We will take a deep dive into these concepts throughout this walkthrough.
The demo includes a Screen called CharacterJoiningScreen. This screen does not inherit from the GameScreen - it is not a level. Rather, it is a screen which contains only Gum UI and logic.
It is marked as the startup screen to give players a chance to join/leave the game before starting Level1.
The CharacterJoiningScreen uses Gum and the MVVM pattern to make the UI update according to the join state stored in the ViewModel. The UI does not participate in the logic of the game - it only displays the values stored in the underlying view model objects. The state is stored in the CharacterJoiningScreen's ViewModel object.
The code modifies this ViewModel in a few different places.
The ViewModel object is instantiated and assigned as the BidingContext to the Gum screen in CustomInitialize. Notice that we instantiate the ViewModel and assign it initially, but thereafter we use the ViewModel property. Once the ViewModel is instantiated and assigned as the Gum UI BindingContext, we can initialize it based on the connected state of the controllers and whether the player had already joined.
Notice that if a controller is connected, we check the GameScreen.PlayerJoinStates to see if the player should be fully joined, or plugged in but not yet joined. This demo uses a static object in the GameScreen to store the joined status. Since the values are static, they are not reset when moving between screens. Larger games may store this information in a dedicated object such as a singleton which also stores profile information like inventory and experience points.
This screen needs to respond to Xbox360GamePads being connected and disconnected. The InputManager provides a ControllerConnectionEvent event which we can subscribe to in CustomInitialize:
The HandleControllerConnectionEvent method checks if the Xbox360GamePad was connected or disconnected and sets the ViewModel values accordingly.
As mentioned earlier, Gum is bound to the view model, so changing these values automatically updates the Gum visuals. We won't discuss this in much depth in this guide.
The demo checks each gamepad for button presses. The following buttons are considered:
A button - joins if the player isn't already joined
B button - unjoins if already joined
Start button - moves to Level1 if the Xbox360GamePad represents a player who has already joined
Buttons cannot be pushed on an Xbox360GamePad so we don't need to check the connected status when looping through the InputManager list.
As mentioned earlier, the values must be stored in variables which are not instance variables in any of the screens (either CharacterJoiningScreen or GameScreen). Instead, these values are stored as static values in the GameScreen.
These values are used to instantiate players in the GameScreen's CustomInitialize:
This for loop in CustomInitialize is solely responsible for creating Players. Notice that the GameScreen does not automatically create any Player instances through Glue.
Therefore, if the PlayerJoinStates are not assigned, then the game will begin without any Players. The CharacterJoiningScreen is responsible for assigning these values, and it does so right before moving into a level.
If using the Glue Wizard, then the PlayerList will automatically have a collision relationship set up between the PlayerList and SolidCollision. We recommend always creating collision relationships with lists rather than individual objects (such as Player1) so that moving to a multiplayer game is easy.
If your game includes more collision relationships, you will want to make sure that they always include the PlayerList.
Each player in the demo can be controlled by a separate Xbox360GamePad and is colored uniquely based on player index. Since FlatRedBall only supports four Xbox360GamePad instances, the demo is coded to handle up to four players. The game pad and index are assigned in the GameScreen's CustomInitialize method where each Player is assigned. The following snippet shows just the creation of the players inside the loop:
The SetIndex method notifies the player of their index. In a full game the player's index could be used for many reasons such as separate score display, game stats, and experience point awarding. This game is simpler so it only changes which AnimationChain is used, resulting in each player being drawn as a different color.
We also call InitializePlatformerInput which is a method provided automatically by the Player's generated code since it is marked as a Platformer character. This method assigns the input device which tells the Player which input device to read for platformer movement. It also results in the Player's CustomInitializeInputDevice method being called, where the run button is assigned.
The Player code includes an AnimationController which sets the current animation based on input and collision state. This code assigns names like CharacterWalkLeft and CharacterJumpRight, but it does not consider whether the Player is index 0, 1, 2, or 3. This code applies regardless of index because each of the animations have the same names. This approach is a common way to reduce code when displaying multiple player indexes or enemy types. Once a standard set of animations has been decided upon, the code can be written against this common set and it will work regardless of the .achx file used.
This walkthrough has shown how a typical FlatRedBall multiplayer game is created, including setting whether a player has joined, using this value to create players, and assigning input device and animations according to player index.
The sample project can be downloaded from Github:
Now that we have a basic project (with a Player instance falling off the screen) we can create our map. Our map will have:
Solid collision to keep the player on screen
Graphics so our tutorial project looks like a real game
Tiles defining where to place our breakable Block instances
The Glue Wizard created a Map in our Level1 screen, which is useful if we were going to make our levels from scratch. Instead, we'll use a TMX file to speed things up. Download the following three files to the same folder (such as your downloads folder):
FRBPlatformer.png - the PNG containing the art for our game
FRBPlatformerTileset.tsx - the tileset for the visuals in our game
Level1Map.tmx - the map for our game which has the pre-made visuals
Our game already contains a file named Level1Map.tmx - this is the default name of the TMX added to our Level1 Screen. We can replace this file on disk with the downloaded file. We need to remember to also copy over the other two files. To do this:
In Glue, expand Level1 Files
Right-click on Level1Map.tmx
Select View in explorer to open the containing folder
Once open, drag+drop the three downloaded files into the Level1 content folder. If asked, replace the existing file.
Now our game will run and display the level, but our character still falls through the screen. We'll fix this next.
Our map already has visuals for a platformer game, but no tiles are marked as solid collision. We will add the standard tileset to our map and create a new layer which defines solid collision. To do this:
Double-click the new Level1Map.tmx - either in the file explorer or in Glue
Open the Content folder
Drag+drop the StandardTileset.tsx in the Content folder onto Tiled to access this tileset in Level1Map.tmx\
Add a new layer to the map called GameplayLayer\
Outline the solid collision areas in the level using the top-left brick tile to mark the SolidCollision tiles. Be sure to place these tiles on the GameplayLayer
Don't forget to save the TMX file after adding the collision. Since we used the StandardTileset.tmx file, our game automatically uses these tiles for the SolidCollision TileShapeCollection, and our player can walk around the level.
The GameplayLayer visibility can be toggled in Tiled. You may want this off at times to see the art of the game without the solid collision tiles blocking the visuals, or you may want it on to help diagnose issues.
Now that we have a functional level, we will create the Block entity.
We'll begin this tutorial with an empty project. Mine will be called BreakingBlocksProject.
The New Project Wizard can help us get a quick project set up. We will leave most of the options to their default, but we will change the following:
Change What kind of control will the player have? to Platformer
Uncheck Add Sprite to Player Entity unless you intend to add graphics to your Player entity. This tutorial will not cover how to add graphics to the Player entity.
Change Number of levels to create to 1. We will only have one level in this game
Now our project is ready to go and we can start adding our level.
This walkthrough covers the concepts of climbing ladders. When climbing a ladder, the platformer Player is able to move vertically by pressing up or down on the analog stick or d-pad. Ladders and vines are used to provide access to areas normally not reachable by jumping alone.
The sample project can be downloaded from GitHub: https://github.com/vchelaru/FlatRedBall/tree/NetStandard/Samples/Platformer/LadderDemo
This walkthrough refers to LadderDemo as this demo and the demo.
This walkthrough covers a number of concepts for climbing ladders:
Defining ladder platformer values to control climbing speed
Defining ladders in the TMX file
Controlling whether currently climbing or not according to ladder collision and input
Limiting the climbing height
The Player entity defines a set of movement values for climbing called Climbing.
When this is set as the CurrentMovement, the player has direct control over vertical movement. When climbing up and down, the Climbing Speed is set as the player's Y velocity. As we will see later in the walkthrough, these values are explicitly set when the player presses Up to grab the ladder. Notice that the Player has a non-zero Max Speed under the Horizontal Movement section. This means that the player can move horizontally on the ladder. Some games like Super Mario world allow horizontal movement on ladders. Other games like Mega Man X only allow vertical movement on ladders. This game allows vertical movement, but changing the value to 0 results in no horizontal movement.
Ladders are placed in the TMX file as tiles. The following image shows just the GameplayLayer with ladders.
Notice that the ladder tiles define the maximum height that the player can climb.
The code for this is defined below, but we can add extra climb height by adding additional tiles to the map. Keep in mind the GameplayLayer tiles do not need to match the visual layer exactly.
These ladders tiles use the Ladder type.
This allows the creation of a LadderCollision TileShapeCollection.
Platformer Entities do not (currently) support automatic switching to climbing movement values. The demo includes custom code to enable switching to climbing. The main logic controlling the movement values is in the Player.cs CustomActivity method.
The CustomActivity method checks if the current movement can climb. If CurrentMovement.CanClimb is false, then the player is not climbing a ladder so we can do regular platformer logic for ducking and running. Otherwise, if the player is climbing, the game checks if the player is on ground (solid collision is colliding with the player from below) and if the user is pressing down. If so, we set the ground movement back to Ground so players can climb to the bottom and leave the climbing state.
The second half of CustomActivity code performs logic which switches between being on the ground, in the air, and on th eladder. Rather than only relying on a null check with LastCollisionLadderRectangle, the code also checks if the player's center point (X) is inside the bounds of the LastCollisionLadderRectangle. This prevents the player from moving too far off of the ladder horizontally before falling off. This code could be adjusted to allow the player to move more or less horizontally.
If the player is colliding with the ladder and presses up, then the player can grab a ladder. The player's movement on the horizontal axis is stopped and the player snaps its X position to the ladder's position. We also force the player to be on the ground and to use the Climbing movement values. If the player is not over the ladder but CurrentMovement.CanClimb is true, then the player has moved horizontally off of a ladder, so the player's movement is changed to air (falling). The LastCollisionLadderRectangle property is necessary rather than a bool value so that the player can snap to the ladder's X position. This is a regular property defined at the top of Player.cs.
This value is controlled by GameScreen. The ladder collision requires logic to be executed before collision occurs, so the PlayerListVsLadderCollision relationship does not automatically run.
Instead, all Players have their LastCollisionLadderRectangle explicitly set to null, then the PlayerListVsLadderCollision relationship is manually called in GameScreen CustomActivity.
Whenever a collision occurs, the LastCollisionLadderRectangle is set, as shown in GameScreen.Event.cs OnPlayerListVsLadderCollisionCollisionOccurred.
All of this results in the LastCollisionLadderRectangle storing a rectangle if the player collides with a ladder, and storing null if not. Note that this implementation will not work effectively if two ladders are placed next to each other.
Simple games may make use of automatically assigning movement values on collision as shown in the Adding Ice and Water document. In this document, platformer values are assigned through the FlatRedBall dropdowns on the Collision Relationship. While this is convenient, note that ladder movement is optionally assigned based on game logic rather than simple collision. Therefore, if your game uses ladders, you may need to move all assignment of movement values to code.
When a player climbs down a ladder and collides with solid collision, IsOnGround will be set to true and the climbing movement values will be undone. The demo performs extra logic to prevent the player from climbing above the top of the ladder. Platformer entities like Player automatically have a TopOfLadderY property which can be assigned in custom code. The demo assigns this in the OnPlayerListVsLadderCollisionCollisionOccurred.
Whenever a collision occurs with ladder rectangles, the code moves up one tile at a time (using GridSize) until it finds the last tile. This is marked as the TopOfLadderY which prevents the player from climbing up indefinitely. The Player's position is defined as the bottom of the player, so in this case, the code limits the height that the player to the bottom of the ladder. This can be changed by modifying the last line of code in the code above. For example, we could let the player's feet reach the top of the ladder by changing the last line to:
This walkthrough has covered how to add ladder climbing to a platformer game.
This tutorial covers how to add an Enemy to your project. To speed up the process we'll be importing an existing entity rather than building a new one ourselves. Once imported, we will modify the entity so it has the functionality we'll need for this tutorial - specifically adding the ability for the enemy to take damage.
To import the entity
Download the exported entity file from here: https://github.com/vchelaru/FlatRedBall/raw/NetStandard/Samples/Platformer/DealingDamage/Enemy.entz
In Glue, right-click on the Entities folder
Select Import Entity
Navigate to the location where you saved the Enemy file earlier and click OK
We now have a fully-functional enemy which has:
Collision shape named AxisAlignedRectangle
Sprite displaying a walking animation
Platformer values so that it can collide with solid collision
EnemyInput object which will keep the enemy from moving (does not use the gamepad or keyboard)
We will add a list to our GameScreen and an instance of our Enemy to Level1 so we can see the enemy in game. To add an enemy list to GameScreen:
Select the Enemy entity
Select the Quick Actions tab
Click the Add Enemy List to GameScreen button - this creates a list of enemies which we'll use to create collision relationships later
Click the Add Enemy Factory button - this allows us to create enemies in code and Tiled maps.
To add an enemy to your Level1:
Drag+drop the Enemy entity onto Level1\
Select Enemy1 and click on the Variables tab
Set X to 160
Set Y to -160
Now we have an enemy in the game, but it falls through the level. We can fix this by telling the enemies to collide against our GameScreen's SolidCollision object:
Expand GameScreen
Expand the Objects folder
Drag+drop the EnemyList onto the SolidCollision object
If we run the game now, the enemy will fall and land in the level next to the player.
We’ll begin this tutorial with an empty project. Mine will be called EnemyDamage.
The Glue Wizard can help us get a quick project set up. We will leave most of the options to their default, but we will change the following:
Change What kind of control will the player have? to Platformer
Uncheck Add Sprite to Player Entity unless you intend to add graphics to your Player entity. This tutorial will not cover how to add graphics to the Player entity.
Note that we will not be explicitly working with the Player entity in this game, but we still create one since most games need one eventually.
Change Number of levels to create to 1. We will only have one level in this game
Now our project is ready to go as a fully functional platformer. We can begin adding the Enemy entity in the next tutorial.
Now that we have a simple platformer game with a Player moving around in a level with solid collision, we can add blocks to our game. We will be creating a new Block entity, instantiate blocks through our TileMap, and adjusting collision RepositionDirections.
Our Block entity will contain an AxisAlignedRectangle for collision and a Sprite for visual. Since the player will be able to walk on Block instances, we want to make sure the AxisAlignedRectangle size matches the tile size of our map (16x16). To create a Block entity:
Select the Quick Actions tab and click the Add Entity button
Enter the name Block
Check the AxisAlignedRectangle option
Check the Sprite option
Click OK
Select the newly-created AxisAlignedRectangle inside of Block
Click the Variables tab
Change the Width and Height to 16
FlatRedBall automatically creates a BlockList in our GameScreen, so we are finished creating our Block entity and can move on to instantiating it in our Tiled map.
To create Block instances in Tiled, we must decide which tile we want to use to represent the Block. When creating entities, we recommend using tiles in the standard tileset. This is the same tileset we used in the previous tutorial to add solid collision. To mark a tile as a Block tile:
Open Level1Map.tmx in Tiled
Select the TiledIcons tileset
Click the Edit button to make changes to the Tileset
Select the desired tile to mark as Block. I'll use the tile that looks like the solid collision with cracks
Change the Type to match the name of your Entity. In this case, it should be Block
Save the TSX file
Place blocks as desired in the GameplayLayer in Level1Map. Be sure to do this in the GameplayLayer, since mixing tilesets in a single layer will prevent your game from running.
Save the map file so the game can use the changes
Your game should now be creating Block instances for each Block tile placed in Level1Map.tmx. Since we haven't yet added graphics to the blocks, the blocks currently display only the white collision rectangles. Notice that each instance also has a small black square - this is the Sprite which we will be modifying next.
Our Block objects are currently displaying a black dot in the center of the collision rectangle for its graphics. This Sprite is not currently assigned a Texture yet. To fix this:
Expand the Block entity
Right-click on Files
Select Add File -> Existing File. We will be using the same file which is used for the graphics in our Level1Map.tmx - it has a graphic for a breakable block.
Search for FRBPlatformer.png and click OK
Drag+drop the FRBPlatformer file onto the SpriteInstance to set its Texture
Change the pixel coordinates to the following:
Left Texture Pixel = 0
Right Texture Pixel = 16
Top Texture Pixel = 144
Bottom Texture Pixel = 160
For more information on texture pixels, see the Sprite.Texture Coordinates page. Now our entity is using the breakable block graphic.
Currently our Player can jump through blocks rather than being able to hit and stand on them. We can fix this by creating a CollisionRelationship:
Expand the GameScreen Objects folder in Glue
Drag+drop the PlayerList onto the BlockList to create a new CollisionRelationship
Select the newly-created PlayerListVsBlockList and change the Collision Physics to Platformer Solid Collision
The Player can now collide with the blocks.
So far our game seems fairly functional, but it has a collision bug which can result in snagging - the unexpected stopping of the Player's velocity when moving across flat surfaces. This bug can be difficult to reproduce, so you may not have noticed it in the game yet, but it can definitely happen given our current setup. This problem is caused by the Block instances currently having AxisAlignedRectangles with RepositionDirections in all four directions. The following image provides a visualization of this problem:
The purple lines indicate possible RepositionDirections which can occur, and if these occur the Player will experience snagging. The topic of RepositionDirections is fairly extensive, and interested readers can see the following pages for more information:
We will be applying some of the concepts and code discussed in the tutorials above, but for the sake of keeping the tutorial shorter we will not take a deep dive into every topic here. To adjust the RepositionDirections of our Block instances, we will use a new TileShapeCollection which is created purely for this purpose. In other words, we'll make a new TileShapeCollection, but we won't create any collision relationships or fill it in Glue the way we normally do with other TileShapeCollections. First we'll create a TileShapeCollection to be used for adjusting the Block RepositionDirections:
Select the GameScreen in Glue
Select the Quick Actions tab and click Add Object to GameScreen
Select TileShapeCollection
Enter the name CombinedShapeCollection
Click OK
Now we can add the rectangles from our BlockList to the CombinedShapeCollection. To do this:
Open GameScreen.cs in Visual Studio
Modify CustomInitialize as shown in the following code snippet
Now our BlockList will have proper RepositionDirections. Remember the CombinedShapeCollection - we will be returning to this when we work on creating/destroying our Blocks in the next tutorial.
Our blocks are now fully functional as platforming collision. The next tutorial will add the ability to break the blocks when the player hits the block from below.
This tutorial creates an Enemy entity which is used in the remainder of the tutorials. This enemy is similar to the Player entity - it has collision and uses Platformer physics, but it does not use input from a keyboard or gamepad - instead its movement is controlled purely in code.
To create the Enemy entity:
Click the Quick Actions tab
Click the Add Entity button
Enter the name Enemy
Check AxisAlignedRectangle
Check Platformer for the Input Movement Type
Leave the rest of the defaults and click OK
You can optionally change the color of your Enemy if you would like by selecting the newly-created AxisAlignedRectangle and setting its color to Red.
Entities such as Enemy are usually added directly to levels such as Level1. Note that it is possible to add Entities in more ways including through Tiled and directly in code, but we will be adding an instance directly in the FlatRedBall Editor.
Note that the EnemyList object is defined in GameScreen, but it is also accessible in all levels, such as Level1.
The EnemyList must be accessible in both screens. It is used in GameScreen to create collision relationships, which should always be the same across all levels. It is used in each level to define which enemies should appear in a particular level. This setup is enabled by the EnemyList having its ExposedInDerived property set to true, which is set up by default from when we created our Enemy entity.
To add an enemy to Level 1, drag+drop the Enemy entity onto the Level1 Screen.
The newly created entity is automatically added to Level1's EnemyList.
Now we have a fully-functional enemy, but it falls through the solid collision since we haven't yet set up an EnemyList vs SolidCollision relationship. To do this:
Expand GameScreen Objects folder
Drag+drop the EnemyList onto SolidCollision. Notice that we are doing this in the GameScreen rather than Level1 because we want all enemies to collide with the SolidCollision regardless of level. By doing this in the GameScreen, this new Collision Relationship will apply to all levels, including Level1.
Glue automatically sets the Collision Physics in the new Collision Relationship to Platformer Solid Collision since the Enemy entity is marked as a Platformer.
Now we have an Enemy entity and an instance of this Enemy in Level1. This instance collides with the game's SolidCollision and has full support for platformer physics. Currently both the Player and Enemy are controlled by the keyboard (or gamepad if one is plugged in). We will remove this input control from the Enemy and replace it with logic-based movement in the next tutorial.
This tutorial adds a Bullet entity which the player can shoot. The Bullet entity has the following characteristics:
It will be visually represented by a circle
It moves left or right depending on which way the Player is facing when shooting
It will be destroyed when colliding with the SolidCollision
It will be destroyed when colliding with the Enemy
To create a Bullet:
Click the Quick Actions tab
Click the Add Entity button
Enter the name Bullet
Click the Circle checkbox under Collisions
Leave all of the rest of the values default and click OK
When a Bullet is created, it will move either left or right. We need to control the speed of the bullet. We will create a variable which we'll use in our code later:
Select the Bullet entity
Click on the Variables tab
Click the Add New Variable button
Verify that float type is selected
Enter the name BulletSpeed
Click OK
Enter a value of 300 for BulletSpeed
We will also want to change the radius of the Bullet's CircleInstance:
Expand the Bullet Objects folder
Select CircleInstance
Click the Variables tab
Change Radius to 6
The Bullet creation logic will be added to the Player entity. We need to detect if the shoot button has been pressed. If so, we'll create a new bullet and have it move in the direction that the player is facing. To do this, open Player.cs in Visual Studio and modify the code as shown in the following snippet:
Whenever the input device is set on a platformer entity, the CustomInitializePlatformerInput method is called. Since our entity has custom input for shooting, we add the CustomInitializePlatformerInput where we assign shootingInput according to our InputDevice type. In this case we assign shooting to the right ALT key if using a keyboard and the X button if using an Xbox360GamePad. Any re-assignment of input should be done in CustomInitializePlatformerInput rather than CustomInitialize. This is because the order in which code is executed. When considering input, assignment, the following is performed:
CustomInitialize
InputDevice is assigned (can be assigned in generated code or custom code)
CustomInitializePlatformerInput which is called whenever the input device is assigned.
CustomInitialize always runs before an InputDevice is assigned. We want our shootingInput-assigning code to run after the InputDevice is assigned, so we should put it in CustomInitializePlatformerInput.
Finally, we check our shootingInput.WasJustPressed to see if the user just pushed the input. If so, we create a bullet and set its XVelocity according to the direction that the Player is facing. If we run our game now, we can shoot bullets in the direction we're facing.
Currently, our bullets can move through walls and enemies. First we'll add collision between our GameScreen BulletList and SolidCollision:
Expand the GameScreen Objects folder
Drag BulletList onto SolidCollision to create a new collision relationship
Select the new BulletListVsSolidCollision relationship
Click the Collision tab
Click the Add Event button
Click OK to accept the defaults
Now we can destroy the bullet whenever the event occurs:
Open the project in Visual Studio
Go to GameScreen.Event.cs
Find the BulletListVsSolidCollisionCollisionOccurred method
Modify the code as shown in the following snippet:
Now we can shoot bullets and they will get destroyed when they hit the wall.
We end this tutorial with the ability to shoot bullets and destroy them when they hit the wall. The next tutorial will implement the ability to damage and destroy enemies.
This tutorial shows how to replace the default keyboard (and gamepad) input logic with movement controlled only in code.
First we'll remove the Input Device from the Enemy entity:
Select the Enemy entity
Select the Enemy Input Movement tab
Check None under Input Device
Now the Enemy will not move in response to keyboard or gamepad input. Note that if we do not assign an InputDevice in code, the game crashes so we must do so before running the game.
Next we will create our own custom InputDevice which will control the movement of the enemy. The reason we are creating this is because Platformer entities expect to receive commands from an input device like a keyboard. We can create a class which simulates input commands without actually having physical input. To create this class:
Open the project in Visual Studio
Create a new class called EnemyInput. I will place mine in an Input folder
Modify the code so that the EnemyInput class inherits from FlatRedBall.Input.InputDeviceBase
This class provides many virtual methods which we can override to customize the behavior of our enemy. For example, we can override the method for GetHorizontalValue to control which way the Enemy is walking, as shown in the following code snippet:
To use EnemyInput, we can modify the CustomInitialize method in the Enemy.cs class to assign its InputDevice. The Enemy.cs file can be found in the Entities folder in the Visual Studio Solution Explorer.
To use EnemyInput as the Enemy's InputDevice, modify the Enemy CustomInitialize method as shown in the following code snippet:
Now if we run the game, the Enemy automatically walks to the right and ignores keyboard and gamepad input.
This tutorial does not include adding jumping to the enemy behavior. If your game needs jumping, you can add this by including a primary input override in the EnemyInput class. Of course, you would only want to press the jump button under some condition. The following code snippet shows how this could be accomplished:
Our EnemyInput object can be expanded to support any type of input - we just need to have the GetHorizontalValue function return a value between 0 and 1. Note that we are only using horizontal movement for this tutorial, but we could also have the Enemy jump by implementing the GetPrimaryActionPressed method, which controls whether the jump button is down.
For this tutorial we need access to a value to indicate whether the Enemy should move to the left or right. We will ignore values inbetween -1 (left) and +1 (right), but a full game may support enemies which may stand still or move at various speeds. We'll create a new enum value and expose a property in EnemyInput so that it can be controlled externally. To do this, modify the EnemyInput class as shown in the following code snippet:
Many platformer games include enemies which turn around when colliding with other objects such as walls, platform edges, and other enemies. We will implement wall collision here since it is the simplest scenario to control enemies turning around. First we will add an event whenever Enemies collide with SolidCollision:
Expand the GameScreen -> Objects -> Collision Relationships item in Glue
Select EnemyListVsSolidCollision
Select the Collision tab
Click the Add Event button\
Click OK to accept the defaults
Glue will add an event to GameScreen.Event.cs which we can modify to adjust the EnemyInput DesiredDirection, as shown in the following snippet.
The code above assigns the EnemyInput DesiredDirection according to the enemy's AxisAlignedRectangleInstance.LastMoveCollisionReposition. We'll take a deeper look at how this property works in the next section.
The collision relationship between Enemies and SolidCollision prevents Enemy instances from overlapping the SolidCollision TileShapeCollection. Whenever an Enemy overlaps one of the rectangles in the SolidCollision TileShapeCollection, the Enemy must be repositioned so it does not overlap the solid collision. For example, consider a falling Enemy (white). Initially the enemy may be falling but not overlapping any solid collision (red).
As the Enemy continues to fall, it eventually overlaps the solid collision.
When this happens, the Enemy must be repositioned up to resolve the overlapping collision.
In this case, the RepositionDirection would have a positive Y value. For example, it may be (0,1,0). Similarly, whenever an Enemy collides with a wall in the SolidCollision TileShapeCollection, the Enemy is repositioned horizontally, so the X value of the RepositionDirection is non-zero, as shown in the following diagram:
In the diagram above, the RepositionDirection has a negative X value (points to the left) so we know that the wall is to the right of the Enemy. If the RepositionDirection has a positive X value, then the wall is to the left of the Enemy.
Now that we have this logic in place, our Enemy will automatically walk until colliding with a wall, then it turns around and walks in the other direction.
The next tutorial will enable Enemies to turn around when reaching the end of a platform.
Currently our game is fully playable as a platformer, but it is missing the feature we've been working towards on this whole tutorial - the removal of blocks on collision. Games which remove blocks based on collision are common. Some games, such as Mega Man and Metroid, remove blocks in response t being shot by a weapon. Super Mario Bros removes blocks in response to collision with the player. Specifically, if Mario has collected a power-up which makes him grow, then hitting a block from below destroys it.
This tutorial will implement this type of destruction since it will give us the opportunity to perform more advanced collision logic.
Before we add logic to destroy Blocks, we will make some minor adjustments to the game to make it easier to control, and easier to see our player. First, we'll adjust the collision on the player.
Expand the Player Objects folder
Select the AxisAlignedRectangle
Change the Width to 16
Change the Height to 24
We'll also adjust the movement of the player so it's a little easier to control.
Click on the Player entity
Select the Entity Input Movement tab
Change the variables in this tab to the following values
Ground
Max Speed = 130
Jump Speed = 270
Air
Max Speed = 130
Next we'll zoom in the camera to make it easier to see the player.
Click the Camera icon in Glue
Change Resolution to 360x240
Change Scale to 300%
Now our character is easier to control and see.
To help us understand the code that we will be writing, let's first look at block breaking implementation concepts. For the player to break the blocks, a number of things must happen:
The player must collide with the blocks. This may seem like an obvious requirement, but it's worth noting since it we will be writing code in the PlayerListVsBlockList collision relationship.
The player must hit the block from below.
Only one block can be destroyed at a time. Modern Super Mario Bros. games do support destroying multiple blocks at the same time, but the older games only support one at a time. We will follow the old approach.
Once a block is destroyed, the surrounding block RepositionDirections must be adjusted to prevent snagging.
Of the four concepts listed above, the one which requires the most attention is the third.
To understand why breaking one block at a time adds complexity to our implementation, let's consider a few possible collision scenarios. The first scenario is the simplest - where the player collides with a single block as shown in the following image:
The player is represented by a blue rectangle and the block is represented by a red rectangle. In this case, the two overlap, and the player is hitting the block from below so the block should break. Keep in mind that the player may not be exactly below the block. The block should break even if the player is slightly offset, as shown in the following image:
In the image above, the block would still break. Of course things get a little more complicated when we add multiple blocks. For example, consider the following image:
Which block should break in this situation? The player overlaps more of the block on the right than the block on the left, so breaking the block on the right seems best. Unfortunately, when we perform collision between entities and players, the block on the right may not ever collide with the player. CollisionRelationships perform collisions between one player/block pair at a time, and which pair is checked first depends on the order of blocks in the BlockList, which can change depending on partitioning. In other words, when using CollisionRelationships, we cannot depend on collision being performed in a particular order. With this in mind, it's possible that the left-most tile collides with the player first, which would result in the player being shifted down, as shown in the following image:
If the left block collides first and the player is pushed down as a result of the collision, the right block will not perform collision with the player. The events raised for collision will only be raised for the left block. As explained above, the block which actually collides with the player is arbitrary - it could be the left or it could be the right. This means that we cannot rely purely on the Player vs Block relationship to decide which block to destroy - at least, not if we plan on controlling which block is destroyed in such a situation.
We can solve this problem by adding a second collision object to the Player to help us decide which block to destroy. Glue supports objects with multiple collision objects. Consider the following image, where the Player object has two collision shapes - a blue rectangle for the body and a green rectangle for determining which rectangle to destroy.
Of course, it is possible that a block collision may occur even without the green rectangle colliding, as is the case in a single-block collision where the player is offset from the block.
This means that we can not rely completely on either the body (blue) or sub collision (green) to decide which block to destroy. Instead, we need to use both to decide which to destroy. The logic should be as follows:
If the player collides with a block from below, then the player will destroy a block.
First, check all blocks to see if any block collides with the sub collision (green rectangle). If so, destroy that block.
Otherwise, if the sub collision (green rectangle) does not collide with any blocks, destroy the block that collided with the player body (blue)
This means that the sub collision is an optional collision. It should only be performed if the player body collides with the blocks from below. Therefore, we will be performing this collision manually in code rather than creating a collision relationship.
As mentioned above, Glue supports objects with multiple collision shapes. To add a new shape to the Player:
Select the Player in Glue
Select the Quick Actions tab and click the Add Object to Player button
Select the AxisAlignedRectangle object type
Enter the name BlockCollision
Click OK
Select the newly-created BlockCollision
Set the following values:
Y = 12
Width = 1
Height = 5
Our player now has a small rectangle at the top. However, this rectangle is currently considered as part of the entire player. This is most evident when hitting blocks from below. Notice that the new sub-collision performs solid collision.
We don't want this rectangle to perform solid collision - it should only be used in our code to check which block to break. To fix this, we can mark the BlockRectangle as being excluded from the default list of shapes used in collision. To do this:
Select the BlockCollision object under Player
Click the Properties tab
Set the IncludeInICollidable to false
Now BlockCollision will still be part of the Player object but will not be considered in any CollisionRelationships (by default).
Now that we have a BlockCollision rectangle, we can perform the logic explained above. We'll treat each of the four steps separately.
We can handle Player vs Block collision by adding an event to this collision relationship:
Expand GameScreen Objects folder in Glue
Expand the Collision Relationships item
Select PlayerListVsBlockList
Select the Collision tab
Click the Add Event button
Click OK to accept the defaults
Glue has now added an event to the GameScreen.Events.cs file which is called whenever the Player collides with a Block.
The OnPlayerListVsBlockListCollisionOccurred method is called whenever the player collides with a block from any side. This means that if the player is standing on a block, this function will be called every frame. We only want to destroy blocks if the player hits a block from below. Whenever a solid collision occurs, the objects being collided must be separated to prevent overlap. Since blocks cannot be moved when a collision occurs, the player must be moved. When hitting a block from below, the player must be moved downward (negative Y). We can check the player's AxisAlignedRectangleInstance.LastMoveCollisionReposition.Y value to see if the player was moved down due to the collision. If so, then the player hit the Block from below. Modify the OnPlayerListVsBlockListCollisionOccurred method as shown in the following code snippet:
Now we can perform our destroy logic. As mentioned above, we will first check if the BlockCollision collides with any blocks. If so, we will destroy the block. Otherwise we will destroy the block that the body collided with. To do this, modify the OnPlayerListVsBlockListCollisionOccurred method as shown in the following code snippet:
Now if we run the game, the Player can only destroy one Block at a time. If the Player collides with multiple Blocks, then the one which is directly above the Player will be destroyed. Of course, we there are some collision problems caused by the RepositionDirections not being adjusted properly. This is evident when trying to destroy the top row of blocks.
The RepositionDirections are initially set when all of the Blocks are created, and will work properly until one of the Blocks is removed. Once a Block is removed, surrounding Blocks must have their RepositionDirections updated. To do this, we will remove the Block's collision from the CombinedShapeCollection before destroying the Block. Doing so will result in all adjacent collisions updating their RepositionDirections. To do this, modify the OnPlayerListVsBlockListCollisionOccurred method as shown in the following code snippet:
Notice that the rectangle is removed before the Block is destroyed. Also, notice that the RemoveRectangle method is called in two spots since we destroy blocks in two spots in the code above. Now the RepositionDirections are updated properly and the player will be able to collide with the blocks properly even after blocks are destroyed.
If you've made it this far, congratulations! You have worked through a complex collision example in FlatRedBall. Now you should be able to add blocks freely in Tiled and the game will collide properly.
This walkthrough looks at the FlatRedBall DialogBoxDemo project and explains important details of implementing a dialog box in response to talking to NPCs. Platformers may include dialog boxes to display conversations between characters either as part of a cutscene (such as in Mega Man X) or in response to player input (such as in Castlevania 2).
This walkthrough will refer to the DialogBoxDemo as this demo and the demo.
This demo includes a number of important concepts which combine to create NPCs which the player can talk to using a talk button (X on the Xbox360 GamePad) to display unique dialog.
Dialog is dialog as defined in a CSV file. This file could also be used to support multiple languages, but this demo only includes English.
NPCs are placed in the Level1Map file through Tiled. NPCs can be moved and their dialog can change by making changes in Level1Map.
The DialogBox object from FlatRedBall Forms is used to display dialog. It is displayed in response to collision (player talk collision vs NPC) and button presses.
A typical game which supports NPC dialog may have hundreds or even thousands of pages of dialog. While it is possible to write the dialog directly into the C# code, maintaining this dialog can be very difficult. This is especially true if the game is being developed with a dedicated writing team, or if the game supports multiple languages. To address both of these considerations, FlatRedBall provides a standard way to store and access dialog. The demo includes a CSV file called LocalizationDatabase.csv which contains all dialog. This is added to Global Content Files so that the dialog can be accessed throughout the entire project, and it is marked as IsDatabaseForLocalizing.
Additional columns can be added for games which support multiple languages. Similarly, additional rows can be added to support more NPCs. We separate each page of text with a newline. The English text for T_Npc2 and T_Npc3 both have two pages. Even if the game includes a single language, it must be told whether to display this language or whether it should display the string IDs. This is done in the initialization for GameScreen. A game with more screens may set the CurrentLanguage property in the first screen which is shown (such as a splash screen) or even in Game1. Also, if the game supports multiple languages, this property would change if the user changes the current language in a settings page.
Notice that the first column (Id) is index 0, so English is index 1. The text contained in this file is accessed through the LocalizationManager.Translate method whenever we display dialog boxes. We will return to this code later in the walkthrough to examine how the DialogBox works, but for now we'll highlight the call to Translate in GameScreen.Event.cs ShowDialogBox.
The call to Translate passes in stringId which will be one of the IDs (T_Npc1, T_Npc2, or T_Npc3) depending on which NPC the player has talked to. For example, calling Translate with the string "T_Npc1" results in the string "Hi, I'm just hanging out over here." being returned. The returned string is then split according to the newline character ('\n') to create an IEnumerable<string> where each string is a separate page. The pages are passed to the currentDialogBox.ShowDialog call.
The demo includes an entity called Npc which represents a character in the game which the player can talk to. These NPCs are entities similar to the Player entity. Specifically, they are marked as platformer entities and collide with the level's solid collision.
It's worth noting that the Npc instances do not move in this game, so they could have been implemented as static entities with no collision. A full game may include NPCs which walk around a level, follow the player, or move in response to cinematic sequences. Therefore, they have been created as platformer entities so that they can be fully functional if a larger game calls for it. NPCs should not respond to input, so we mark the input device as None in Glue.
We must assign an input device to avoid NullReferenceExceptions from being thrown, so we do so in the Npc's CustomInitialize code by calling InitializePlatformerInput.
As shown in the code above, the demo also creates an AnimationController so that the Npc faces left or right in response to its DirectionFacing property. As we'll see later, we use this so the Npc faces the Player when dialog is shown. As mentioned earlier, each Npc includes its own dialog. This dialog is assigned in Tiled, but to support this we must create a variable in Glue so the Npc entity includes a DialogId variable.
This variable is defined in Glue, but set in Tiled.
Npc instances can be added to levels through Tiled. To do this, a Tile must have its Type set to Npc. We use one of the tiles in the standard tileset.
Any instance of this tile in Level1Map will result in an Npc instance at runtime.
Notice that the instances are on an object layer rather than a tile layer. This allows the setting of properties on each instance. For example, the selected tile in the screenshot above has a DialogId of T_Npc1. This must match one of the entries in the localization CSV file mentioned earlier. When the Level1Map is loaded, Npc instances are created automatically and added to their respective list (in this case NpcList) in GameScreen. The DialogId variable is also assigned automatically and it can be used to display dialog, as is done in GameScreen.Event.cs where the ShowDialogBox method is called.
For dialog to appear, two things must occur:
The Player must push the Talk button.
The Player must be close to the NPC. The Player entity includes an AxisAlignedRectangleInstance named TalkCollision specifically to test proximity.
If both occur, then dialog should be displayed.
The TalkInput is defined in the Player.cs file. The platformer generated code does not automatically add an input object for talking, so the demo adds it in custom code and assigns it in CustomInitializePlatformerInput.
The demo uses the same input for running and talking, but this could be any button. The TalkInput property must also be public so that we can inspect whether it has been pressed when colliding with the Npc.
As mentioned earlier, the Player includes an AxisAlignedRectangle named TalkCollision. This is excluded from the iCollidable collision so that it does not bump into walls or keep the player on a ledge.
A collision relationship specifically checking the TalkCollision vs the NpcList is included in the GameScreen.
This is handled in an event in GameScreen.Event.cs.
Notice that this collision relationship event may trigger every frame, but we only want to perform the dialog box logic if there isn't already a dialog box active, and if the user has just pressed the TalkInput. We could have also manually performed the CollisionRelationship logic in CustomActivity to reduce the number of collision checks, but games like this will typically have a small number of NPCs so the overhead of checking collision every frame is negligible. As mentioned earlier, we manually set the DirectionFacing on the Npc to turn the Npc towards the player. We also temporarily disable Player input so that players cannot move while the dialog is displayed. Notice that the event is an async method, and that we await the DialogBox. Code using the async/await pattern is not very common in FlatRedBall, but it can be very useful when displaying UI using FlatRedBall.Forms. Awaiting the ShowDialogBox method allows us to turn input off and on in a single method without continually checking if the DialogBox is displayed and without callbacks. The ShowDialogBox method displays the dialog box by calling ShowDialog. The ShowDialog method is responsible for the following whether dealing with the DialogBox type or any other FlatRedBall.Forms type:
Displaying new instances of the DialogBox - specifically adding the visuals to FlatRedBall and allowing the Cursor and Xbox360GamePad logic to be performed on the control
Displaying the pages of text as passed in to this method
Removing the DialogBox completely from the engine - both display and logic
If awaited, the ShowDialog call will not return until all pages have been shown. Since the game uses gamepads, we provide a custom AdvancePageInputPredicate which specifies how text is advanced. In this case, we progress a page of text whenever the A, X, B, or Y buttons are pressed, and only for the InputDevice for the player that talked to the NPC. This would matter if the game were to be expanded to support multiple players.
This walkthrough has covered how to add a dialog box to NPCs. Each NPC defines its own set of dialog using a CSV file which can also be used to support multiple languages.
Some games include enemies which turn around when reaching a platform edge. For example, in Super Mario World, the red Koopa enemies (turtles) walk until they reach an edge, then turn around.
We will add this logic to our Enemies in this tutorial.
Currently Enemies have a single collision shape called AxisAlignedRectangleInstance.
This shape acts as the Enemy's body. It can be used to keep the enemy from moving through the ground, walls, and ceiling (the SolidCollision TileShapeCollection). Currently this shape does not provide enough information to detect ledges. From the point of view of the AxisAlignedRectangleInstance, this first situation...
... provides the same information as this second situation...
Both situations are identical in the collision event - they both result in collision and both have the same RepositionDirection. To detect if the Enemy is near the edge of a platform, we can use a new collision rectangle (displayed in green). If this green rectangle is not colliding with SolidCollision, then the player is near a ledge. For example, in the following diagram the Enemy is near the edge of a platform on the right side:
If the player is not near a ledge, then the green rectangle will collide with solid collision.
Since we want to detect ledges on both the left and right side, we need to have one new rectangle on each side.
When we define these rectangles, we need to remember a few things:
These rectangles should not be used for solid collision - they should be excluded from the ICollidable interface
These rectangles should be smaller than the size of our tiles (16x16) so they do not reach across gaps or down pits
These rectangles will be used to apply logic when not colliding with the SolidCollision. Normally we perform logic when a collision occurs, so we will need to write custom code to handle this situation.
As mentioned above, we'll need to create two AxisAlignedRectangle instances in our Enemy entity - one for left edge collision and one for right edge collision. To create the left edge collision rectangle:
Select the Enemy entity
Select the Quick Actions tab and click the Add Object to Enemy button
Select the type AxisAlignedRectangle
Enter the name LeftEdgeCollision
Click OK
With the LeftEdgeCollision object selected, click on the Variables tab
Change X to -12
Change Y to -12
Change Width to 8
Change Height to 8
To help visualize, change the Color to Green
Repeat the steps above to create RightEdgeCollision, but change the X value to a positive 12 so it sits on the right side of the Enemy.
Now our enemy has two extra rectangles which it can use for detecting edges, but these rectangles are being used for solid collision. To fix this:
Select the LeftEdgeCollision
Click on the Properties tab
Change IncludeInICollidable to False so that the rectangle is not used in default collision relationships (like EnemyListVsSolidCollision in GameScreen)
Repeat the steps above for the RightEdgeCollision and the Enemy will no longer use these two rectangles for solid collision.
Normally when performing logic related to collision, we do so inside a collision relationship event. Detecting an edge is different because we need to respond to a situation when there is no collision. Therefore, we will be writing code which happens every frame inside of our GameScreen CustomActivity. To detect if the enemy should turn around, modify the GameScreen.cs file so that its CustomActivity matches the following code snippet:
This code shows that we can check collisions directly in code at any time, rather than relying purely on CollisionRelationships. Notice that we only perform this logic if the Enemy is on the ground. Otherwise, enemies would alternate directions very quickly when falling. If using the default level, this logic won't do anything since there are no raised platforms. We can modify Level1Map to add a few platforms. Feel free to adjust it however you like, as long as you have platforms with edges which we can use to test this logic.
We should also adjust the Enemy1 starting position so it starts on a ledge instead of falling from the top of the level. Change the X and Y values on Enemy1 in Level1 so that it starts in the right spot. This may take a little trial-and-error. Once you have the Enemy on a platform, you may notice that it still runs off the edge. The reason for this is because the Enemy moves very quickly and it takes time to slow down when changing directions.
To do this:
Select the Enemy entity
Click the Entity Input Movement tab
Change the Max Speed to 100
Change the Slow Down Time to 0.03
These changes enable the enemy to turn around without falling off of the platform. It's important to note that with enough Max Speed or a large enough Slow Down Time will result in an enemy sliding off of an edge.
Now our enemy has functionality for turning around based on wall collision and edge detection. The two approaches shown here could also be used to turn around when Enemies collide with other Enemies (also using reposition values), but we'll leave this and other situations to individuals who are interested.
Return to Glue and click the Folder icon to open the project folder
Next the Enemy should be positioned so it is standing on the ground. You can position this through trial-and-error by running the game and adjusting the position of the entity, but the fastest way to accomplish this is in Live Edit mode. For information about setting up Live Edit, see the page.
The first line of code in the Player class defines an IPressableInput. This is an object which can reference any pressable input hardware such as a keyboard key or an Xbox360GamePad button. We create this IPressableInput so that we can write code which will work regardless of input device. For more information on IPressableInput, see the .
The sample project can be downloaded from Github:
For detailed information about how to create a localization database, see the . The LocalizationDatabase.csv file includes two columns. The leftmost column is the string ID - this is how code accesses the text in the CSV. The second column is the text for the string ID in English (as the column name indicates).
This set of tutorials covers how to change a Player's movement values in response to colliding with various ground types (such as ice). It also covers how to implement swimming, and how to transition between in and out of water movement. Multiple terrain types and water movement are common in many platformers such as Super Mario World.
This tutorial begins with an empty project and will create a Player which changes its movement values according to collision with different ShapeCollections.
This set of tutorials covers how to create enemies which can receive damage from Bullets created by the Player. Many games include enemies which can take damage from the player such as Mega Man X and Super Metroid.
The following pages cover a number of topics related to dealing damage:
Dynamic entity creation and destruction (Bullets)
HP and damage amounts
Damage over time and area of effect
Friendly fire and teams
This tutorial shows how to add ice and water collision. We'll be setting up the TileShapeCollections for these two types of tiles, and creating collision relationships to control the interaction between the Player and these tiles.
As shown in the previous tutorial, the Player already collides with the solid collision. This is automatically added by the New Project Wizard, so we don't have to do any setup for ground collision. We'll add ice collision first. To do this:
Click GameScreen
Select the Quick Actions tab and click the Add Object to Game Screen button
Select TileShapeCollection
Enter the name IceCollision
Click OK
Select the newly-created IceCollision object
Click the TileShapeCollection Properties tab
Click the From Type option
Change the Source TMX File/Object to Map
Change the Type to IceCollision
We'll repeat the process above to create water collision:
Click GameScreen
Select the Quick Actions tab and click the Add Object to Game Screen button
Select TileShapeCollection
Enter the name WaterCollision
Click OK
Select the newly-created WaterCollision object
Click the TileShapeCollection Properties tab
Click the From Type option
Change the Source TMX File/Object to Map
Change the Type to Water
Now our game has two new collision relationships: IceCollision and WaterCollision. This means that when our game runs, collision shapes are created based on the water and ice tiles, but we haven't yet told the game how to handle collisions between the Player and these collision relationships. First, we'll set up collisions between the Player and IceCollision:
Drag+drop PlayerList onto IceCollision
The newly-created PlayerVsIceCollision is automatically set up to use Platformer Solid Collision physics, so we don't need to make any changes. We also need to create collision between PlayerList and WaterCollision, but this time we need to disable Platformer Solid Collision since the Player should be able to fall into the water. To do this, drag+drop PlayerList onto WaterCollision and select No Physics.
Now if we run our game we can collide with the ice tiles and fall through water.
Now our game has ice and water TileShapeCollections and collision relationships. You may have noticed that the ice currently acts identical to solid collision (bricks). The next tutorial will create new platformer variables for moving on ice and swimming in water, and will switch between them in response to collision.
This tutorial will implement dealing damage to an Enemy. Rather than immediately destroying the Enemy when it collides with a Bullet, we'll implement health points (HP) which can be reduced when an Enemy collides with a Bullet. Once an Enemy's HP is reduced to 0, it will be destroyed.
Typically enemies will have two values for HP:
MaxHP - the starting number of HP when an Enemy is first spawned
CurrentHP - the number of HP that the enemy currently has
The MaxHP is a designer variable - a variable which a game designer may change during the development of the game to adjust difficulty. We'll define this in Glue so it can be changed easily. By contrast, CurrentHP is a logic variable - a variable which is used in game logic and which should not be modified by a game designer. It will initially be assigned to MaxHP and will be reduced whenever the Player takes damage. To define a MaxHP variable:
Select the Enemy entity
Click on the Variables tab
Click the Add New Variable button
Select the int type
Enter the name MaxHP
Click OK
Set the new MaxHP variable to 6
Now we can add the CurrentHP value to the Enemy:
Open the project in Visual Studio
Open Enemy.cs
Add the CurrentHp variable and modify CustomInitialize as shown in the following code snippet
In a typical game, whenever an Enemy takes damage the game may perform many actions such as:
Flashing the Enemy or playing some visual effect
Playing a sound effect
Showing the amount of damage dealt (as a floating number)
Modifying how much health is shown in a health bar
Destroying the Enemy and showing a death effect if the Enemy has died
Keeping this logic in the Enemy.cs file can help keep code organized, so we'll be adding the following function to the Enemy.cs file:
Be sure to make the TakeDamage method public so it can be called from outside of the Enemy class.
Now we can create a collision relationship between the BulletList and EnemyList objects in the GameScreen:
Expand the GameScreen Objects folder
Drag+drop the BulletList object onto EnemyList\
Select the new BulletListVsEnemyList relationship
Click on the Collision tab
Click the Add Event button
Accept the defaults and click OK
Now we can fill in the event method. Open GameScreen.Event.cs and modify the BulletListVsEnemyListCollisionOccurred method as shown in the following snippet:
Now we can shoot at the enemy. After six shots, the enemy is destroyed.
Now we've created a game where we can destroy an enemy with 6 shots. A real game would have more than one enemy, but if we add additional enemies, our game will automatically collide bullets against each enemy, and each enemy will keep track of its own HP.
This tutorial, like many others in this documentation, begins with an empty Glue project. I'll call mine PlatformerMovement.
Keep the Open New Project Wizard option checked.
Once the project finishes loading, Project Setup Wizard appears. Select the Standard Platformer option.
Now our project is ready to go. If we run the game we have a standard platformer project.
The default Platformer project creates a game with a level (Level1). We will modify this level to create ice and water tiles.
We can open Level1Map.tmx to add additional tiles.
Level1Map.tmx should appear in the Tiled app. To make sure that no tiles from other maps are being used, be sure to close other TMX files in Tiled.
Default maps include a tileset named TiledIcons. Most of these icons have no built-in functionality, so they can be used to add custom behavior to your game. In this case we will use the ice and water tiles which are part of the tileset.
We can paint these tiles, along with more solid collision tiles, to create a test level with ice and water.
We have a level where the player can collide with the solid ground, but ice and water do not yet affect the Player's movement.
The next tutorial shows how to add collision and change the player's movement values on ice and in water.
As of the last tutorial our game has three tile shape collections:
SolidCollision - the bricks that make up most of the level
IceCollision - floating blocks of ice which should be slippery
WaterCollision - an area of the level where the player should be able to swim
This tutorial shows how to create new platformer values for ice and water, and how to change between them depending on collision type.
Before we change which platformer values are used based on collision, we first need to define the platformer values for the different movement types. As mentioned before, we will have the following platformer values:
Ground - already defined by default
Air - already defined by default
Ice - will apply when user collides with the ice TileShapeCollection
Water - will apply when the user collides with the water TileShapeCollection
To add a new movement value:
Click the Player entity
Click the Entity Input Movement tab
Click the Add Control Values button
Set the following values:
Movement Type = Ice
Max Speed = 160
Speed Up/Down = true (click radio button)
Speed Up Time = 1
Slow Down Time = 1
Jump Speed = 270
Hold to Jump Higher = true (click checkbox)
Max Jump Hold Time = .17
Repeat the process above to create a water movement. Click Add Control Values again, and set the following values:
Movement Type = Water
Max Speed = 120
Speed Up/Down = true (click radio button)
Speed Up Time = 1.3
Speed Down Time = 0.5
Jump Speed = 120
Hold to Jump Higher = true (click checkbox)
Max Jump Hold Time = 0.3
Gravity = 210
Max Falling Speed = 90
Now that we have Ice and Water movement defined, we can write code in the Player class to set the movement values according to what the player has collided with. To do this:
Open the project in Visual Studio
Open Player.cs
Add the following code to CustomActivity:
Now our Player will change movement values when moving on ice and solid ground.
This concludes the platformer movement value tutorials where we use multiple TileShapeCollections to change the movement values for the player between regular ground/air movement, ice, and water movement.
Platformer games (such as Super Mario World) include animated characters which play animations in response to input, physics, and collision with the environment. For example, a game like Super Mario World includes a number of animations:
Idle
Walk
Run
Jump
Duck
Look up
Skid (Trying to turn around when running)
Each of these animations is played in response to logic determining which should play. We can think of each of these animations as having different priority relative to other animations. For example, if the player is pushing the Up direction in Super Mario World, Mario looks upward. This animation has priority over the Idle animation, which plays if the Up is not held. Similarly, the Run animation is played if the player is moving faster than a certain speed, is on the ground, and is holding the run button. If this condition fails, then other animations (such as Walk) perform their logic. The logic to set animations can be performed through standard logic, but FlatRedBall offers a standard animation solution through the class. This tutorial explores AnimationLayers and which are used to update the Player animations.
By default our Player already has an AnimationController. It doesn't appear in the Player object in the FlatRedBall Editor, but it is generated automatically based on the default settings on the Player. AnimationControllers (both in code and in the FlatRedBall Editor) are containers for AnimationLayers. Each AnimationLayer typically maps to a single animation (or two animations - a left and right facing version). By default, our player has animations for idle, walk, fall (in air, moving downward), and jump (in air, moving upward). We can see these layers by selecting the Player, clicking on the Entity Input Movement tab, and selecting the Animation
Each animation layer plays if its conditions evaluate to true and if the animations below it evaluate to false. Another way to think about this is, entries further down can "overwrite" the current animation if they evaluate to true, with the bottom-most getting the highest priority if it evaluates to true.
Each animation layer defines which animation is played, conditions for playing, and animation speed to play. For example, the CharacterWalk animation plays only if the player has a greater velocity than 1 and if all other animations (CharacterFall and CharacterJump) fail to play.
If we change the Min X Velocity Absolute, we can modify the behavior of the player. For example, if we change the value to 140, the player will only play the walk animation if walking faster than 140 units per second.
While this specific example may not be a practical change to the game, it shows how the logic controls whether an animation is played, and how that logic can cascade up through the layers. The following conditions can be used to control whether an animation plays:
Min X Velocity Absolute - this is the minimum speed that the player must be moving to play the animation. For example, we set the minimum speed to 140 for the CharacterWalk animation, which means the character will only walk if moving faster than 140 units per second. Note that this is Absolute which means the character must be moving either greater than 140 pixels (to the right) or less than -140 pixels (to the left).
Max X Velocity Absolute - this is the maximum speed that the player can move to play the animation. For example, setting the maximum speed to 200 means that if the player is moving faster than 200 units (absolute), then the animation will not play.
Min Y Velocity - the minimum Y velocity (vertical movement) the player must have to play the animation. For example our player has CharacterJump animation which plays only if the velocity is greater than 0.
Max Y Velocity - the maximum Y Velocity (vertical movement) that the player can move to play the animation. If the player is moving faster than this value upward, then animation will no longer play.
Movement Type - this option restricts an animation to whether the player is on the ground, air, or either. For example, this can be used to restrict animations to only play when in the air, such as the CharacterFall and CharacterJump animations.
Movement Name - this option restricts an animation to only play when a particular set of Movement Values are active. This is a dropdown which contains all of the movement values defined in the Movement Values section. This is especially effective if you have logic which is already setting the Movement Values in code - such as allowing a user to run when the run button is held.
Custom Condition - This allows the entry of code (such as a bool variable name) to be checked for whether an animation should be played. See the section below for more information on this property.
The Custom Condition property is the most flexible solution for setting animations. Typically this is used in combination with a bool field or property in the Player object. For example, the Player may have an IsTired property which would return true if the Player's health is below 10% of the max.
This can be used by the animation layer by entering the IsTired variable in the Custom Condition text box.
This produces generated code which checked IsTired in code. The text entered in Custom Condition is directly copied over so you can add any text there which evaluates to true, although the recommended approach is to encapsulate any logic in a property (as shown above) which is then referenced.
The Player contains an .achx file (Animation Chain List XML) which defines all available animations for the Player.
This file can be opened in the Animation Editor to view all animations. To open this file, either:
Double-click the .achx file. The FlatRedBall Editor uses the Windows file association for .achx --or--
Go to the <unzipped FRBDK location>/FRBDK/Xna 4 Tools/AnimationEditor/AnimationEditor.exe
Notice that this .achx file happens to include animations which are not used by the Player - that's okay because not all animations must be used, and a single file can be shared by multiple entities. The FlatRedBall Editor scans this file and provides these animations in the dropdown.
Note that the .achx file contains "Left" and "Right" versions of character animations, but the FlatRedBall Editor only provides the names without Left/Right suffixes. This keeps the Animation Name dropdown smaller and makes it easier to read. This naming convention is encouraged for character animations, and if the Has Left and Right checkbox is checked, then only animations with Left and Right suffixes appear in the dropdown. If animations do not have left and right options, then this option can be unchecked. Then the full name of all animations show up in the dropdown.
The Animation Speed Assignment dropdown provides the animation speed assignment logic. By default, all animations use the ForceTo1 speed, which means the animation plays at the normal speed as defined in the .achx. This dropdown provides a number of options:
ForceTo1 - the Sprite's AnimationSpeed will be forcefully set to 1, so the animation will play at the same speed as defined in the .achx
NoAssignment - the Sprite's AnimationSpeed is left untouched. Previous assignments on the AnimationSpeed (either in custom code or through other animation layers) will remain on the Sprite.
BasedOnVelocityMultiplier - if values are specified, then the Absolute X Velocity Animation Speed Multiplier or Absolute Y Velocity Animation Speed Multiplier values are multiplied by the Player's runtime velocity to determine the speed. For example, if the X Velocity multiplier is set to .1, then the animation speed plays at full speed if the Player is moving at 10 units per second, and plays at 10 times the speed if the Player is moving at 100 Units per second.
BasedOnMaxSpeedRatioMultiplier - if these values are specified, the animation speed will be specified based on the speed of the Player relative to the max speed of the current movement value. For example, the Max Speed on the ground is set to 160 units on the Player. If the MaxSpeedXRatioMultiplier value is set to 1, then the animation plays at full speed when the player moves at max speed. If the MaxSpeedXRatioMultiplier is set to 2, then the animation plays at twice full speed when the player moves at max speed.
For example, the CharacterWalk animation can be set to use BasedOnMaxSpeedRatioMultiplier with a Max Speed X Ratio Mutiplier of 2. This results in the walk animation playing faster based on the movement speed of the player.
Animation layers can be added in the FlatRedBall Editor. New animations layers can be added through the Add button at the bottom of the animation list.
Also, existing animations can be copied with the copy button.
This guide covers all of the options available when defining animation layers. The next tutorial covers changing the platformer movement values.
Now that we have our animations set up, we can work in our platformer values. These values control the way the Player entity moves in response input (such as max speed) and the physics which impact the Player's movement (such as gravity). Our Player entity automatically gets a set of default values which is why we are already able to walk and jump around the level. This tutorial will modify the default values and add additional platformer values for running and ducking.
Before we begin modifying the control values, we'll change the resolution of our game to match the original .
In Glue, click the Camera icon
Change the resolution width to 256
Change the resolution height to 224
Change the TextureFilter to Point so that pixels draw without any blurring
Change the Scale so that the game is larger on your screen. A typical 1080 monitor can support the game at 400% Scale.
After making these changes the game should more closely resemble the resolution of the original Super Mario World.
Currently our game has two set of platformer movement values:
Ground
Air
Earlier we added running animations which play when the run button is held. We will be modifying our game so the Player entity can run faster when the run button is held. We will be creating two new set of platformer movement variables for running. We will also be creating a new type of movement for when the player is ducking. Therefore, we'll have three more sets of movement variables:
Running
RunningAir
Ducking
Click the Add Control Values button three times, and name the new control values as listed above.
You should now have five sets of values.
Next we'll modify the values to make the game feel a little more like Super Mario World. We'll be modifying these to get close - this is not intended to be an exact copy. Of course if you want to tune the values to make the game feel different, or if you want to mimic Super Mario World even more closely, feel free to change these values.
Max Speed = 100
Speed Up/Down
Speed Up Time = 0.25
Slow Down Time = 0.15
Jump Speed = 230
Hold to Jump Higher = true (checked)
Max Jump Hold Time = 0.2
Max Speed = 100
Speed Up/Down
Speed Up Time = 0.7
Slow Down Time = 0.7
Jump Speed = 0
Gravity = 900
Max Falling Speed = 260
Max Speed = 150
Speed Up/Down
Speed Up Time = 0.3
Slow Down Time = 0.2
Jump Speed = 250
Hold to Jump Higher = true (checked)
Max Jump Hold Time = 0.25
Max Speed = 150
Speed Up/Down
Speed Up Time = 0.7
Slow Down Time = 0.7
Jump Speed = 0
Gravity = 900
Max Falling Speed = 260
Max Speed = 0
Speed Up/Down
Custom deceleration above max speed = true (checked)
Custom deceleration value = 200
Jump Speed = 230
Hold to Jump Higher = true (checked)
Max Jump Hold Time = .2
Now that we have our platformer movement values created in Glue, we can assign them in code. To switch values, we can change the GroundMovement and AirMovement variables in CustomActivity. We will be looking at the VerticalInput (holding up/down) and the RunInput to decide whether the player should be using the default values, running values, or ducking. To switch between these platformer movement values, modify CustomActivity in Player.cs as shown in the following code snippet:
Now the Player will switch its values according to input, of course, the running animations are not currently being used. Use what was covered in the previous tutorial to see if you can modify the Player's animations to play the running and running jump animations as shown in the following animation.
This tutorial begins with a new platformer project. To create a new platformer project, see the tutorial. In short, the easiest way to get an animated character is to use the new project wizard.
If you started a new project without using the wizard, you would need to and to the sprite. If you don't have a .achx file prepared already, use these below to give yourself a head start.
Once finished, your project will have a character which is animated and ready to go. The remainder of the tutorials in this series cover how to modify the animations on the player and how to add new animations in response to game logic.
This set of tutorials covers how to create enemy movement in a platformer game. Enemies in many platformers move horizontally until hitting a wall or the edge of a platform, then turn around.
This tutorial creates an Enemy entity which uses Platformer physics, but is fully controlled by logic rather than input devices like the keyboard.
This set of tutorials covers how to write logic to control a platformer animation and to change platformer values according to input (such as a run button and ducking).
This tutorial will create a game similar to Super Mario World. The first begins with a completely empty project and covers the FlatRedBall platformer technology and best practices.
This walkthrough covers the concept of wall sliding and wall jumping. When a player is in the air and pressing against a wall, the player's fall speed will slow the player's falling speed and enable jumping.
This tutorial assumes a project that has been created with the platformer wizard options, but this is not a requirement. Wall sliding and jumping can be added to any platformer project.
This walkthrough covers a number of main concepts for wall jumping
Detecting if the user is "pressing" against a wall when in the air
Defining sliding platformer values
Forcing the player to move "outward" on wall jumps
Playing animations when wall jumping
When the player is sliding against a wall, the player's movement is different than when the player is in the air. The most obvious changes are:
The player falls more slowly (max fall speed is reduced)
The player can jump off of the wall
We can create a new set of movement values by copying the existing Air movement values:
Once copied, change the following values:
Movement Type = WallSliding
Jump Speed = 250
Max Falling Speed = 150
Once the WallSliding values have been defined, we need to detect if the player has collided with a solid wall. We can do this by adding the following method to Player.cs which returns whether the player is wall sliding.
The code above assumes that any horizontal reposition on a collision allows wall jumping. You may want to further restrict wall jumping to certain collision types to prevent the player from wall jumping in some situations. For example, you may not want the player to wall jump when sliding against spikes.
To do this, you can check the LastFrameItemsCollidedAgainst or LastFrameObjectsCollidedAgainst to only allow wall jumping if colliding against certain objects. For example, your if statement may be modified as shown in the following code snippet:
The GetIfIsSlidingOnWall can then be used in the CustomActivity to decide whether the player's AirMovement should be WallSliding or Air, as shown in the following code:
Note that this code uses a property IsSlidingOnWall rather than a local variable. Although a property provides no additional benefits on this code, it will be used later when assigning animations.
With this code in place, the player can now slide down walls and jump when sliding.
The implementation above results in the Player being able to jump against walls indefinitely. Many games push the player outward on jumps. Some games such as Mega Man X push the player outward only slightly, allowing the player to climb a wall through wall jumps. Other games such as New Super Mario Bros U Deluxe push the player outward far enough that the player cannot climb up a single wall.
Whether a player can climb a wall depends on the outward XVelocity applied on a jump.
This code adds a handler to when the player jumps. The jump checks if the player is sliding on the wall and if so, pushes the player "outward". Note that the outwardVelocity is defined here in the HandleJumped method, but you may want to put this variable in the Player entity so that it can be tuned without changing code. Also, keep in mind that a smaller outwardVelocity value results in the player being pushed outward less. If the value is small enough then the player will be able to climb the wall through wall jumping. With the values used here, this value is roughly around 30.
We can add sliding animations by checking the IsSlidingOnWall variable either in code or in the Animations tab for the player. If you are already using the Animations UI, then handling the IsSlidingOnWall variable requires no code. To do so:
Select the Player Entity, and select the Entity Input Movement tab
Click on the Animation item
Copy the CharacterFall animation row
Select CharacterWallSlide animation on the new row
Enter IsSlidingOnWall for the Custom Condition on the new row
The player will now play the CharacterWallSlide animations if the IsSlidingOnWall property is set to true. Note that the .achx file contains CharacterWallSlideLeft and CharacterWallSlideRight, but the generated code selects the appropriate one based on which way the player is facing.