• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • 🏆 Hive's 6th HD Modeling Contest: Mechanical is now open! Design and model a mechanical creature, mechanized animal, a futuristic robotic being, or anything else your imagination can tinker with! 📅 Submissions close on June 30, 2024. Don't miss this opportunity to let your creativity shine! Enter now and show us your mechanical masterpiece! 🔗 Click here to enter!

Best way to randomize unit start areas?

Status
Not open for further replies.
Level 11
Joined
May 9, 2021
Messages
194
Is this the right place for this?

Basically, I'm wondering what the best way is to move units to random areas of the map without having them spawn too close to each other or having them spawn in immovable areas (such as on top of walls or mountains).

This would be for 9 separate units, each owned by a different player. This is for an Arena-type map.

I've already tried moving them to random map areas, but both of the above problems occur. I'm currently using two regions to avoid the issue of being moved to immovable areas, but they still have a chance to start next to each other and the regions don't cover the entire map due to the above problem.

Anyone know the best way?
 
Level 40
Joined
Feb 27, 2007
Messages
5,132
I think the best solution realistically is just to make a large number of minimum-size regions that are all safe spawn spots. If you are concerned about spawns being 'knowable' beforehand just make a fuckload of possible spawns all over the arena. It will be tedious to create/name them and put their center locations into a bigass location array on map init, but once you've done so you can just randomize an integer, use that int as an index for the array, and then check that location against all other 'picked' spawn locations to see if any are too close. If so, randomize that most recent spawn again until it's far enough away. It's possible that this method results in the Mth through Nth out of N spawns always being too close to others, so you could build a failsafe into the process to start over if that happens, or just accept the suboptimal spawn.

A variant of this is to abuse what a region actually is. Turns out what GUI calls regions are actually JASS rects (in this reply from here onward I will be using the true JASS definitions to describe the two objects, to avoid confusion). The difference is that rects are rectangular boxes that can't be rotated and regions are abstract collections of rects. It's possible to get "random point in rect" but not possible to get "random point in region"; you can, however, check if a given point/xy-coordinate is within a particular region. Cover the arena in one big rect, then also make a bunch of smaller rects that cover all areas of the map that should be un-spawnable. On map init, add all the un-spawnable rects into one big region; when you need a safe point, randomize points in the big region until you find one that isn't in the un-spawnable region. Check distance to other chosen spawn points same as above (with the same caveat about closeness).

(It's possible this could be done 'in reverse' by subtracting all the bad rects from the main big rect using RegionClearRect instead of RegionAddRect, but I don't know for sure that will work properly.)

As long as there aren't any enclosed areas where a unit would have enough room to move around in but never leave, you can test randomized locations for pathability by placing an item on the location you randomized. The item will automatically be moved to the next closest pathable spot the game can find for it. Randomize point, place item, update point to the item's new location, then compare against all prior chosen points (same caveats).

To avoid the caveats about closeness you could instead divide the arena radially (or some geographic blob-like method) into 9 zones, and then either use the item method to test randomized points in each zone or use the regions-made-up-of-rects approach to determine safe locations. More work but potentially better results.
 
Last edited:

Uncle

Warcraft Moderator
Level 66
Joined
Aug 10, 2018
Messages
6,743
Here's a little demo map that doesn't use Regions to get random "safe" spawn points. It's not guaranteed to work but you can customize it to be extremely close to 100% success rate. At the moment I have it setup to loop up to 1,000,000 times over 5.00 seconds with each loop cycle attempting to find a safe spawn point. It does this for any number of units while making sure that they're all 1200.00 range away from one another and not stuck on islands. The island solution is nowhere close to perfect but it does help a bit. All of this is easily customizable in the Setup trigger.

Notes:
  • You will need to customize the settings in the Setup trigger to work with your map layout.
  • I disabled the RS_CliffHeight variable by default but it can be enabled again and used for further control over where units spawn.
  • The Finish trigger is setup to easily implement Regions for when the system fails to find a safe spawn point for one or more units. What you could do is create the safe regions outside of the random spawn area so that you ensure that they won't be near the successful units. Then as long as you have at least 1 region for each unit you'll guarantee that in a worst case scenario the units will be evenly distributed amongst these. That's EXTREMELY unlikely to happen but it will guarantee that the randomization will always work.
  • Another strategy you could do is place Pathing Blockers on islands and other no-go areas.
  • You could also create Regions at no-go areas and check if units are inside of these as part of the Finish process. If a unit is inside of a no-go region then move them to one of the unoccupied safe regions.
 

Attachments

  • Random Spawns 1.w3m
    35.1 KB · Views: 1
Last edited:
Level 11
Joined
May 9, 2021
Messages
194
Here's a little demo map that doesn't use Regions to get random "safe" spawn points. It's not guaranteed to work but you can customize it to be extremely close to 100% success rate. At the moment I have it setup to loop up to 50,000 times over 5.00 seconds with each loop cycle attempting to find a safe spawn point. It does this for any number of units while making sure that they're all 1200.00 range away from one another and not stuck on islands. The island solution is nowhere close to perfect but it does help a bit. All of this is easily customizable in the Setup trigger.

