Adding Triggers to the Platformer Starter Kit
I’ve seen a lot of posts where people are using the Platformer Starter Kit and as I was reading one, I thought having the ability to have triggers in a level might be something someone would find useful.
Triggers give the level designer the ability to make levels more challenging and diverse, instead of simply having the player run around collecting gems and power-ups in a wide open level. The added dimension of a goal that may have to be reached before exiting the level makes the game more challenging, which leads to the game possibly being more interesting to play.
The first use that I thought of for a trigger was to remove a block( s ) that impedes reaching the exit. Should be simple enough, right? Let’s find out.
I started out by figuring that two kinds of triggers might be good to have – visible and invisible. Even though triggers aren’t tiles, the level designer needs to see them in the level data, which means the code that reads the text files that hold the level data have to account for them. “T” and “I” are what I selected for the two trigger types.
In the Level.cs file’s LoadTile method, add the following to the end of the switch statement before the “default” case:
// visible trigger case 'T': return new Tile(null, TileCollision.Passable); //invisible trigger case 'I': return new Tile(null, TileCollision.Passable);
For the visible trigger, the player will have to have something to see, which means we’ll need a sprite. Behold, the awesome programmer art!
I placed the file in the Sprites folder in the Content project.
The sprite doesn’t look exactly like this (at least not as I’m typing this). You can open the file in the attached source linked to at the bottom of this post to see it. The important thing is to ensure that whatever you use is sized correctly (I just opened the gem.png file and had at it) and has a transparent background.
The sprite needs an object to load it into and we’ll need something to hold one or more triggers in the level, so add the following at the top of the Level.cs file’s Level class:
private List<Trigger> triggers = new List<Trigger>(); private Texture2D triggerTexture;
The Trigger class looks like this:
public enum TileTriggerType { None, MoveTile, RemoveTile } public class Trigger { public Vector2 Location; public bool IsTriggerVisible = true; public Vector2 TriggerTarget = Vector2.Zero; public TileTriggerType TriggerType = TileTriggerType.None; public bool IsActive = true; }
Since the text file that holds the level data is fairly set in stone as far as it’s content, we’ll use separate files to hold the data for a trigger. I decided on something fairly straightforward as far as format:
69,13|2|70,0|1
The pipe character separates the pieces of data. The first piece is the location of the trigger, the second the type of trigger, the third, the location of the tile that the trigger acts upon, the last whether or not the trigger is visible.
The following code reads the trigger file for the current level:
//load level triggers data try { using (Stream stream = TitleContainer.OpenStream("Content/triggers/" + levelIndex.ToString() + ".txt")) { using (StreamReader sr = new StreamReader(stream)) { string line = sr.ReadLine(); while (line != null) { Trigger trigger = new Trigger(); string[] data = line.Split('|'); Vector2 vec = new Vector2(Convert.ToInt16(data[0].Substring(0, data[0].IndexOf(','))), Convert.ToInt16(data[0].Substring(data[0].IndexOf(',') + 1))); trigger.Location = vec; trigger.TriggerType = (TileTriggerType)Convert.ToInt16(data[1]); vec = new Vector2(Convert.ToInt16(data[2].Substring(0, data[2].IndexOf(','))), Convert.ToInt16(data[2].Substring(data[2].IndexOf(',') + 1))); trigger.TriggerTarget = vec; trigger.IsTriggerVisible = data[3] == "1" ? true : false; triggers.Add(trigger); line = sr.ReadLine(); } } } } catch (Exception ex) { //do nothing } triggerTexture = Content.Load<Texture2D>("Sprites/trigger");
The last piece of the puzzle is to handle the player hitting the trigger. Add the following to the Update method of the Level class after the UpdateEnemies(gameTime) line:
//check for hitting trigger if (Player.IsAlive && Player.IsOnGround) { foreach(Trigger trigger in triggers) { if(trigger.IsActive) { //offset to center of trigger texture if (Player.BoundingRectangle.Contains((int)trigger.Location.X * Tile.Width + Tile.Width / 2, (int)trigger.Location.Y * Tile.Height + Tile.Height / 2)) { trigger.IsActive = false; switch (trigger.TriggerType) { case TileTriggerType.MoveTile: { break; } case TileTriggerType.RemoveTile: { tiles[(int)trigger.TriggerTarget.X, (int)trigger.TriggerTarget.Y] = new Tile(null, TileCollision.Passable); break; } } } } } }
I’ve only handled the RemoveTile trigger type. I’m leaving the MoveTile as an exercise for the reader (that’s my story and I’m sticking to it!).
I modified the first level to add a wall blocking the exit and set the trigger near it so the player can see the tile being removed:
......................................................................#....... ......................................................................#....... ...........................G..........................................#....... ..........................###.........................................#....... ......................G...............................................#....X.. .....................###................G.GDG.............G.G.G...############ .................G....................#########..........#######.............. ................###........................................................... ............P...................G.G...............G.G......................... ...........###.................#####.............#####........................ .......G...................................................................... ......###...............................GDG.G.............G.G.G.G.G.G.G.G.G.G. ......................................#########.........##.G.G.G.G.G.G.G.G.G.. .1........................................................GCG.G.GTGCG.G.G.GCG. ####################......................................####################
Save everything and give it a try. Assuming I didn’t miss anything you should be able to hit the trigger and see the top block in the wall disappear, enabling you to reach the exit. If I did miss something please let me know. In that case you can download the source from here and give it a try.
Other possible triggers that I could see being useful:
- Moving blocks to enable the player to reach areas not accessible otherwise
- Moving blocks to crush enemies (this would be a pretty big modification)
- Moving blocks to prevent enemies from reaching and area and allowing the player to safely move through that area
- Moving blocks to allow access to power-ups
- One thing that currently isn’t handled by this code is the ability to have a trigger modify multiple tiles. This isn’t a big enhancement. Simply change the TriggerTarget member of the Trigger class to a List<Vector2>, change the code to read the trigger file to handle one or more pieces of data (possibly by making the target data the last piece of data read) and changing the code in the Level class Update method to loop through this List.
- There are a lot of areas of level design that are opened up by a pretty simple modification to the game code. I’d love to see what people can come up with.
- If anyone can think of other small enhancements to the kit, let me know.
Hello, I know its been some months so im not sure if you will reply. Your visible targets lack a drawing method I think, and it shoudn’t be null in the switch and case. Also I’m not sure I understand how the trigger.txt works, does the function to read go in level.cs along with tile reading, or in the main?
Thanks!
+1
Error 1 Cannot autodetect which importer to use for “triggers.txt”. There are no importers which handle this file type. Specify the importer that handles this file type in your project. C:\Users\Bjørn\Documents\Koding for fun\Platform spill, idk\Content\triggers.txt GameStateManagementSample (Windows)
.. help??? :O
You need to set the file to not compile. Set the Build Action for it to None and Copy to Output Directory to Copy if newer.