Tutorial Simple Ice Mechanics in vJass

Reaction score
86
Simple Ice Mechanics in vJass
By Infinitegde

Note: So, while I was working on a map I heard someone on the forums mention help on Ice mechanics. Now, because this is related to my map, I thought, why not make a quick and simple tutorial? So here it is!

Needed for this:
-Intelligence o_O
-Basic knowledge of Structs
-Jass New Gen Pack Can be found here.

Table of Contents
I. Explanation of Ice Mechanics
II. Planning it out
III. Overview
IV. Collision Section - Add on

I. Explanation of Ice Mechanics

Let's take a look at what this system should have... Well, it needs to increase the speed right? You need to slide... You also need to have a deceleration factor... A lot more is needed but I will mainly cover this... Now, let's see what is needed for each of these functions...

Increasing speed, why? Because ice is very slippery, you take advantage of this and slide across the ice which increases your speed by a lot.

Sliding? Well, of course! Without sliding, you wouldn't be doing very much! Sliding requires a periodic timer...

And last, deceleration... Well, a lot of forces act on you when you on ice, which is why you eventually slow down!

Now lets go from the idea stage to the planning stage...

II. Planning it out

So what do we do first? Well here's where structs and vJass come in! Why? Because we need to create variables for the units on the ice. Why use structs? We need one for each that is on the ice don't we? Yep, now let's go through it. So we need to make a struct, I named mine "onIce" What do we need in here? Thankfully, no functions, all we need are variables. We need to keep 4 variables. All reals, one is LastX, LastY, XVelocity, and YVelocity. This is very simple way to do this, and, I think, the most efficient.

Now lets move on to detecting if the unit is on ice. For this, you don't have to make preset regions, you can just check if the unit is on the terrain type of ice. However, for the sake of time, I made a region and just detected when the unit entered it and left it. So, for this trigger what do we need to do? Well need to create a struct and attach it to the unit. We also need to make sure to set the lastX and lastY equal to the current X and Y of the units. Why? Because it wouldn't be very good if we entered the ice and our Velocity instantly increases for no apparent reason. Now to move on to the leaving trigger.

Now we just detect if the unit leaves the region. We simply take the retrieve the struct and destroy it. We do this because one, save space, to it wouldn't be to good if we reentered the ice and our old velocities returned and were effecting our units would it?

And FINALLY to our engine, the main part of this entire ice mechanics part. Ok, so the event fires off a periodic timer. So in this we need to pick all the units in the region, and then do these actions: First, since we will refer to the unit a lot, set a unit equal to GetEnumUnit(). Next, retrieve the struct onIce from the unit. Now we actually have to get to work o_O... Well, a lot of this depends on the actual order of the actions, so I will mention them in the correct order...

First, we need to set the units X to it's current X + XVelocity. Same with Y. I didn't use location's because they are annoying to deal with. Now, this is important we need to get the current x of the unit but we need to -XVelocity because here is where we want to check if the unit is moving on it's own! If we do this, we will get the X of the unit without the last increase. So store it in a variable as well as the Y, because we will refer to it quite a bit. Next we have to change the lastx and lasty of the struct. But, we need to have some more variables... Because we need to refer to this later. So make a variable and set it to LastX and do the same for LastY. Now set LastX and LastY to the actual X and Y of the unit. (Do not need to subtract velocities or anything.)

Here's another important part, here's where we actually increase or decrease the XVelocity and YVelocity. So we take those variables we just set and we have to subtract them, so we subtract the variable's like this, CurrentX - LastX. Next we have to make this smaller because if it's very large then it will increase the velocities to quickly and make it seem unrealistic. So I divided it by 4. Now do the same for the Y vars and then just add the values you get from that to X and Y velocity.

Now, because we don't want it to increase super fast we want to have a speed cap. So we check if XVelocity is greater than the speed cap, if it is, we set XVelocity = SpeedCap. Next we check if XVelocity is less than negative Speed Cap. If it is, make it equal to negative speed cap. Do the same for YVelocity.

