• Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.

[Solved] Creating a Lane-Defense Map

Level 5
Joined
Jun 24, 2024
Messages
59
Hi!

Back with Map design shenanigans, this time i am trying to create a Lane Defense Game, similar to games like Plants vs Zombies.
Here i want to make a defending unit / Tower only to attack enemies appearing in its lane (for now), i tried working with neutral passive units (because they dont get automatically attacked) but their ai is wierd and makes them flee with half health even tho fleeing is turned off. I tried player 2 enemies but disallowing attacks per trigger ("Order to stop") but then they get stuck on a unit and don't search for new enemies on their lane.

I'm quite sure the best approach would be custom spells with one direction (here 90°) of cast, single target, aoe, shockwave like behavior ect. but i can't get those to work, especially because of hit detection and me being a newb still. I've tried using example maps for custom spells from this forum but to no avail so far...

Does anyone have a optimal solution on how i can manage custom spells and maybe explane to me how i can make sure, a single arrow as a spell for example can be optimally created and working with multiple cast instances (something something arrays :)

Best regards
SMOrc
 
Level 13
Joined
Feb 5, 2018
Messages
567
I would probably use unit groups at first to make the attack preventing triggers.

Add the units into a unit group when they enter certain region and later we see if the unit and tower match.

Generally speaking using "A unit is attacked event" is bad to use, but in your case it might just be a good solution.

  • Creeps
    • Events
      • Unit - A unit enters Lane 1 <gen>
    • Conditions
      • (Owner of (Triggering unit)) Equal to Player 2 (Blue)
    • Actions
      • Unit Group - Add (Triggering unit) to LaneGroup[1]
  • Towers
    • Events
      • Unit - A unit enters TowersLane 1 <gen>
    • Conditions
      • ((Triggering unit) is A structure) Equal to True
    • Actions
      • Unit Group - Add (Triggering unit) to TowersLaneGroup[1]
  • Attack Trigger
    • Events
      • Unit - A unit Is attacked
    • Conditions
    • Actions
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • ((Attacking unit) is in LaneGroup[1].) Equal to True
          • ((Attacked unit) is in TowersLaneGroup[1].) Equal to True
        • Then - Actions
          • -------- Filter Only --------
        • Else - Actions
          • Unit - Interrupt (Attacking unit)'s Attack
          • Unit - Order (Attacking unit) to Stop.
For the alternative e.q using custom spells with 90 degree angle and missiles, you would probably want to use missile system. Since it is MUI by default and you just need to use premade variables. Relativistic Missiles [vJASS][LUA][GUI]
 
Level 29
Joined
Sep 26, 2009
Messages
2,596
If you don't need to control your towers, you could give them to a computer player that is ally both to you and to the enemy units. That way the towers won't auto attack any enemy.

If you want to control your towers, then you could instead remove their attacks (or leave them, but change attack flags so the towers cannot attack anything) and use hidden abilities that you will force your towers to cast periodically via triggers. That + using a missile system could give you space for various types of attacks.
 
Level 5
Joined
Jun 24, 2024
Messages
59
Giving the tower to a computer allie sounds like a solid idea, since the mobs won't give rewards either, maybe drops some items but that's that. How can i use the missile system in combination of areas, location etc.? I looked into the Relativistic Missiles Threat but i can't help but scratch my head :))
 

Uncle

Warcraft Moderator
Level 73
Joined
Aug 10, 2018
Messages
7,877
Here's a triggered example that showcases a Lane system with redirecting attacks against unwanted targets.

The logic basically boils down to this:
A unit is attacked -> If a tower is the attacker and it's attacking a unit it shouldn't -> Order it to attack something valid instead.

This is achieved using a Unit Indexer as a means to store which Lane the Towers and Spawns (enemy waves) belong to. I use an Array of Regions to track my different Lanes and associate the Towers/Spawns with these Lanes as they come into play. This is done by assigning them the [index] of the Region they were created in. So if a Tower is built in Region[3] it will be assigned the value 3. If an enemy is spawned in Region[2] it will be assigned the value 2. If that Tower then tries to attack that enemy we will know that it's an invalid attack because their Integer values don't match -> 3 is NOT equal to 2. In other words, a Tower in Lane 3 should NOT be attacking an Enemy in Lane 2.
 

Attachments

  • Tower Attack AI.w3m
    21.9 KB · Views: 5
Last edited:
Level 5
Joined
Jun 24, 2024
Messages
59
Thank you very much Uncle! This helps me alot and is well put to understand :D
Now that i have a working redirection and retargeting, i will try to make it, so the player2 computerenemies also have to attack the towers on their respective lane and get blocked by it. Will this affect the towers when for example only the middle has one enemie walkin up and attacking? With that i mean, usually the surrounding towers on other lanes within range will try to defend it. The triggers should stop that, right?

Edit: I also am trying to add a method to the creeps that they can't switch between lanes without using blockers, because then the builder gets funky with its movement, even if i set it to none, so it can ignore the blockers on short walk distance but still consideres them on longer walk distance. Kind of wierd :X
Optimally the creeps would also not run back to attack a newly build tower but that seems to work on its own most of the time
 
