Simplistic Cover in Unreal Engine (2): Character States and Input Handling

Previous Post:
Simplistic Cover in Unreal Engine (1): The Cover Slide and Cover Point Selection - Memories of Melon Pan

Now that we got our basic cover point selection out of the way, we have to make the player move towards it. While we're at it, we can set up the player character for cover movement.

The basics... again, are basic. Right now, we only have a few states the player can be in: he starts in normal movement, then can be in a state where he's sliding to some cover point. When he gets there, he's fully under cover and moves around the object he's using as cover. That's three states.

So, we can define three character states.

UENUM(BlueprintType)
enum class ECoverState : uint8 { Normal = 0, Sliding, Cover };

When we calculate the point to slide to, we can store both that point and the direction to cover as a class variable, then change the character's state to Sliding. Then, every frame we move the player towards that point... but only if he's sliding. When he hits that piece of cover, we change his state to Cover.

(Header file)
ECoverState CoverState;
FVector CoverDirection;
FVector CoverSlidePoint;

At each step of the way, we gotta decide what we're going to do about movement input from the player. If he says move forwards, normally he would... unless he's using the cover system in some way. If he's sliding to cover, we're ignoring anything the player says until he gets to cover. Once he's under cover, he'll move forward... but only along the cover surface. Our movement functions have to do all of these. If you're coming straight out of the Unreal Engine tutorials like I was, then you'll probably have made your two movement functions MoveForward and MoveRight. Also, since we're taking over the inputs when we're sliding, we're going to want to look at the Tick function (inherited from ACharacter), and slip in inputs there.

In a nutshell, here's what we gotta do in each state.

ECoverState::Sliding

  • Tick has to move the player towards cover
  • MoveForward and MoveRight does nothing

ECoverState::Cover

  • MoveForward and MoveRight must move the player along cover

The sliding part is pretty easy. For MoveForward and MoveRight, a simple check for ECoverState::Sliding is all that's needed. If that's the character state, do nothing - we're ignoring player input. Instead, we're putting adding our own input vector in the Tick function.

When we calculated which cover to slide to, we also calculated the point on the cover surface to move to. All we need to do is move towards that point as if the player input that direction on the controller.

FVector Direction = (this->CoverSlidePoint - this->GetActorLocation());
Direction.Normalize();


AddMovementInput(Direction, 1.0f);

... And honestly, when we're in ECoverState::Cover, we're doing much of the same thing, except in the MoveForward and MoveRight functions. See, I already said I did this in the simplest way possible, so what I'm doing in the movement functions is accepting player input, then adding more input which moves the character towards cover. Unreal Engine's own class, UCharacterMovementComponent, combines both of these vectors and calculates where the character should move to, which in this case will always be along cover.

So, this is going to be pretty disappointing, but those functions'll look something like this.

MoveForward_Normal(Value, OutDirection, OutValue);
AddMovementInput(OutDirection, OutValue);
AddMovementInput(this->CoverDirection);


Where:

  • MoveForward_Normal is the movement function in ECoverState::Normal,
  • OutDirection is an output variable, the direction to move in,
  • OutValue is an output variable, the intensity of the movement, and
  • AddMovementInput is the movement function that adds a movement vector to UCharacterMovementComponent

The structure for the functions I made were a just a tad more complicated than this - I had some helper functions running around - but generally this is how it went.

The reason we're just adding a movement vector is largely because of curved surfaces. With a flat surface, it's pretty easy to calculate its tangent, and then make your movement go around that tangent vector. The tangent of a curved surface changes all the time, though. Even for a simple cylinder, you can't move along the tangent at any given point or you'll slowly spiral away from the cylinder. Imagine what ducking behind a wavy surface would be like. So it was infinitely easier to add an additional movement vector to move the player back towards cover.

Either way, once you're behind cover, it's a good idea to store the direction to cover and recalculate it every frame. We can do this by running a line trace every frame that goes in the last known direction to cover, then seeing what point it hits. If it hits nothing, or hits something too far away, then whatever we've been using as cover has disappeared, so we have to get out of cover mode. Once again, if you're taking cover behind a curved surface (e.g. a cylinder), the direction to cover is going to change as you move around it.

At its simplest, this is a cover system, but we're kinda missing something else - cover edges. See, if we did things like this and were walking across a flat surface, we'd eventually pass a corner. Now, we can release the player from cover if it's too far away. We also do this if the cover ceases to exist for any reason (for instance, it gets blown up). And if we do this, we might be able to walk past that corner and automatically come out of cover mode.

But what if we want to stop movement at that corner? This is where things get more complicated, but I'll save that for another post.

Next Post:
http://d.hatena.ne.jp/caelk/20160419/1461043147