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!
For a forest area of my map, I would like to have complete procedural unit generation, revival and so on.
I would use a region but the shape is shown below.
Are there any other tricks/systems people use for such circumstances?
I thought about placing invisible cues throughout and using them as locations.
Yes you could just place 100 destructible objects and on map init pick all destructibles of that type and add them to array. Then pick a random one from the array and spawn unit.
I had ChatGPT whip up some code real quick, this allows you to create your own shapes with up to 100 points. (It could be more, just increase Array size)
vJASS:
function CreateTriangle takes nothing returns nothing
local Polygon pol = Polygon.create()
// Triangle
call pol.addPoint(Point.create(-500.0, 0.0))
call pol.addPoint(Point.create(500.0, 0.0))
call pol.addPoint(Point.create(0.0, 1000.0))
call pol.finish()
// Track it globally
set LastPolygon = pol
endfunction
vJASS:
library Polygons
globals
Polygon LastPolygon
endglobals
struct Point
real x
real y
static method create takes real x, real y returns thistype
local thistype this = thistype.allocate()
set this.x = x
set this.y = y
return this
endmethod
endstruct
struct Polygon
Point array points[99]
integer count = 0
real minX
real maxX
real minY
real maxY
method addPoint takes Point p returns nothing
set this.count = this.count + 1
set this.points[this.count] = p
endmethod
method finish takes nothing returns nothing
local real array out
local integer i = 1
local Point p = this.points[1]
// Calculate bounds
set out[0] = p.x
set out[1] = p.x
set out[2] = p.y
set out[3] = p.y
loop
exitwhen i > this.count
set p = this.points[i]
if p.x < out[0] then
set out[0] = p.x
endif
if p.x > out[1] then
set out[1] = p.x
endif
if p.y < out[2] then
set out[2] = p.y
endif
if p.y > out[3] then
set out[3] = p.y
endif
set i = i + 1
endloop
set this.minX = out[0]
set this.maxX = out[1]
set this.minY = out[2]
set this.maxY = out[3]
endmethod
endstruct
function DistSq takes real x1, real y1, real x2, real y2 returns real
local real dx = x2 - x1
local real dy = y2 - y1
return dx*dx + dy*dy
endfunction
function IsInPolygon takes Point p, Polygon poly returns boolean
local real minX
local real maxX
local real minY
local real maxY
local integer i
local integer j
local boolean inside = false
local Point qi
local Point qj
if poly.count < 3 then
return false
endif
// --- Bounding box check (optional optimization)
set minX = poly.points[1].x
set maxX = poly.points[1].x
set minY = poly.points[1].y
set maxY = poly.points[1].y
set i = 1
loop
exitwhen i > poly.count
set qi = poly.points[i]
if qi.x < minX then
set minX = qi.x
endif
if qi.x > maxX then
set maxX = qi.x
endif
if qi.y < minY then
set minY = qi.y
endif
if qi.y > maxY then
set maxY = qi.y
endif
set i = i + 1
endloop
if p.x < minX or p.x > maxX or p.y < minY or p.y > maxY then
return false
endif
// --- pnpoly logic
set j = poly.count
set i = 1
loop
exitwhen i > poly.count
set qi = poly.points[i]
set qj = poly.points[j]
if ((qi.y > p.y) != (qj.y > p.y)) and (p.x < (qj.x - qi.x) * (p.y - qi.y) / (qj.y - qi.y) + qi.x) then
set inside = not inside
endif
set j = i
set i = i + 1
endloop
return inside
endfunction
//////////////////////////////////////////
// TEST TRIANGLE //
// REPLACE WITH YOUR OWN SHAPES + LOGIC //
//////////////////////////////////////////
function CreateTriangle takes nothing returns nothing
local Polygon pol = Polygon.create()
// Triangle
call pol.addPoint(Point.create(-500.0, 0.0))
call pol.addPoint(Point.create(500.0, 0.0))
call pol.addPoint(Point.create(0.0, 1000.0))
call pol.finish()
// Track it globally
set LastPolygon = pol
endfunction
function TestTriangle takes real x, real y returns nothing
local Point p = Point.create(x, y)
if IsInPolygon(p, LastPolygon) then
call BJDebugMsg("XY is inside polygon")
else
call BJDebugMsg("XY is outside polygon")
endif
endfunction
function CreateUnitsInTriangle takes integer count, real minDistance returns boolean
local integer created = 0
local integer attempts
local integer maxAttempts = count * 25 // failsafe
local real minDistSq = minDistance * minDistance // minimum dist between units
local real x
local real y
local integer i
local boolean valid
local Point p
// Store accepted spawn points
local real array spawnX
local real array spawnY
loop
exitwhen created >= count
set attempts = 0
set valid = false
loop
exitwhen attempts >= maxAttempts or valid
// Random point in bounding box
set x = GetRandomReal(LastPolygon.minX, LastPolygon.maxX)
set y = GetRandomReal(LastPolygon.minY, LastPolygon.maxY)
set p = Point.create(x, y)
if IsInPolygon(p, LastPolygon) then
set valid = true
set i = 0
loop
exitwhen i >= created
if DistSq(x, y, spawnX[i], spawnY[i]) < minDistSq then
set valid = false
exitwhen true
endif
set i = i + 1
endloop
endif
set attempts = attempts + 1
endloop
// Failsafe: impossible placement
if not valid then
call BJDebugMsg("CreateUnitsInPolygon failed: not enough space")
return false
endif
// Accept point
set spawnX[created] = x
set spawnY[created] = y
call CreateUnit(Player(0), 'hfoo', x, y, 0.0)
set created = created + 1
endloop
return true
endfunction
endlibrary
I attached a map with some example code for spawning units within a triangle shape. It also enforces a minimum distance between each of them so that you can separate the spawns. You can change it to a shape that matches your picture above and adjust the code to create "spawn points" or whatever instead of Footman.
vJASS:
call CreateUnit(Player(0), 'hfoo', x, y, 0.0) // Create anything you want at x/y
Could also have a few regions to get "close enough" shape (having like 3-5 regions can get pretty close), and select one at random.
Chance of selecting a region should be proportional to its size if you want an even distribution. But probably that isn't super important.
When creating a unit in an unpathable location, it will be "moved" to closest valid location, so regions are not required to be perfect either
I had ChatGPT whip up some code real quick, this allows you to create your own shapes with up to 100 points. (It could be more, just increase Array size)
This was my initial thought as well, but this approach would require a way to set up the polygon vertices interactively in the World Editor. Afaik the best way to do this would be with some sort of invisible model (units/destructables). Then, the question becomes, why not use these invisible cues to do the spawning instead of a polygon region?
This functionality can probably be useful in all other sorts of things though.
One thing about spawning units at random [x,y] coordinates is that units won't always be spaced out evenly across the region. Quite often you will encounter situations where most units are spawned cluttered around same part of a region and few spawned at other parts.
On the other hand, while using min distance as a prerequisite for picking unit's spawn point will resolve the above issue (Uncle's script is good example of that), it may lead to situation where unit won't respawn at all, because there just won't be any free place to respawn it.
Let's say you want min distance to be 100 range. By nature of randomness, you may select 3 locations which are spaced ~190 distance from each other. That satisfies the 100 min range requirement, but now you have 3 locations that have quite a lot of free space in between them, but that space is just not large enough to fit another spawn point there.
You may for example want to keep respawning 90 creeps around 100 locations in the rect you've shown in your original post, but because of the spacing + randomness factor, one game may fit in there only ~70 locations, while other ~95 locations, etc.
To counter that, you would need to decrease the min distance requirement, but then you get back to the original issue with cluttering
Honestly, I would just use units for this. Create a custom unit that will serve as a spawn point. Give it movement type Fly and 0 pathing size so that it does not collide with anything else.
A good model for such dummy could be the 'Start Location' model under 'Extra' category. That model at scale 1.0 has ~180.00 radius, so it could serve as a good visual cue for a potential spawn area.
If only we had natives for accessing destructible's scale, then I would propose using destructibles with variable min/max scale as spawn points, but alas, no such natives exist AFAIK.
Anyway, place the dummy units in your map in areas that you want to serve as spawn points. On map initialization pick all those dummy units, get their location and store them in a point array global variable. Finally, remove those dummy units.
When you want to spawn a unit, rather than spawn it exactly at picked location, you can spawn it within random distance (between 0 and 180.00) and random angle from the picked location. That way you would utilize the fully area that the "Start Location" model is taking.
What you do with those locations is then up to you - if you want to spawn only a single enemy or group of enemies at that location and then make that spawn location unavailable until the enemy/group of enemies die; or just make given spawn point unavailable for choosing for X seconds since it last spawned minions, then you could use the array swapping method, where you keep available location at the start of the array and also keep track of how many are available. Then pick randomly one of the available locations, spawn minion and then swap the location with last available location and decrease number of available locations by 1.
Something like this pseudo-code:
Code:
let spawnLocations = [loc1, loc2, ..., loc10]
let availableLocations = 10
// pick location randomly
let pickedIndex = rand(1, availableLocations)
let pickedLoc = spawnLocations[ pickedIndex ]
// swap picked location with last available location
set spawnLocations[pickedIndex] = spawnLocations[availableLocations]
set spawnLocations[availableLocations] = pickedLoc
// decrease available locations by 1
set availableLocations -= 1
of course, if you don't care for that, you can just completely randomly pick one location using let pickedLoc = spawnLocations[ rand(1, availableLocations) ] without ever decreasing availableLocations variable.
This was my initial thought as well, but this approach would require a way to set up the polygon vertices interactively in the World Editor. Afaik the best way to do this would be with some sort of invisible model (units/destructables). Then, the question becomes, why not use these invisible cues to do the spawning instead of a polygon region?
This functionality can probably be useful in all other sorts of things though.
The system uses x/y coordinates, so you don't need any kind of object to represent them. Move your mouse to a vertice, then look at the bottom left corner of the World Editor and you'll see "Point: X, Y, Z". Write each of those X/Y values down in Notepad.
It looks like your shape has about 10 vertices, so it shouldn't take more than a few minutes.
The benefits:
1. It's bloat free, everything is contained within a single script.
2. The Polygons, which are basically custom shaped Regions, can be stored and used throughout the entire game.
3. You can run functions designed to interact with a Polygon, to ease the overall process of your goal: In this case procedural generation.
I have an example of procedurally generating Footman from within a triangle shaped area.
Of course you can do whatever you'd like, I just figured this was a clean and expandable approach.
The system uses x/y coordinates, so you don't need any kind of object to represent them. Move your mouse to a vertice, then look at the bottom left corner of the World Editor and you'll see "Point: X, Y, Z". Write each of those X/Y values down in Notepad.
It looks like your shape has about 10 vertices, so it shouldn't take more than a few minutes.
The benefits:
1. It's bloat free, everything is contained within a single script.
2. The Polygons, which are basically custom shaped Regions, can be stored and used throughout the entire game.
3. You can run functions designed to interact with a Polygon, to ease the overall process of your goal: In this case procedural generation.
I have an example of procedurally generating Footman from within a triangle shaped area.
Of course you can do whatever you'd like, I just figured this was a clean and expandable approach.
of course, if you don't care for that, you can just completely randomly pick one location using let pickedLoc = spawnLocations[ rand(1, availableLocations) ] without ever decreasing availableLocations variable.
The system uses x/y coordinates, so you don't need any kind of object to represent them. Move your mouse to a vertice, then look at the bottom left corner of the World Editor and you'll see "Point: X, Y, Z". Write each of those X/Y values down in Notepad.
It looks like your shape has about 10 vertices, so it shouldn't take more than a few minutes.
Don't get me wrong, I can think of many ways I would use polygon shaped regions, it is just not practical having to manually set up the vertices and if it changes later, setting them up again. This would be a great addition to the editor as a function.
For my purposes I have currently created 2 destructables without a model and placed them around the forest area. I use a loop to gather them in an array at initialisation and then loop though all of them to spawn either 1-2 mobs in the 1st type and 2-3 in the 2nd type (like a creep camp). I use random distance/angle from their location to fake a scattering.
This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
By continuing to use this site, you are consenting to our use of cookies.