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

RPG Movement System

Overview

RPG Controls is a variant of my Click-And-Hold Movement system that is tailored towards RPGs and other single-hero maps. It works well with Fixed Camera Lock.

Make your RPG more polished by replacing the awful default movement system that floods the player's ears with meaningless, repetitive unit emotes ("A sound plan." "For honor." "Of course." "A sound plan." "For honor." ad infinitum) and improve the user-experience for your players.

Features:
  • Click-and-hold (mouse-drag) movement: A very welcome addition to any RPG. You can spare your players carpal tunnel syndrome by not having them click every single time they want to move to a new location.
  • Auto-walk toggle: With a key press (TAB by default), the player's hero will start moving automatically towards the mouse-cursor until it is disabled or another command is given.
  • Disabled pathing: You can change the pathing AI of the heroes by making them ignore obstacles and simply walk towards the direction of the mouse-cursor. This better reflects the implementation of most isometric RPGs like Diablo and avoids the frustrating problem that the hero sometimes runs directly into monsters because you clicked behind a wall by accident.

Installation

Copy the RPGControls script file into your map. You also need TotalInitialization. All other requirements are optional. These are:
  • Fixed Camera Lock: If you're using a locked camera, I highly recommend using this particular system as RPG Controls is specifically designed to work with it without issues.
  • World2Screen Transform Synced: Required if you want to to integrate the system with Fixed Camera Lock.
  • Synced Cameras: Required for World2Screen Transform Synced.
  • Pathing Wizard: Required if you want to disable pathing AI.

Once installed, edit the GetHeroOfPlayer function to return the main hero of each player. Activate the system with EnableRPGControls().

Singleplayer version

The singleplayer version shares the Total Initialization and Pathing Wizard requirements, but differs in that it works well with any camera lock system, not just Fixed Camera Lock. If you're using a locked camera, I recommend installing World2Screen Transform (async version).
Contents

RPGControls (Map)

RPGControls (Binary)

RPGControlsSingleplayer (Binary)

Reviews
Wrda
Issue fixed in this latest update. Approved again.
Hey this seems interesting. Can you elaborate a bit more on this part?
edit the GetHeroOfPlayer function to return the main hero of each player

I see in your example map that your function returns
Code:
return FCL_Anchor[whichPlayer]
, which is a table type variable. I have a single player offline map where I just want to set one unit to be the movement unit. Let's say my unit variable is udg_Hero. How can I set the movement unit for player 0 to be this unit? I'm not that versed in Lua and struggle to figure it out. In your "Test Map" script you're running a function on init that sets a local variable equal to GetPrePlacedWidget("Hvwd"), but changing this to be local sylvanas = udg_Hero does not seem to work, but rather breaks the function operations, stopping the function from running altogether.

So yeah, my question is basically if you can provide a simple "explain it like I'm 5" explanation on how to get this working. It would be much appreciated.

Here's what I did, that didn't work:
Lua:
EnableMovementUnit = function()
        local sylvanas = udg_Adevramies
        FCL_Lock(sylvanas)
        EnableRPGControls(Player(0))
        EnableSelect(false, false)
        SelectUnitSingle(sylvanas)
end

  • Enable Movement Unit
    • Events
      • Time - Elapsed game time is 1.00 seconds
    • Conditions
    • Actions
      • Custom script: EnableMovementUnit()
I figured this was the easier way, since your system was already set to use the "sylvanas" variable, so I thought I could piggyback off of your setup instead of editing the GetHeroOfPlayer function. I did actually try to edit the GetHeroOfPlayer function first, but none of my attempts succeeded.
 
Last edited:
The GetPreplacedWidget("Hvwd") is a a function from my Preplaced Widget Indexer. It's an easy way for me to store the Sylvanas I put on the map into the sylvanas variable. I then enable the Fixed Camera Lock system and anchor it to the Sylvanas with FCL_Lock(sylvanas).