Last edited:
Level 5
Joined
Jun 24, 2024
Messages
59
That is probably for the best, yeah. I tried giving it an insane movement speed so it seems like the builder is teleporting but even tho i overwrote the maximum possible speed in the game settings and shift changed the movement speed of the builder to something like 5000, it still walks at the maximum speed of around 500.

Also I noticed that units from player 2 try to attack the tower on a different lane when walking by and a unit on that lane getting attacked right next to them.

I tested: Setting the acquisition range to 100, removing player 2 AI in the player settings
I also opened up the end of the lanes to they could theoretically walk over towards the lane and know their path but they still kind of get stuck wanting to cross over the blockers, which of course doesn't work.

A third thing is: sometimes they stop after destroying a building in their path even tho they are ordered to attack-move towards the end of the lane.
Is there a simple trigger i can add like "If unit x idles - order it to move towards y"?.

Thank you for helping me!
 

Uncle

Warcraft Moderator
Level 73
Joined
Aug 10, 2018
Messages
7,877
That is probably for the best, yeah. I tried giving it an insane movement speed so it seems like the builder is teleporting but even tho i overwrote the maximum possible speed in the game settings and shift changed the movement speed of the builder to something like 5000, it still walks at the maximum speed of around 500.
Movement Speed settings are changed in the Gameplay Constants. Movement Speed is capped at 522. You can make a trigger that instantly moves the Builder to wherever you right click or just rely on the Blink ability.

Also I noticed that units from player 2 try to attack the tower on a different lane when walking by and a unit on that lane getting attacked right next to them.
I assumed the Units would be using the Move-To order like they do in most TD maps. In that case the AI will automatically try to Attack any towers that block it (anti-mazing). If you want them to Attack-Move down their Lane then things get complicated.

But you can try the following...

Reuse the tower retargeting logic, although simplify it:
A unit is attacked -> Attacker is a Spawn enemy and attacked unit is a Tower and they're not in the same Lane -> Order attacker to Move-To it's destination.

Modify the "Call for help" range in the Gameplay Constants and any other settings that would make a unit react to allies being attacked. Keep a low acquisition range like you have now (note that this will mess with Ranged attackers).

A third thing is: sometimes they stop after destroying a building in their path even tho they are ordered to attack-move towards the end of the lane.
Is there a simple trigger i can add like "If unit x idles - order it to move towards y"?.
After a tower dies, use a Unit Group to find nearby Spawns in the same Lane and order them to Move-To their destination. This is the standard Pick Every Unit logic that you will use time and time again throughout your triggers:
  • Actions
    • Set Variable Point = (Somewhere)
    • Set Variable Unit_Group = (Units within 600.00 range of Point)
    • Unit Group - Pick every unit in Unit_Group and do (Actions)
      • Loop - Actions
        • -------- If (Picked unit)'s Lane is Equal to the Dying tower's Lane then Order it to move --------
    • Custom script: call RemoveLocation( udg_Point )
    • Custom script: call DestroyGroup( udg_Unit_Group )
Another safety measure could be to use a Periodic Interval to loop over Unit Groups containing the Spawns and have them move to their destinations:
  • Events
    • Time - Every 5.00 seconds of game-time
  • Conditions
  • Actions
    • For each integer (X) from 1 to 3 do (Actions)
      • Loop - Actions
        • Unit Group - Pick every unit in Lane_Units[X] and do (Actions)
          • Loop - Actions
            • Unit - Order (Picked unit) to Move-To their destination point
^ You could add an If Then Else to see if they're in combat as to not interrupt one that's attacking a Tower.
 
Level 5
Joined
Jun 24, 2024
Messages
59
Hi again, i have a question about your suggested trigger example with the unit indexing. How can i adjust or rather build onto it so i can create wave like spawns with random intervals and also random pick of spawn?

So far i have tried to simply change:
  • Set VariableSet Spawn_Cycle = (Spawn_Cycle + 1)
to
  • Set VariableSet Spawn_Cycle = (Spawn_Cycle + (Random integer number between 1 and 5))
- and i removed the Function
  • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
    • If - Conditions
      • Spawn_Cycle Equal to 5
    • Then - Actions
      • Set VariableSet Spawn_Cycle = 0
    • Else - Actions
Now they pick their Spawn at random, that's sort of working. How could im implement a timing system so in the beginning it spawns slow and only 1 enemy at a time and it steadily ramps up to spawning fast and sometimes groups of enemies.

My guess is that i just have to create manual Waves with what i want to spawn.
 

Uncle

Warcraft Moderator
Level 73
Joined
Aug 10, 2018
Messages
7,877
Now they pick their Spawn at random, that's sort of working. How could im implement a timing system so in the beginning it spawns slow and only 1 enemy at a time and it steadily ramps up to spawning fast and sometimes groups of enemies.

My guess is that i just have to create manual Waves with what i want to spawn.
You can look into Timer variables. The concept is no different than the Timer app on your phone. So you would start a Timer that either repeats every X seconds or runs once. Whenever the timer expires it will run any triggers that use it as their Event. Also, when you start a Timer that's already running it will restart, which can be very useful.

Here's an example:
  • Events
    • Time - MyTimer expires
1725719022861.png


