Triggered Unit Pathing

From TDGwiki

Jump to: navigation, search

Written by CHUNK

Contents

Introduction

Good pathing is essential in almost any custom map where units are being controlled by the computer, but especially so in tower defense maps, where the quality of the map depends on consistent and unexploitable pathing. In this tutorial we are going to cover all of the basics of pathing commonly used, including a step-by-step guide to creating each of the most common pathing types used in TD and AoS map types.

This tutorial meant for a general audience; basic and advanced elements explained so that anyone should be able to follow along. An effort will be made to point out how elements of the triggers work and in some cases provide examples of when not to use certain techniques. Methods of optimizing pathing code to reduce lag and reduce the number of triggers used will also be discussed.

There are a lot of different methods that people use to get units to follow a specific route, so in this tutorial we will discuss the most common methods. To simplify examples, all of the pathing examples used will utilize a chat command to spawn units and start them pathing. In practice, maps would normally use other methods including timers, periodics, or events to spawn pathing units. If you are not familiar with pathing triggers you may want to follow the tutorial from beginning to end, even if you may not be interested in one style of pathing, because some sections may include skip steps discussed previous sections.

Chasm-style Pathing

Chasm-style Pathing

The simplest method for forcing units to follow along a specific route is to limit their ability to take other routes by placing them in a type of "chasm" created by terrain cliff boundaries. Provided that the units cannot fly or otherwise cross over terrain height boundaries, the units in the chasm have only one option to reach their destination.