Notes:
  • You will need to customize the settings in the Setup trigger to work with your map layout.
  • I disabled the RS_CliffHeight variable by default but it can be enabled again and used for further control over where units spawn.
  • The Finish trigger is setup to easily implement Regions for when the system fails to find a safe spawn point for one or more units. What you could do is create the safe regions outside of the random spawn area so that you ensure that they won't be near the successful units. Then as long as you have at least 1 region for each possible unit, you'll guarantee that in a worst case scenario the units will be divided amongst these. That's EXTREMELY unlikely to happen but it will guarantee that the randomization will always work.
  • Another strategy you could do is place Pathing Blockers on islands and other no-go areas.
  • You could also use Regions as no-go areas and check if units are inside of these as part of the Finish process. If a unit is inside of a no-go region then move them to one of the safe regions.
Map won't open so I assume our Warcraft versions are different?
 

Uncle

Warcraft Moderator
Level 66
Joined
Aug 10, 2018
Messages
6,743
Map won't open so I assume our Warcraft versions are different?
Yes, I'm on the latest patch.
  • Randomize Spawn Setup
    • Events
      • Time - Elapsed game time is 0.00 seconds
    • Conditions
    • Actions
      • -------- (CUSTOMIZE) This is the max number of times the random spawn timer will repeat: --------
      • Set VariableSet RS_TimerExecutions = 100
      • -------- --------
      • -------- (CUSTOMIZE) This is the max number of attempts for a random and safe spawn that will occur each timer execution: --------
      • Set VariableSet RS_LoopAttempts = 500
      • -------- --------
      • -------- (CUSTOMIZE) This is the minimum distance each unit must be from one another: --------
      • Set VariableSet RS_MinimumDistance = 1200.00
      • -------- --------
      • -------- (CUSTOMIZE) When ENABLED, this is the only accepted cliff height for placement (I'm using the default Initial Cliff Level): --------
      • Set VariableSet RS_CliffHeight = 2
      • -------- --------
      • -------- (CUSTOMIZE) The system will check if the random spawn point is on an island by testing 4 other points which are north, south, east, and west of it: --------
      • -------- East: --------
      • Set VariableSet RS_IslandX[1] = 400.00
      • Set VariableSet RS_IslandY[1] = 0.00
      • -------- West: --------
      • Set VariableSet RS_IslandX[2] = -400.00
      • Set VariableSet RS_IslandY[2] = 0.00
      • -------- North: --------
      • Set VariableSet RS_IslandX[3] = 0.00
      • Set VariableSet RS_IslandY[3] = 400.00
      • -------- South: --------
      • Set VariableSet RS_IslandX[4] = 0.00
      • Set VariableSet RS_IslandY[4] = -400.00
      • -------- --------
      • -------- (CUSTOMIZE) Start the timer that will repeatedly attempt to move your units. Define it's interval: --------
      • Countdown Timer - Start RS_Timer as a Repeating timer that will expire in 0.05 seconds
      • -------- --------
      • -------- (CUSTOMIZE) Track the spawn units in an array as well as a unit group: --------
      • Set VariableSet RS_UnitGroup = (Units in (Playable map area) matching (((Matching unit) is A Hero) Equal to True))
      • Unit Group - Pick every unit in RS_UnitGroup and do (Actions)
        • Loop - Actions
          • Set VariableSet RS_Loop = (RS_Loop + 1)
          • Set VariableSet RS_Unit[RS_Loop] = (Picked unit)
          • Unit - Hide (Picked unit)
          • Unit - Pause (Picked unit)
      • -------- --------
      • -------- This is the total number of units that will be moved around: --------
      • Set VariableSet RS_TotalUnits = (Number of units in RS_UnitGroup)
  • Randomize Spawn Positions
    • Events
      • Time - RS_Timer expires
    • Conditions
    • Actions
      • -------- Randomize the position of the units: --------
      • For each (Integer RS_Loop) from 1 to RS_LoopAttempts, do (Actions)
        • Loop - Actions
          • Set VariableSet RS_Point = (Random point in (Playable map area))
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • DISABLED BY DEFAULT --> (Terrain cliff level at RS_Point) Equal to RS_CliffHeight
              • (Terrain pathing at RS_Point of type Floatability is off) Equal to True
              • (Terrain pathing at RS_Point of type Walkability is off) Equal to False
            • Then - Actions
              • Set VariableSet RS_Success = True
              • -------- --------
              • -------- Make the distance checks to prevent units from being too close to one another: --------
              • Unit Group - Pick every unit in RS_UnitGroup and do (Actions)
                • Loop - Actions
                  • Set VariableSet RS_PickedUnit = (Picked unit)
                  • Set VariableSet RS_Point2 = (Position of RS_PickedUnit)
                  • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                    • If - Conditions
                      • RS_PickedUnit Not equal to RS_Unit[RS_Index]
                      • (Distance between RS_Point and RS_Point2) Less than RS_MinimumDistance
                    • Then - Actions
                      • Set VariableSet RS_Success = False
                    • Else - Actions
                  • Custom script: call RemoveLocation (udg_RS_Point2)
              • -------- --------
              • -------- Check if the point is on an island (not 100% guaranteed to work): --------
              • Set VariableSet RS_IsNotIsland = 0
              • For each (Integer RS_Loop2) from 1 to 4, do (Actions)
                • Loop - Actions
                  • Set VariableSet RS_Point2 = (RS_Point offset by (RS_IslandX[RS_Loop2], RS_IslandY[RS_Loop2]))
                  • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                    • If - Conditions
                      • DISABLED BY DEFAULT --> (Terrain cliff level at RS_Point2) Equal to RS_CliffHeight
                      • (Terrain pathing at RS_Point2 of type Floatability is off) Equal to True
                      • (Terrain pathing at RS_Point2 of type Walkability is off) Equal to False
                    • Then - Actions
                      • Set VariableSet RS_IsNotIsland = (RS_IsNotIsland + 1)
                    • Else - Actions
                  • Custom script: call RemoveLocation (udg_RS_Point2)
              • -------- --------
              • -------- If 2 or more Points failed then we assume it was on an island: --------
              • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                • If - Conditions
                  • RS_IsNotIsland Less than or equal to 2
                • Then - Actions
                  • Set VariableSet RS_Success = False
                • Else - Actions
              • -------- --------
              • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                • If - Conditions
                  • RS_Success Equal to True
                • Then - Actions
                  • Unit - Move RS_Unit[RS_Index] instantly to RS_Point
                  • Unit - Unhide RS_Unit[RS_Index]
                  • Custom script: call RemoveLocation (udg_RS_Point)
                  • -------- --------
                  • -------- Get next unit / finish process: --------
                  • Set VariableSet RS_Index = (RS_Index + 1)
                  • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                    • If - Conditions
                      • RS_Index Greater than RS_TotalUnits
                    • Then - Actions
                      • Set VariableSet RS_TimerCounter = RS_TimerExecutions
                      • Custom script: exitwhen true
                    • Else - Actions
                • Else - Actions
              • -------- --------
              • Custom script: call RemoveLocation (udg_RS_Point)
            • Else - Actions
              • Custom script: call RemoveLocation (udg_RS_Point)
      • -------- --------
      • -------- --------
      • -------- --------
      • Set VariableSet RS_TimerCounter = (RS_TimerCounter + 1)
      • Game - Display to (All players) for 5.00 seconds the text: (Timer Executions: + (String(RS_TimerCounter)))
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • RS_TimerCounter Greater than or equal to RS_TimerExecutions
        • Then - Actions
          • Countdown Timer - Pause RS_Timer
          • Trigger - Run Randomize Spawn Finish <gen> (ignoring conditions)
        • Else - Actions
  • Randomize Spawn Finish
    • Events
    • Conditions
    • Actions
      • -------- Unpause all units and randomize any that have somehow failed to move (should almost never happen): --------
      • Unit Group - Pick every unit in RS_UnitGroup and do (Actions)
        • Loop - Actions
          • Set VariableSet RS_PickedUnit = (Picked unit)
          • -------- --------
          • -------- Leftover units will be still be hidden: --------
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • (RS_PickedUnit is hidden) Equal to True
            • Then - Actions
              • Set VariableSet RS_Point = (Random point in (Playable map area))
              • Unit - Move RS_PickedUnit instantly to RS_Point
              • Unit - Unhide RS_PickedUnit
              • Custom script: call RemoveLocation (udg_RS_Point)
            • Else - Actions
          • -------- --------
          • -------- Unpause all of the units: --------
          • Set VariableSet RS_Point = (Position of RS_PickedUnit)
          • Camera - Pan camera for (Owner of RS_PickedUnit) to RS_Point over 0.00 seconds
          • Unit - Unpause RS_PickedUnit
          • Custom script: call RemoveLocation (udg_RS_Point)
      • -------- --------
      • -------- We don't need the Unit Group anymore: --------
      • Custom script: call DestroyGroup(udg_RS_UnitGroup)
 
Last edited:
Level 11
Joined
May 9, 2021
Messages
194
I think I might've figured an alternate method out, so I'll see how that goes.

I'll try and check back in tomorrow.

Edit: Okay, so I think I solved it.

Basically what I did was:

  • Create a bunch of units around the map.
  • Put them each into different unit groups.
  • Replace a random unit with the unit of my choice.
  • Remove replaced unit from the group.
  • Remove all other units of that group from the map.

Its not as random as some of the suggested ways here, but its easier to manage and still works well enough.

I do appreciate the help though. Thanks everyone :)
 
Last edited:
Status
Not open for further replies.
Top