Hatena::ブログ(Diary)

Memories of Melon Pan

2016-04-19

Simplistic Cover in Unreal Engine (3): 3D Space and the Cover Search Volume

Previous Post:
http://d.hatena.ne.jp/caelk/20160223/1456219054

So right now with what we've gone over, we have the general idea of how a cover system can be set up. Now, I could've gone over basic edge detection for a general (though incomplete) overview, but we've about hit the point where reality comes and bites us in the neck.

See, if we were in a flat world with flat floors to walk on and nothing else, things would be really simple. We'd only have to design a cover system in 2D space, since the player would always stay on the same xy-plane, and the math would be really easy. But uh... that'd be pretty plain world you'd be in, and not even one that's easily believable, right? So we're going to have to start thinking about how to design our cover system so that it can handle the one thing that will wreck our mathematical world.

A triangular wedge.

f:id:caelk:20160419132322p:image

Oh dear god! Can I walk up that? Nooooo, why must this third dimension spoil my plans to do this the lazy way?!?

... All joking aside, ramps like these really will make things more complex. First, I talked about the cover search volume before - that's the invisible box that we look inside to see if there's any cover to be found around the player. If we don't align that to the floor that we're on, and if the floor we're on is a ramp or something, that box is going to go through the ramp.

f:id:caelk:20160419132317p:image

So if this ramp ends in a low wall that you could take cover behind, you won't be able to take cover behind it unless you're edge up against it. The search volume doesn't rotate - it clips through the ramp. There's going to be an entire 180 degree arc where the only thing that can be picked up from a cover search is the ramp itself. Now, there are other tests we can do to call the walkable side of the ramp invalid cover, but the bigger problem is with our search volume. We'll deal with this for now.

If we're on a ramp, our cover search needs to follow the slope of the ramp when looking for cover. Of course, when we're on flat ground, it needs to follow the flat slope of the ground.

We're going to have to apply some rotation to the cover search volume based on the floor we're on every frame, which means we'll have to make a function and call it from Tick. I called this function UpdateSearchVolumeOrientation.

We'll need to know four things.

The floor normal is pretty easy, since Unreal Engine stores that itself.

FHitResult FloorHit = this->CharacterMovement->CurrentFloor.HitResult;


if (FloorHit.bBlockingHit) {

    FloorUpVector = FloorHit.Normal;

} else { // No floor - player could be airborne.

    FloorUpVector = FVector::UpVector;

}

That first line there is where Unreal Engine stores information about the floor the character is standing on - in a hit result. You can think of it as Unreal shooting a line straight down from the character for a short distance. If it hits something, it's considered floor, and it stores the result in that FHitResult.

That FHitResult contains information about that line trace, including the normal of the surface it hit. That's our floor up vector. There's an additional few lines there that say if there is no floor (i.e. the character is falling), then we just calculate things as though the character were on flat ground, when the floor up vector is the same as the world up vector.

We're actually gonna go for the right vector next, since the calculations are much easier and a lot more lax. We can just take the cross product of the floor normal and the world up vector, and we'll get a vector that points orthogonally to the right of each. Of course, this is assuming that up in the game world is always (0, 0, 1) - if your game mucks around with gravity, you'll probably have to cross it with the negative gravity vector instead. Also, you'll have to prepare for times when the floor normal is the same as the up vector - I replace it with the negative world forward vector when that happens.

Lemme switch to mathematical notation for this.

Vr = cross(Vn, Vu)


For the variables:

  • Vr, the right vector,
  • Vn, the floor normal, and
  • Vu, normally the world up vector.


Where:

  • cross(v1, v2) is the cross product of vectors v1 and v2, and
  • Vu is (0, 0, 1) unless equal to Vn, in which case it is (-1, 0, 0).

And from there, you can get the floor forward vector. I actually store it as a class variable. Just remember that Unreal uses a left-handed coordinate system.

Vf = cross(Vr, Vn)


For the variables:

  • Vf, the floor forward vector,
  • Vr, the right vector, and
  • Vn, the floor normal.


