• 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.

Projectile System

Level 3
Joined
Apr 17, 2008
Messages
25
Note - This system does not handle any physics, this is purely for moving units in different ways.

JASS:
=====================================================================================
 ------------------------ Projectile System made by Gwypaas -------------------------

 Use local Projectile p = Projectile.create(<unit>, <Remove Projectile?>) to create a new particle, if you set the boolean to true
 then the projectile will be removed upon finish of the movement. You can also do this with any of the premade types or your own.
 then the syntax would be
 local <Yourtype> p = <YourType.create(<unit>, <Remove Projectile?>)
 
 Then choose what type of movement you want.
 
 Then let the system do the rest <3

 NOTE --- This system DOES NOT handle any physics.

 This system is able to handle premade function pretty much "inserts" code into the timer callback
 You can see the usage of theese by viewing the "Jump", "Slide For Time" and "SLide Towards Unit"
 methods.
 
 
 This system is coded upon the polymorphism part of JASSHelper and when you create a new struct then extend it from on of the premade types.
 
 Check out the heavily commented Jump Struct for further information.
 
 These are the interface methods that you can use:
 
 method onLoop takes nothing returns nothing
 This method gets called on every interval and no of the premade types uses it.
 
 method privOnLoop takes nothing returns nothing
 This is the onloop method that the premade stuff uses.
 
 method onReach takes nothing returns nothing
 This method gets called upon reach of the target. Note - The premade types does not use it.
 

 With the use of the Projectile.create(<unit>, <Should get Removed?>) you create a basic projectile that 


 If you use custom methods and wants to destroy the projectile then set the projectiles "end" variable to true
 and it will be destroyed on the next timer interval.

 The theta = The normal angle you use when you calculate 2d stuff and it's of course in radians
 the phi = The "height" angle that specifies at which height direction it should go and it's also radians.

=====================================================================================
 -------------------- Premade methods in the Projectile struct ----------------------
 method Slide takes real x, real y, real speed returns nothing
 method SlideXYZ takes real x, real y, real z, real speed returns nothing
 method SlideTowardsAngle takes real theta, real phi, real speed, real distance returns nothing
 
 -------------------- Premade methods in the Extenions structs ----------------------
 method Jump takes real x, real y, real speed, real maxH returns nothing
 method SlideTowardsUnit takes unit target, real speed returns nothing
 method SlideXYZLiveTime takes real x, real y, real z, real speed, real TimeLiving returns nothing
 

 ------------------- Premade methods to get the mathematic values -------------------
 
     These methods are inline friendly
 method GetPhi takes real x, real y, real z returns real
 method GetTheta takes real x, real y returns real
 method GetDistance takes real x, real y, real z returns real
 method SetVelocityX takes nothing returns nothing
 method SetVelocityY takes nothing returns nothing
 method SetVelocityZ takes nothing returns nothing
 
 
 These methods are not inline friendly
 method SetVelocities takes nothing returns nothing
 method SetEverything takes real x, real y, real z returns nothing
=====================================================================================

JASS:
library ProjectileSystem
globals
    // WARNING DO NOT MAKE THE VALUE BELOW TOO SMALL OR THE PARTICLE WILL CONTINUE TO SLIDE FOREVER.
    private constant real FINISH_THRESHOLD = 10 // How close it must be to the target to be considered finished.
    private constant real MIN_SPEED = .025 // The minimum speed a projectile can have before it's considered finished
    
    constant real PROJECTILE_TIMER_PERIOD = 0.02
    private constant boolean EXTRA_DEBUG_MODE = false // The "extra" debug mode which shows more messages.
    private constant real FPS = 1/PROJECTILE_TIMER_PERIOD

    
    private Projectile array Datas
    private integer N = 0
    private timer T = CreateTimer()
endglobals