Fixed Camera Lock creates a global table (not really well documented, I will fix that) that holds the unit of each player the camera is anchored to. In the vast majority of cases, this should also be the unit the RPG Controls should be enabled for.

The GetHeroOfPlayer function takes the whichPlayer argument and returns the unit that is the main hero for that player. Since your map is a single-player map, you can ignore the whichPlayer argument. The function should return the global variable that holds the main hero unit. Since I have Fixed Camera Lock combined with RPG Controls in the test map, I don't create a separate variable and simply return the FCL_Anchor, the unit the camera is locked to for the respective player.

Whether you need to change that function depends on whether you want to use Fixed Camera Lock. Note that drag-moving with a locked camera will have some quirks if you're not using it.
 
ignore the whichPlayer argument
When you say I can ignore this argument, do you mean remove it from the function parameters completely like so?
Lua:
    --This function should return the unit that should get ordered.
    local function GetHeroOfPlayer()
        return udg_Adevramies
    end

The above edit didn't work, unfortunately. Neither does this:
Lua:
    --This function should return the unit that should get ordered.
    local function GetHeroOfPlayer(whichPlayer)
        return udg_Adevramies
    end

I have my own camera lock system that builds off a translated version of this system, so I deleted the Fixed Camera Lock system.
 
When you say I can ignore this argument, do you mean remove it from the function parameters completely like so?
Lua:
    --This function should return the unit that should get ordered.
    local function GetHeroOfPlayer()
        return udg_Adevramies
    end
You don't have to remove the function parameter, but simply not use it. But it doesn't matter either way, this should work fine.

The above edit didn't work, unfortunately. Neither does this:
Lua:
    --This function should return the unit that should get ordered.
    local function GetHeroOfPlayer(whichPlayer)
        return udg_Adevramies
    end
From what you're posting here, it should work. Can you pm me your triggers so I can take a look?

I have my own camera lock system that builds off a translated version of this system, so I deleted the Fixed Camera Lock system.
Hm, that system looks like it accomplishes the same thing as my system, albeit way more verbose. I would recommend making the switch. However, since your map is a single-player map, I can make a version that doesn't synchronize camera positions and it will work just fine even without Fixed Camera Lock. I'll just need to change two lines.
 
I think I figured out what the problem is.

You said you deleted Fixed Camera Lock, but you're still doing FCL_Lock(sylvanas) in your Init function. However, that function doesn't exist anymore so the script crashes.

Since you're a GUI user, I don't assume you're using Debug Utils, so the thread just silently aborts.
 
I think I figured out what the problem is.

You said you deleted Fixed Camera Lock, but you're still doing FCL_Lock(sylvanas) in your Init function. However, that function doesn't exist anymore so the script crashes.

Since you're a GUI user, I don't assume you're using Debug Utils, so the thread just silently aborts.
That was one of the ways I tried initializing, but my main way to initialize is like so:
  • Enable Movement Unit
    • Events
      • Time - Elapsed game time is 5.00 seconds
    • Conditions
    • Actions
      • Custom script: EnableRPGControls(Player(0))
I also tried:
  • Enable Movement Unit
    • Events
      • Time - Elapsed game time is 5.00 seconds
    • Conditions
    • Actions
      • Custom script: EnableRPGControls()
Both of these works, and initializes the function. I also added a print ("hello world") which is accurately being printed when I hold the mouse button, but my character is not moving.
 
Guessing until I get it right: You have NO_PATHING in the config enabled, but did not import Pathing Wizard. You need to disable that option if you're not using it. The warning message for the missing library / invalid config was not working. Apologies. I updated the script.
That fixed it, thanks! :)
 
