• 🏆 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!

Efficient patroller system

Status
Not open for further replies.

EdgeOfChaos

E

EdgeOfChaos

Patrollers are the easiest obstacles to make in a maze game - or are they?

It's very easy to custom code every single region and every patroller but that is super boring and time consuming. It's also easy to have them use the Patrol command, but that command is bugged (imprecise and stops after some time). It would be much better to have a system that handles all that region manipulation for you.

The issue is that there is no simple way to detect which region was entered by the entering event. You end up having to check every single region to see if the unit is nearby it to determine which region was entered.

Here's a quick hack I threw together to make easy, efficient patrollers, with up to 10 rally points, who don't use the Patrol command. The time calculation would be dangerous for maps where enemy movement speeds change, but it would work for 99% of mazes (and mine, which is what counts). Uses TimerUtils and made with efficiency/ease of use in mind.

JASS:
struct Patroller
 
    private rect array nodes[10]
    private integer index = 0
    private integer currentNode = 0
    private unit patroller
    private integer patrollerType

    public static method create takes rect startNode, integer patrollerType returns thistype
        local thistype new = thistype.allocate()
        set new.patrollerType = patrollerType
        call new.addNode(startNode)
        return new
    endmethod
  
    public method addNode takes rect node returns nothing
        if(index < 10)then
            set nodes[index] = node
            set index = index + 1
        endif
    endmethod
  
    private static method onReachNode takes nothing returns nothing
        local timer t = GetExpiredTimer()
        local thistype data = GetTimerData(t)
        call ReleaseTimer(t)
        call data.processNextNodeMovement()
    endmethod
  
    private method processNextNodeMovement takes nothing returns nothing
        local real distance
        local real time
        local real x0 = GetRectCenterX(nodes[currentNode])
        local real y0 = GetRectCenterY(nodes[currentNode])
        local real x1
        local real y1
        // find next node
        set currentNode = currentNode + 1
        if(currentNode >= index)then
            set currentNode = 0
        endif
        // calculate distance between this and next node
        set x1 = GetRectCenterX(nodes[currentNode])
        set y1 = GetRectCenterY(nodes[currentNode])
        set distance = DistanceBetweenCoords(x0,y0,x1,y1)
        // calculate time to reach point
        set time = distance / GetUnitMoveSpeed(patroller)
        // issue the move order and start timer to
        // determine when it is reached
        call IssuePointOrder(patroller,"move",x1,y1)
        call TimerStart(NewTimerEx(this),time,false,function thistype.onReachNode)
    endmethod
  
    public method start takes nothing returns nothing
        // begin at starting node
        set patroller = CreateUnit(ENEMY_PLAYER,patrollerType,GetRectCenterX(nodes[0]),GetRectCenterY(nodes[0]),0)
        call processNextNodeMovement()
    endmethod
  
endstruct

Example consumer code to make 5 firelord enemies

JASS:
scope FirelordPatrol initializer onInit
 
    private function makeTwoNodeFirelord takes rect r1, rect r2 returns nothing
        local Patroller p = Patroller.create(r1,FIRELORD)
        call p.addNode(r2)
        call p.start()
    endfunction

    private function onInit takes nothing returns nothing
        call makeTwoNodeFirelord(gg_rct_FNode1,gg_rct_FNode2)
        call makeTwoNodeFirelord(gg_rct_FNode3,gg_rct_FNode4)
        call makeTwoNodeFirelord(gg_rct_FNode5,gg_rct_FNode6)
        call makeTwoNodeFirelord(gg_rct_FNode7,gg_rct_FNode8)
        call makeTwoNodeFirelord(gg_rct_FNode9,gg_rct_FNode10)
    endfunction
endscope
 
Last edited by a moderator:
way to detect which region was entered
There's GetTriggeredRegion(), that returs the corresponding region (not rect) onEnter event.

I also wrote something and used a periodic check approach, checkig distace to xy: Patrol System v1.6

Not sure, but I personaly feel less save with a pure expired-time approach, hm. In my approach I tried to compensate the "random stop" with ignoring unit's collision at distance calculation, and having some tolerance range a unit may have when reaching a checkpoint. The required data structure and periodic tracking was no performance problem for me, practicaly, but it surely differs from your approach.
 

EdgeOfChaos

E

EdgeOfChaos

Right - I meant rect.

This will be unsafe under certain conditions:
1) Patroller movement speed can change
2) Obstacles will get in the enemy's way
3) The enemies are un-locusteda and can be stunned/etc.
If none of these conditions are possible, it will be safe. Even if unsafe and the unit's path gets messed up once, it should then return to normal when the cycle resets.

Wherever possible, I need to avoid timers running at 0.0325 interval.

I already have two of them going and I don't even like that many and I know I'm going to need at least one more. I'd love to make a terrain-type-event system like death on terrain that does not require a timer and is precise as the timer trigger (like, not extensive manual regions).
One thing I've learned from making my other maps is that 0.0325 timers kill slower computers. Even if you only have a couple and they do some fairly complex things. In an RPG, fps drop by 5-10 isn't a critical issue, but in a maze it definitely is.

That's why I'm writing everything to be as simple as possible in my timers. For example, the very first thing my terrain-type timer does is check the terrain type and exit if it's safe/etc, so most of the time this should be a very non-costly. I also have a timer that moves a floating text over the escapers' head with their HP values (since HP is important and should be visible). I'd love to get rid of that too.

If you'd have any workaround ideas for those, that would be great
 
Last edited by a moderator:
(On mobile atm)

Timer existing itself isnt too costly i believe. So having 2 or 20 timers doesnt really matter imo, but the code matters that is executed periodicly, or very often.

Object creation, Group enums and things alike are costly and may have Bad impact when executed too often. A Few calculations with "normal" getter functions, Like terrain Type, or x/y of unit wont harm much your fps in normal cases.

How Ever, there are systems that prodive peripdic events and internaly use only 1 timer. I think agd currently Has one in the jass submissions.

Viewing many effects and units together on screen isnt very good , too, for your fps, Maybe the map can save things there, too, hm.
 
Status
Not open for further replies.
Top