interface ProjectileBase
    boolean end // Setting this to true will destroy the struct on the next interval.
    boolean remUnit // Set this to true if you want to remove the unit
    
    unit u // The projectile
    
    real x // The current x
    real y // The current y
    real z // The current z
    
    real resistance = 1// If you want the projectiles to have friction or similar 
    //then use the right formula to get the resistance the system should use and set
    // this variables to it. resistance 1 = no resistance, less than 1 = slowing down, more than 1 = speeding up.
    
    // Velocities
    real xV
    real yV
    real zV 
    
    real tx // The target x
    real ty // The target y
    real tz // The target z
    
    real theta // Angle
    real phi //Z Angle
    real rho // radius / speed
    
    
    real distLeft // The distance that is left to the target
    
    static location tloc = Location(0,0) // The location used to determine terrain height
    
    method onLoop takes nothing returns nothing defaults nothing // What you do on each loop
    method privOnLoop takes nothing returns nothing defaults nothing // What the struct you are extending does on each loop
    method onReach takes nothing returns nothing defaults nothing // What you do when the projectile reaches it's target
    
endinterface

    
struct Projectile extends ProjectileBase


    // Some inline friendly methods for easier creating of your own extensions
    method GetPhi takes real x, real y, real z returns real
        return Atan2(SquareRoot((x-.x) * (x-.x) + (y-.y) * (y-.y)), (z-.z))
    endmethod
    
    method GetTheta takes real x, real y returns real
        return Atan2((y - .y), (x - .x))
    endmethod
    
    method GetDistance takes real x, real y, real z returns real
        return SquareRoot((.x-x) * (.x-x) + (.y-y) * (.y-y) + (.z-z) * (.z-z))
    endmethod
    
        
        
    // Inline friendly Set Methods
    method SetVelocityX takes nothing returns nothing
        set .xV = Sin(.phi) * Cos(.theta) * .rho
    endmethod
    
    method SetVelocityY takes nothing returns nothing
        set .yV = Sin(.phi) * Sin(.theta) * .rho
    endmethod
    
    method SetVelocityZ takes nothing returns nothing
        set .zV = Cos(.phi) * .rho
    endmethod
    
    // Not inline friendy but easier to use.
    method SetVelocities takes nothing returns nothing
        set .xV = Sin(.phi) * Cos(.theta) * .rho
        set .yV = Sin(.phi) * Sin(.theta) * .rho
        set .zV = Cos(.phi) * .rho
    endmethod

    method SetEverything takes real x, real y, real z returns nothing
        set .phi = Atan2(SquareRoot((x-.x) * (x-.x) + (y-.y) * (y-.y)), (z-.z))
        set .theta = Atan2((y - .y), (x - .x))
        set .distLeft = SquareRoot((.x-x) * (.x-x) + (.y-y) * (.y-y) + (.z-z) * (.z-z))
        
        set .xV = Sin(.phi) * Cos(.theta) * .rho
        set .yV = Sin(.phi) * Sin(.theta) * .rho
        set .zV = Cos(.phi) * .rho
    endmethod
    
    
    static method Callback takes nothing returns boolean
        local Projectile p 
        local integer i = N
        local real sl 
        local boolean b
        local real tz
        loop
            exitwhen i == 0
            set p = Datas[i]
            set sl = (RAbsBJ(p.xV) + RAbsBJ(p.yV) + RAbsBJ(p.zV)) / 3
            set b = sl <= MIN_SPEED and sl >= -MIN_SPEED
            if p.distLeft <= FINISH_THRESHOLD or p.end == true or b then
                if p.onReach.exists == true then
                    call p.onReach()
                endif
            
                call p.destroy()
                set Datas[i] = Datas[N]
                set N = N - 1
                if N == 0 then
                    call PauseTimer(T)
                endif
            else
                
                set p.xV = p.xV * p.resistance
                set p.yV = p.yV * p.resistance
                set p.zV = p.zV * p.resistance
                
                set p.x = p.x + p.xV
                set p.y = p.y + p.yV
                set p.z = p.z + p.zV
                
                call MoveLocation(.tloc, p.x, p.y)
                set tz = GetLocationZ(.tloc)
                
                if p.onLoop.exists == true then
                    call p.onLoop()
                endif
                
                if p.privOnLoop.exists == true then
                    call p.privOnLoop()
                endif
                
                call SetUnitX(p.u, p.x)
                call SetUnitY(p.u, p.y)
                call SetUnitFlyHeight(p.u, p.z - tz, 0)
                
                call SetUnitFacing(p.u, p.theta * bj_RADTODEG)
                
                // I know that this doesn't seem efficient but I need this for moving targets :)
                set p.distLeft = p.GetDistance(p.tx, p.ty, p.tz) 
            endif

            set i = i-1
        endloop
        return false
    endmethod
    
    method Start takes nothing returns nothing
        if N == 0 then
            call TimerStart (T, PROJECTILE_TIMER_PERIOD, true, function Projectile.Callback)
        endif
        set N = N + 1
        set Datas[N] = this
    endmethod
    
    
    static method create takes unit u, boolean RemoveProjectile returns Projectile
        local Projectile p = .allocate()
        set p.u = u
        set p.x = GetUnitX(p.u)
        set p.y = GetUnitY(p.u)
        set p.z = GetUnitFlyHeight(p.u)
        
        set p.resistance = 1

        call UnitAddAbility(p.u, 'Amrf')
        call UnitRemoveAbility(p.u, 'Amrf')
        
        
        set p.end = false
        set p.remUnit = RemoveProjectile
        
        return p
    endmethod


    method Slide takes real x, real y, real speed returns nothing
        set .rho = speed
        set .distLeft = .GetDistance(x,y,0)
        set .phi = .GetPhi(x,y,0)
        set .theta = .GetTheta(x,y)
        
        call .SetVelocityX()
        call .SetVelocityY()
        call .SetVelocityZ()
        
        set .tx = x
        set .ty = y
        set .tz = 0
        
        call .Start()
    endmethod
    
    method SlideXYZ takes real x, real y, real z, real speed returns nothing
        set .rho = speed
        set .theta = .GetTheta(x,y)
        set .phi = .GetPhi(x,y,z)
        set .distLeft = .GetDistance(x,y,z)
        
        call .SetVelocityX()
        call .SetVelocityY()
        call .SetVelocityZ()
        
        set .tx = x
        set .ty = y
        set .tz = z
        
        call .Start()
    endmethod

    
    method SlideTowardsAngle takes real theta, real phi, real speed, real distance returns nothing
        set .rho = speed
        set .theta = theta
        set .phi = phi
        
        set .tx = .x + (Sin(.phi) * Cos(.theta)) * distance
        set .ty = .y + (Sin(.phi) * Sin(.theta)) * distance
        set .tz = .z + Cos(.phi) * distance
        set .distLeft = .GetDistance(.tx, .ty, .tz)
        call .SetVelocityX()
        call .SetVelocityY()
        call .SetVelocityZ()
        call .Start()
    endmethod
    
    method onDestroy takes nothing returns nothing
        debug if EXTRA_DEBUG_MODE then
            debug call BJDebugMsg("Instance ID = " + I2S(this) +" || End Results:  x = " + R2S(GetUnitX(.u)) + " : y = " + R2S(GetUnitY(.u)) + " : z = " + R2S(GetUnitFlyHeight(.u)))
        debug else
            debug call BJDebugMsg("Instance ID = " + I2S(this))
        debug endif
        if .remUnit then
            call RemoveUnit(.u)
        endif
        set .u = null
    endmethod