Alternatively, you could have a bunch of Triggers that use Periodic Interval events and turn them on/off as needed.
 
Last edited:
Level 5
Joined
Jun 24, 2024
Messages
59
Hi again, so far things are going great, thanks to your help, but i struggle to change something:
@Uncle your provided Unit Indexer is really good, it works well with the tower Attack AI. How can i build onto it to make it work in multiplayer with 3 Players playing, each having their own area with 5 lanes, enemies spawning and so on? I'm sure im missing something important :|

Edit: Since i have 15 Lanes now, wouldn't it be best to just count up to 15 in the variables and set 1-5 to player 1, 6-10 to player 2 and 11-15 to player 3?


Also you added the note that i can disable indexing of undesired units, in my case im using dummy units for poison stacking for example, would that look like this?

  • Poison Stacking
    • Events
      • Unit - A unit owned by Player 2 (Blue) Takes damage
    • Conditions
    • Actions
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (Unit-type of (Damage source)) Equal to Poison Archer
        • Then - Actions
          • Set VariableSet UnitIndexerEnabled = False
          • Unit - Create 1 Poison for Player 1 (Red) at (Position of (Attacked unit)) facing Default building facing degrees
          • Unit - Order (Last created unit) to Attack (Attacked unit)
          • Unit - Add a 1.00 second Generic expiration timer to (Last created unit)
          • Set VariableSet UnitIndexerEnabled = True
        • Else - Actions
 
Last edited:

Uncle

Warcraft Moderator
Level 73
Joined
Aug 10, 2018
Messages
7,877
Hi again, so far things are going great, thanks to your help, but i struggle to change something:
@Uncle your provided Unit Indexer is really good, it works well with the tower Attack AI. How can i build onto it to make it work in multiplayer with 3 Players playing, each having their own area with 5 lanes, enemies spawning and so on? I'm sure im missing something important :|
Edit: Since i have 15 Lanes now, wouldn't it be best to just count up to 15 in the variables and set 1-5 to player 1, 6-10 to player 2 and 11-15 to player 3?
Yes, you can use any number to represent a Lane which would allow you to account for different players.

To track the ranges (1-5, 6-10, 11-15) for each Player you could do this:
  • Events
    • Time - Elapsed game time is 0.00 seconds
  • Conditions
  • Actions
    • Player Group - Pick every player in (All players) and do (Actions)
      • Loop - Actions
        • Set Variable PN = (Player number of (Picked player))
        • Set Variable Lane_Range_Max[PN] = (5 x PN)
        • Set Variable Lane_Range_Min[PN] = (Lane_Range_Max[PN] - 4)
Now all you have to do is plug in a Player Number into the [index] of those variables to get the minimum and max values of their Lane range. In other words, if you plugged Player 2's number -> [2] into the Index of Lane_Range_Min[] then you would get the value 6. If you did the same with Lane_Range_Max[] you would get the value 10. This could be useful for looping over their specific data in your Arrays.
  • Actions
    • Set Variable PN = (Player number of (Some player you want to interact with))
    • For each integer (Loop) from Lane_Range_Min[PN] to Lane_Range_Max[PN] do (Actions)
      • Loop - Actions
        • -------- Loop will cycle between 1-5 for Player 1, 6-10 for Player 2, 11-15 for Player 3, etc --------
Alternatively, you can learn how to use Hashtables which are more advanced versions of the Array. Although, they're a little more cumbersome to use.

Also you added the note that i can disable indexing of undesired units, in my case im using dummy units for poison stacking for example, would that look like this?
There's not much of a reason to avoid indexing the Dummy, but yes, that's how you would do it. Also, you may want to use Attack Once instead of Attack and use a shorter expiration timer. The Dummy unit should be setup to apply it's attack Instantly so an expiration timer of say 0.20 seconds should suffice.
 
Level 5
Joined
Jun 24, 2024
Messages
59
I can't quite get it right :/

I've added the PN Variables like you suggested:
  • Player Group - Pick every player in (All players) and do (Actions)
    • Loop - Actions
      • Set VariableSet PN = (Player number of (Picked player))
      • Set VariableSet Lange_Range_Min[PN] = (PN x 5)
      • Set VariableSet Lange_Range_Min[PN] = (Lane_Range_Max[PN] - 4)
and added the Loop logic for the Lane Range:
  • Set VariableSet PN = (Player number of Player 2 (Blue))
  • For each (Integer A) from Lange_Range_Min[PN] to Lane_Range_Max[PN], do (Actions)
    • Loop - Actions
      • Set VariableSet Spawn_Cycle = (Spawn_Cycle + (Random integer number between 1 and 5))
      • -------- --------
      • Set VariableSet Tower__Points[0] = (Center of Tower__Spawn_Regions_Start[Spawn_Cycle])
      • Set VariableSet Tower__Points[1] = (Center of Tower__Spawn_Regions_End[Spawn_Cycle])
      • -------- --------
      • Unit - Create 1 Zombie for Player 2 (Blue) at Tower__Points[0] facing 180.00 degrees
      • -------- --------
      • -------- Register the lane that the spawn unit belongs to (important so we can compare to attacking towers): --------
      • Set VariableSet Spawn_Unit = (Last created unit)
      • Set VariableSet Spawn__ID = (Custom value of Spawn_Unit)
      • Set VariableSet Occupied_Lane_Number[Spawn__ID] = Spawn_Cycle
      • -------- --------
      • AI - Ignore Spawn_Unit's guard position
      • Unit - Order Spawn_Unit to Attack-Move To Tower__Points[1]
      • -------- --------
      • Custom script: call RemoveLocation( udg_Tower__Points[0] )
      • Custom script: call RemoveLocation( udg_Tower__Points[1] )
