• 🏆 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!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

[JASS] Advanced air unit movement

Status
Not open for further replies.

Ardenian

A

Ardenian

I would like to receive some help and advices for creating a decent air movement, please.

I aim to create a system that makes units move automatically, as a plane or spaceship does, for example.

I am aware there are systems for this in the spell section, I do not understand them though,
as I suck at interpreting codes from others.

For now I have this simple function:
JASS:
globals
    real udg_UpdateReal = 8.00
    unit udg_TestUnit
endglobals
 
function UpdatePosition takes nothing returns nothing  
    local real x = CosBJ(GetUnitFacing(udg_TestUnit))*udg_UpdateReal
    local real y = SinBJ(GetUnitFacing(udg_TestUnit))*udg_UpdateReal
    
    call SetUnitX(udg_TestUnit, GetUnitX(udg_TestUnit)+x)
    call SetUnitY(udg_TestUnit, GetUnitY(udg_TestUnit)+y)
endfunction

It works, more or less.
However, issueing orders to a close point bugs this update function, making the unit rotate around itself, for example.
Is there a better simple way to update a unit's position based on facing ?

I would be glad to receive some advices to the following:
How can I consider an issued order ?
The unit, for example, is ordered to move to a certain point not being part of the upgrade coordinates.
Currently, this does work, but the unit immediatly adjusts its facing.
My idea was to manually adjust it over a certain time t.
However, how would I write a function to make the facing be updated over the whole distance appropriately ?
Do I calculate the angle based on unit facing and ordered point and then calulcate how it long it takes the unit to get there, using that information to update the facing each update ?
Or is there a better way ?
 
Level 14
Joined
Jul 1, 2008
Messages
1,314
I tested your function in an empty map to get a clearer picture, of how this behaves.

Seeing this, I thought, whether you even need to take into account the units facing, if you want the unit to be ordered by the player anyway?

Maybe just store the x/y of the issued order, if it is smart (= right click), calculate the angle between this point and the current unit location - and then move the unit in this direction. It will automatically turn into that direction, so you could adjust its turning speed in the object editor.. Sry cannot test this suggestion at the moment.

Edit: Another way would be the use of a dummy interface. The interface is selected and the player can right-click on the map, the camera is centered on the flying unit, which is then moved to towards the ordered locations ...
 

Ardenian

A

Ardenian

Ah, I should have attached the test map, excuse!
I attached it.

Excuse if that point was not clear,
I would like to simulate an automatically moving.
The player can order the unit to move somewhere, but it is not going to move there straight away.
I would like to add factors like a custom turn rate ( not from the object data).

As you suggested, I could store the ordered point, the angle between this point and then calculate how I need to adjust the facing and movement based on my custom turn rate which is based on a variable based on unit type.

This might actually already work, I am going to do some testing right now.
Thank you!

By the way, the best way to get the angle is AngleBetweenPoints() ?
Or is this function deppreciated due to another better description ?
 
Last edited by a moderator:
Level 22
Joined
Feb 6, 2014
Messages
2,466
By the way, the best way to get the angle is AngleBetweenPoints() ?
Or is this function deppreciated due to another better description ?
No, use Atan2 instead. If I understand correctly, you want the unit to move based on it's facing angle? If that's the case, your code in the 1st part will do fine, but use Cos and Sin instead of CosBJ and SinBJ and you need to convert the facing angle to radian. Then the only thing left to do is apply the movements periodically after initially ordering the "spaceship" to face the destination (Facing will not be instant therefore the movement will be curved).
 

Ardenian

A

Ardenian

Yes, exactly.
Could you please explain what Atan2 does ?
First time I hear of that.


I tried using SetUnitFacingTimed to simulate the curved movement, but there seems to be a mistake.
If I choose an equal or longer time than 0.1 seconds as duration, it bugs and the ship moves to the angle of the opposite sign.

Example:
Unit is at 0° degree, ordered to -20° ( this value comes from AngleBetweenPoints)
-> Rotates over -20° up to 20°

Triggers here and new test map.


JASS:
globals
    real udg_UpdateReal
    unit udg_TestUnit
    real udg_TestMaxAngle
    
    trigger gg_trg_Loop
    trigger gg_trg_Order
    unit gg_unit_u000_0001 // test unit in test map
endglobals
 
function Trig_Loop_Actions takes nothing returns nothing    
    local location source = Location(GetUnitX(udg_TestUnit),GetUnitY(udg_TestUnit))
    local location update = PolarProjectionBJ(source,udg_UpdateReal,GetUnitFacing(udg_TestUnit)) 
    call SetUnitX(udg_TestUnit, GetLocationX(update))
    call SetUnitY(udg_TestUnit,GetLocationY(update))
    call RemoveLocation(source)
    call RemoveLocation(update)
    set source = null
    set update = null