Where:

  • cross(v1, v2) is the cross product of vectors v1 and v2.

And with this, the floor forward vector will always point up whatever surface we're on, never down.

The last piece is the character forward vector - which direction the character is facing - which must be parallel to the floor. If the character is on a ramp, facing straight forwards in what would normally be (1, 0, 0), this vector must instead point up the ramp. Something like (0.71, 0.0, 0.71) for a ramp with a 45 degree incline.

Let's think about the floor for a bit. The floor is essentially a plane in 3D space, and you may have noticed me refer to it as the floor plane before. We already know the normal of this plane. We've called it the floor normal or the floor up vector, but with just that one vector, the floor plane is mathematically defined. Our character may be facing some direction in absolute coordinates, but if we project that vector onto the floor plane, we'll get the direction the character faces parallel to the floor.

Yup, planar projection. You only need the plane normal (our floor normal) and the vector to project onto it (the absolute character facing vector) to do this. I must've used this graphic a billion times by now, but...

f:id:caelk:20140930034807p:image

Va = this->GetActorRotation().Vector();


Pn = dot(Va, Vn) * Vn

Vc = normalize(Va - Pn)


For the variables:

  • Va, absolute character facing vector,
  • Vn, the floor normal,
  • Pn, the projection onto the floor normal, and
  • Vc, the character forward vector parallel to the floor plane.


Where:

  • dot(v1, v2) is the dot product of vectors v1 and v2, and
  • normalize(v) is the normalized vector v.

Alright, we got all four parts! Now we can start rotating the cover search volume. The first thing to do is really simple.

SearchVolumeRotation = CharacterForwardVector.Rotation();


this->CoverSearchVolume->SetWorldRotation(SearchVolumeRotation);

This takes care of the yaw and pitch of the rotation, and will orient the search volume in the direction the character is facing. If he's directly facing the slope of the ramp, this make the search volume run up and down it.

f:id:caelk:20160419132319p:image

f:id:caelk:20160419132318p:image

The only problem remaining is what happens when he starts turning left or right.

f:id:caelk:20160419132316p:image

Directly forward and backward look right, but directly left and right don't follow the slope of the floor at all. We're going to have to introduce some amount of roll, which is the only rotation axis not set by that call to FVector::Rotation(). By observation, we can determine:

f:id:caelk:20160419141029p:image

The maximum amount of roll is pretty easy: it's the same as the floor's angle of incline.

Rmax = 90 - acos(dot(Vf, Vu))


For the variables:

  • Rmax, maximum amount of roll for the floor, and
  • Vf, the floor forward vector, and
  • Vu, the world up vector (0, 0, 1).


Where:

  • acos(x) is the inverse cosine of x in degrees, and
  • dot(v1, v2) is the dot product of vectors v1 and v2.


Vu is not substituted for anything in this calculation.

How much of this roll we use is determined by how much we've deviated from the floor forward vector. When facing directly left or right, we need the full amount of roll, when facing directly forward or backwards, we need none of it. Notice what happens with the dot product as we make our way around the right vector.

f:id:caelk:20160419141030p:image

Falls right in line, doesn't it?

Rc = dot(Vc, Vr)

R = Rmax * Rc


For the variables:

  • Rc, the roll coefficient,
  • Vc, the character forward vector parallel to the floor plane,
  • Vr, the right vector,
  • R, the calculated amount of roll, and
  • Rmax, maximum amount of roll for the floor.

f:id:caelk:20160419132320p:image

f:id:caelk:20160419133004p:image

Excellent! With that, orienting the cover search volume is done, but I should point out that while mostly perfect (for me), there are still things you'll have to design around.

First, the cover search volume is aligned to the floor you're currently on. If you have a short ramp with cover at the top of it, the search volume won't see that cover from the bottom of the ramp, since the search volume will be aligned to be parallel to the floor at the base of the ramp. It's not until you get on the ramp itself that the search volume gets enough pitch rotation to catch the wall at the end of the ramp. With large enough ramps, you can get around this, but it's something to keep in mind.