endstruct
endlibrary

JASS:
//=====================================================================================//
// ------------------- Projectile System Extensions made by Gwypaas -------------------//
//
// This is the library that you create your own projectile types in.
// Check out the "examples" below if you want more info
//
// Note, you NEED to do the typecasting that I do with the "local Jump p = pp" beacause
// otherwise it will give you syntax error because of how function interfaces are constructed
//
//=====================================================================================//

library ProjectileSystemExtensions requires ProjectileSystem



struct Jump extends Projectile // This is what makes it a projectile
    
    // Private variables for the method
    real jextra    
    real totalDist 
    real distDone  
    
    // The private method that gets called every interval
    method privOnLoop takes nothing returns nothing
        set .z = (4 * .jextra / .totalDist) * (.totalDist - .distDone) * (.distDone / .totalDist)
        // Credits to Moyack and Spec for this formula.
        set .distDone = .distDone + .rho
    endmethod
    
    // The method you use to start the "sliding" of your Jump 
    method Jump takes real x, real y, real speed, real maxH returns nothing
        local real td = .GetDistance(x, y,0) // Getting the distance
        set .rho = speed // Setting the speed
        set .theta = .GetTheta(y, x) // Getting the XY angle
        set .phi = .GetPhi(x,y,0) // GEtting the Z angle
        
        // Setting hte private variables
        set .distLeft = td 
        set .totalDist =  td
        set .jextra = maxH
        set .distDone = 0
        
        // Setting the Velocities based on your rho, theta and phi
        call .SetVelocityX() 
        call .SetVelocityY()
        call .SetVelocityZ()
        
        // Setting the starting position
        set .tx = x
        set .ty = y
        set .tz = 0

        
        call .Start() // Fire the sliding up
    endmethod