endfunction

//===========================================================================
function InitTrig_Loop takes nothing returns nothing
    set gg_trg_Loop = CreateTrigger(  )
    call TriggerRegisterTimerEventPeriodic( gg_trg_Loop, 0.03125 )
    call TriggerAddAction( gg_trg_Loop, function Trig_Loop_Actions )
endfunction


function Trig_Order_Actions takes nothing returns nothing
    local unit u = GetOrderedUnit()
    local real facing = GetUnitFacing(u)
    local location source = Location(GetUnitX(u), GetUnitY(u))
    local location aim = GetOrderPointLoc()
    local real angle = AngleBetweenPoints(source, aim)
    call UnitRemoveAbility(u, 'Amov')
    call SetUnitFacingTimed(u, facing+angle, 0.1)
    call UnitAddAbility(u, 'Amov')
    set u = null       
endfunction

//===========================================================================
function InitTrig_Order takes nothing returns nothing
    set gg_trg_Order = CreateTrigger(  )
    call TriggerRegisterUnitEvent( gg_trg_Order, gg_unit_u000_0001, EVENT_UNIT_ISSUED_POINT_ORDER )
    call TriggerAddAction( gg_trg_Order, function Trig_Order_Actions )
endfunction


Loop is the periodically trigger to set movement based on unit's facing.
Order triggers the facing change.

There is also another problem: http://www.hiveworkshop.com/forums/.../re-add-amov-ability-stop-point-order-279199/
 
Last edited by a moderator:
Level 24
Joined
Aug 1, 2013
Messages
4,657
Atan2 is the inverse tangent with a divide by 0 exception catch.

In my missile system, I created this feature:
JASS:
    struct TargetingUnit extends TargetingType
        
        private unit target
        private real turnRate
        
        public static method create takes unit target, real turnRate returns thistype
            local thistype this = .allocate()
            set .target = target
            set .turnRate = turnRate
            return this
        endmethod
        
        public method destroy takes nothing returns nothing
            set .target = null
            call .deallocate()
        endmethod
        
        public method setTurnRate takes real turnRate returns nothing
            set .turnRate = turnRate * Missile.INTERVAL
        endmethod
        
        public method getTurnRate takes nothing returns real
            return .turnRate / Missile.INTERVAL
        endmethod
        
        public method applyTargeting takes Missile m, real x, real y returns nothing
            local real targetX = GetUnitX(.target)
            local real targetY = GetUnitY(.target)
            local real angle = DenormalizeAngleRad(AngleBetweenCoordinatesRad(x, y, targetX, targetY) - m.angle)
            
            if angle > .turnRate then
                set angle = .turnRate
            elseif angle < -.turnRate then
                set angle = -.turnRate
            endif
            call m.setAngle(m.angle + angle)
            
            if m.isFlying then
                set angle = DenormalizeAngleRad(AngleBetweenCoordinatesRad(0, GetUnitFlyHeight(m.missile)+GetCoordinateZ(x, y), DistanceBetweenCoordinates(x, y, targetX, targetY), GetUnitFlyHeight(.target)+GetCoordinateZ(targetX, targetY)) - m.pitch)
                
                if angle > .turnRate then
                    set angle = .turnRate
                elseif angle < -.turnRate then
                    set angle = -.turnRate
                endif
                call m.setPitch(m.pitch + angle)
            endif
            
        endmethod
        
    endstruct

The missile is M, it's position is x and y (which are already loaded in the function that calls this and this allows me to not have those two functions again).
The turnRate is the rate in which it turns in radians.
The target is the targeted unit.
The applyTargeting is called every interval.
All those fancy functions are not really interesting.

First, I calculate the angle of the rotation that the missile wants to make.
So if the missile is pointing at 50 degrees and the angle between the missile and its target is 90 degrees (from x axis), the missile wants to rotate +40 degrees.
(Calculations in the code are all done in radians though.)

Then I check if that is bigger than he turnRate or smaller than the negative turnRate and limit it to that value.
Then I know how much the missile would rotate in that one update and apply that to its current rotation.

Izi.