Also remember that since the cover search is based on objects that overlap the search volume, you have to mark anything you want to be used as cover as something that responds to overlaps.

2016-02-23

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

Simplistic Cover in Unreal Engine (1): The Cover Slide and Cover Point Selection

Previous Post:
Simplistic Cover in Unreal Engine (0): Introduction - Memories of Melon Pan

When we think about cover systems, there's really only one way to activate it as far as the player goes - press the dang button - but when you program the activation, you gotta get your player to muzzle right up to cover if he's not already there. You can make this a little transparent and only allow cover activation when the player's already toe to toe with a surface, but if you wanna give the player a hand, you'll have to take over movement for a second and move the player right up to cover for him. I did things the second way (i.e. the Gears of War way), but it's obviously the more difficult way.

There are two parts to this, which ironically enough, can be further chopped up into other little problems you'll encounter along the way.

  • Selecting the cover surface
  • Calculating the point at which to take cover

Since you're automatically moving the player to some sort of cover, the first thing you need to do is find out what cover to move the player towards.

We can search for cover around the player by attaching a bounding volume to him. We're not really caring about up or down in this case - the player can't take cover while he's jumping, and we're not going to go so far as to make him jump up to cover. This means that our bounding volume is gonna be flat. Ideally, it would be a cylinder, but for some reason, Unreal Engine doesn't have a bounding cylinder. So I used a box instead.

(Header file)

UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))

UBoxComponent* CoverSearchVolume;


(Constructor)

CoverSearchVolume = CreateDefaultSubobject<UBoxComponent>(TEXT("Cover Search Volume"));

CoverSearchVolume->SetBoxExtent(FVector(450.0f, 450.0f, 75.0f));

CoverSearchVolume->AttachTo(RootComponent);

f:id:caelk:20160223173001j:image

That sets up the bounding box to be centered on the player's character (or specifically, the root component, which is an Unreal Engine specific thing). It actually has a bit of vertical leeway, stretching a little above and below the center of the character, to catch walls that might not be as tall as the player. To find cover around the player, all we have to do is search within this box. Unreal Engine makes this easy.

this->CoverSearchVolume->GetOverlappingActors(OverlappingActors, AStaticMeshActor::StaticClass());


Where:

  • OverlappingActors is the output, an array of all actors that overlap the search volume, and
  • AStaticMeshActor::StaticClass() is an inclusive class filter, so that the function only considers actors of this class.

We're going to assume that any static mesh in the game world can be used as cover - that's the AStaticMeshActor class - and we're going to search the box for everything inside it of that class, dumping all the hits into OverlappingActors.

f:id:caelk:20160223173000j:image

You might have to strain your eyes a bit to see that wireframe box, but if we hit the cover button right now, that pipe and that crate will be inside our search volume (read: wireframe box). The wooden ramp you see in the corner is outside the box - while it can be used as cover, since it's not inside our search box, we know it's pretty far away. And if it's too far away, we don't want to slide across the map to it.

So great! We have a bunch of things that can be used as cover surfaces! So which one do we use?

... Yet another question we gotta make up an answer to. Meh, it happens.

The easiest thing you can do is calculate the distance to each piece of cover, then choose the closest one. Again, Unreal Engine (being awesome) can turn this into a single function call... that admittedly calls a slew of other functions I'm sure, but that's all transparent to us.

float Distance = Object->ActorGetDistanceToCollision(CoverSearchPosition, ECC_WorldStatic, ClosestPoint);


For the variables:

  • CoverSearchPosition, the position where the cover search is started from,
  • ECC_WorldStatic, the collision channel used in this collision test, and
  • ClosestPoint, the output, the closest point on the surface to Position.

For CoverSearchPosition, we could use the center of the player character, and that's mostly okay. However, when we do any line trace from our player to cover, we'll actually want to use a point somewhat below the center of the player, so the traces don't soar over any low walls that could also be crouched behind.

