Dismiss Notice
60,000 passwords have been reset on July 8, 2019. If you cannot login, read this.

Advanced Third Person Camera

Discussion in 'JASS/AI Scripts Tutorials' started by Megafyr, Oct 4, 2009.

  1. Megafyr

    Megafyr

    Joined:
    Oct 29, 2007
    Messages:
    957
    Resources:
    1
    Tutorials:
    1
    Resources:
    1
    Advanced Third Person Camera
    by Megafyr

    This tutorial will teach you how to make an advanced third person camera that focuses on a unit in your warcraft III map. You will have to use the NewGen editor to add this to your map. With basic jass experience this tutorial will become significally easier to understand, and easier to follow.

    What this camera system includes:

    - camera locks to a given unit and views it from behind
    - the rotation of the camera follows the rotation of the unit
    - height changes according to the terrain
    - the angle of attack changes according to the terrain height
    - the distance to target changes according to pathing blockers (walls, obstacles, etc...)

    In order to make this camera system work you will have to import the pathability check by Rising_Dusk into your map. ( http://www.wc3c.net/showthread.php?t=103862 )

    (Obs! This tutorial does not show you how to import any systems apart from the camera code into your map.)

    The pathability check will check if there are any obstacles behind the unit the camera focuses on, and then set the correct camera distance. I have attached a demo map, which you can copy the entire system from. Otherwise you can follow along the tutorial.

    During the tutorial I recommend to look for comments between the jass lines. They are part of the tutorial.

    Okay, let's get started!

    Now that you have imported the pathing code by Rising_Dusk, you can create a trigger that runs every 0.04 seconds of game time, which is every time the camera will be updated. The reason I use 0.04 is because 1 second divided by 25 equals 0.04 seconds. That's a quite acceptable framerate. Convert the trigger to jass. Mine looks like this:

    Code (vJASS):
    function Trig_Multiplayer_Camera_Actions takes nothing returns nothing
    endfunction

    function InitTrig_Multiplayer_Camera takes nothing returns nothing
        set gg_trg_Multiplayer_Camera = CreateTrigger(  )
        call TriggerRegisterTimerEventPeriodic( gg_trg_Multiplayer_Camera, 0.04 )
        call TriggerAddAction( gg_trg_Multiplayer_Camera, function Trig_Multiplayer_Camera_Actions )
    endfunction


    Now we have to make a couple of locals:

    Code (vJASS):
    function Trig_Multiplayer_Camera_Actions takes nothing returns nothing

        //For the pathability trick we need a boolean.
        local boolean b
        //We need this integer for a loop.
        local integer i = 1
        //A location we need later on
        local location l
        //Represents the angle of attack.
        local real aoa
        //Represents the view distance.
        local real v
        //X of the unit the camera will be focusing on.
        local real x
        //Y of the unit the camera will be focusing on.
        local real y
        //Z of the location the unit the camera focuses on is located.
        local real z
        //New X.
        local real x1
        //New Y.
        local real y1
        //New Z.
        local real z1

        //Earlier we created location l, now we null it, so it does not leak.
        set l = null

    endfunction


    function InitTrig_Multiplayer_Camera takes nothing returns nothing
        set gg_trg_Multiplayer_Camera = CreateTrigger(  )
        call TriggerRegisterTimerEventPeriodic( gg_trg_Multiplayer_Camera, 0.04 )
        call TriggerAddAction( gg_trg_Multiplayer_Camera, function Trig_Multiplayer_Camera_Actions )
    endfunction


    Now that we have our locals let's continue. In case I want the system to work for more players at once, I will create a loop. (The system can also be done without a loop, but I am assuming it will be used mostly for multiplayer maps, so you have to convert it on your own if you don't.)


    Code (vJASS):
    function Trig_Multiplayer_Camera_Actions takes nothing returns nothing

        local boolean b
        local integer i = 1
        local location l
        local real aoa
        local real v
        local real x
        local real y
        local real z
        local real x1
        local real y1
        local real z1

        loop
        //The loop will end when i equals to 1.
        exitwhen i == 1
        //This will count every time the loop runs.
        set i = i + 1
        endloop

        set l = null
    endfunction


    If you want the system to work for more players just change the exitwhen i == x. x = players who should have the camera. In this case only Player(1). If you have exitwhen x == 2, the camera will work for Player(1) and Player(2) etc...

    In the demo map I have a variable udg_player[index] which is the unit the camera is attached to. As long as it is a unit variable it doesn't matter what it is called. For this code it is important though that the index is the same number as the owning player.

    Player(1) owns udg_player[1], Player(2) owns udg_player[2] etc...

    Code (vJASS):

    function Trig_Multiplayer_Camera_Actions takes nothing returns nothing

        local boolean b
        local integer i = 1
        local location l
        local real aoa
        local real v
        local real x
        local real y
        local real z
        local real x1
        local real y1
        local real z1

        loop
            //With this we make sure the camera only works when udg_player[i] is a unit that is alive. We will place the rest of the functions inside this "if".
            if udg_player[i] != null and (GetWidgetLife(udg_player[i]) > 0.405) then
            endif  
            exitwhen i == 1
            set i = i + 1
        endloop

        set l = null
    endfunction


    function InitTrig_Multiplayer_Camera takes nothing returns nothing
        set gg_trg_Multiplayer_Camera = CreateTrigger(  )
        call TriggerRegisterTimerEvent(gg_trg_Multiplayer_Camera,0.04,true)
        call TriggerAddAction( gg_trg_Multiplayer_Camera, function Trig_Multiplayer_Camera_Actions )
    endfunction


    I have made sure the camera is only works if udg_player is an alive unit. This way we can spare some performance if for instance Player(1) doesn't have a unit, the functions inside the "if" wont be triggered, but you can have the conditions the way that fits your map.

    Now I am going to add a lot of new things, but I will explain what they do in comments between the jass lines.

    Code (vJASS):
    function Trig_Multiplayer_Camera_Actions takes nothing returns nothing

        local boolean b
        local integer i = 1
        local location l
        local real aoa
        local real v
        local real x
        local real y
        local real z
        local real x1
        local real y1
        local real z1

        loop
             if udg_player[i] != null and (GetWidgetLife(udg_player[i]) > 0.405) then
                //v is the view distance.
                //25 will be the closest possible view distance.
                //I will explain it more detailed later on.
                set v = 25
                //We get X of udg_player[i] and store it into real x.
                set x = GetUnitX(udg_player[i])
                //We get Y of udg_player[i] and store it into real y.
                set y = GetUnitY(udg_player[i])
                //We set l according to the X and Y we just gained which you stored into the reals x and y.
                set l = Location(x, y)
                //We set Z of location l to real z.
                //This will be used later to change the angle according to the height.
                set z = GetLocationZ(l)
                call RemoveLocation(l)
                //We set a new X to real x1 from real x.
                set x1 = x + 64 * Cos(GetUnitFacing(udg_player[i]) * bj_DEGTORAD)
                //We set a new Y to real y1 from real y.
                set y1 = y + 64 * Sin(GetUnitFacing(udg_player[i]) * bj_DEGTORAD)
                //The two functions above are used to get the X and Y of a location in front of udg_player[i] according to its facing.
                //Then we set a new location to those real X and real Y.
                set l = Location(x1, y1)
                //We set a new height.
                //Now we have stored two different terrain heights in real z and real z1.
                //These will be used to set the camera angle according to the terrain.
                set z1 = GetLocationZ(l)
                call RemoveLocation(l)
            endif  
        exitwhen i == 1
        set i = i + 1
        endloop

        set l = null
    endfunction


    function InitTrig_Multiplayer_Camera takes nothing returns nothing
        set gg_trg_Multiplayer_Camera = CreateTrigger(  )
        call TriggerRegisterTimerEvent(gg_trg_Multiplayer_Camera,0.04,true)
        call TriggerAddAction( gg_trg_Multiplayer_Camera, function Trig_Multiplayer_Camera_Actions )
    endfunction


    We now have two different Z values which we are going to use for setting the angle of attack according to the terrain later on.

    Now I am going to check if there are any pathing blockers behind the unit. If that is the case, the distance to target of the camera will be decreased so that it does not collide with obstacles.

    Code (vJASS):
    function Trig_Multiplayer_Camera_Actions takes nothing returns nothing

        local boolean b
        local integer i = 1
        local location l
        local real aoa
        local real v
        local real x
        local real y
        local real z
        local real x1
        local real y1
        local real z1

        loop
             if udg_player[i] != null and (GetWidgetLife(udg_player[i]) > 0.405) then
                set v = 25
                set x = GetUnitX(udg_player[i])
                set y = GetUnitY(udg_player[i])
                set l = Location(x, y)
                set z = GetLocationZ(l)
                call RemoveLocation(l)
                set x1 = x + 64 * Cos(GetUnitFacing(udg_player[i]) * bj_DEGTORAD)
                set y1 = y + 64 * Sin(GetUnitFacing(udg_player[i]) * bj_DEGTORAD)
                set l = Location(x1, y1)
                set z1 = GetLocationZ(l)
                call RemoveLocation(l)
                //This loop will check every location behind udg_player[i] for pathing blockers.
                loop
                    //As mentioned the minimum view distance was real v, which equals to 25.
                    //Now we get the X and Y of a location real v behind udg_player[i].
                    set x1 = x - v * Cos(GetUnitFacing(udg_player[i]) * bj_DEGTORAD)
                    set y1 = y - v * Sin(GetUnitFacing(udg_player[i]) * bj_DEGTORAD)
                    //Then we check if this location is pathable.
                    //If it is not, we will set real v = v + 25, and check if that location is walkable etc...
                    //If any location lower than the distance of 450 is not walkable we store that information into real v.
                    //Then we set real = 450, and that will be the maximum view distance.
                    set b = IsTerrainPathingType(x1, y1, TERRAIN_PATHING_WALKABLE)
                        if ((b == true) and (v < 450)) then
                        set v = v + 25
                        elseif ((b == false) or (v == 450)) then
                        exitwhen true
                        endif
                endloop
            endif  
        exitwhen i == 1
        set i = i + 1
        endloop

        set l = null
    endfunction


    function InitTrig_Multiplayer_Camera takes nothing returns nothing
        set gg_trg_Multiplayer_Camera = CreateTrigger(  )
        call TriggerRegisterTimerEvent(gg_trg_Multiplayer_Camera,0.04,true)
        call TriggerAddAction( gg_trg_Multiplayer_Camera, function Trig_Multiplayer_Camera_Actions )
    endfunction


    The loop that was added above will check every location with a 25 space in a straight line behind the unit the camera focuses on. If it encounters any obstacles it will store that distance into v. v will later be used to set the camera distance. The reason I set v = 25 above, was to make sure camera doesn't get that close it goes inside the unit it focuses on If v was to be 0, the distance to target of the camera would become 0, and we would view udg_player from inside.

    The number 450 is the maximum view distance if nothing else blocks the camera. You can change the value to whatever you want, but you should change it at both places, and they have to be equally large, otherwise the system will bug.

    Anyway, let's continue, we are almost done!

    Code (vJASS):
    function Trig_Multiplayer_Camera_Actions takes nothing returns nothing

        local boolean b
        local integer i = 1
        local location l = Location(0,0) //declare this in the beginning
        local real aoa
        local real v
        local real x
        local real y
        local real z
        local real x1
        local real y1
        local real z1

        loop
             if udg_player[i] != null and (GetWidgetLife(udg_player[i]) > 0.405) then
                set v = 25
                set x = GetUnitX(udg_player[i])
                set y = GetUnitY(udg_player[i])
                call MoveLocation(l,x,y) //instead, just move the location to prevent several location creations/removals
                set z = GetLocationZ(l)
                set x1 = x + 64 * Cos(GetUnitFacing(udg_player[i]) * bj_DEGTORAD)
                set y1 = y + 64 * Sin(GetUnitFacing(udg_player[i]) * bj_DEGTORAD)
                call MoveLocation(l,x1,y1)
                set z1 = GetLocationZ(l)
                loop
                    set x1 = x - v * Cos(GetUnitFacing(udg_player[i]) * bj_DEGTORAD)
                    set y1 = y - v * Sin(GetUnitFacing(udg_player[i]) * bj_DEGTORAD)
                    set b = IsTerrainPathingType(x1, y1, TERRAIN_PATHING_WALKABLE)
                        if ((b == true) and (v < 450)) then
                        set v = v + 25
                        elseif ((b == false) or (v == 450)) then
                        exitwhen true
                        endif
                endloop
                //The following "if" works for Player(1), the owner of udg_player[i].
                if GetLocalPlayer() == Player(i - 1) then
                    //We change the rotation according to udg_player[i].
                    call SetCameraField(CAMERA_FIELD_ROTATION, GetUnitFacing(udg_player[i]), 0.08)
                    //We set the view distance to real v.
                    call SetCameraField(CAMERA_FIELD_TARGET_DISTANCE, v, 0.08)
                    //We change the angle of attack according to the terrain.
                    //By changing the number 335, the angle of attack will change ingame.
                    call SetCameraField(CAMERA_FIELD_ANGLE_OF_ATTACK, 335 + ((z1 - z) * 0.5), 0.32)
                    //We change the field of view.
                    //I prefer to have it at 120.
                    call SetCameraField(CAMERA_FIELD_FIELD_OF_VIEW, 120, 0)
                    //Change the view distance.
                    //You can change it to whatever you like.
                    call SetCameraField(CAMERA_FIELD_FARZ, 5000, 5)
                    //We lock the camera to udg_player[i], to make sure the player doesn't reset it.
                    call SetCameraTargetController(udg_player[i], 0, 0, false)
                    //Set the height of the camera according to the height of the terrain.
                    call SetCameraField(CAMERA_FIELD_ZOFFSET, GetCameraField(CAMERA_FIELD_ZOFFSET) + z - GetCameraTargetPositionZ() + 128, -0.01)
                    call SetCameraField(CAMERA_FIELD_ZOFFSET, GetCameraField(CAMERA_FIELD_ZOFFSET) + z - GetCameraTargetPositionZ() + 128, 0.01)
                endif
            endif  
        exitwhen i == 1
        set i = i + 1
        endloop
       
        call RemoveLocation(l)
        set l = null
    endfunction
    function InitTrig_Multiplayer_Camera takes nothing returns nothing
        set gg_trg_Multiplayer_Camera = CreateTrigger(  )
        call TriggerRegisterTimerEvent(gg_trg_Multiplayer_Camera,0.04,true)
        call TriggerAddAction( gg_trg_Multiplayer_Camera, function Trig_Multiplayer_Camera_Actions )
    endfunction
     


    What I did above was to set the field camera of the owner of udg_player – which is Player(i) – according to the variables we had stored. If you have followed all the above steps correctly your camera system should work fine now. You can view the testing map I attached to view what mine looks like.

    Final words:

    Once again, remember to set the array of variable for the unit to the same number as the owning player. Otherwise you will have to edit some things in the system to make it work.

    I suggest you play around with the variables until you find fitting settings for your map. I can imagine there might be one or two things about this configuration you do not like, or want to change. I am happy if this tutorial was what you were after. If you think I should change something, you can tell me in a comment below, and I will consider your suggestion.

    ~Megafyr
     

    Attached Files:

    Last edited by a moderator: Apr 30, 2010
  2. aznricepuff

    aznricepuff

    Joined:
    Feb 22, 2006
    Messages:
    749
    Resources:
    4
    Maps:
    2
    Spells:
    1
    Tutorials:
    1
    Resources:
    4
    You should probably stop using
    Player(i-1)
    and just use
    Player(i)
    . Anybody who has any hope of understanding this tutorial is going to have to know JASS anyway, and there's no reason why anybody using JASS should still be working with converted player ids.
     
  3. Cweener

    Cweener

    Joined:
    Oct 10, 2009
    Messages:
    1,440
    Resources:
    1
    Maps:
    1
    Resources:
    1
    hmmm. its kind of hard to click directly in front of you. anyways to say the camera higher perhaps?
     
  4. .Ragnaros

    .Ragnaros

    Joined:
    Aug 13, 2009
    Messages:
    181
    Resources:
    0
    Resources:
    0
    Cweener, this kind of cameras are made for RPGs which have Arrow Keys movement system.
     
  5. Megafyr

    Megafyr

    Joined:
    Oct 29, 2007
    Messages:
    957
    Resources:
    1
    Tutorials:
    1
    Resources:
    1
    Bump. Can a moderator please review this? :)
     
    Last edited: Mar 24, 2010
  6. Megafyr

    Megafyr

    Joined:
    Oct 29, 2007
    Messages:
    957
    Resources:
    1
    Tutorials:
    1
    Resources:
    1
    Bump once again. "Tis said that once each decade, the light of this thread shines upon the eyes of a moderator, so that this thread might be reviewed!"
     
    Last edited: Mar 24, 2010
  7. Darkness-4ever

    Darkness-4ever

    Joined:
    Nov 4, 2007
    Messages:
    763
    Resources:
    0
    Resources:
    0
    Moderator of tutorials got disappeared by unknown forces, your tutorial will never make it past submission part.
     
  8. PurgeandFire

    PurgeandFire

    Code Moderator

    Joined:
    Nov 11, 2006
    Messages:
    7,429
    Resources:
    18
    Icons:
    1
    Spells:
    4
    Tutorials:
    9
    JASS:
    4
    Resources:
    18
    @Darkness-4ever: Have no fear! =D

    @Megafyr: Great tutorial. I have to say, it is definitely an awesome effect. I would have gone with a 0.03125 interval (32 fps) but that is just me. =P I updated the code with the native equivalent of the event, and I also just reused the location by moving it rather than creating/destroying one each part of the loop. That way, it is a bit more efficient.

    Overall though, great job.

    ~Approved.
     
  9. Megafyr

    Megafyr

    Joined:
    Oct 29, 2007
    Messages:
    957
    Resources:
    1
    Tutorials:
    1
    Resources:
    1
    Thank you very much :D
     
  10. ChaosKnight

    ChaosKnight

    Joined:
    Jul 18, 2009
    Messages:
    196
    Resources:
    1
    Spells:
    1
    Resources:
    1
    set gg_trg_Init = CreateTrigger()

    Didnt know the name of your Init trigger.

    Nvm, thats not very good. Use local trigger.
    local trigger T = CreateTrigger()


    Anyways, awsome tutorial.
     
  11. Bommelding

    Bommelding

    Joined:
    Aug 31, 2009
    Messages:
    20
    Resources:
    0
    Resources:
    0
    can you post another link for the pathability check cause i can't open this one..