(I also don't really care if the target point is too close to actually reach it though... so the missile would be rotating around that point.)
 
Level 37
Joined
Jul 22, 2015
Messages
3,485
Could you please explain what Atan2 does ?
First time I hear of that.

Atan stands for arctangent, which is also called inverse tangent. It is a trigonometric function that will give you the angle of a triangle by dividing the opposite leg over the adjacent leg. In terms of WC3, that is the Y distance over the X distance.

Whenever you call AngleBetweenPoints(), it basically draws a triangle between two points like so:
attachment.php


To find x and y distance, you need two points, with a total of two x values and two y values. Since locations are just two points, two locations will give you the points you need to find the distances:
  • x distance = x2 - x1
  • y distance = y2 - y1
attachment.php


Since we are trying to look for angle θ, we just divide y distance by x distance to get the angle, which is given in radians. The AngleBetweenPoints() BJ converts it into degrees for you as you can see here:
JASS:
function AngleBetweenPoints takes location locA, location locB returns real
    return bj_RADTODEG * Atan2(GetLocationY(locB) - GetLocationY(locA), GetLocationX(locB) - GetLocationX(locA))
endfunction
Since you are coding in JASS/vJASS, you can just use X and Y values directly, as Flux suggested. Doing so will get rid of the need to use locations.


Recap of what the function does step by step:
  1. Take two points: locA(x1, y1) & locB(x2, y2)
  2. Find Y distance -> dy = y2 - y1
  3. Find X distance -> dx = x2 - x1
  4. Find angle -> θ = dy / dx
  5. Convert angle into degrees -> θ * bj_RADTODEG

If you want to expand your knowledge even more, bj_RADTODEG works by taking a number (in radians) and multiplying it to (180/pi). bj_DEGTORAD works the other way by taking a number (in degrees) and multiplies it to (pi/180). There are a lot more things to inverse trig functions like their restricted domains, and why their restricted domains are what they are, but I don't think that needed to be explained here.
 

Attachments

  • 1.png
    1.png
    256.4 KB · Views: 224
  • 2.png
    2.png
    258.6 KB · Views: 208

Ardenian

A

Ardenian

Thanks you, Wietlol, this gives me an idea on how to update the unit's facing.

Thank you, Flux, good example for the simpliest solution.
However, what if I would like to add a delay to the turning ?
The minimum turn rate is 0.1, it cannot be 0, it is hardcoded ( or it bugs, cannot remember).
That's why all the stuff with the order event and SetUnitFacingTimed came up.


Thank you for the detailled explanation, KILLCIDE!
After your recap, this is how it will look, isn't it ?
JASS:
function GetAngle takes location locA, locB returns real
    local real dx = GetLocationX(locB)-GetLocationX(locA)
    local real dy = GetLocationX(locB)-GetLocationX(locA)
    return bj_RADTODEG*Atan2(dy/dx)
endfunction

//to use x and y directly:

function GetAngleXY takes nothing returns real
    local real dx = GetOrderPointX() - GetUnitX(u)
    local real dy = GetOrderPointY() - GetUnitY(u)
    return bj_RADTODEG*Atan2(dy/dx)
endfunction

I save the ordered point in a variable with relation to the unit.

Now I would check, as Wietlol explained, if this angle is greater than the custom unit's turn rate ( custom, too, variable-saved).
If it is, adjust angle with turn rate, if the angle is smaller, set facing to current facing + angle, while considering signs, as in Wietlol's system.

Thank you guys, I am going to test it out!
 
Level 37
Joined
Jul 22, 2015
Messages
3,485
Thank you for the detailled explanation, KILLCIDE!
After your recap, this is how it will look, isn't it ?
JASS:
function GetAngle takes location locA, locB returns real
    local real dx = GetLocationX(locB)-GetLocationX(locA)
    local real dy = GetLocationX(locB)-GetLocationX(locA)
    return bj_RADTODEG*Atan2(dy/dx)
endfunction

//to use x and y directly:

function GetAngleXY takes nothing returns real
    local real dx = GetOrderPointX() - GetUnitX(u)
    local real dy = GetOrderPointY() - GetUnitY(u)
    return bj_RADTODEG*Atan2(dy/dx)
endfunction

The Atan2 parameters are wrong. They should be Atan2(dy, dx), not Atan2(dy/dx). The division is done behind the scenes.

Also there's no need to use locations if you're going to code in JASS. The only reason you'd want to use locations in JASS is if you want to use GetLocationZ() or if you want your system to be GUI friendly. Another minor issue is that you don't have to use locals for your example. Locals do add readability, but there's really no need to store it somewhere if you're only going to use it once.

Here's my suggestion:
JASS:
function GetAngle takes location locA, locB returns real
    return Atan2(GetLocationY(locB) - GetLocationY(locA), GetLocationX(locB) - GetLocationX(locA))*bj_RADTODEG
endfunction

//to use x and y directly:

function GetAngleXY takes real x1, real y1, real x2, real y2 returns real
    return Atan2(y2-y1, x2-x1)*bj_RADTODEG
endfunction

Of course, it would be completely pointless to create a whole seperate function for this :p I'm just assuming you did it for the sake of explanation.
 
Level 22
Joined
Feb 6, 2014
Messages
2,466
Thank you, Flux, good example for the simpliest solution.
However, what if I would like to add a delay to the turning ?
The minimum turn rate is 0.1, it cannot be 0, it is hardcoded ( or it bugs, cannot remember).
That's why all the stuff with the order event and SetUnitFacingTimed came up.
If that's the case use the code I gave but instead of removing 'Amov', Pause and Unpause the unit. I basically optimized your code already writing it on "how i would write it" so I don't know why you're still confused how to write the code.
 

Ardenian

A

Ardenian

Yup, KILLCIDE, noticed that after some time, too, thank you!
I usually keep locals until everything works as intended, then merge stuff up.

Thanks Flux! I wasn't aware turn rate does not matter then.

Hm, guys, is there a way to get the point of a unit's point order outside the event ?
Something like set x = GetOrderPointX(GetUnitPointOrder(u))
Is storing it the only way to keep track of it ?

I would also like to hear opinions to the following:
Since the current code does not allow many customizations, like acceleration and decceleration,
I thought about using timers to dynamically change the unit's movement speed.
Sounds good in the first moment, however, there are going to be many many timers then, if many units are alive.
Is this a good solution ?
I know there exists something like TimerUtils, haven't taken a look, maybe I should use that.

EDIT:
Here is the new code.
Still does not work, but I cannot think of what could be wrong.

EDITEDIT:
New code.

Does work now. Apearently the problem was related to having a very high turn rate.

However, the current code takes the angle of unit position and order point as reference
for adjusting the unit's facing and therefore movement.
It does not actually order the unit to move to the point.
Dunno whether I like that or not.

EDITEDITEDIT: Can someone please tell me why udg_Angle is faster reduced than the facing is updated and why the facing is always changing although udg_Angle is 0 at a certain point, not allowing changes to run ?


JASS:
globals
    real udg_UpdateReal
    unit udg_TestUnit
    real udg_OrderPointX
    real udg_OrderPointY
    boolean udg_IsOrdered
    real udg_TurnRate
    real udg_Angle
    
    trigger gg_trg_Loop
    trigger gg_trg_Order
    unit gg_unit_u000_0001 // test unit in test map
endglobals
 
function Trig_Loop_Actions takes nothing returns nothing    
    local real unitx = GetUnitX(udg_TestUnit)
    local real unity = GetUnitY(udg_TestUnit)
    local real facing = GetUnitFacing(udg_TestUnit)
    call BJDebugMsg("Old facing: "+R2S(facing))
    if udg_IsOrdered == true then
        if udg_Angle > 0 and udg_Angle >= udg_TurnRate then
            set facing = facing + udg_TurnRate
            set udg_Angle = udg_Angle - udg_TurnRate
        elseif udg_Angle < 0 and udg_Angle <= -udg_TurnRate then
            set facing = facing - udg_TurnRate
            set udg_Angle = udg_Angle + udg_TurnRate
        else
            set udg_IsOrdered = false
            set udg_Angle = 0
        endif
    endif 
    call BJDebugMsg("Current angle: "+R2S(udg_Angle))
    call SetUnitFacing(udg_TestUnit, facing)
    call BJDebugMsg("New facing: "+R2S(facing))
    call SetUnitX(udg_TestUnit, unitx + udg_UpdateReal*Cos(facing*bj_DEGTORAD))
    call SetUnitY(udg_TestUnit, unity + udg_UpdateReal*Sin(facing*bj_DEGTORAD))    
endfunction

//===========================================================================
function InitTrig_Loop takes nothing returns nothing
    set gg_trg_Loop = CreateTrigger(  )
    call TriggerRegisterTimerEventPeriodic( gg_trg_Loop, 0.03125 )
    call TriggerAddAction( gg_trg_Loop, function Trig_Loop_Actions )
endfunction


function Trig_Order_Actions takes nothing returns nothing
    local unit u = GetOrderedUnit()
    local real unitx = GetUnitX(u)
    local real unity = GetUnitY(u)
    local real a = 0
    local real b = 0
    call PauseUnit(u, true)
    call IssueImmediateOrder(u,"stop")
    call PauseUnit(u, false)
    set udg_IsOrdered = true
    set udg_OrderPointX=GetOrderPointX()
    set udg_OrderPointY=GetOrderPointY()
    set udg_Angle = bj_RADTODEG * Atan2(udg_OrderPointY - unity,udg_OrderPointX - unitx) 
    if udg_Angle < 0 then
        set udg_Angle = udg_Angle + 360
    endif
    set a = udg_Angle-GetUnitFacing(u)
    set b = 360-udg_Angle
    if a <= b then 
        set udg_Angle = a
    else
        set udg_Angle = -b
    endif
    set u = null
endfunction

//===========================================================================
function InitTrig_Order takes nothing returns nothing
    set gg_trg_Order = CreateTrigger(  )
    call TriggerRegisterUnitEvent( gg_trg_Order, gg_unit_u000_0001, EVENT_UNIT_ISSUED_POINT_ORDER )
    call TriggerAddAction( gg_trg_Order, function Trig_Order_Actions )
endfunction
 
Last edited by a moderator:
People, let's not forget that D4RK_G4ND4LF made a tutorial about movements :

http://www.hiveworkshop.com/forums/miscellaneous-tutorials-456/about-movement-172309/

JASS:
call PauseUnit(u, true)
    call IssueImmediateOrder(u,"stop")
    call PauseUnit(u, false)
> this doesn't work. Paused units can't receive orders, they forget the orders

If you want immediate stop (faster than stop, afaik it was stated by Troll-Brain in some thread?) : IssueImmediateOrderById(myUnit, 851973)
 
Level 37
Joined
Jul 22, 2015
Messages
3,485
People, let's not forget that D4RK_G4ND4LF made a tutorial about movements
What we talked about is hardly covered in that tutorial.

they forget the orders

Is this confirmed? The GUI function for Pause/Unpase unit explicitly states "...but remembrs its orders and continues them upon being unpaused." Unless that means it only remembers orders before being paused?
 

Ardenian

A

Ardenian

I was told so there, Almia: http://www.hiveworkshop.com/forums/.../re-add-amov-ability-stop-point-order-279199/

Since this script does matter a lot for me, I would like to write down the theory behind it here:

The trigger Loop updates the unit's position, makes it 'move' using SetUnitX and SetUnitY dependent on the unit's facing.
JASS:
call SetUnitX(udg_TestUnit, unitx + udg_UpdateReal*Cos(facing*bj_DEGTORAD))
call SetUnitY(udg_TestUnit, unity + udg_UpdateReal*Sin(facing*bj_DEGTORAD))

Now turning the unit comes into the game.
To do so, I have the trigger Order.
This trigger fires when the unit is ordered an order targetting a point
( in the test map, use the Flare item to do so, since I disabled unit movement for now).
Order calculates the angle between the unit's position and the orderpoint.
Additionally, I convert that angle to an actual facing:
JASS:
set udg_OrderPointX=GetOrderPointX()
set udg_OrderPointY=GetOrderPointY()
set udg_Angle = bj_RADTODEG * Atan2(udg_OrderPointY - unity,udg_OrderPointX - unitx) 
if udg_Angle < 0 then
    set udg_Angle = udg_Angle + 360
endif

Now I check what direction to turn is shorter:
JASS:
if facing >= udg_Angle then
    set a = facing - udg_Angle
else 
    set a = udg_Angle - facing
endif
set b = 360-a

a means turning against the clock, b means turning with the clock.

JASS:
if a <= b then 
    set udg_Angle = a
else
    set udg_Angle = -b
endif

The global variable udg_Angle is a test variable. It is set to the angle the unit has to rotate to face the location.
udg_IsOrdered allows the Loop trigger to change the unit's facing.

Now we get back to the Loop trigger.
If udg_Angle is != 0, then we run lines to slightly adjust the unit's facing based on the variable udg_TurnRate

JASS:
if udg_IsOrdered == true then
    if udg_Angle > 0 and udg_Angle >= udg_TurnRate then
        set facing = facing + udg_TurnRate
        set udg_Angle = udg_Angle - udg_TurnRate
    elseif udg_Angle < 0 and udg_Angle <= -udg_TurnRate then
        set facing = facing - udg_TurnRate
        set udg_Angle = udg_Angle + udg_TurnRate
    else
        set udg_IsOrdered = false
        set udg_Angle = 0
    endif
endif
If udg_Angle is smaller than the turn rate or negative udg_Angle is higher than negative turn rate, then we disable the facing adjustion.

Now we change the unit's facing to consider it when updating the position.

JASS:
call SetUnitFacing(udg_TestUnit, facing)
call SetUnitX(udg_TestUnit, unitx + udg_UpdateReal*Cos(facing*bj_DEGTORAD))
call SetUnitY(udg_TestUnit, unity + udg_UpdateReal*Sin(facing*bj_DEGTORAD))


JASS:
globals
    real udg_UpdateReal
    unit udg_TestUnit
    real udg_OrderPointX
    real udg_OrderPointY
    boolean udg_IsOrdered
    real udg_TurnRate
    real udg_Angle
    
    trigger gg_trg_Loop
    trigger gg_trg_Order
    unit gg_unit_u000_0001 // test unit in test map
endglobals
 
function Trig_Loop_Actions takes nothing returns nothing    
    local real unitx = GetUnitX(udg_TestUnit)
    local real unity = GetUnitY(udg_TestUnit)
    local real facing = GetUnitFacing(udg_TestUnit)
    call BJDebugMsg("Old facing: "+R2S(facing))
    if udg_IsOrdered == true then
        if udg_Angle > 0 and udg_Angle >= udg_TurnRate then
            set facing = facing + udg_TurnRate
            set udg_Angle = udg_Angle - udg_TurnRate
        elseif udg_Angle < 0 and udg_Angle <= -udg_TurnRate then
            set facing = facing - udg_TurnRate
            set udg_Angle = udg_Angle + udg_TurnRate
        else
            set udg_IsOrdered = false
            set udg_Angle = 0
        endif
    endif 
    call BJDebugMsg("Current angle: "+R2S(udg_Angle))
    call SetUnitFacing(udg_TestUnit, facing)
    call BJDebugMsg("New facing: "+R2S(facing))
    call SetUnitX(udg_TestUnit, unitx + udg_UpdateReal*Cos(facing*bj_DEGTORAD))
    call SetUnitY(udg_TestUnit, unity + udg_UpdateReal*Sin(facing*bj_DEGTORAD))    
endfunction

//===========================================================================
function InitTrig_Loop takes nothing returns nothing
    set gg_trg_Loop = CreateTrigger(  )
    call TriggerRegisterTimerEventPeriodic( gg_trg_Loop, 0.03125 )
    call TriggerAddAction( gg_trg_Loop, function Trig_Loop_Actions )
endfunction


function Trig_Order_Actions takes nothing returns nothing
    local unit u = GetOrderedUnit()
    local real unitx = GetUnitX(u)
    local real unity = GetUnitY(u)
    local real a = 0
    local real b = 0
    local real facing = GetUnitFacing(u)
    call PauseUnit(u, true)
    call IssueImmediateOrder(u,"stop")
    call PauseUnit(u, false)
    set udg_IsOrdered = true
    set udg_OrderPointX=GetOrderPointX()
    set udg_OrderPointY=GetOrderPointY()
    set udg_Angle = bj_RADTODEG * Atan2(udg_OrderPointY - unity,udg_OrderPointX - unitx) 
    if udg_Angle < 0 then
        set udg_Angle = udg_Angle + 360
    endif
    
    if facing >= udg_Angle then
        set a = facing - udg_Angle
    else 
        set a = udg_Angle - facing
    endif
    set b = 360-a
    if a <= b then 
        set udg_Angle = a
    else
        set udg_Angle = -b
    endif
    set u = null
endfunction

//===========================================================================
function InitTrig_Order takes nothing returns nothing
    set gg_trg_Order = CreateTrigger(  )
    call TriggerRegisterUnitEvent( gg_trg_Order, gg_unit_u000_0001, EVENT_UNIT_ISSUED_POINT_ORDER )
    call TriggerAddAction( gg_trg_Order, function Trig_Order_Actions )
endfunction


Here is the test map, too.

Current issue:

When I order the unit to a point ( cast a point ability),
the unit's facing starts to get adjusted. However, as the debug messages show,
the udg_Angle variable is reduced by far faster than the facing local and therefore the unit's facing.
The local facing is adjusted properly.
I have absolutly no idea how this is possible, since:
JASS:
set facing = facing + udg_TurnRate
set udg_Angle = udg_Angle - udg_TurnRate

//...

set facing = facing - udg_TurnRate
set udg_Angle = udg_Angle + udg_TurnRate
Both should be simultaneously updated.
 
Last edited by a moderator:
Level 14
Joined
Jul 1, 2008
Messages
1,314
The problem seems to be to me, that the unit cannot turn that fast!

Unfortunately, neither using executeFunc() nor setting the unit's turn rate to 1. nor setting the interval from 0.03125 to 0.6 solved this hypothesis.

I think the calculation of the angles is correct, at least I tried with debug messages in the order trigger, and it seemed to display correct values.

So I agree with you, that the problem is the unit turning. what you said by "the udg_Angle variable is reduced by far faster than the facing" right?

JASS:
the unit turns something like this:

starting angle 360
it is ordered to 355
it turns to 359
it is ordered to 350
it turns to 357
...

So I think, you have to include some condition into the actual angle decreasing/increasing part of the loop, which checks, if the unit already reached the wanted facing until going one step further.

Something like:
JASS:
starting angle is x
set temp = new wanted facing
if facing == new wanted facing then
      it is ordered to x -5
endif

I have no time to try it now, sry, but I wanted to share my idea and hope it solves the problem!
 

Ardenian

A

Ardenian

I checked your debug, you are right, facing is not applied immediatly
and therefore wrong, thank you!

I made some quick research on that, I saw a thread where people said
call SetUnitFacingTimed(u, angle,0.00) would adjust the facing immediatly.
I cannot confirm this though, does not work for me.

I had a look on my debug messages, the maximum turn rate/facing adjustion when using 0.3125 seems to be around ~1.3° per run.
attachment.php

However, when I set my maximum turn rate to 1.3°, it still does not work.
Shouldn't it now, since udg_Angle is reduced by the maximum possible turning ?

About your suggestion, I am not sure whether it is the same in green.
If I choose the maximum possible turn rate per periodical interval, it shouldn't matter, should it ?

This really sucks, since it overthrows some plans I had.
I am going to ask Wietlol how he managed to do the turning.
From what I can see and understand he did it the same, didn't he ?
 
Last edited by a moderator:

Ardenian

A

Ardenian

If they cannot turn immediatly, what's their maximum turn rate/time ?

Wietlol, from the example trigger you posted here, you do it the same as I do, don't you ?
You check if the angle to rotate is higher than a custom turn rate value and if so, add the turn rate to the unit's facing.
How did you manage not to face/ avoid my current problem ?

I am not sure whether my angle calculation is correct.
I haven't thought about it in detail yet, but there might be +/- sign issues,
though it seems to be correct when testing the direction.
 

Ardenian

A

Ardenian

My tests give me ~0.12 turn rate/0.3125 seconds when using turn rate 1.00

However, when I reduce the turn rate to 0.12, it only gives me 0.05 turn rate/ 0.3125 seconds.

I feel a bit like grabbing in the dark,
so how do people like Bribe and Nestharus solved it ?
Did they re-create the unit ? I could imagine that's quit heavy with ~192 unit creations per second.
 
Level 24
Joined
Aug 1, 2013
Messages
4,657
Wietlol, from the example trigger you posted here, you do it the same as I do, don't you ?
You check if the angle to rotate is higher than a custom turn rate value and if so, add the turn rate to the unit's facing.
How did you manage not to face/ avoid my current problem ?

The calculations shouldnt be a problem... but how fast are you trying to rotate the unit?
I dont add the turn rate to the unit's facing, I add the turn rate to the unit's angle... my own variable.
For example in a knockback, you dont want the unit to face where he is going, because he was knocked back, not knocked front after rotating.

The calculations are separate from the actual facing of the unit, the facing of the unit on the other hand can rotate quite fast if it is rotating gradually. You shouldnt have a problem with it except if you have ridiculous values.
 

Ardenian

A

Ardenian

Hm, I don't understand most code parts immediatly, but I think they use SetUnitFacing, too.

Wietlol, why is adding the turn rate to a unit's angle different than adding it to the facing ?
Visually, you still want to adjust the facing, don't you ?
I wanted to trigger the turn rate/facing, since the minimum turn rate of the units is quite high,
not allowing badass starship cruisers to slowly turn with majesty.

I tried as well very low values, like 0.1, and high ones, 1.00.
However, I always have a different actual turn rate, as stated in my last comment.
Turn rate 1.00 makes it turn 0.12 instead, 0.12 makes it turn 0.05 instead.
It absolutly makes no sense to me.
 

Ardenian

A

Ardenian

From what I understand they split a circle into different sections and set a unit's facing to the nearest split ?
 

Ardenian

A

Ardenian

Ah I see, thank you

JASS:
        if udg_Angle > 0 and udg_Angle >= udg_TurnRate then
            set facing = facing + udg_TurnRate*2
            set udg_Angle = udg_Angle - udg_TurnRate
        elseif udg_Angle < 0 and udg_Angle <= -udg_TurnRate then
            set facing = facing - udg_TurnRate*2
            set udg_Angle = udg_Angle + udg_TurnRate
        else
            set udg_IsOrdered = false
            set udg_Angle = 0
        endif

    call SetUnitFacing(udg_TestUnit, facing)

Multiplying the added turn rate*2 works for whatever reason.
If I need to optimize the whole thing, I am going to use the bucket solution, thank you!
Do you know by chance the minimum turning angle angle bucket without a difference to the human eye ?
 
Level 24
Joined
Aug 1, 2013
Messages
4,657
Wietlol, why is adding the turn rate to a unit's angle different than adding it to the facing ?
Visually, you still want to adjust the facing, don't you ?
I wanted to trigger the turn rate/facing, since the minimum turn rate of the units is quite high,
not allowing badass starship cruisers to slowly turn with majesty.

Post
You didnt notice?
The old angle is not the same as the new angle in the game logs.
However, according to the code, it should be.
If you instead made it so that you saved the angle that the ship would have if it rotated completely, you wouldnt have this, but as you only save the remaining rotation it has to do, (which is not so nice,) you do get that problem.
That is at least one part of the error.
 
Level 14
Joined
Jul 1, 2008
Messages
1,314
Multiplying the added turn rate*2 works for whatever reason.

This does not work much better in the test map for me ... But as long as it seems to work for you, everything is fine I guess :)