I ended up using somewhere around knee-level, though it was actually a point relative to the height of the character's bounding volume at 25% of the way up. Keep in mind you can change this to suit your needs, but that's the general idea.

f:id:caelk:20160223173002j:image

As for ECC_WorldStatic... I could explain it, but that opens up a big can of worms that'd fill up a few posts. Suffice to say that it's like a tag that you can apply to objects that are part of the level you're playing in, and ActorGetDistanceToCollision takes this tag and only searches for collisions with objects that respond to this tag.

All of you itching for a better explanation, set aside some time and go through the Unreal Engine tutorial vids instead - the explanations there are made of silicon gold. You'll be looking for videos about collisions and collision channels. You can also look at their documentation.

Link: https://docs.unrealengine.com/latest/INT/Engine/Physics/Collision/index.html

But all that said... in its most basic form, you can just iterate through all of the static meshes that overlap with your search volume, and keep a running variable of the closest thing in there. You can leave out any hits that are some distance away from the player, effectively making your search area like a cylinder. When you're done, you can return ClosestPoint - that's the point to automatically move to, which is what we wanted to find.

Formally, what we're doing is scoring each piece of cover, but we're only using distance to score cover surfaces so our scoring function is really simple. If you want to make things a little more complex, you can do something like multiplying distance by some directional bias so that the cover button favors cover in front of the player.

Sound simple? It is... for now. Things're gonna get more complex, but for now, we can leave it at this.

Next Post:
Simplistic Cover in Unreal Engine (2): Character States and Input Handling - Memories of Melon Pan

2015-09-10

Simplistic Cover in Unreal Engine (0): Introduction

Last year, Epic Games released their Unreal Engine out for free. Anyone can download it! It just takes heaps of gigs.

I hit up a few tutorials and was putting together some really basic stuff in my spare time. After enough fiddling around, I thought, "Epic Games made Gears of War, and I liked the cover system in it. Why not try to do the same?" So, I tried and... well, if we're talking about movement only, I'd like to say I kinda succeeded.

If I'm remembering right, most of the movement during cover was done by players - when the AI took cover, it'd usually sit around in one spot until it had to move. 'Course the game's not on me right now, so I can't check, but the point is, you don't have to worry about the AI doing this. You just have to make it for the player.

So to get started on a cover system, well... we need a cover button. But once you got one (pretty easy) what does it have to do?

  • If the cover button is pressed, the player should slide to the nearest usable cover
  • Once in cover, all movement should make the player walk along the surface of the cover, even if it's curved
  • If the player reaches a cover edge, he should not be able to move past it

Honestly, I did this in the most simple way possible - by forcibly adding movement input towards cover in addition to what the player input, and I mainly settled for this because of curved cover.

With square cover, you can easily calculate a destination to move to every frame by getting the tangent of the cover surface. However, do this with curved cover, and you might steadily spiral away from the cover since its tangent changes so much. 'Least, if you want to calculate destination points like this, you'll have to do some more complex math.

Cover edges adds a more complex dimension to the entire shebang, but at least I was able to deal with it. You can check it out what I did at Youtube, but I'm gonna go into the details here on my blog.

D

There's probably a way to do it by subclassing UCharacterMovementComponent, but that class is pretty labyrinth. I imagine a second pass at this would see me making that subclass, but for now, we're just subclassing ACharacter and overriding functions there.

And also, I'm not going into cover actions right now. Personally, I think that's more simple than the movement part, so that's where I put my effort for now.

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

2015-09-07

Fish and chips

So, I've had this written down for a while, but never bothered to post it. Probably because I'm not too good of a cook, and probably because I'm also lazy. Meh.

My short ventures in cooking started when a restaurant I went to for one specific dish messed it up. No other place served it, and it didn't come out the way I liked it that time, so naturally I was worried. So I tried making it myself.