And on to the bulkiest part, the Deceleration... Well, when you skate on ice, if you don't have skates, then you decelerate in a parabola like fashion. You don't need to know what this means, but it means you decelerate faster as you have more speed. Why? Because your displacing a lot more air if you are moving faster, then if you are moving slower... How do we do this? Well, we check if the Velocities are greater than or less than 0. If its greater than 0, than we subtract from it. So I did, if its greater than 1 then we subtract one. Simple. then as an else if, if its greater than 0.10 than subtract 0.10. And finally if its greater than 0.01 than subtract 0.01! Now do the same for if its negative, just check if the velocities are less than -1 then add 1 and so on.

And, tada, your finished. Now their's a few bugs with this system :(, it ignores collision detection, meaning you can stack 7 footmen onto each other!, and although this may seem funny, it can be really annoying. This can be fixed, but it makes the system more complicated, and I'm sticking to the basics. Now, let's look at if we accomplished our goals.

III. Overview

So our goals where to create a slide effect, we needed to have a deceleration effect, and we also needed to increase the speed. Well, if you look at your past work, as you move in a direction, your velocities increase in that direction therefore your speed increases! We, clearly have added the deceleration effect. And we also clearly have a slide effect. And you may also have noticed we have a resistance effect! This is a side effect which also occurs. If you try to change direction, it resists change!

IV. Collision Section

So, does it annoy you that the units can slide through each other? It did to me too o_O. So I created a small bit of code that detects if a unit is nearby and then a formula that hits it away. THIS SECTION IS NOT REALISTIC COLLISION! If someone can show me a collision formula and explain it to me o_O, it would be much appreciated!

So let's dive into this bad boy. First, we want to add a new variable to the struct. boolean Hit! I'll explain why we need it later! So, back into the engine, the Slide Trigger! What we have to do is create a group. Then for check units in range of the point where the unit is going to be, meaning get the location then add the Velocities to both the X and Y. Next make the range close to something like 50 - 100. If you want to be really accurate check out this great function by Vexorian, GetUnitCollisionSize
Anyway, so now that you can detect if a unit is close to next point, we want to do the actual Collision mechanics. So do a ForGroup and make a new function for this. Inside this function is where the magic happens o_O... But before that we want to add a global struct to keep track of the ice struct. So we set the global struct to the current struct in the function and then go into the forgroup. Now, simply find the avg of the XVelocities and YVelocities. Set the X and Y avgs to one of the structs, then set the negative values of them to the other struct. Then set the hit of both of the structs = true. Now put all of it in a if statement and say if this.Hit == false and that.Hit == false then, and put all of that code in there. This checks if it has already hit something or not. Also, back into the earlier function, set the hit, before the forgroup, = false. Finally thats it. I'll update my code to include the Collision and upload the new map!

And that's the end, thanks for reading this short tutorial!

I will post the code and the map!

Code
Header Code
JASS:
globals
    //Acceleration Cap... You can set this ridiculously high, but if you do, it will mess up the game XD
    real Speedcap = 50.00
    //These 3 should go from greatest to least, and should all be positive
    real Decel1 = 0.20
    real Decel2 = 0.05
    real Decel3 = 0.01
    real AccelRate = 6 //The lower this is, the faster the acceleration
    real CollisionFactor = 0.75 // The higher this is the bigger the collision (Don't make it negative.) 
    real MapBminX
    real MapBminY
    real MapBmaxX
    real MapBmaxY
    unit Tempu
    onIce GlobalStruct
endglobals

struct onIce
    boolean Hit
    real LastX
    real LastY
    real XVelocity
    real YVelocity
endstruct


Init Trigger
JASS:
function Trig_Init_Actions takes nothing returns nothing
    set MapBminX = GetRectMinX(bj_mapInitialPlayableArea)
    set MapBminY = GetRectMinY(bj_mapInitialPlayableArea)
    set MapBmaxX = GetRectMaxX(bj_mapInitialPlayableArea)
    set MapBmaxY = GetRectMaxY(bj_mapInitialPlayableArea)