.. but i don't get consisten results. What i want to achieve is: 1 have 3x5 Lanes, 5 for each player. I would like to have it so each player gets creeps at the same time, same amout of course, but the lane numbers should be randomized (it can be 3 seperate Wave Triggers,1 for each player).
In my exampe i queued a bunch of those trigger after another to test it for player one, but they spawn randomly on all lanes and even in the center of the map. What am i doing wrong?

Edit2: It would be simpler i think if i used the given variables and create them for each player and give each player its own Wave trigger with its own variables. Triggering all at the same time is no problem. But how do i implement it into your Indexer and Tower Targeting AI x.x
 

Uncle

Warcraft Moderator
Level 73
Joined
Aug 10, 2018
Messages
7,877
You're constantly increasing Spawn_Cycle which will eventually exceed 5 and continue growing infinitely.

I think you meant to do this:
  • Set VariableSet Spawn_Cycle = (Random integer number between 1 and 5)
Anyway, here's a new map based on the Tower Attack AI map I sent you earlier. I expanded upon it to make it support any number of Players.

In the map I showcase:
  • How to organize your Triggers to be more clean, efficient, and give you full control over when things happen.
  • How to organize your Players to have easy access to them at any time. This includes restricting Triggers from running for Inactive players.
  • How to design your Triggers/Variables to interact with Players/Units in a Generic way.
  • How to use Timers to run logic periodically for specific Players. Specifically, I use a Timer array in the enemy Spawn System.
  • How to use Hashtables to store data for specific Players. Think of them like bigger and better Arrays but instead of Setting and Getting values you Save values and Load values. Same idea, it's just that Hashtables require extra steps due to their flexibility.
Note that I never fully implemented Player 3 and I only use 3 Lanes instead of 5. But adding more lanes and players should only take a few minutes once you understand how I've structured everything. You don't HAVE to use this map, but I figured it'd be a useful learning tool.

Some things I've left up to you (there's obviously much more):
  • Expanding the Spawn System to choose what types of units and the number of units that spawn. IE: Wave 1 = 30 Footman, Wave 2 = 25 Rifleman, Wave 3 = 15 Gryphons (special air round), etc. Hint: You can Pause/Resume/Restart timers freely!
  • Figuring out how to handle abilities that can interfere with other players. IE: A Tower with Chain Lightning might be able to zap some enemies in another player's lane if it's cast range is long enough. Easiest solution = Keep player's a good distance from one another.
  • Expanding the Tower System to handle selling towers, upgrading towers, towers dying, etc.
 

Attachments

  • Lane Tower Defense 1.w3m
    35.8 KB · Views: 3
Last edited:
Level 5
Joined
Jun 24, 2024
Messages
59
Thank you very much, i have already added quite alot to my map and it would be very difficult to switch to yours ^^
I want to copy some of your Triggers and code but it crashes when i try. I have changed Names and Variables if they were already existing, but to no avail..
What can cause this crash? For example when i try to copy the Player System Folder.
 

Uncle

Warcraft Moderator
Level 73
Joined
Aug 10, 2018
Messages
7,877
Thank you very much, i have already added quite alot to my map and it would be very difficult to switch to yours ^^
I want to copy some of your Triggers and code but it crashes when i try. I have changed Names and Variables if they were already existing, but to no avail..
What can cause this crash? For example when i try to copy the Player System Folder.
It crashes? How exactly? That shouldn't happen.
 
Level 5
Joined
Jun 24, 2024
Messages
59
I don't quite know where it came from but i played around a little and it worked after a few tries.

Now a different and real difficult thing for me: If enemies progress towards the end region, i can set towers behind them to lure them back towards the spawn and therefore cheese it. Is there a way to make the units prioritise walking forwards and ignore attacking towers from behind?

I've tried but there is no way i found yet that makes units not turn around, even disabling their ability to turn by setting their turn rate to 0.
Then they just stop and wait. Maybe a focus trigger?

Edit to this: Maybe i could create regions lying on the Y Axis over the 5 lanes and have a similar retargeting system? I have to add, that i am mostly eyeballing what i do, i have yet to fully understand how this editor, the triggers and custom code especially works :)

Another idea i have is, that the units create something like starcraft zerg creep below then when walking forward so all tiles where they already walked on are blocked for building. And this (best invisible) creep always resets itself to the enemies which came furthest. so when i push back i can build farther towards the spawn again.

Edit2: What about the priority system, can i use that to make the units want to move forward?
For example i set a invisible target behind /on the end trigger and give it the highest priority. Would they target it when the way is free even tho another enemy is closer / attacking from behind?

Edit 3: optimally i would also restrict towers to attack "forwards and not attack enemies behind them.
 