Chasm pathing is common in tower defense maps. Some maps use this chasm-style pathing in a subtle way, routing units through a wide chasm and allowing players to build within it (e.g. Team TD, Mort's Power Tower), and others use a more obvious, narrow chasm in which players are meant to build towers along the shoulder (never in the chasm) and rain missile attacks down on the hapless pathing units (e.g. Liquid TD). For simplicity sake, we will discuss the narrow chasm-type pathing first.

This method is extremely simple, requires very few triggers, and has no real exploits because the pathing units and the towers are isolated from each other at all times (or at least they should be), typically by using an unbuildable terrain-type on the floor of the chasm.

Because pathing is dependent on the terrain, I have created a simple chasm terrain for the purpose of this tutorial. Feel free to create a chasm terrain of your own and follow along in the World Editor. As was stated above, the terrain on the floor of the chasm should be an unbuildable type, so that towers cannot be built upon it and the path cannot be blocked by player units.

The thumbnail image is a screenshot of the terrain that shows a simple chasm-style terrain. I have made the ground pathing visible (Hotkey P or View | Pathing - Ground); purple-shaded terrain is impassable to ground units, blue-shaded terrain is unbuildable.

Basic 'narrow chasm' pathing (such as our terrain above) only requires two elements once the terrain is complete: an origin (where units spawn) and a destination (where units are ordered to go). Regions are not the only way to mark the origin and destination, but they are probably the most straightforward tool, so for our example we will create an origin and a destination region, but keep in mind that in practice you can use other objects, such as walkable units, to mark these points in the route.

'Narrow-chasm' pathing

Placing Regions

Create a region at the origin (spawning point) and destination (leak point). The size of the origin region is not exceptionally important, but you may want to size the destination region so that out boundaries are where your unit should stop or leak. I recommend making the regions the width of the chasm. In practice you may want to add some sort of cliff-cave or portal near the origin and destination so it doesn't look like the units are forming/disappearing out of thin air.
It is good practice to name the regions after you make them, so that later you can reference them easily. Pick useful names like Start and End or Spawn1 and Leak1 so you aren't going to have trouble remembering how they were meant to be used. Also, if you have a lot of regions, it sometimes helps to color-code them, so that you can tell just by glancing whether the region is origin, destination, or some other type of region. Naming and color-coding regions can be done by right-clicking on the region in the list from the tool palette and selecting edit properties.

Spawning Units

You will need to make a trigger to test your pathing that will spawn a unit at the origin and then order it to move to the destination region. For this tutorial we will use a chat-command to force the pathing units to spawn, but in practice you will probably want to create a more sophisticated system.
There are also a couple of different ways to spawn creeps (one-by-one or all-at-once); for testing our pathing in this tutorial, we will spawn 5 units (total), one at a time, ordering each unit to move as it is spawned. We will included a slight pause between one spawn and the next to allow the unit to move out, and then another unit will spawn at the origin region.
Event
Player - Player 1 (Red) types a chat message containing -start as An exact match
Keep in mind, this is a temporary event, it is set up so that you can test your pathing only. In this case, the trigger will go off when player 1 types "-start".
Action:
Unit - Create 1 Night Elf Runner for Player 12 (Brown) at (Center of Start <gen>) facing Default building facing degrees
For the sake of the tutorial, we are making the unit created a runner (cute, eh?) and using the "Unit - Create Unit Facing Angle" action. You can also use the action that creates a unit facing a point, but as we will discuss later, this creates an extra memory management hassle.
Because we are using a one-at-a-time spawn approach, we are only creating one unit here. Later we will add a looping action to our trigger so that it will create the other four.
Note that I have chosen a player slot, controlled by a computer, (Player 12 (Brown)) for my example. You can pick any computer player for your pathing units to be controlled by, however, it is in your best interest NOT to chose Neutral Hostile for pathing units. Neutral controllers act differently than players 1 - 12, and their AI programming will cause you a number of problems if you attempt to issue them commands. There are some methods for getting around these issues with some of the neutral player slots, but if you don't have to use them, it is best if you don't.

Ordering Units to Move

In chasm-style pathing there is no where else for the unit to go but where you order them because of the terrain cliff boundaries, so units will always take the shortest available route to reach the destination point.
Note: If you want to allow for flying units in chasm-style pathing can set their movement type to 'hover', which will give them the appearance of flying but force them to follow the same pathing as ground units. You will still need to make sure that the unit is set to be targeted as air in the unit properties if you want hovering units to be treated flying for targeting purposes.
Action
Unit - Order (Last created unit) to Move To (Center of End <gen>)
For this action, we are using Unit - Issue Order Targeting A Point (be careful not to confuse this with the unit-group variety). There are three components to this action: who gets the order, what is the order, and ordered to what point.
Because we have just created a unit in the previous action, there is no mistaking which unit is the last created unit. Do not use this approach if you are not planning on ordering the unit directly after it is created (in other words, no wait actions between creation and ordering), or it may cause you problems if other units are created during that time.
The command we are using is "Move To", essentially because there is no reason to use any other commands. There are no enemy units in the chasm in this example, and as a result, you don't need to worry about them interfering with your pathing.
In the example, we are ordering the unit to move to a point. If you were to stop at this point, and test your map, you would type "-start" and a single Runner would start at the Start region, and immediately begin moving to the End region. Nothing else is needed, however, generally people want more than one unit to spawn per wave.

Looped Spawning

In theory, you could just copy and paste the above actions 5 times, with a wait action between them, and the trigger would still create 5 units that path... however this is inefficient programming, considering you only really need to repeat the actions you already have. The simplest way to use a loop that will count once for each loop and spawn a unit, ordering it to move. There are a number of ways to make effective loops in the World Editor. In this example we will have a wait action between each time the loop runs, so we will want to make sure that we don't over-write our counting variable between loops. The most efficient way to do this is to use a for loop with a unique integer variable. If you aren't familiar with for loops you should check out this tutorial on fundamentals of triggering before continuing onward.
To add a for loop the counts using a unit variable, you must first create an integer variable (name it whatever, I will call mine SpawnCount). Once you have created this variable, add a for loop action into your spawning trigger:
Actions
For each (Integer SpawnCount) from 1 to 5, do (Actions)
Loop - Actions
Note that I am using the action For Each Integer Variable, Do Multiple Actions as opposed to the type that relies on integer A or integer B. The reason for this is that integer A/B might be used in other places in the map and I don't want to overwrite them. This action allows us to use a unique variable. The numbers 1 and 5 determine how many loops will be run.
Caveat

Make sure that in practice you DO NOT run this loop a second time while this loop is already running. Global integers inside for loops with waits are only effective if the loop ends (completely) before the trigger is run a second time. You don't want to accidentally overwrite this integer variable.

Once you have added this action to your trigger, you will want to move your unit creation action and the order command inside the loop. We want our loop to have a short period where nothing else happens to allow the spawned unit to move out, so you will want to add a wait action after the unit is ordered to move, but all inside the loop.
Wait 1.00 game-time seconds
Note: You may eventually want to make triggers that spawn units in multiple places for multiple players. If this is the case, and all of the spawns will be spawning at the same time and rate, you can simple stack more locations into this loop before the wait command.
Your chasm-type pathing trigger should look basically like this now:
Chasm Pathing
Events
Player - Player 1 (Red) types a chat message containing -start as An exact match
Conditions
Actions
For each (Integer SpawnCount) from 1 to 5, do (Actions)
Loop - Actions
Unit - Create 1 Night Elf Runner for Player 12 (Brown) at (Center of Start <gen>) facing Default building facing degrees
Unit - Order (Last created unit) to Move To (Center of End <gen>)
Wait 1.00 seconds
This is all the you really need to know to make a pathing system for a chasm-type pathing system. It is extremely simple to design and even if you add more regions, you generally do not need anything more complex than this. If you are creating a tower defense-type map, you will need one more trigger to kill/remove units that 'leak':
Leaking
Events
Unit - A unit enters End1 <gen>
Conditions
Actions
Unit - Remove (Entering unit) from the game
If you want to create a fancy special effect when the unit is removed, you can. You can also opt to use something like Unit - Kill Unit (entering unit) if you prefer to have the units die rather than simply disappear.

'Wide-chasm' pathing

Chasm-style pathing where towers can be built in the chasm are slightly more complex, but the issues caused by allowing players to build in the chasm are all related to unit properties - the pathing triggers are actually basically identical.

When you have units moving among the towers as they path, there are a number of issues to consider. First, you must decide how to deal with the spawned units: How will you deal with a blocked pathway? Do you want unit collision sizes to come into play? Will flying units actually fly over towers, rather that pathing as ground units? Let's address some of these issues.

Blocking the Path

If you allow players to build on the path, you need to find some way to thwart players from simply blocking the path completely and picking off your spawned units. The most common method for stopping blocked paths from becoming an issue is to just allow the units to attack. If units are given a move order, they will attempt to move to that point (not attacking anything) and if there is no other available route, they will attempt to make one (if possible) by attacking units that block the route. You may want to make sure that if you allow units to attack that you have some method in place for reminding them to continue on to their destination, because once a unit begins attacking it may just keep attacking until there is nothing left to attack.

Collision Size

Issues with Collision Size
Units have a collision size, which keeps them from becoming stacked on top of each other and determines the size of the pathing required for them to be able to move through it. This becomes important to consider when you are using pathing where ground units can become trapped if their collision size is larger than the available path they are presented with. Even if you set collision sizes to very small numbers, units can still become trapped between narrow gaps between towers:

To avoid this issue, you may want to consider giving the pathing units the ability Ghost (Visible). This ability will allow units to ignore the collision of allied mobile units (like other spawns) but does not allow them to walk through ground pathing limits such as enemy structures. This technique allows players to create very narrow gaps between towers (see above) without having the units get 'backed up' in the maze waiting for other units to pass or inciting them to get lost in the pathing and perceive there to be a blocked pathway when there really isn't.

Flying Units

In most tower defense maps, the point of flying units is to create a special unit that cannot be forced to maze, making the players cover both ground and air differently. Flying units may provide a slightly different challenge for your chasm pathing, particularly if your chasm bends in any way (such as the one above), because flying units can fly over terrain cliff boundaries.
To avoid this problem, you can do two things:
  1. Use a different pathing system, such as the ones described below, where units are sent through a system of regions, each time being ordered to the next region until they reach the destination. However, this generally defeats the entire purpose of using 'chasm-style' pathing, because you can no longer just rely on the terrain cliff boundaries to designate the path.
  2. Line the edges of your chasm with air-pathing blocker doodads. Air-pathing blockers are tiny invisible doodads that stop units from crossing over them. The advantage of using them is that you can simply keep your current pathing triggers if you line your chasm with these things, because the air units can fly but can't pass over the chasm edges. The disadvantage is that, because they are small, they require you to place hundreds of these doodads in your map to ensure that flying units take no shortcuts

Other Issues

Aside from the issues described above, you will need to find a means to make sure that splashing attacks will not accidentally hit friendly units or structures. Make certain that all splashing towers have enemy selected for targets of the attack and the splash. Also, your peon units will be forced to path between buildings in this system - so make sure that you have set their movement type flying to allow them to pass easily over structures you build.

Simple Region Pathing

In this section, we will be discussing approaches for something that I will call "simple region pathing". This system is commonly used in AoS style maps and also Tower Defense maps where the spawned units are 'punished' like the Greek figure Tantalus, in that each time they think they have reached their destination, they are given new orders.

This system differs from chasm-style pathing in that the unit movement is not restricted exclusively by terrain boundaries. Spawned units are often forced to walk along a path with regions marking the way, even if there are no terrain boundaries blocking the direct path towards their final destination. In tower defense maps, this style of pathing is often used when routes are complex the path that units are forced to walk along is usually unbuildable (e.g. Burbenog TD, Hive's Revenge TD). In AoS maps, this type of pathing is usually used to keep spawned units from always taking the shortest available route to the opponent base (forcing them to travel down prescribed lanes).

Ordering Units from Region to Region

There are lots of effective ways to control groups of units moving from one point to another. Probably the most effective approach is to store the unit's current destination on the unit as a custom value. If you aren't familiar with using custom value, check out this short tutorial by Zizz before continuing. This approach requires that you create a point array variable and then assign each possible destination point to some index of that array. I'll discuss how this is done next, using the 'all-at-once' spawning method, as opposed to the method we used in the chasm-style pathing above.

Make a terrain and place regions

Placing Regions
In order to make the best use custom value, we need to create a point variable array that will track all of the possible target points that will be issued in the game. For simple region pathing, we will use the centers of regions as target points. Each value in our point array can be set at map initialization to the center of a preplaced region. Let's make a quick terrain with an unbuildable path and place some regions around the path to mark the route.
In this very simple example, units will spawn at the Start region (brown tiles), be ordered from region to region around the loop and out at the End region (arrow).
Note: In this tutorial, I placed the regions across the entire path (for convenience), but this will cause the spawns to run along the inside edge of the route. In practice you will need to make some adjustments to the placement of regions so that the spawns run the route along the center by realigning these regions.

Set up an Array

Create a point variable array in the same fashion that you have made variable arrays previously, except in this case, your variable type will be "point". To fill the point array, you will want the event to be something that happens early in the game, and the simplest candidate is to use Map Initialization as the event. At map initialization, you will want to use the action Set Variable = value to assign each region in your map to an array value. It makes the most sense to assign the values following some pattern that makes sense and is easy to track. In this tutorial, I have used an array (variable is called "Path") index value that matches the region's name.
Pathing Point Array
Events
Map initialization
Conditions
Actions
Set Path[1] = (Center of Start <gen>)
Set Path[2] = (Center of Node2 <gen>)
Set Path[3] = (Center of Node3 <gen>)
Set Path[4] = (Center of Node4 <gen>)
Set Path[5] = (Center of End <gen>)
Filling arrays is tedious work, but the benefit is that now you can set a custom value on each unit equal to the index number of that point array and you can order the unit to that point. You can use other means to store a value on units, but for our purposes, custom value makes an ideal choice for controlling pathing behavior, because it allows us to simplify our triggers. The default value of a unit' custom value is 0), but can be easily changed by the following action:
Unit - Set custom value of unit


Set up the Spawning Trigger

Once your array is set, you can begin setting up your spawning trigger. For this part of the tutorial, we will use a system where units are created all at once and sent from point to point en masse. To do this, I generally use a unit group variable to make it easy to order them and to help discriminate from different groups if multiple groups are pathing from different areas of the map.
SimpleRegionPathing
Events
Player - Player 1 (Red) types a chat message containing -start as An exact match
Conditions
Actions
Unit Group - Remove all units from PathingGroup
Unit - Create 5 Night Elf Runner for Player 12 (Brown) at Path[1] facing Default building facing degrees
Unit Group - Add all units of (Last created unit group) to PathingGroup
Unit Group - Pick every unit in PathingGroup and do (Actions)
Loop - Actions
Unit - Set the custom value of (Picked unit) to 2
Unit - Order (Picked unit) to Move To Path[(Custom value of (Picked unit))]
Here the actions are to create 5 runners at once, then adds those runners to a unit group variable (named PathingGroup), then assign all of the units in that group a custom value equal to the region array index of their destination (in this case, a custom value of 2 is associated with the point Path[2], which we set at map initialization equal to the second region (Node2)) and order them to move to that destination, accessing their custom value to do this.
If you were to test this now, all of the units would be created when the chat command is given and they would be ordered off to the second region. This is essentially the same thing we did (only in a different way) in the first section of this tutorial, but now we need to have a second trigger to order the units on to their next destination when they near Path[2].

Continuing Orders

Let's move on to the pathing trigger itself. You will find that using a point array and custom values to track the orders issued allow us to create one very simple trigger that can be used to set all of the orders. When the unit enters the next region, all we need to do is advance the current value of the unit's custom value by one and order the unit to move again. But, while we are at it, each node along the way will actually use the exact same triggers, so why not just add all of the regions to the same trigger as Unit Enters Region events?
Pathing
Events
Unit - A unit enters Node2 <gen>
Unit - A unit enters Node3 <gen>
Unit - A unit enters Node4 <gen>
Conditions
((Entering unit) is in PathingGroup) Equal to True
Actions
Unit - Set the custom value of (Entering unit) to ((Custom value of (Entering unit)) + 1)
Unit - Order (Entering unit) to Move To Path[(Custom value of (Entering unit))]
Simple. Now all of your pathing is done down to the last region (which is the unit's final destination). Now if you were doing this with an AoS map, rather than using "Move To" you could simply change this to "Attack-Move To" and the unit will then attack any enemy units that happen to get into range along the route.
You'll also note that I've added a condition to check that the unit entering the region is in the pathing group unit-group. The reason for this is that we don't want player units or non-pathing computer controlled units to be forced to move when they enter the pathing regions.
Believe it or not, this is all of the triggers that you will need for pathing in this fashion. You don't need to make a separate trigger for each region this way and if you have multiple unit groups following the same path (as they do in Burbenog TD) you can just make different unit groups and change their behavior for each one.
If you are having a hard time following why this works, here is the order of events. Units are spawned, added to a unit group, and assigned the custom value of 2. They are then ordered to move to a point defined in an array that is associated with the number 2 (Path[2]). When they get to the region that is near Path[2], each unit's custom value is changed to 3 (current value =2 + 1) and they are ordered to move again, this time to a new point (Path[3]). When they get close to that point, they enter another region, their custom value is increased, and they are ordered to move again to a new point. The custom value for each unit is stored on the unit, so their next destination is always being carried with them. You can continue to do this until they reach their final destination.

Caveats

You may experience some odd behavior when you apply this method if you have units that enter the same region more than once, accidentally. Let's say, for the sake of our example, that you have a tower that knocks pathing units backwards a bit. Now there is the potential that a unit will enter the same region twice and skip ahead a region because their custom value was advanced when they re-entered the region.
The same sort of problem could be created if you use this approach in a tower defense where units are moving around among towers and players create a maze that forces the unit to cross the same region multiple times. If you are making a tower defense where players can build where units are pathing to create mazes that baffle the unit's progress towards some destination and they must follow a multi-step route, you should consider placing unbuildable terrains where the regions are located.
To avoid this issue, you should make more sophisticated conditions. You can, for example, create a unit group for each region (probably best as an array) and then "hand-off" units from one unit group to the next when they pass between regions in the correct order. This, of course, would require that you make a separate trigger for each region, checking the unit group of the unit coming in and only adding it to the new group if it belongs in the right unit group - which defeats some of the elegance of this compact system using custom values.
A better technique is to check to see if the unit is in the region that is associated with their destination point or not. In other words, if a unit enters Path[3] but it already has a custom value of 4, then don't add to the value of the custom value. The problem with this is that Unit in Region events sometimes trigger before the unit is completely within the region, so you can't reliably just check to see if the unit is in the region it should be.
To get around this, you could create a region around the unit's destination point and see if the unit is in that region. So what would that look like?
((Region centered at Path[(Custom value of (Entering unit))] with size (512.00, 512.00)) contains (Entering unit)) Equal to True
In this example it is essential that the region you are creating is at least a little bigger than the region the unit is entering. Most simple region pathing uses regions that are fairly far apart, so there shouldn't be any issue if you make it a little on the large size just to make sure (the two numbers listed are the width and height (respectively).
Why does this work? Well, when the unit enters Region 2 (for example), this trigger will run and it will create a region centered around the destination point (Path[custom value]). If the custom value is a point in the region the unit just entered, it will meet the conditions for the trigger and give the unit a new destination; if not, it will just fail the conditions and no new orders or destination will be given to the unit.
Here is what the final pathing trigger would look like:
PathingAhead
Events
Unit - A unit enters Node2 <gen>
Unit - A unit enters Node3 <gen>
Unit - A unit enters Node4 <gen>
Conditions
((Entering unit) is in PathingGroup) Equal to True
((Region centered at Path[(Custom value of (Entering unit))] with size (512.00, 512.00)) contains (Entering unit)) Equal to True
Actions
Unit - Set the custom value of (Entering unit) to ((Custom value of (Entering unit)) + 1)
Unit - Order (Entering unit) to Move To Path[(Custom value of (Entering unit))]


Unit Node Pathing

Unit Node Pathing
Unit Node Pathing is the name I am using for the technique used in non-chasm terrains where units are ordered to move from point to point, but instead of being driven by regions, the pathing triggers utilize Unit in Range events and walkable units to mark the waypoints. This system is similar in nearly every aspect to the Simple Region Pathing technique described above, but I thought I would present it here as an alternate technique because the unit in range mechanics operate slightly differently than unit enters region events.

Set up Nodes

Create a terrain with several point in buildable terrain and small islands of unbuildable terrain that will operate as nodes. Place a walkable unit in the center of the unbuildable region. For this example, we will use a Circle of Power as our pathable unit. You can change the model's appearance to anything you'd like, but make sure that the unit is not going to create a barrier to ground pathing units. Here's a screenshot of our new example terrain for this tutorial.

Set up an Array

Create a point variable array (as discussed in the previous section). We will fill this array points that are the position of the pre-placed circle of power units. In this section of the tutorial we will use the a point variable array called Node. Note in the image above that some units will be assigned twice, this is to accommodate for our slightly-more-complex pathing system in this example (we will spawn units at two different points and have their paths merge at the third node and continue on the the forth).
UnitNodePoints
Events
Map initialization
Conditions
Actions
Set Node[1] = (Position of Circle of Power 0000 <gen>)
Set Node[2] = (Position of Circle of Power 0001 <gen>)
Set Node[3] = (Position of Circle of Power 0002 <gen>)
Set Node[4] = (Position of Circle of Power 0003 <gen>)
Set Node[5] = (Position of Circle of Power 0005 <gen>)
Set Node[6] = (Position of Circle of Power 0004 <gen>)
Set Node[7] = (Position of Circle of Power 0002 <gen>)
Set Node[8] = (Position of Circle of Power 0003 <gen>)
The event needs to be something early, so we have, once again used map initialization.

Testing Pathing

Create a spawner that will allow you to test your pathing. It doesn't matter which type of spawner you use to test this example, although it will be easier to tell what is going on with the one-by-one method, so I will go ahead and use that type for this example.
Node Spawn
Events
Player - Player 1 (Red) types a chat message containing -start as An exact match
Conditions
Actions
For each (Integer SpawnCount) from 1 to 5, do (Actions)
Loop - Actions
Unit - Create 1 Night Elf Runner for Player 12 (Brown) at Node[1] facing Default building facing degrees
Unit - Set the custom value of (Last created unit) to 2
Unit - Order (Last created unit) to Move To Node[(Custom value of (Last created unit))]
Unit - Create 1 Night Elf Runner for Player 12 (Brown) at Node[5] facing Default building facing degrees
Unit - Set the custom value of (Last created unit) to 6
Unit - Order (Last created unit) to Move To Node[(Custom value of (Last created unit))]
Wait 1.00 seconds
This will, of course, create a total of ten runners (5 from each side, offset by 1 second waits). I am using this example to show how easy it is to adapt this type of spawner to create spawns from multiple locations in the same loop - even though they are being generated at different point and ordered to different locations.

Getting New Orders

As before, you will need to produce some means to get units to move to their next point. In this example, we will use Unit in Range triggers, rather than regions, because we have units representing the waypoints.
Node Pathing
Events
Unit - A unit comes within 256.00 of Circle of Power 0001 <gen>
Unit - A unit comes within 256.00 of Circle of Power 0002 <gen>
Unit - A unit comes within 256.00 of Circle of Power 0004 <gen>
Conditions
Actions
Unit - Set the custom value of (Entering unit) to ((Custom value of (Entering unit)) + 1)
Unit - Order (Entering unit) to Move To Node[(Custom value of (Entering unit))]
Now, from this point you could go ahead and use the region method described above to see in the unit was in a region that was near the the destination path point. You don't need an a preplaced region to trigger the event (you are using unit in range) so long as your created region is bigger than your 'in range' distance, you should be able to detect the unit just as you did in the simple region pathing above.

Advanced Topics

Limiting Memory Leaks

If you aren't already familiar with methods for managing memory, you should be thinking about it, particularly in a map where you are constantly creating and ordering units about. This is why in the latter examples above I have opted to use a point array variable to designate the locations at map initialization. Let's take a look at why this is important and why we might want to use an array of predefined points instead of micromanaging the point creation and destruction for each order.

Chasm Pathing
Events
Player - Player 1 (Red) types a chat message containing -start as An exact match
Conditions
Actions
For each (Integer SpawnCount) from 1 to 5, do (Actions)
Loop - Actions
Unit - Create 1 Night Elf Runner for Player 12 (Brown) at (Center of Start <gen>) facing Default building facing degrees
Unit - Order (Last created unit) to Move To (Center of End <gen>)
Wait 1.00 seconds

This is the code we used for the chasm pathing above. I have deliberately not used a point array so we could use it as an example for what you can do and what you should do with regards to limiting memory management. In the trigger above, the spawner loops through the creation and ordering of 5 units. Each time the unit is created, it is created at a point and ordered to a point. Each time these functions reference a point (5x2=10 in this trigger alone), they actually generate an object that tracks the x and y coordinates of the point that is referenced. (If you had used Create Unit Facing a Point, you would actually be generating 15 objects - one extra point for each loop).

Some people might suggest that you just destroy these objects as you create them, so for each point, you would assign a global variable, then destroy this global point reference after you use it. That code would look something like:

Chasm Pathing
Events
Player - Player 1 (Red) types a chat message containing -start as An exact match
Conditions
Actions
For each (Integer SpawnCount) from 1 to 5, do (Actions)
Loop - Actions
Set tempPoint = (Center of Start <gen>)
Set tempPoint2 = (Center of End <gen>)
Unit - Create 1 Night Elf Runner for Player 12 (Brown) at (tempPoint) facing Default building facing degrees
Unit - Order (Last created unit) to Move To (tempPoint2)
Custom script: call RemoveLocation( udg_tempPoint )
Custom script: call RemoveLocation( udg_tempPoint2 )
Wait 1.00 seconds

Does this effectively solve the problem? Yes, the objects we create with each loop are now destroyed with each loop, so there is no net gain of memory used, however, why keep creating and destroying points when you are always referencing the same points? Generally in AoS and Tower Defense maps the spawned units always follow the same general route - so if we are going to keep referencing those points, why destroy them when we can simply define them and use them for the entire game?

This is exactly what I've done with the latter examples. Despite not having any memory management actions, those triggers do not create any point leaks, because they always reference predefined points that have been assigned to a global variable. In other words, the objects are created exactly once and then continuously re-referenced. This is how I would suggest that you deal with point leaks in your pathing triggers. Don't needlessly waste resources creating and destroying point objects - just use a point array variable. If we were going to re-write the chasm-style code to be leak free, we'd just make a point array as we have with the other examples, setting the centers of the regions Start and End to points and then change our trigger to reference those global points.

Chasm Points
Events
Map initialization
Conditions
Actions
Set Path[0] = (Center of Start <gen>)
Set Path[1] = (Center of End <gen>)
Leakfree Chasm Pathing
Events
Player - Player 1 (Red) types a chat message containing -start as An exact match
Conditions
Actions
For each (Integer SpawnCount) from 1 to 5, do (Actions)
Loop - Actions
Unit - Create 1 Night Elf Runner for Player 12 (Brown) at Path[0] facing Default building facing degrees
Unit - Order (Last created unit) to Move To Path[1]
Wait 1.00 seconds

Now, point leaks aren't the only types of leaks that could occur in AoS/Tower Defense maps, but they are the most common and the most problematic, because there is the potential for so many points to be created and destroyed.

Pathing Triggers in a Custom GUI Coordinates

If you have read Shvegait's tutorial on Modifying the World Editor, you know that there are ways to make custom script functions into GUI actions. In this next section we are going to utilize this to create some custom GUI actions to create a slightly different version of the 'create region at point' check for seeing whether the unit is near the pathing point that is associated with its current custom value.

Make yourself familiar with how to add new functions into your WE by following along with Shvegait's tutorial. We will be using several functions in this example, including the native functions IssuePointOrder , CreateUnit, GetUnitX, GetUnitY, and a custom function for calculating the distance between units DistanceBetweenUnits.

Go ahead and add these as functions to your WE, following the methods in that tutorial. If you want a template for these functions, look at their GUI counterparts and how they were added by searching the txt files for IssuePointOrderLocBJ, CreateNUnitsAtLoc and DistanceBetweenPoints. If you've already modified your WE to include these functions or you prefer to work in JASS, you can move on to the next step.

There is a reason that we are adding these as custom GUI functions (aside from just generating an exercise in modifying your World Editor) and that is that while it is possible to add custom script actions in the standard WE, there's no equivalent tool for adding a custom script conditions - and we need to use that distance between units functions as part of a condition. Plus, we will be switching to a system that uses coordinates of units rather than points to facilitate calculating the distances, and it makes sense to just move over completely to this coordinate system for spawning and pathing.

You might ask why not simply use distance between points and compare the distances between the unit's current point and their destination point - and the answer is that you can, however when you do this, you generate a point leak in the condition which you also can't clean up using the common method of setting it to a variable and removing it without moving the whole operation down into the actions area of the trigger and taking care of it with an if/then - and this is just a little uglier and maybe a bit less efficient.

So, now that you have these new functions in your WE, how would we write the triggers using just coordinates rather than points? Well, first off, we don't want to use a point array variable, but we still want to make our lives easy and not fill up a bunch of real arrays. The way that we do this is just convert the point-of-units array we used in the node pathing example above into a unit array. Now the units will act as our array elements.

UnitNodes
Events
Map initialization
Conditions
Actions
Set NodeUnit[1] = Circle of Power 0000 <gen>
Set NodeUnit[2] = Circle of Power 0001 <gen>
Set NodeUnit[3] = Circle of Power 0002 <gen>
Set NodeUnit[4] = Circle of Power 0003 <gen>
Set NodeUnit[5] = Circle of Power 0005 <gen>
Set NodeUnit[6] = Circle of Power 0004 <gen>
Set NodeUnit[7] = Circle of Power 0002 <gen>
Set NodeUnit[8] = Circle of Power 0003 <gen>

Now, we are no longer going to be able to create units at point, so we are just going to adapt our spawner to create units at the coordinates of our node unit. Because we are switching to natives, we may as well switch to the JASS way of creating units, which allows us to assign units to variables as they are created. For this we will need to create a temporary unit variable but otherwise use a similar system as before:

CGUI Node Spawn
Events
Player - Player 1 (Red) types a chat message containing -start as An exact match
Conditions
Actions
For each (Integer SpawnCount) from 1 to 5, do (Actions)
Loop - Actions
Set tempUnit = (Create a unit for Player 12 (Brown) of type Night Elf Runner at ((X of NodeUnit[1]),(Y of NodeUnit[1])) facing Default building facing degrees)
Unit - Set the custom value of tempUnit to 2
Unit - Order tempUnit to Move To ((X of NodeUnit[(Custom value of tempUnit)]), (Y of NodeUnit[(Custom value of tempUnit)]))
Set tempUnit = (Create a unit for Player 12 (Brown) of type Night Elf Runner at ((X of NodeUnit[1]),(Y of NodeUnit[1])) facing Default building facing degrees)
Unit - Set the custom value of tempUnit to 5
Unit - Order tempUnit to Move To ((X of NodeUnit[(Custom value of tempUnit)]), (Y of NodeUnit[(Custom value of tempUnit)]))
Wait 1.00 seconds

Note that you cannot use "Last Created Unit" with the native function "CreateUnit", so we have just switched everywhere to use the tempUnit variable.

Now, as we did before, set up the pathing triggers so that the unit's custom value is advanced when the unit is in range, but we add a condition that checks the that the coordinate distance between the unit and the unit's destination point are less than some value that is slightly larger than the unit in range events:

CGUI Node Pathing
Events
Unit - A unit comes within 256.00 of Circle of Power 0001 <gen>
Unit - A unit comes within 256.00 of Circle of Power 0002 <gen>
Unit - A unit comes within 256.00 of Circle of Power 0004 <gen>
Conditions
(Owner of (Triggering unit)) Equal to Player 12 (Brown)
(Distance between (Triggering unit) and NodeUnit[(Custom value of (Triggering unit))]) Less than 300.00
Actions
Unit - Set the custom value of (Triggering unit) to ((Custom value of (Triggering unit)) + 1)
Unit - Order (Triggering unit) to Move To ((X of NodeUnit[(Custom value of (Triggering unit))]), (Y of NodeUnit[(Custom value of (Triggering unit))]))

And that's it. Your pathing triggers are compact, readable, and you need only add more units to the unit array and the unit is in range events and your units will path smoothly along without ever using points - which means you don't need to bother with memory management issues at all.

For those people following along who want to know how this looks as JASS:

function Trig_CGUI_Node_Spawn_Actions takes nothing returns nothing
set udg_SpawnCount = 1
loop
exitwhen udg_SpawnCount > 5
set udg_tempUnit = CreateUnit(Player(11), 'enec', GetUnitX(udg_NodeUnit[1]), GetUnitY(udg_NodeUnit[1]), bj_UNIT_FACING)
call SetUnitUserData( udg_tempUnit, 2 )
call IssuePointOrder( udg_tempUnit, "move", GetUnitX(udg_NodeUnit[GetUnitUserData(udg_tempUnit)]), GetUnitY(udg_NodeUnit[GetUnitUserData(udg_tempUnit)]) )
set udg_tempUnit = CreateUnit(Player(11), 'enec', GetUnitX(udg_NodeUnit[1]), GetUnitY(udg_NodeUnit[1]), bj_UNIT_FACING)
call SetUnitUserData( udg_tempUnit, 5 )
call IssuePointOrder( udg_tempUnit, "move", GetUnitX(udg_NodeUnit[GetUnitUserData(udg_tempUnit)]), GetUnitY(udg_NodeUnit[GetUnitUserData(udg_tempUnit)]) )
call TriggerSleepAction( 1.00 )
set udg_SpawnCount = udg_SpawnCount + 1
endloop

endfunction

//=========================================================================== function InitTrig_CGUI_Node_Spawn takes nothing returns nothing

set gg_trg_CGUI_Node_Spawn = CreateTrigger( )
call TriggerRegisterPlayerChatEvent( gg_trg_CGUI_Node_Spawn, Player(0), "-start", true )
call TriggerAddAction( gg_trg_CGUI_Node_Spawn, function Trig_CGUI_Node_Spawn_Actions )

endfunction

function Trig_CGUI_Node_Pathing_Conditions takes nothing returns boolean
if ( not ( GetOwningPlayer(GetTriggerUnit()) == Player(11) ) ) then
return false
endif
if ( not ( DistanceBetweenUnits(GetTriggerUnit(), udg_NodeUnit[GetUnitUserData(GetTriggerUnit())]) < 300.00 ) ) then
return false
endif
return true

endfunction

function Trig_CGUI_Node_Pathing_Actions takes nothing returns nothing

call SetUnitUserData( GetTriggerUnit(), ( GetUnitUserData(GetTriggerUnit()) + 1 ) )
call IssuePointOrder( GetTriggerUnit(), "move", GetUnitX(udg_NodeUnit[GetUnitUserData(GetTriggerUnit())]), :GetUnitY(udg_NodeUnit[GetUnitUserData(GetTriggerUnit())]) )

endfunction

//=========================================================================== function InitTrig_CGUI_Node_Pathing takes nothing returns nothing

set gg_trg_CGUI_Node_Pathing = CreateTrigger( )
call TriggerRegisterUnitInRangeSimple( gg_trg_CGUI_Node_Pathing, 256.00, gg_unit_ncop_0001 )
call TriggerRegisterUnitInRangeSimple( gg_trg_CGUI_Node_Pathing, 256.00, gg_unit_ncop_0002 )
call TriggerRegisterUnitInRangeSimple( gg_trg_CGUI_Node_Pathing, 256.00, gg_unit_ncop_0004 )
call TriggerAddCondition( gg_trg_CGUI_Node_Pathing, Condition( function Trig_CGUI_Node_Pathing_Conditions ) )
call TriggerAddAction( gg_trg_CGUI_Node_Pathing, function Trig_CGUI_Node_Pathing_Actions )

endfunction

Navigation
Clan TDG

Main About the Clan Join.gif Clan TDG Members Community Events Friends Links Forum.gif

Resources

Maps Tutorials Tools Custom Skins, Icons, & Models

Wiki

Website Feedback Community Portal Recent Changes Jump to a Random Page Wiki Help

Toolbox
Clan TDG

Main About the Clan Join.gif Clan TDG Members Community Events Friends Links Forum.gif

Resources

Maps Tutorials Tools Custom Skins, Icons, & Models

Wiki

Website Feedback Community Portal Recent Changes Jump to a Random Page Wiki Help

LANGUAGES
Clan TDG

Main About the Clan Join.gif Clan TDG Members Community Events Friends Links Forum.gif

Resources

Maps Tutorials Tools Custom Skins, Icons, & Models

Wiki

Website Feedback Community Portal Recent Changes Jump to a Random Page Wiki Help