endfunction

//===========================================================================
function InitTrig_Init takes nothing returns nothing
    set gg_trg_Init = CreateTrigger(  )
    call TriggerAddAction( gg_trg_Init, function Trig_Init_Actions )
endfunction


Enters Ice trigger
JASS:
function Trig_Enters_Ice_Actions takes nothing returns nothing
    local onIce TempStruct = onIce.create()
    local unit u = GetTriggerUnit()
    set TempStruct.LastX = GetUnitX(u)
    set TempStruct.LastY = GetUnitY(u)
    call SetUnitUserData(u,TempStruct)
    set u = null
endfunction

//===========================================================================
function InitTrig_Enters_Ice takes nothing returns nothing
    set gg_trg_Enters_Ice = CreateTrigger(  )
    call TriggerRegisterEnterRectSimple( gg_trg_Enters_Ice, gg_rct_Ice )
    call TriggerAddAction( gg_trg_Enters_Ice, function Trig_Enters_Ice_Actions )
endfunction


Ice Slide Trigger
JASS:
function PathFilter takes nothing returns boolean
    return not(GetFilterUnit()== Tempu)
endfunction
function Collision takes nothing returns nothing
    local onIce Opposing = GetUnitUserData(GetEnumUnit())
    local onIce Friend = GlobalStruct
    local real AvgX  
    local real AvgY
    if(Opposing.Hit == false and Friend.Hit == false) then
        if(Opposing.XVelocity+Friend.XVelocity==0) then
            set AvgX = 0
        else
            set AvgX = (Opposing.XVelocity + Friend.XVelocity)*(CollisionFactor)
        endif
    
        if(Opposing.YVelocity+Friend.YVelocity==0) then
            set AvgY = 0
        else
            set AvgY = (Opposing.YVelocity + Friend.YVelocity)*(CollisionFactor)
        endif
        set Opposing.XVelocity = AvgX
        set Opposing.YVelocity = AvgY
        set Friend.XVelocity = -AvgX
        set Friend.YVelocity = -AvgY
        set Opposing.Hit = true
        set Friend.Hit = true
    endif