-> This bucket solution sounds a lot like my suggestion to ask, whether the unit already acquired the reduced facing until reducing it further.

If you instead made it so that you saved the angle that the ship would have if it rotated completely
This suggestion by Wietlol sounds pretty straight forward to me . I would try this idea ...
 

Ardenian

A

Ardenian

Post
You didnt notice?
The old angle is not the same as the new angle in the game logs.
However, according to the code, it should be.
If you instead made it so that you saved the angle that the ship would have if it rotated completely, you wouldnt have this, but as you only save the remaining rotation it has to do, (which is not so nice,) you do get that problem.
That is at least one part of the error.
Yes, the angle is faster reduced than the actual facing is increased.
I cannot see a logic behind that though. No matter what values to choose, this issue persists.

But why would saving the angle the ship is going to have better than my solution ?
In theory, both should work like a charm.
Hm, moment, when I save the complete angle then it doesn't matter how much the facing is adjusted per run, right ?
Is that the point ?

I still do not understand what's wrong with my solution.
Even if we consider there is a maximum turning/time possible,
then it should work appropriately when using that maximum or lower, which is not the case.

I am going to try yours and Emm-A-'s solution with saving the complete angle,
though I am not very happy with it, not understanding what's wrong with mine.

30 degrees should be fine.

This does not work much better in the test map for me ... But as long as it seems to work for you, everything is fine I guess :)