Last edited:

Uncle

Warcraft Moderator
Level 73
Joined
Aug 10, 2018
Messages
7,877
The first thing that comes to mind would be to make your Spawns "passive". By that I mean they won't attack towers unless you manually tell them to do so. I believe giving them the Worker classification will achieve this. Then use Distance checks to determine if a Spawn should be doing one of the following at any given moment:
1) Attacking the closest tower IN FRONT of it.
2) Moving to it's end destination.

Here's how you could achieve that:
  • Issue Spawn Order
    • Events
    • Conditions
    • Actions
      • -------- Get information about the Spawn_Unit: --------
      • Set VariableSet Spawn_ID = (Custom value of Spawn_Unit)
      • Set VariableSet Current_Lane = The_Spawns_Lane[Spawn_ID]
      • -------- --------
      • -------- Track the end of the lane and where the Spawn currently is: --------
      • Set VariableSet Spawn_Points[0] = (Center of END REGION <gen>)
      • Set VariableSet Spawn_Points[1] = (Position of Spawn_Unit)
      • -------- --------
      • -------- Try to find the closest Tower in it's Lane (that's in front of it): --------
      • Set VariableSet Closest_Tower = No unit
      • Set VariableSet Closest_Distance = 999999.00
      • Set VariableSet Distance_Between_Spawn_And_End = (Distance between Spawn_Points[0] and Spawn_Points[1])
      • -------- --------
      • Unit Group - Pick every unit in Towers_In_Lane[Current_Lane] and do (Actions)
        • Loop - Actions
          • Set VariableSet Tower_Unit = (Picked unit)
          • -------- --------
          • -------- Track the distance between the spawn and the tower we're currently comparing it to: --------
          • Set VariableSet Spawn_Points[2] = (Position of Tower_Unit)
          • Set VariableSet Current_Distance = (Distance between Spawn_Points[1] and Spawn_Points[2])
          • -------- --------
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • Current_Distance Less than Closest_Distance
            • Then - Actions
              • -------- Ensure that the tower is actually in front of the spawn: --------
              • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                • If - Conditions
                  • Distance_Between_Spawn_And_End Greater than (Distance between Spawn_Points[0] and Spawn_Points[2])
                • Then - Actions
                  • Set VariableSet Closest_Distance = Current_Distance
                  • Set VariableSet Closest_Tower = Tower_Unit
                • Else - Actions
            • Else - Actions
          • -------- --------
          • Custom script: call RemoveLocation( udg_Spawn_Points[2] )
      • -------- --------
      • -------- Tell the Spawn to either keep on Moving (no towers in front) or Attack the closest Tower in front of it: --------
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • Closest_Tower Equal to No unit
        • Then - Actions
          • Unit - Order Spawn_Unit to Move To Spawn_Points[0]
        • Else - Actions
          • Unit - Order Spawn_Unit to Attack Closest_Tower
      • -------- --------
      • Custom script: call RemoveLocation( udg_Spawn_Points[0] )
      • Custom script: call RemoveLocation( udg_Spawn_Points[1] )
So you would Run this trigger after creating and setting up a Spawn unit. You could also run it periodically to ensure that a Spawn has the correct Orders. Better yet you should detect whenever a Tower dies and Run this for any Spawns that were trying to Attack that Tower.

Towers_In_Lane[] is a Unit Group array variable. The [index] represents the Lane Number the Tower is in. In other words, each Lane will have it's own Unit Group that tracks the Towers inside of it. So you would need to Add your Towers to their assigned unit group the moment they begin construction. Then you would Remove them from their assigned unit group the moment they die. Remember that Unit Group arrays are special and need to be given a Size in the Variable Editor (A Size of 100 would allow the Array to work for up to 100 different lanes).

Obviously this is a bit more advanced but the map you're trying to make isn't very simple.
 
Last edited:
Level 5
Joined
Jun 24, 2024
Messages
59
I am sorry... The solution was rather simple, you gave the perfect solution and it doesn't even need triggers.
I set the acquisition range to 60, the attack ranke to 50 and made them have the classification worker.
This alone already prevented them to draw aggro from towers sitting behind them and attacking them.
This plus a slightly larger hitbox then the model should work wonders against cheesing enemies :grin:

Regarding the towers i will leave it like it is right now, it includes some interesting strats ^^

My next and probably final question for now in this post would be: How can i set up a Spawning system.
Obviously by spawning enemies, much like you suggested in multiple answers. But i am trying to create a more controlled and at the same time random, regarding the the lanes.

This on its own i can already accomplish with your method of Spawncycles and Random integer numbers choosen for the lane.
Here i am simply wondering if there is an easier solution to it,