endfunction
function SetAndPick takes nothing returns nothing
    local unit u = GetEnumUnit()
    local onIce Temp = GetUnitUserData(u)
    local real CX
    local real CY
    local real LX = Temp.LastX
    local real LY = Temp.LastY
    local group pathing = CreateGroup()
    set Temp.Hit = false
    set Tempu = u
    set GlobalStruct = Temp
    call GroupEnumUnitsInRange(pathing, GetUnitX(u)+Temp.XVelocity,GetUnitY(u)+Temp.YVelocity, 50, Condition(function PathFilter) )
    call ForGroup(pathing, function Collision)
    call DestroyGroup(pathing)
    if not(GetUnitX(u)+Temp.XVelocity < MapBminX) and not(GetUnitX(u)+Temp.XVelocity > MapBmaxX) then
        call SetUnitX(u,GetUnitX(u)+Temp.XVelocity)
    else
        set Temp.XVelocity = 0
    endif
    if not(GetUnitY(u)+Temp.YVelocity < MapBminY) and not(GetUnitY(u)+Temp.YVelocity > MapBmaxY) then
        call SetUnitY(u,GetUnitY(u)+Temp.YVelocity)
    else
        set Temp.YVelocity = 0
    endif
    set CX = GetUnitX(u)-Temp.XVelocity
    set CY = GetUnitY(u)-Temp.YVelocity
    set Temp.LastX = GetUnitX(u)
    set Temp.LastY = GetUnitY(u)
    set Temp.XVelocity = Temp.XVelocity + (CX-LX)/AccelRate
    set Temp.YVelocity = Temp.YVelocity + (CY-LY)/AccelRate
    if Temp.XVelocity > Speedcap then
        set Temp.XVelocity = Speedcap
    endif
    if Temp.XVelocity < -Speedcap then
        set Temp.XVelocity = -Speedcap
    endif
    if Temp.YVelocity > Speedcap then
        set Temp.YVelocity = Speedcap
    endif
    if Temp.YVelocity < -Speedcap then
        set Temp.YVelocity = -Speedcap
    endif
    if Temp.XVelocity > 0 then
        if(Temp.XVelocity > Decel1)then
           set Temp.XVelocity = Temp.XVelocity-Decel1
        elseif(Temp.XVelocity > Decel2)then
           set Temp.XVelocity = Temp.XVelocity-Decel2
        elseif(Temp.XVelocity > Decel3)then
           set Temp.XVelocity = Temp.XVelocity-Decel3
        endif
    endif
    if Temp.XVelocity < 0 then
        if(Temp.XVelocity < -Decel1)then
           set Temp.XVelocity = Temp.XVelocity+Decel1
        elseif(Temp.XVelocity < -Decel2)then
           set Temp.XVelocity = Temp.XVelocity+Decel2
        elseif(Temp.XVelocity < -Decel3)then
           set Temp.XVelocity = Temp.XVelocity+Decel3
        endif
    endif
    if Temp.YVelocity > 0 then
        if(Temp.YVelocity > Decel1)then
           set Temp.YVelocity = Temp.YVelocity-Decel1
        elseif(Temp.YVelocity > Decel2)then
           set Temp.YVelocity = Temp.YVelocity-Decel2
        elseif(Temp.YVelocity > Decel3)then
           set Temp.YVelocity = Temp.YVelocity-Decel3
        endif
    endif
    if Temp.YVelocity < 0 then
        if(Temp.YVelocity < -Decel1)then
           set Temp.YVelocity = Temp.YVelocity+Decel1
        elseif(Temp.YVelocity < -Decel2)then
           set Temp.YVelocity = Temp.YVelocity+Decel2
        elseif(Temp.YVelocity < -Decel3)then
           set Temp.YVelocity = Temp.YVelocity+Decel3
        endif
    endif
endfunction

function Trig_Ice_Slide_Actions takes nothing returns nothing
    local group Temp = CreateGroup()
    call GroupEnumUnitsInRect(Temp,gg_rct_Ice,null)
    call ForGroup(Temp, function SetAndPick )
    call DestroyGroup(Temp)
endfunction

//===========================================================================
function InitTrig_Ice_Slide takes nothing returns nothing
    set gg_trg_Ice_Slide = CreateTrigger(  )
    call TriggerRegisterTimerEventPeriodic( gg_trg_Ice_Slide, 0.03 )
    call TriggerAddAction( gg_trg_Ice_Slide, function Trig_Ice_Slide_Actions )
endfunction


Leaves Ice Trigger
JASS:
function Trig_Leaves_Ice_Actions takes nothing returns nothing
    local onIce TempStruct = GetUnitUserData(GetTriggerUnit())
    call TempStruct.destroy()
endfunction

//===========================================================================
function InitTrig_Leaves_Ice takes nothing returns nothing
    set gg_trg_Leaves_Ice = CreateTrigger(  )
    call TriggerRegisterLeaveRectSimple( gg_trg_Leaves_Ice, gg_rct_Ice )
    call TriggerAddAction( gg_trg_Leaves_Ice, function Trig_Leaves_Ice_Actions )
endfunction


Here, take a look!
 

Attachments

  • Ice Mechanics.w3x
    21.2 KB · Views: 210

UndeadDragon

Super Moderator
Reaction score
447
Good tutorial.

Haven't seen one with a deceleration before.
 
Reaction score
86
o_O, is there one like this? I made this because I thought no one made one...

Side note: Adding collision soon... Updated :D
 
General chit-chat
Help Users
  • No one is chatting at the moment.

      The Helper Discord

      Members online

      No members online now.

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top