In today's episode of "everything is a shiny black material because I thought it was at least better than the default UE4 grey squares", we have the final pieces of basic cover movement done. To be honest, I can hardly believe it... I had scheduled to be where I currently am by March, I've just been putting so much time into this that things are moving faster than expected (which is good!)
I put together a video showing the new movement, how moves can be canceled into each other, and a little bit of the inclined cover movement at the end. As always, check it out for a bit of extra context on the article:
Alright, let's see what we got done this week...
What's "Advanced" Cover Movement?
The three actions I consider "advanced cover movement" are moving out of cover from an edge, cancelling a cover slide into a vault, and moving from cover to cover. They are not difficult to do per say, but I classify them like this because I'd consider them not "necessary" to actually playing the game. If you can get in cover, move in cover, and vault over cover, you can get anywhere on the map and you can play, whereas these are extra options to give players an edge over their opponents.
In the next three sections, I'll give a high-level overview of each of these three features and how I implemented them in my game.
Move Out of Cover
Moving out of cover happens when a player is at the edge of a wall, they are holding their left stick towards the wall, and then they press the "Action Button". The player will then move to the side and a little bit forward to get around cover. The way I've implemented this is as follows:
- Code detects that all the necessary conditions to perform "Move Out of Cover" have been met and runs the "MoveOutOfCover" function.
- The function calculates a position to the left/right of the player (depending on which edge they are on) that is a constant distance away horizontally. It calls the "MoveComponentTo" function, which moves the player there over about 0.15 seconds. Finally, it sets the player's acceleration to a very high value (this makes it so that the player doesn't feel like they're being "delayed" or "jerked" backwards after the move completes).
- In-place animation of the character moving around cover begins to play.
- After moving horizontally, the "MoveComponentTo" function callback is another function called "MoveOutOfCover2". This function performs a similar calculation and movement, but this time in a forward direction, over an ever so slightly smaller amount of time (about 0.10 seconds). Because the player is already rotated in the right direction by this point due to the previous function call, I just use the forward vector of the capsule for the direction of this second movement.
- After both "MoveComponentTo" functions finish, we reach the final callback function which sets variables as needed and returns the player to their original acceleration after less than a second.
Biggest take away from this for me was the acceleration thing. I had never considered the subtlety of how it feels to move out of cover, and how different it is depending on whether you move from zero velocity (i.e. "idle") vs. cancelling into it from a cover slide. In my case, I wanted to make sure the player was able to quickly move out either way without feeling any sense of being held back, so increasing the acceleration so that they essentially reach sprint speed "instantly" took care of that.
I can see situations where one might want to adjust this acceleration based on whether the player starts from zero velocity or not, but for now I like how this feels. Might play with it more as the game becomes more fleshed out.
Vault While Sliding
Vault while sliding refers to cancelling a slide to cover and going straight into a vault. The way I've seen this done in most games is they quickly move you to the end of the cover slide and then vault you over, but I really wanted the player to just jump over it like a hurdle without losing much (if any) momentum:
- If the player presses and releases the "Melee" button while cover sliding, code checks to see whether the conditions are met for the player to cancel into a vault.
- One of these conditions is a line trace out to the side to check if there is actually a wall there to vault over. For example, imagine the player is sliding towards the edge of cover from the left, and they press the button to vault before they actually have any cover to their right to vault over. We could either wait for them to reach cover and then vault, or just finish the slide normally. In my case, I chose the latter option.
- The same "VaultCover" function that was used for idle vaulting is called, but we use some checks based on a boolean (which I'm calling "bVaultWhileSliding") to adjust things like the distance and time required to get over cover (consider that the distance will vary based upon when the player presses the button, so we need to add the remaining distance to the wall to the original VaultDistance value contained in the CoverBox itself).
- Acceleration is adjusted pre- and post-vault, much like Move Out of Cover, to prevent any feeling of delay when walking/sprinting after the vault.
And wallah, we play a different animation and it works like a charm! Definitely was glad that I was able to extend my original VaultCover function so easily to get this implemented.
To be honest, I've called this SWAT since like... forever, but I can't remember why. And I tried to find another instance of someone using the term "SWAT" to describe this motion, but I couldn't, so I'm slightly worried people are like "what the hell is a SWAT?" when I say it, but oh well.
Basically, it's moving from one wall of cover to another wall over a gap. I'm planning on having separate animations for "short" and "long" SWATs, but for now I just play the normal "cover walking" animation because I didn't want to waste time looking for more animations I'll be replacing later anyway. The actual functionality is working great in any case:
- Code checks to ensure the required conditions for SWAT-ing are met. There is a set angle the player can SWAT at (for example, you couldn't have your camera pointed directly forward towards the cover you want to SWAT to, nor could you SWAT backwards; it needs to be somewhat positioned to your right/left, otherwise you accidentally SWAT sometimes and it feels really stupid!)
- The "MoveComponentTo" function is used to move the player to the location of the SwatComponent reference contained in the CoverEdgeBox they are currently on. The SwatComponent reference is just a reference to the ArrowComponent of the edge we're going to SWAT to. Since you can never SWAT to multiple possible edges from the same box, it makes sense for me to do it this way.
- A timer is started which sets how long the player has until they can no longer cancel a SWAT. This ensures that the player can't cancel their SWAT forwards when there's a cover wall already in front of them (something like 0.05 or 0.1 seconds before reaching the wall after a SWAT).
Also note that the acceleration trick is being used here as well, to make sure movement speed is consistent after SWAT-ing. This might change if I decide to add a delay after a SWAT, but that's unlikely since I want to make it very easy for the player to get out of cover whenever they want to (something I've talked about in previous articles when touching on my philosophy for designing the cover system, especially DevLog #2).
Implementing this was a bit interesting in terms of deciding how robust I wanted the solution to be. What I mean by that isn't that the solution is flawed in any way, just that I had an option to either use "more code" or to use "more in-editor design". I opted for the latter in the end, because I'm a stickler for using as little code as possible to accomplish things.
Basically, the issue was that I needed to calculate the correct "Z" value so that the character moves up or down an incline smoothly without either A) clipping through the ground and being awkwardly pushed up by the physics system, or B) ending up too high at the end of the slide and being awkwardly pulled down by gravity.
There's a low tolerance for error to keep things feeling smooth; something like 3 cm above and 1 cm below the desired position, IIRC. What I ended up doing was checking to see how my actor's Z position on a flat surface compares to the distance from an ArrowComponent to the ground on a flat surface, because if those values are the same, we're good and we can just move to the ArrowComponent's Z location. Turns out this value can vary depending on the incline amount, surface, etc.
So, I created a function, which will only be used for internal debugging, which calculates the distance from the Character's location to the ground and from the desired ArrowComponent's location to the ground. It also draws some pink line traces, which look this:
The function then logs the two distances and tells me how far up or down on the Z-axis (world location Z-axis, not the relative location Z-axis of the ArrowComponent) I need to move the ArrowComponent so it's right where it needs to be (screenshot below was taken after I adjusted the components, hence the low error):
And then... I just do that! I move the component in the editor, and like magic, I can move to the ArrowComponent's Z location and everything works as it should. Like I said at the beginning of this section, it could be a more "robust" solution in code. I could get the two distances, get the distance of the player character to the ground right before reaching the box, run another MoveComponentTo function and adjust the Z position accordingly, etc., but... I really prefer this. It's easy to move the components, it works, and there's less code mess, so yay!
It's crazy how fast things are moving along considering I'm developing this by myself >_< Definitely motivates me to keep the pace going! My next steps for the upcoming week are...
- Find and squash as many bugs as possible (1 day)
- Refine movement values (times to move, distances, delays, etc.) (0.5 - 1 day)
- Work on some of the magic FX related to weapons. This includes the weapon dissolving and reappearing when the player switches hands or equips a new weapon, as well as the FX that play when the player "charges" their weapon while in Magic Firing Mode. (1 day)
- Shooting over cover. Make sure player can aim over cover a reasonable amount. Possibly use center-screen shooting at all times while the player is in cover to avoid some of the frustration of shooting the top of cover when not intended. (1 - 2 days)
- Any remaining time will be spent starting on networking. My first checkpoint is to get multiple players into a lobby, then into a map, then back into a lobby post-match, and then into another map.
- Flash <3