Currently:
  • P1Wave1
    • Events
      • Game - WaveStarter becomes Equal to 1.00
    • Conditions
    • Actions
      • Wait 10.00 seconds
      • -------- Zombie --------
      • Set VariableSet Spawn_CycleP1 = 0
      • Set VariableSet Spawn_CycleP1 = (Spawn_CycleP1 + (Random integer number between 1 and 5))
      • Set VariableSet Tower__PointsP1[0] = (Center of Tower__Spawn_Regions_StartP1[Spawn_CycleP1])
      • Set VariableSet Tower__PointsP1[1] = (Center of Tower__Spawn_Regions_EndP1[Spawn_CycleP1])
      • Unit - Create 1 Zombie for Player 9 (Gray) at Tower__PointsP1[0] facing 180.00 degrees
      • Set VariableSet Spawn_UnitP1 = (Last created unit)
      • Set VariableSet Spawn__IDP1 = (Custom value of Spawn_UnitP1)
      • Set VariableSet Occupied_Lane_NumberP1[Spawn__IDP1] = Spawn_CycleP1
      • AI - Ignore Spawn_UnitP1's guard position
      • Unit - Order Spawn_UnitP1 to Attack-Move To Tower__PointsP1[1]
      • Custom script: call RemoveLocation( udg_Tower__PointsP1[0] )
      • Custom script: call RemoveLocation( udg_Tower__PointsP1[1] )
      • Wait 7.00 seconds
      • -------- New Spawn --------
      • -------- Zombie --------
      • Set VariableSet Spawn_CycleP1 = 0
      • Set VariableSet Spawn_CycleP1 = (Spawn_CycleP1 + (Random integer number between 1 and 5))
      • Set VariableSet Tower__PointsP1[0] = (Center of Tower__Spawn_Regions_StartP1[Spawn_CycleP1])
      • Set VariableSet Tower__PointsP1[1] = (Center of Tower__Spawn_Regions_EndP1[Spawn_CycleP1])
      • Unit - Create 1 Zombie for Player 9 (Gray) at Tower__PointsP1[0] facing 180.00 degrees
      • Set VariableSet Spawn_UnitP1 = (Last created unit)
      • Set VariableSet Spawn__IDP1 = (Custom value of Spawn_UnitP1)
      • Set VariableSet Occupied_Lane_NumberP1[Spawn__IDP1] = Spawn_CycleP1
      • AI - Ignore Spawn_UnitP1's guard position
      • Unit - Order Spawn_UnitP1 to Attack-Move To Tower__PointsP1[1]
      • Custom script: call RemoveLocation( udg_Tower__PointsP1[0] )
      • Custom script: call RemoveLocation( udg_Tower__PointsP1[1] )
      • Wait 1.00 seconds
      • -------- New Spawn --------
-i spawn every zombie after a chosen time randomly for each player, meaning i can control the timing and amount.
Because of the randomness every player gets the same amount of enemies but at random lanes thanks to the random number.

I would like to keep the control to for example spawn the first enemy after 10 seconds, the next after 7, then 5, then 6 again and so on, to make it feel random and very custom to each wave.
 

Uncle

Warcraft Moderator
Level 73
Joined
Aug 10, 2018
Messages
7,877
That's good to hear about the simple solution. The distance checks would've introduced a decent amount of overhead.

Anyway, what you want to do is create a big database of information that tracks spawn related data: Type of spawn(s), Amount of spawn(s), Interval of spawn(s). Then use Timers to manage when things should happen.

So first you create a database:
  • Events
    • Time - Elapsed game time is 0.00 seconds
  • Conditions
  • Actions
    • -------- Round [1] --------
    • Set Variable Spawn_Type[1] = Weak Zombie (Lvl 1)
    • Set Variable Spawn_Total[1] = 30
    • Set Variable Spawn_Interval[1] = 1.00
    • -------- Round [2] --------
    • Set Variable Spawn_Type[2] = Dumb Zombie (Lvl 2)
    • Set Variable Spawn_Total[2] = 26
    • Set Variable Spawn_Interval[2] = 1.25
    • -------- Round [3] --------
    • Set Variable Spawn_Type[3] = Big Zombie (Lvl 3)
    • Set Variable Spawn_Total[3] = 20
    • Set Variable Spawn_Interval[3] = 2.00
The [index] here is meant to represent the current round a player is on (or if rounds are shared, the current round in general). You can reference this data at any given time to determine what should happen next. Of course, if you want things like Spawn_Interval to be random then you would simply remove that variable from the database and use your own (Random interval number) logic.

Then you create a trigger that handles preparing for the next round:
  • Events
    • Countdown Timer - Next_Round_Timer expires
  • Conditions
  • Actions
    • Set Variable Current_Round = (Current_Round + 1)
    • Set Variable Spawns_Remaining = (Spawn_Total[Current_Round] x (Number of players in PG_Users_Playing))
    • Player Group - Pick every player in PG_Users_Playing and do (Actions)
      • Loop - Actions
        • Set Variable Player_ID = (Player number of (Picked player))
        • Countdown Timer - Start Spawn_Timer[Player_ID] as a One-shot timer that expires in (Random real between 6.00 and 10.00) seconds
So whenever you start the Next_Round_Timer and it expires, it'll start a new round. You can do this anywhere at any time.