Sorry for the double post, but I have to commend you on this system. I have tried various other movement systems, but none of them integrated well with my campaign. Specifically, there were consistent movement stutter bugs when transitioning/loading between maps in the other systems. This system does not have that stutter from what I can tell. Also works seamlessly with my walk/run system:

 
Great to hear, thank you very much! Is the toggle auto-walk working as well?
I haven't tested that, but I'll make sure to test it for the sake of testing (I'm not gonna have auto walk in my campaign). I'll edit this post with an update on the auto walk.

Taking the opportunity to flex your terrain, I can respect it :plol:.
Haha sorry, was more to showcase your movement system. I'll remove the video if you feel it hijacks your thread :p

EDIT: After testing your auto walk feature a bit, I gotta say it's a nice addition. I'd love to use it, but my systems introduces some issues. For example, I have a walk/run system that makes the character either run or walk. Pressing the hotkey for the walk and run ability stops the auto walk for some reason. Here's my run/walk triggers:

  • Walk
    • Events
      • Unit - A unit Starts the effect of an ability
    • Conditions
      • Walking Equal to False
      • (Ability being cast) Equal to Walk
    • Actions
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • InCave Equal to True
        • Then - Actions
          • Animation - Add the alternate animation tag to Adevramies
          • Unit - Set Adevramies movement speed to 200.00
          • Unit - Set Unit: Adevramies's Real Field: Animation - Run Speed ('urun') to Value: 120.00
          • Unit - Set Unit: Adevramies's Real Field: Animation - Walk Speed ('uwal') to Value: 120.00
          • Wait 0.00 seconds
          • Set VariableSet Walking = True
        • Else - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • Indoors Equal to True
            • Then - Actions
              • Game - Display to (All players) the text: |cffff0000You can't...
            • Else - Actions
              • Animation - Add the alternate animation tag to Adevramies
              • Unit - Set Adevramies movement speed to 200.00
              • Unit - Set Unit: Adevramies's Real Field: Animation - Run Speed ('urun') to Value: 120.00
              • Unit - Set Unit: Adevramies's Real Field: Animation - Walk Speed ('uwal') to Value: 120.00
              • Wait 0.00 seconds
              • Set VariableSet Walking = True
  • Run
    • Events
      • Unit - A unit Starts the effect of an ability
    • Conditions
      • Walking Equal to True
      • (Ability being cast) Equal to Walk
    • Actions
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • InCave Equal to True
        • Then - Actions
          • Animation - Remove the alternate animation tag to Adevramies
          • Unit - Set Adevramies movement speed to 522.00
          • Unit - Set Unit: Adevramies's Real Field: Animation - Run Speed ('urun') to Value: 260.00
          • Unit - Set Unit: Adevramies's Real Field: Animation - Walk Speed ('uwal') to Value: 260.00
          • Wait 0.00 seconds
          • Set VariableSet Walking = False
        • Else - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • Indoors Equal to True
            • Then - Actions
              • Game - Display to (All players) the text: |cffff0000You can't...
            • Else - Actions
              • Animation - Remove the alternate animation tag to Adevramies
              • Unit - Set Adevramies movement speed to 522.00
              • Unit - Set Unit: Adevramies's Real Field: Animation - Run Speed ('urun') to Value: 260.00
              • Unit - Set Unit: Adevramies's Real Field: Animation - Walk Speed ('uwal') to Value: 260.00
              • Wait 0.00 seconds
              • Set VariableSet Walking = False
EDIT 2: It should be noted that the ability is based on wind walk, so it doesn't break animations, orders etc.
 
Last edited:
I discovered what I think is a bug in your system. When I remove all the optionals, leaving only the TotalInit and your system, and initialize your function like this:
  • Enable
    • Events
      • Map initialization
    • Conditions
    • Actions
      • Set VariableSet sylvanas = Sylvanas Windrunner 0001 <gen>
      • Camera - Lock camera target for Player 1 (Red) to sylvanas, offset by (0.00, 0.00) using Default rotation
      • Custom script: EnableRPGControls()
The movement unit will start to "stutter" if I hold the mouse still, while holding down the movement button. This happens when I stop moving the mouse, and the unit reaches the point where I stopped moving the mouse. See video for example:

I'm also attaching a map so you can see what I have done. Full list of changes below:
  • Removed all optional stuff
  • Removed your "Test Map" script
  • Made my own "Enable" GUI trigger
  • Disabled auto walk feature (have also tried enabling it, outcome is the same)
  • Set NO_PATHING to false

I guess since the mouse natives requires the mouse to actually move for them to track the mouse, the target point of the movement order remains unchanged. It works fine with all the optional stuff though, so you have some sort of workaround which breaks when removing the optional stuff (just my theory).
 

Attachments

  • RPGControls - Stutter bug.w3x
    417.1 KB · Views: 1
I guess since the mouse natives requires the mouse to actually move for them to track the mouse, the target point of the movement order remains unchanged. It works fine with all the optional stuff though, so you have some sort of workaround which breaks when removing the optional stuff (just my theory).
Yes, if you're using a locked camera and the mouse is not moving, the mouse position will not get updated. The workaround is to calculate the mouse screen position with World2Screen Transform and store it, then move the unit to the point in the world calculated with the reverse transform function using that screen position.

This is only necessary when you have a locked camera, so if you disable Fixed Camera Lock, it isn't done. The reason other locked camera systems aren't supported is that camera positions are async, so the World2Screen transform function would return a non-synchronized result and therefore the players would not agree on the locations of the order given and it would cause a desync.

So, camera positions have to be constantly synced. However, the synced camera position would always lag behind the actual position due to latency and therefore the ordered location would be inaccurate. The workaround here is that, because we know that the camera eye position is the anchor unit, we can use that unit position instead of syncing the camera position. Then we only need to sync camera distance, angle of attack, and rotation, which a player barely ever changes during the game.

The reason this only works with Fixed Camera Lock is that, without the elimination of the z-offset, there is an additional free camera parameter which we cannot obtain other than by syncing it.

A single-player map makes all of this obsolete and I can make a version of RPG Controls that works well with any camera lock system.
 
Yes, if you're using a locked camera and the mouse is not moving, the mouse position will not get updated. The workaround is to calculate the mouse screen position with World2Screen Transform and store it, then move the unit to the point in the world calculated with the reverse transform function using that screen position.

This is only necessary when you have a locked camera, so if you disable Fixed Camera Lock, it isn't done. The reason other locked camera systems aren't supported is that camera positions are async, so the World2Screen transform function would return a non-synchronized result and therefore the players would not agree on the locations of the order given and it would cause a desync.

So, camera positions have to be constantly synced. However, the synced camera position would always lag behind the actual position due to latency and therefore the ordered location would be inaccurate. The workaround here is that, because we know that the camera eye position is the anchor unit, we can use that unit position instead of syncing the camera position. Then we only need to sync camera distance, angle of attack, and rotation, which a player barely ever changes during the game.

The reason this only works with Fixed Camera Lock is that, without the elimination of the z-offset, there is an additional free camera parameter which we cannot obtain other than by syncing it.

A single-player map makes all of this obsolete and I can make a version of RPG Controls that works well with any camera lock system.
I'm pretty sure I encountered this exact issue (not the desync issue) when I tried making my own movement system long ago. I circumvented it by using the unit position, and unit facing angle, and then set the target point of movement order to +100 away from the mouse location, in the unit facing direction, and then ordered the unit to move there instead. If that made sense lol. I had a periodic trigger that calculated the distance between the unit, and the distance between the point of the movement order, and if they were too close, the movement order would increment in the facing direction of the movement unit. I can't remember exactly, but it was something like that.
 
Level 1
Joined
May 20, 2024
Messages
5
Hello, do you think it would be possible to add cam lock toggle? I really love this, but I don't like the idea of being forced to locked camera else it doesnt work [if I dont use FCL_Lock(GetPreplacedWidget("H000")), it will not work. ]
 
Top