That recipe I'll save for another time, but after getting a taste of what it was like to not be dependent on restaurants for my food, I thought I'd take it a step further. Fish and chips is a nice little meal that I usually went out for, but I decided to have a go at it myself.

Amateur recipe it may be, but for the moment, here's how I made fish and chips.

Batter
1/2 cup water
1/2 cup flour
1 1/2 tablespoons corn starch
1/4 teaspoon baking soda
1/4 teaspoon baking powder
1/4 teaspoon salt
1/4 teaspoon pepper
1 egg

Other Stuff
2 cod fillets
potato
oil
cornmeal (optional)

Chop the potato into chips, and soak in a bowl of warm water for 20 minutes. You can add a teaspoon of salt (or thereabouts, depending on how much water you have) to the water if you want.

Mix all the batter ingredients, mixing in the water and egg last. Press the cod fillets to drain the water from them.

Add oil to a pan and drop the potatoes in (without the water you soaked them in). Cook the potatoes until they start browning, then take them out and let them drain on paper towel. Leave the oil in the pan.

Dip the cod fillets in the batter, and get a good coat on them, then put 'em in the pan to deep fry on medium-high heat. Flip after the first minute or two, then keep flipping every two minutes or so until they're done. Four flips should do it. Take the fillets out when they're done and let them drain on paper towel.

Put the potatoes back in for a minute or two to make 'em a little more crisp and to heat them up again - they've been sitting for a good six or seven minutes.

If you want some hushpuppies, you can make some simple ones by adding cornmeal to the batter and cooking that in the oil. One spoonful of batter should be one hushpuppy.

Here's... kinda how it should look after it's done. I let them sit too long in the pan on one side, which is why the fish a little brown. If you don't flip the fillets, the side that's down will start overcooking.

f:id:caelk:20150509005942j:image

Or, if you want to see how the fish looks like when done right, albeit with store-bought scalloped potatoes and not homemade chips...

f:id:caelk:20150413222016j:image

Save the oil if you can - you can use it again to put a dash of seafood flavor into whatever you use to cook with it next. If you have any leftover batter, you can save it to make okonomiyaki later!

2015-08-20

Only a madman would take on this mission (without auto-map)

You know, I should've blogged about the Idolm@ster 10th Live, but I left for Japan touring a few days after, so I never got around to it.

But well... Comiket 88 is done and over! I got most of everything I wanted, except for one guy who sold out pretty quick (like, in the first hour or two).

f:id:caelk:20150814235605j:image

I could've bought the catalog and used the map from that, but I decided I was too tired to walk to the train, then walk to Animate or something, and do the same thing in reverse. So despite this being my first actual Comiket (I went to a smaller Comiket Special a few years ago, but that's it), I went without the catalog.

I did use the web catalog for a while, though I soon found out you can only view it for a few days or something before it says to pay up. Fortunately, I had already made my notes on what the place looked like, so I used a hand-drawn half map to wander around Big Sight.

Friday didn't have too much that interested me. I went to two booths to get music CDs. I actually forgot that there were two or so other booths that had stuff worth checking out, but since this day's run was hastily planned (partly because there wasn't much), I missed out on those guys. Meh. It would've been a great day if I were a Teitoku, though.

Saturday... was probably a good day if you were into Pokemon or Gundam or something, but I already knew I didn't have anything I wanted to get so I didn't mind getting there past noon. The most I did was cosplay hunt, after an hour of browsing gave me a grand total of zero things I wanted. I considered not even going that day.

However, Sunday was the doujin music and Idolm@ster day, and so I kinda went wild. I think maybe half the stuff I ended up buying were impulse buys? Either way, I made some gambles on doujin music, some of which is actually pretty nice. I got a bunch of Cinderella Girls doujin, and... well, lucked into a lost and abandoned Eri Ayase button in the cosplay area. I got pretty much everything I wanted this day, save for one guy who sold out in the first one or two hours.

And when Comic Market 88 was about to close, I finally decided to support the event and bought the catalog. Just when I didn't need it. ^_^;

f:id:caelk:20150819232739j:image