Then you create trigger(s) that handle the creation of the zombies:
  • Events
    • Countdown Timer - Spawn_Timer[1] expires
  • Conditions
  • Actions
    • Set Variable Player_ID = 1
    • Set Variable Spawns_Remaining = (Spawns_Remaining - 1)
    • Set Variable Random_Lane_Number = (Random number between Lane_Min_Range[Player_ID] and Lane_Max_Range[Player_ID])
    • Unit - Create 1 Spawn_Type[Current_Round] for Player 9 (Gray) at Spawn_Start_Points[Random_Lane_Number] facing 180.00 degrees
    • Set Variable Last_Spawn = (Last created unit)
    • Unit - Order Last_Spawn to Attack-Move To Spawn_End_Points[Random_Lane_Number]
    • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
      • If - Conditions
        • Spawns_Remaining Greater than 0
      • Then - Actions
        • Countdown Timer - Start Spawn_Timer[Player_ID] as a One-shot timer that expires in (Random real between 6.00 and 10.00) seconds
      • Else - Actions
        • Countdown Timer - Start Next_Round_Timer as a One-shot timer that expires in 30.00 seconds
In my demo map I handle each player's Timer in one central trigger, but if that's too confusing for you then you can break it up into multiple Triggers for multiple Players. In this example I kept it simple and dedicated this trigger to Player 1, but as you may be able to tell by the lack of Player-specifics, the trigger is very close to being generic. You would just need to determine which Timer expired and get the appropriate Player_ID that matches it. (Refer to my demo map to see how)

Anyway, this trigger is doing the following:

1) Gets the ID of the Player whose Timer expired so that we can get their other personal data.
2) Uses that Player's personal data to determine the Position where the zombie is created and where it's told to Attack-Move to. You would have setup Point variables at the start and end of their Lane Regions beforehand. The same is true for their Lane number ranges (1-5, 6-10, 11-15).
3) Updates how many Spawns remain. This way we know when they have all been created so that we can move on to the next round.
4) Creates a Zombie based on the current Round, referencing our spawn database that we created earlier.
5) Starts the timer again to spawn a new zombie OR if all zombies have spawned then prepare for the next Round.


Some comments about your current triggers:

1) If you're typing "P1" in your variable name then you're likely doing something wrong. Most of these variables can either be Arrays where you simply plug a Player's Number into the [index] to get their associated data OR you don't need an Array at all and one variable will suffice. For example, my Last_Spawn variable does NOT need to be player specific or an Array. It's simply a shortcut to the (Last created unit), with the benefit of being more efficient and less prone to error.

2) Stop repeating yourself. If you're copying and pasting the same chunk of Actions multiple times then you're likely doing something wrong. You can use Arrays, For Loops, Timers, Run Trigger, and other methods to make a single trigger work for everyone and to be very clean with only ~10 Actions instead of ~1000 of the same Actions copied over and over again (like your P1Wave1 trigger).

3) Waits generally suck and are the quick and dirty solution. Use them only when precision isn't needed since they will be inconsistent when playing online. For a crucial system like this one you will want predictability and control which comes with using Timers.
 
Last edited:
Level 5
Joined
Jun 24, 2024
Messages
59
I currently only have one thing Player-Specific which is:

Specific Player events that require a player owned zone where the Unit Targeting/retargeting takes place as well as the Setup for the Setting and Tracking of player owned units and items (And stops other players from building there) - i don't think i can generalize this. Does it do any harm to have a minimal amount of Triggers set specific for a player number?
 

Uncle

Warcraft Moderator
Level 73
Joined
Aug 10, 2018
Messages
7,877
I currently only have one thing Player-Specific which is:

Specific Player events that require a player owned zone where the Unit Targeting/retargeting takes place as well as the Setup for the Setting and Tracking of player owned units and items (And stops other players from building there) - i don't think i can generalize this.
Your entire P1Wave1 in your last post is Player-Specific. The design and the variables are all specific to Player 1.

Also, you can generalize EVERYTHING.

Does it do any harm to have a minimal amount of Triggers set specific for a player number?
I'm not sure I understand the question. Using an Event specific to a Player is perfectly fine but you should be asking yourself, am I wasting my time repeating myself? Compare the following:

This is one trigger that manages Player 1, 2 and 3 all in one place. If I want to change something then I only have to do it once:
  • Player Score
    • Events
      • Unit - A unit Dies
    • Conditions
      • (Team number of (Owner of (Triggering unit))) Equal to 0
    • Actions
      • Set Variable Player_ID = (Player number of (Owner of (Triggering unit)))
      • Set Variable Score[Player_ID] = (Score[Player_ID] - 1)
      • Game - Display to (All players) for 5.00 seconds the text: ((Name of (Owner of (Triggering unit))) + has lost a point!)
This is three triggers, each doing essentially the same exact thing. If I want to change something then I have to do it 3 times:
  • P1 Score
    • Events
      • Unit - A unit owned by Player 1 (Red) Dies
    • Conditions
    • Actions
      • Set Variable Score_P1 = (Score_P1 - 1)
      • Game - Display to (All players) for 5.00 seconds the text: ((Name of Player 1 (Red)) + has lost a point!)
  • P2 Score
    • Events
      • Unit - A unit owned by Player 2 (Blue) Dies
    • Conditions
    • Actions
      • Set Variable Score_P2 = (Score_P2 - 1)
      • Game - Display to (All players) for 5.00 seconds the text: ((Name of Player 2 (Blue)) + has lost a point!)
  • P3 Score
    • Events
      • Unit - A unit owned by Player 3 (Teal) Dies
    • Conditions
    • Actions
      • Set Variable Score_P3 = (Score_P3 - 1)
      • Game - Display to (All players) for 5.00 seconds the text: ((Name of Player 3 (Teal)) + has lost a point!)
