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: 207

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.
  • Ghan Ghan:
    Still lurking
    +3
  • The Helper The Helper:
    I am great and it is fantastic to see you my friend!
    +1
  • The Helper The Helper:
    If you are new to the site please check out the Recipe and Food Forum https://www.thehelper.net/forums/recipes-and-food.220/
  • Monovertex Monovertex:
    How come you're so into recipes lately? Never saw this much interest in this topic in the old days of TH.net
  • Monovertex Monovertex:
    Hmm, how do I change my signature?
  • tom_mai78101 tom_mai78101:
    Signatures can be edit in your account profile. As for the old stuffs, I'm thinking it's because Blizzard is now under Microsoft, and because of Microsoft Xbox going the way it is, it's dreadful.
  • The Helper The Helper:
    I am not big on the recipes I am just promoting them - I use the site as a practice place promoting stuff
    +2
  • Monovertex Monovertex:
    @tom_mai78101 I must be blind. If I go on my profile I don't see any area to edit the signature; If I go to account details (settings) I don't see any signature area either.
  • The Helper The Helper:
    You can get there if you click the bell icon (alerts) and choose preferences from the bottom, signature will be in the menu on the left there https://www.thehelper.net/account/preferences
  • The Helper The Helper:
    I think I need to split the Sci/Tech news forum into 2 one for Science and one for Tech but I am hating all the moving of posts I would have to do
  • The Helper The Helper:
    What is up Old Mountain Shadow?
  • The Helper The Helper:
    Happy Thursday!
    +1
  • Varine Varine:
    Crazy how much 3d printing has come in the last few years. Sad that it's not as easily modifiable though
  • Varine Varine:
    I bought an Ender 3 during the pandemic and tinkered with it all the time. Just bought a Sovol, not as easy. I'm trying to make it use a different nozzle because I have a fuck ton of Volcanos, and they use what is basically a modified volcano that is just a smidge longer, and almost every part on this thing needs to be redone to make it work
  • Varine Varine:
    Luckily I have a 3d printer for that, I guess. But it's ridiculous. The regular volcanos are 21mm, these Sovol versions are about 23.5mm
  • Varine Varine:
    So, 2.5mm longer. But the thing that measures the bed is about 1.5mm above the nozzle, so if I swap it with a volcano then I'm 1mm behind it. So cool, new bracket to swap that, but THEN the fan shroud to direct air at the part is ALSO going to be .5mm to low, and so I need to redo that, but by doing that it is a little bit off where it should be blowing and it's throwing it at the heating block instead of the part, and fuck man
  • Varine Varine:
    I didn't realize they designed this entire thing to NOT be modded. I would have just got a fucking Bambu if I knew that, the whole point was I could fuck with this. And no one else makes shit for Sovol so I have to go through them, and they have... interesting pricing models. So I have a new extruder altogether that I'm taking apart and going to just design a whole new one to use my nozzles. Dumb design.
  • Varine Varine:
    Can't just buy a new heatblock, you need to get a whole hotend - so block, heater cartridge, thermistor, heatbreak, and nozzle. And they put this fucking paste in there so I can't take the thermistor or cartridge out with any ease, that's 30 dollars. Or you can get the whole extrudor with the direct driver AND that heatblock for like 50, but you still can't get any of it to come apart
  • Varine Varine:
    Partsbuilt has individual parts I found but they're expensive. I think I can get bits swapped around and make this work with generic shit though
  • Ghan Ghan:
    Heard Houston got hit pretty bad by storms last night. Hope all is well with TH.
  • The Helper The Helper:
    Power back on finally - all is good here no damage
    +2
  • V-SNES V-SNES:
    Happy Friday!
    +1
  • The Helper The Helper:
    New recipe is another summer dessert Berry and Peach Cheesecake - https://www.thehelper.net/threads/recipe-berry-and-peach-cheesecake.194169/

      The Helper Discord

      Staff online

      Members online

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top