EDN Admin
Well-known member
ScriptTD is an open source project that allows anyone to easily create a new Tower Defense game for the Windows Phone 7 platform, without any prior programming knowledge. The project lets you create new art & audio and edit some XML files to bring it all together into a polished game without having to write all of the code required to make the game work. If you are looking to just create a game without coding, this article isn’t for you, head to http://scripttd.codeplex.com http://scripttd.codeplex.com and follow the instructions there to get started. http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/HomeScreen%5B3%5D%5B1%5D%5B3%5D.png <img title="HomeScreen[3][1]" src="http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/HomeScreen%5B3%5D%5B1%5D_thumb%5B1%5D.png" alt="HomeScreen[3][1]" width="550" height="330" border="0 <h3>Extending the Code</h3> You can easily get away with editing the XML and adding new assets to make a whole new Tower Defense game with ScriptTD as it is. If you know a bit of C#, however, you can take it further and extend the game to make a unique creation. The entire source code for the project is available for free on Codeplex (see links above), so feel free to download that and change any part of it that you want. There are three key areas that are easy to change in ScriptTD: Weapons The GUI The Game Screens (Menus) In this article we are going to focus on the main one you might want to edit—the weapons. <h3>Straight from the Labs</h3> The game already has a number of different weapon types, ranging from projectiles to earthquake generators; the laser included with the project, however, only fires along one of the four cardinal directions (N, S, E, W). We will extend the code to add in a new type of laser, one that follows a target as it shoots. Remember that the code simply defines the behavior of the weapon, so you could change the art into flame images and create a flamethrower, without changing any of the code you are about to create. <h3>Getting Started</h3> This first thing you need to do before you can start creating your custom weapon is grab the source code for http://scripttd.codeplex.com/ ScriptTD from Codeplex . Every weapon in the game implements the IWeapon interface, which is provided by the engine. This allows the game to interface in a common way with each weapon type. Once you implement that, simply register the weapon type when the game starts up, and then refer to it in the XML data files when you need to. For the purposes of this article we will work with the sample game, though everything you learn here can easily be applied to your own version of the game. First begin by creating a new class in the Coding4Fun.ScriptTD.Sample project and name it TrackingWeapon . Once you are done you should have an empty class that looks like this: <pre class="brush: csharp using System.Collections.Generic;
using Coding4Fun.ScriptTD.Engine.Data;
using Coding4Fun.ScriptTD.Engine.Data.Abstracts;
using Coding4Fun.ScriptTD.Engine.Logic;
using Coding4Fun.ScriptTD.Engine.Logic.Instances;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace Coding4Fun.ScriptTD.Sample
{
public class TrackingWeapon : IWeapon
{
public Texture2D Texture { get; set; }
public TowerData TowerData { get; set; }
public bool CanFire()
{
throw new System.NotImplementedException();
}
public bool TargetAndFire(ref List<EnemyInstance> enemies,
ref Vector2 towerPos, float gridCellSize)
{
throw new System.NotImplementedException();
}
public void Update(float elapsedSeconds, ref GameSession session)
{
}
public void Draw(GraphicsDevice device, SpriteBatch sb)
{
}
}
}
[/code] When working with weapons in this project, you need to remember that weapons act purely as logic operating on the provided data. Accordingly, the game will provide the texture and specifications for the tower, and you need to make use of that to make the weapon behave as it should. Inside the base class above, we have two properties that are set by the game: Texture and TowerData . These will be set automatically upon creation, so you can generally ignore those unless you want to get info from the texture whenever it changes. Next we have the CanFire method; this is usually used to check if the tower has finished reloading. Depending on your tower, however, you could do other checks here. The key thing is that this is just telling the game if the tower is ready to fire, rather than if it has any targets. Finding targets is done in the next method, TargetAndFire . This is where you have access to the list of enemies that you can test against for range/suitability, and based on that choose a target and fire. After that we have the usual Update and Draw methods, which let you manage shots in flight as well as keeping track of reloading. For this weapon, we won’t worry about reloading. Instead the weapon will constantly fire at its closest target. <h3>1, 2, 3, Fire!</h3> First we need to tell the game it is ok to fire at any time, so simply make CanFire() return true: <pre class="brush: csharp
public bool CanFire()
{
return true;
}
[/code] Once that is done, we can begin with the code of the weapon, the TargetAndFire() method. And so we will we doing the following: Check which enemy is the closest Make sure we are allowed to shoot at the enemy Make that enemy the target First we need a variable to keep track of the closest enemy, and their distance, so we need an EnemyInstance variable and a float. I have named them closest and distSq, respectively, and initialised distSq to float.MaxValue (you’ll see why shortly): <pre class="brush: csharp
EnemyInstance closest = null;
float distSq = float.MaxValue;
[/code] Once we do that, we need to prepare our Min and Max ranges so that we can ensure we only target enemies within that area. To do so, we need to add the following code: <pre class="brush: csharp
float maxRange = TowerData.MaxRange * gridCellSize;
maxRange *= maxRange;
float minRange = TowerData.MinRange * gridCellSize;
minRange *= minRange;
[/code] The MaxRange is stored as the number of cells, so we need to expand this into the actual distance by multiplying with the provided cell size. Then we square it because we will be doing all of our distance tests using the squared length values. We do this to save on performance, and since a square root operation (as required by the Length formula) can be costly, we instead use the squared distance, and as long as everything uses squared distance, it will all be correct. Next we need to loop through every enemy and check the following: Can we target the enemy? Is it a flyer or land enemy, can we target either? Is it the closest? Is it within the Min/Max range If all of the above is true, we set the enemy to be our closest, and continue iterating through the list of enemies: <pre class="brush: csharp
for (int i = 0; i < enemies.Count; ++i)
{
if ((TowerData.CanShootFlyers && enemies.Data.CanFly)
|| (TowerData.CanShootLand && !enemies.Data.CanFly))
{
float d = (enemies.Position - towerPos).LengthSquared();
if (d <= distSq && d <= maxRange && d >= minRange)
{
distSq = d;
closest = enemies;
}
}
}
[/code] Next, we set the closest enemy as the target, and store some helper data for when we draw later. This helper data includes the position of the tower and the size of a grid cell. Finally, we return true if we have a target, which lets the game know if it should play the "Shoot" sound effect: <pre class="brush: csharp
_towerPos = towerPos;_cellSize = gridCellSize;
_target = closest;
return _target != null;
[/code] <h3>I’m Taking Damage!</h3> Now we need to make the weapon actually damage the enemy, to do this we add the following code to the Update method: <pre class="brush: csharp
if (_target != null)
_target.TakeDamage(TowerData.FullName, TowerData.DPS * elapsedSeconds);
[/code] If we are currently targeting something, we tell it to take damage based on the information inside TowerData . We do this every update because the tower instantly hits the enemy, and will never miss. The enemy itself will handle decreasing its health and applying any resistances it may have, the only thing we need to do is get the correct damage from the DPS (Damage Per Second) value by multiplying it with the elapsedSeconds variable. <h3>Draw their Fire!</h3> Now we will draw the laser as it fires. First check if we have a target to shoot at: <pre class="brush: csharp
if (_target == null)
return;
[/code] Now we can start preparing the data we need to create a continuous line of fire towards the enemy: <pre class="brush: csharp
var origin = new Vector2(Texture.Width / 2f, Texture.Height / 2f);float numSteps = (_target.Position - _towerPos).Length() / _cellSize;
[/code] Here we get the center of the texture; we will use this as the origin when drawing in order to ensure it rotates correctly. We also get the number of grid cell sized textures to draw between the tower and the enemy. Then we can loop through this to draw each texture: <pre class="brush: csharp
for (float i = 1; i < numSteps; ++i)
{
float x = MathHelper.Lerp(_towerPos.X, _target.Position.X, i / numSteps);
float y = MathHelper.Lerp(_towerPos.Y, _target.Position.Y, i / numSteps); Vector2 pos = new Vector2(x, y);
Vector2 dir = _target.Position - pos;
dir.Normalize();
float angle = (float)Math.Atan2(dir.Y, dir.X); sb.Draw(Texture, pos, null, Color.White, angle, origin, 1, SpriteEffects.None, 0);
}
[/code] Here we loop through each step, drawing the texture at an interpolated position. Note the i=1 in the for loop, this lets us start drawing after the tower, so it looks like the laser is emerging from the tower, rather than behind it. Next we get the X and Y coordinates for the texture. To do this we need to interpolate along the line from the tower to the enemy. We can make use of the built in Lerp function to do this by providing the start and end coordinates (the tower and enemy respectively) and then getting a value from 0 to 1 based on which step we are drawing. Next we need to ensure the texture is rotated correctly, we do this using some math by getting the direction vector, normalizing it, and then getting the ArcTan value. Once we have all of the data, we call the appropriate Draw method inside SpriteBatch . The parameters in order are: The Texture The Position to draw the texture A Rectangle describing what portion of the texture to draw (in our case: all of it) The tint to apply (in our case: no tint, which is Color.White) The angle with which to rotate the texture around its origin The origin of the texture The scale (In our case: 1, we don’t scale the texture) The SpriteEffects to apply The depth at which to draw the texture (in our case: in front with everything else) <h3>Weapons in the Armory</h3> Now that we have completed the behavior for the weapon, we can register it with the game, ready for use. To do this, find the SampleGame class inside the Coding4Fun.ScriptTD.Sample project and scroll down to the RegisterWeapons() method (around line 84). Add the following line anywhere inside that method: <pre class="brush: csharp
Armory.AddWeaponType("Tracking", typeof(TrackingWeapon));
[/code] Here we are registering the weapon type, and telling the game that it will be called "Tracking" inside the XML files. This lets us use friendlier names whilst maintaining naming conventions in our code. Now we just need to add this weapon to a tower and try it out. From the root directory for the project, navigate to: <blockquote> Coding4Fun.ScriptTD.ContentDataTowers </blockquote> And open laser.xml and change the WeaponType attribute from "Laser" to "Tracking". With that done, compile the game and run it! You can find a laser inside Mission 4, so to try it out select that mission and place the fourth tower. Wait for it to build and for enemies to arrive, and then bask in the glory of your new weapon. http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/clip_image002%5B6%5D.gif <img title="clip_image002" src="http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/clip_image002_thumb%5B3%5D.gif" alt="clip_image002" width="550" height="330" border="0 <h3>Conclusion</h3> You now know how to add a completely new weapon type to the game, which will let you add a unique touch to your Tower Defense game. There are other areas in the game that you can extend in code, and there is a lot of power in the XML definition files. So if you are interested in learning more, browse the code and the documentation at the Codeplex link at the start of the article. Play around with the behavior, create a new tower in XML, and tweak the values. If you find the sound annoying you can add a delay in between changing targets, or completely remove the sound from the XML. Make whatever you want using the tools available—turn it into something you made. Remember, the tools are free and with some creativity you can make a really great game! <h3>About The Author</h3> I am a student at the University of Technology Sydney, and an R&D Engineer for Orion Integration Pty Ltd, specializing in 3D Visualization, as well as Computer Vision. I have a strong interest in game development, particularly on the Graphics and GPU programming side. I am also a Microsoft Student Partner at my university and the State Lead for the MSPs in New South Wales. You can contact me at <a>michael@mquandt.com or visit my blog at http://mquandt.com/blog http://mquandt.com/blog . Feel free to also follow me on Twitter: http://twitter.com/quandtm @quandtm . If you have any questions or suggestions, contact me through one of the above methods, or if you find any issues with ScriptTD, report them on the Codeplex page. <img src="http://m.webtrends.com/dcs1wotjh10000w0irc493s0e_6x1g/njs.gif?dcssip=channel9.msdn.com&dcsuri=http://channel9.msdn.com/Feeds/RSS&WT.dl=0&WT.entryid=Entry:RSSView:9818e1ba2a49441eb60d9f2c0164e78f
View the full article
using Coding4Fun.ScriptTD.Engine.Data;
using Coding4Fun.ScriptTD.Engine.Data.Abstracts;
using Coding4Fun.ScriptTD.Engine.Logic;
using Coding4Fun.ScriptTD.Engine.Logic.Instances;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace Coding4Fun.ScriptTD.Sample
{
public class TrackingWeapon : IWeapon
{
public Texture2D Texture { get; set; }
public TowerData TowerData { get; set; }
public bool CanFire()
{
throw new System.NotImplementedException();
}
public bool TargetAndFire(ref List<EnemyInstance> enemies,
ref Vector2 towerPos, float gridCellSize)
{
throw new System.NotImplementedException();
}
public void Update(float elapsedSeconds, ref GameSession session)
{
}
public void Draw(GraphicsDevice device, SpriteBatch sb)
{
}
}
}
[/code] When working with weapons in this project, you need to remember that weapons act purely as logic operating on the provided data. Accordingly, the game will provide the texture and specifications for the tower, and you need to make use of that to make the weapon behave as it should. Inside the base class above, we have two properties that are set by the game: Texture and TowerData . These will be set automatically upon creation, so you can generally ignore those unless you want to get info from the texture whenever it changes. Next we have the CanFire method; this is usually used to check if the tower has finished reloading. Depending on your tower, however, you could do other checks here. The key thing is that this is just telling the game if the tower is ready to fire, rather than if it has any targets. Finding targets is done in the next method, TargetAndFire . This is where you have access to the list of enemies that you can test against for range/suitability, and based on that choose a target and fire. After that we have the usual Update and Draw methods, which let you manage shots in flight as well as keeping track of reloading. For this weapon, we won’t worry about reloading. Instead the weapon will constantly fire at its closest target. <h3>1, 2, 3, Fire!</h3> First we need to tell the game it is ok to fire at any time, so simply make CanFire() return true: <pre class="brush: csharp
public bool CanFire()
{
return true;
}
[/code] Once that is done, we can begin with the code of the weapon, the TargetAndFire() method. And so we will we doing the following: Check which enemy is the closest Make sure we are allowed to shoot at the enemy Make that enemy the target First we need a variable to keep track of the closest enemy, and their distance, so we need an EnemyInstance variable and a float. I have named them closest and distSq, respectively, and initialised distSq to float.MaxValue (you’ll see why shortly): <pre class="brush: csharp
EnemyInstance closest = null;
float distSq = float.MaxValue;
[/code] Once we do that, we need to prepare our Min and Max ranges so that we can ensure we only target enemies within that area. To do so, we need to add the following code: <pre class="brush: csharp
float maxRange = TowerData.MaxRange * gridCellSize;
maxRange *= maxRange;
float minRange = TowerData.MinRange * gridCellSize;
minRange *= minRange;
[/code] The MaxRange is stored as the number of cells, so we need to expand this into the actual distance by multiplying with the provided cell size. Then we square it because we will be doing all of our distance tests using the squared length values. We do this to save on performance, and since a square root operation (as required by the Length formula) can be costly, we instead use the squared distance, and as long as everything uses squared distance, it will all be correct. Next we need to loop through every enemy and check the following: Can we target the enemy? Is it a flyer or land enemy, can we target either? Is it the closest? Is it within the Min/Max range If all of the above is true, we set the enemy to be our closest, and continue iterating through the list of enemies: <pre class="brush: csharp
for (int i = 0; i < enemies.Count; ++i)
{
if ((TowerData.CanShootFlyers && enemies.Data.CanFly)
|| (TowerData.CanShootLand && !enemies.Data.CanFly))
{
float d = (enemies.Position - towerPos).LengthSquared();
if (d <= distSq && d <= maxRange && d >= minRange)
{
distSq = d;
closest = enemies;
}
}
}
[/code] Next, we set the closest enemy as the target, and store some helper data for when we draw later. This helper data includes the position of the tower and the size of a grid cell. Finally, we return true if we have a target, which lets the game know if it should play the "Shoot" sound effect: <pre class="brush: csharp
_towerPos = towerPos;_cellSize = gridCellSize;
_target = closest;
return _target != null;
[/code] <h3>I’m Taking Damage!</h3> Now we need to make the weapon actually damage the enemy, to do this we add the following code to the Update method: <pre class="brush: csharp
if (_target != null)
_target.TakeDamage(TowerData.FullName, TowerData.DPS * elapsedSeconds);
[/code] If we are currently targeting something, we tell it to take damage based on the information inside TowerData . We do this every update because the tower instantly hits the enemy, and will never miss. The enemy itself will handle decreasing its health and applying any resistances it may have, the only thing we need to do is get the correct damage from the DPS (Damage Per Second) value by multiplying it with the elapsedSeconds variable. <h3>Draw their Fire!</h3> Now we will draw the laser as it fires. First check if we have a target to shoot at: <pre class="brush: csharp
if (_target == null)
return;
[/code] Now we can start preparing the data we need to create a continuous line of fire towards the enemy: <pre class="brush: csharp
var origin = new Vector2(Texture.Width / 2f, Texture.Height / 2f);float numSteps = (_target.Position - _towerPos).Length() / _cellSize;
[/code] Here we get the center of the texture; we will use this as the origin when drawing in order to ensure it rotates correctly. We also get the number of grid cell sized textures to draw between the tower and the enemy. Then we can loop through this to draw each texture: <pre class="brush: csharp
for (float i = 1; i < numSteps; ++i)
{
float x = MathHelper.Lerp(_towerPos.X, _target.Position.X, i / numSteps);
float y = MathHelper.Lerp(_towerPos.Y, _target.Position.Y, i / numSteps); Vector2 pos = new Vector2(x, y);
Vector2 dir = _target.Position - pos;
dir.Normalize();
float angle = (float)Math.Atan2(dir.Y, dir.X); sb.Draw(Texture, pos, null, Color.White, angle, origin, 1, SpriteEffects.None, 0);
}
[/code] Here we loop through each step, drawing the texture at an interpolated position. Note the i=1 in the for loop, this lets us start drawing after the tower, so it looks like the laser is emerging from the tower, rather than behind it. Next we get the X and Y coordinates for the texture. To do this we need to interpolate along the line from the tower to the enemy. We can make use of the built in Lerp function to do this by providing the start and end coordinates (the tower and enemy respectively) and then getting a value from 0 to 1 based on which step we are drawing. Next we need to ensure the texture is rotated correctly, we do this using some math by getting the direction vector, normalizing it, and then getting the ArcTan value. Once we have all of the data, we call the appropriate Draw method inside SpriteBatch . The parameters in order are: The Texture The Position to draw the texture A Rectangle describing what portion of the texture to draw (in our case: all of it) The tint to apply (in our case: no tint, which is Color.White) The angle with which to rotate the texture around its origin The origin of the texture The scale (In our case: 1, we don’t scale the texture) The SpriteEffects to apply The depth at which to draw the texture (in our case: in front with everything else) <h3>Weapons in the Armory</h3> Now that we have completed the behavior for the weapon, we can register it with the game, ready for use. To do this, find the SampleGame class inside the Coding4Fun.ScriptTD.Sample project and scroll down to the RegisterWeapons() method (around line 84). Add the following line anywhere inside that method: <pre class="brush: csharp
Armory.AddWeaponType("Tracking", typeof(TrackingWeapon));
[/code] Here we are registering the weapon type, and telling the game that it will be called "Tracking" inside the XML files. This lets us use friendlier names whilst maintaining naming conventions in our code. Now we just need to add this weapon to a tower and try it out. From the root directory for the project, navigate to: <blockquote> Coding4Fun.ScriptTD.ContentDataTowers </blockquote> And open laser.xml and change the WeaponType attribute from "Laser" to "Tracking". With that done, compile the game and run it! You can find a laser inside Mission 4, so to try it out select that mission and place the fourth tower. Wait for it to build and for enemies to arrive, and then bask in the glory of your new weapon. http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/clip_image002%5B6%5D.gif <img title="clip_image002" src="http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/clip_image002_thumb%5B3%5D.gif" alt="clip_image002" width="550" height="330" border="0 <h3>Conclusion</h3> You now know how to add a completely new weapon type to the game, which will let you add a unique touch to your Tower Defense game. There are other areas in the game that you can extend in code, and there is a lot of power in the XML definition files. So if you are interested in learning more, browse the code and the documentation at the Codeplex link at the start of the article. Play around with the behavior, create a new tower in XML, and tweak the values. If you find the sound annoying you can add a delay in between changing targets, or completely remove the sound from the XML. Make whatever you want using the tools available—turn it into something you made. Remember, the tools are free and with some creativity you can make a really great game! <h3>About The Author</h3> I am a student at the University of Technology Sydney, and an R&D Engineer for Orion Integration Pty Ltd, specializing in 3D Visualization, as well as Computer Vision. I have a strong interest in game development, particularly on the Graphics and GPU programming side. I am also a Microsoft Student Partner at my university and the State Lead for the MSPs in New South Wales. You can contact me at <a>michael@mquandt.com or visit my blog at http://mquandt.com/blog http://mquandt.com/blog . Feel free to also follow me on Twitter: http://twitter.com/quandtm @quandtm . If you have any questions or suggestions, contact me through one of the above methods, or if you find any issues with ScriptTD, report them on the Codeplex page. <img src="http://m.webtrends.com/dcs1wotjh10000w0irc493s0e_6x1g/njs.gif?dcssip=channel9.msdn.com&dcsuri=http://channel9.msdn.com/Feeds/RSS&WT.dl=0&WT.entryid=Entry:RSSView:9818e1ba2a49441eb60d9f2c0164e78f
View the full article