^ Then if you ever wanted to add more players to the game it would become a headache because you'll need to create all new triggers and variables for them.

Ideally, most of your triggers would dynamically adjust to the addition of a new player if you did things correctly.

Anyway, I'm just suggesting good practices. These can take time to fully grasp and apply yourself, so in the meantime feel free to continue doing what makes sense to you.
 
Last edited:
Level 5
Joined
Jun 24, 2024
Messages
59
What i meant is my triggers adapted with your blueprint to Tower Construction, Tower Retargeting and the "Get new Target" Trigger, which look like this:

  • Tower Construction P1
    • Events
      • Unit - A unit enters MainPlayer1 <gen>
    • Conditions
      • ((Triggering unit) is in (Units owned by Player 1 (Red).).) Equal to True
    • Actions
      • Set VariableSet Tower_UnitP1 = (Triggering unit)
      • Set VariableSet Tower__PointsP1[0] = (Position of Tower_UnitP1)
      • For each (Integer Tower__LoopP1) from 1 to 5, do (Actions)
        • Loop - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • (Tower__Lane_RegionsP1[Tower__LoopP1] contains Tower__PointsP1[0]) Equal to True
            • Then - Actions
              • Set VariableSet Tower__IDP1 = (Custom value of Tower_UnitP1)
              • Set VariableSet Occupied_Lane_NumberP1[Tower__IDP1] = Tower__LoopP1
              • Custom script: call RemoveLocation( udg_Tower__PointsP1[0] )
              • Skip remaining actions
            • Else - Actions
      • Custom script: call RemoveLocation( udg_Tower__PointsP1[0] )
- in this example i have different Areas to setup these logics. Tho i understand that it is possible to "neutralize" every kind of trigger to get it working for every player. I also saw possibilities to assign Areas to players and then call them by triggers like "Owner of Region", which can be very handy. So i guess it's back to the drawing board and optimizing. I am currently also checking every trigger for potential leaks thanks to the great guide ^^

This will be about every information i need for 90% of what is to come :D
The rest is Doing

One last last question for now would be, is the website "W3Potect" trustworthy? Nobody likes their work stolen by random strangers, even if it is a small hobby project. I don't know if i need or should protect my map..
There was an information about a website getting every map in their database when it is hosted over the battle.net so in theory it is already stolen :hohum:
 

Uncle

Warcraft Moderator
Level 73
Joined
Aug 10, 2018
Messages
7,877
- in this example i have different Areas to setup these logics. Tho i understand that it is possible to "neutralize" every kind of trigger to get it working for every player. I also saw possibilities to assign Areas to players and then call them by triggers like "Owner of Region", which can be very handy. So i guess it's back to the drawing board and optimizing. I am currently also checking every trigger for potential leaks thanks to the great guide ^^
If you store the Ranges that a Player uses then you can turn that into one trigger:
  • Events
    • Time - Elapsed game time is 0.00 seconds
  • Conditions
  • Actions
    • Set Variable Player_Min[1] = 1
    • Set Variable Player_Max[1] = 5
    • Set Variable Player_Min[2] = 6
    • Set Variable Player_Max[2] = 10
    • Set Variable Player_Min[3] = 11
    • Set Variable Player_Max[3] = 15
^ Now you're tracking a Player's ranges like we discussed earlier, 1 -> 5 for p1, 6 -> 10 for p2, 11 -> 15 for p3. Now you can get these values to interact with Array data specific to the given Player.

So we can restructure your Tower Construction trigger to be generic by using this information:
  • Tower Construction Everyone
    • Events
      • Unit - A unit Begins construction
    • Conditions
    • Actions
      • Set VariableSet Tower_Unit = (Triggering unit)
      • Set VariableSet Tower_Points[0] = (Position of Tower_Unit)
      • Set VariableSet Player_ID = (Player number of (Owner of Tower_Unit))
      • For each (Integer Tower_Loop) from Player_Min[Player_ID] to Player_Max[Player_ID], do (Actions)
        • Loop - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • (Tower_Lane_Regions[Tower_Loop] contains Tower_Points[0]) Equal to True
            • Then - Actions
              • Set VariableSet Tower_ID = (Custom value of Tower_Unit)
              • Set VariableSet Occupied_Lane_Number[Tower_ID] = Tower_Loop
              • Custom script: call RemoveLocation( udg_Tower_Points[0] )
              • Skip remaining actions
            • Else - Actions
      • Custom script: call RemoveLocation( udg_Tower_Points[0] )
The For Loop will go from 1 to 5 for P1, 6 to 10- for P2, and 11 to 15 for P3. That means it'll reference Tower_Lane_Regions[] specific to that Player, assuming that those use the same pattern. Also, note how the variables are generic. There doesn't need to be a P1, P2, P3 version of any of these variables.

You can adjust the Events and add more Conditions to ensure that this trigger runs only for the Units you want.

One last last question for now would be, is the website "W3Potect" trustworthy?
 
Last edited:
Top