endstruct



struct SlideTowardsUnit extends Projectile
    unit target 
    
    method privOnLoop takes nothing returns nothing
        set .tx = GetUnitX(.target)
        set .ty = GetUnitY(.target)
        set .tz = GetUnitFlyHeight(.target)
        set .theta = .GetTheta(.tx, .ty)
        set .phi = .GetPhi(.tx, .ty, .tz)
    
        call .SetVelocityX()
        call .SetVelocityY()
        call .SetVelocityZ()
    endmethod
    
    method SlideTowardsUnit takes unit target, real speed returns nothing
        local real x = GetUnitX(target)
        local real y = GetUnitY(target)
        local real z = GetUnitFlyHeight(target)
        
        set .target = target
        
        set .rho = speed
        set .theta = .GetTheta(x,y)
        set .phi = .GetPhi(x,y,z)
        set .distLeft = .GetDistance(x,y,z)
        
        call .SetVelocityX()
        call .SetVelocityY()
        call .SetVelocityZ()
        
        set .tx = x
        set .ty = y
        set .tz = z
         
        call .Start()
    endmethod
    
    method onDestroy takes nothing returns nothing
        set .target = null
    endmethod
endstruct


struct SlideXYZLiveTime extends Projectile
    real maxTime 
    real timeDone
    method privOnLoop takes nothing returns nothing
        if .timeDone >= .maxTime then
            set .end = true
        endif
        set .timeDone = .timeDone + PROJECTILE_TIMER_PERIOD
    endmethod
    
    method SlideXYZLiveTime takes real x, real y, real z, real speed, real TimeLiving returns nothing
        set .timeDone = 0
        set .maxTime = TimeLiving
        set .rho = speed
        set .theta = .GetTheta(x,y)
        set .phi = .GetPhi(x,y,z)
        set .distLeft = .GetDistance(x,y,z)
        
        call .SetVelocityX()
        call .SetVelocityY()
        call .SetVelocityZ()
        
        set .tx = x
        set .ty = y
        set .tz = z
        
        call .Start()
    endmethod
endstruct
endlibrary


180609 - Made it work better with cliffs

130309 - Made it use methods interfaces instead of function interfaces and also rewrote parts of the documentation.

070209 - Added the "Slide towards angle" method for the people that needs it.

300109(2) - Made so the user can select if a Projectile should get removed, also added movement resistance and made so all projectiles that moves slower than a specified speed are considered finished, also updated the documentation a bit.

300109 - It now uses velocities in some way but I'm not sure if it's right. I also made methods to set the velocities that are inline friendly and one that sets all but isn't inline friendly, I also made a "Set Everything" method for the lazy people, calling that will set the velocities and angles.

290109 - Made it alot easier to use by making the extensions not using the USF function that was meant to be used by the user, It's also easier to create your own types now since I've created the .GetPhi/Theta/Distance methods so you don't need to CnP the ones that I used. The methods will also be inlined due to how I made them.
 

Attachments

  • Projectile System.w3x
    31.3 KB · Views: 69
Last edited:
Level 3
Joined
Apr 17, 2008
Messages
25
(Sin(.phi) * Sin(.theta)) * distance
Store this in a global!
Impossible since the values it uses changes depending on your coordinates and the X calculation uses Sin/Cos and Y uses Sin/Sin.

Also, this is a bit of outdated, I've made a slight improvment that's coming up in a few mins.
 
Level 3
Joined
Apr 17, 2008
Messages
25
Maybe another bump... the system is in the current state approved on wc3c so I think the quality and how useful it is, is alright for an approval now :p
 
Level 11
Joined
Feb 22, 2006
Messages
752
Just so you know your jump thing will screw up if the unit crosses a cliff boundary. You will see the unit's flight path become erratic near the cliff boundary since GetUnitFlyHeight() and SetUnitFlyHeight() get and set the positive offset from the current z value of the location the unit is at, not the absolute z height of the unit.
 
Top