-> This bucket solution sounds a lot like my suggestion to ask, whether the unit already acquired the reduced facing until reducing it further.


This suggestion by Wietlol sounds pretty straight forward to me . I would try this idea ...

Thanks, I am going to try it out.

By the way, there is an issue with the math calculating the angle to rotate.
When ordering the unit to rotate -90 up to -180 ( angle between points),
it reverses the order.
Haven't looked into it, but should be easy to fix. Most likely a small sign mistake.
 
Level 24
Joined
Aug 1, 2013
Messages
4,657
You calculate how much you have to turn (which decreases every 0.03125 seconds), but the unit does not actually turn that exact same value per 0.03125 seconds, so your calculation has a error margin.
If you simply compare the current angle to the final angle, then you will never have such a problem.

Off-Topic: why 0.03125 over 0.03?
 

Ardenian

A

Ardenian

Ah I see, thank you!


60 seconds / 0.03125 = 1290
Eases calculation of velocity ( controlled by udg_UpdateReal)
 

Ardenian

A

Ardenian

Oh, that's even better, thank you.

Currently re-writing the script to change it based on your suggestions.
Up to now works like a charm, only have some problems ( math, hu) with adjusting the angle / choosing the closest way to rotate the ship.
Should be easy deazy though, just need a moment.


If moderators would like to, they could mark this thread as solved.

Thank you all, guys!
 

Ardenian

A

Ardenian

Small update question, do you guys think this can be simplified ?

JASS:
        if facing > udg_Angle then
            set a = facing - udg_Angle
            set c = -1 
        elseif facing < udg_Angle then
            set a = udg_Angle - facing
            set c = 1
        else 
            set udg_IsOrdered = false
            set udg_Angle = 0    
        endif
        set b = 360 - a
    
        if a <= b and udg_Angle != 0 then 
            set facing = facing + udg_TurnRate*c    
        elseif udg_Angle != 0 then
            set facing = facing - udg_TurnRate
        endif

It checks what distance is smaller to rotate, but I think I made it a bit complicated, didn't I ?
 

Ardenian

A

Ardenian

But what if I have facing = 90 and angle = 80 ? Then it would be 10,
but it would rotate in the wrong direction, as it adds to the facing instead of subtracting.

Denormalize means + 360, when angle < 0 ?
 
Status
Not open for further replies.
Top