Protocol Buffers C tutorial – Programmer Sought

“exploring in ue4” game character movement principle (part 1) – programmer sought

Preface

Previous articleMainly for everyone to popularize the “role” in the game how to move. Today’s article will give you a very detailed analysis of how Unreal Engine handles character movement. However, the content of the article may be relatively deep and requires readers to have certain game development experience. It is recommended to understand it in conjunction with the engine source code.

Since the article is very long, I will divide it into two pieces, which can be collected and read slowly.

Know the original link: https://zhuanlan.zhihu.com/p/34257208

Part I

One. Deeply understand the meaning of mobile components

In most games, player movement is the most core basic operation. Even if there is not a so-called “player”, there must be some objects that you can control or AI control.

The GamePlay framework provided by UE4 provides developers with a perfect mobile solution. Since UE adopts a componentized design idea (that is, splitting and encapsulating different functions into a specific component), the core functions of this mobile solution are all handed over to the mobile component to complete. The movement logic will be handled differently according to the complexity of the game. If it is a simple top-view RTS type game, it may only provide basic coordinate movement; for the first-person RPG game, the player may go into the sky. For diving flying, the movement required is more complicated. But no matter which kind, UE basically helped us realize it, which also benefited from its early FPS game development experience.

Protocol Buffers C   tutorial - Programmer Sought

The basic movement provided by the engine does not necessarily achieve our goal, and we should not limit our design. For example, the eaves and the wall of light work, the super-gravity of the magic spaceship, the spring shoes, and the jetpack flight control. These effects require us to further process the movement logic by ourselves. We can modify it on the basis of it, or customize our own movement mode. In any case, these operations require meticulous adjustments to mobile components, so we must have a deep understanding of the implementation principles of mobile components.

Furthermore, in an online game, our handling of mobile will be more complicated. How to make players on different clients experience smooth mobile performance? How to ensure that the character will not “teleport” due to a little delay? UE’s handling of this aspect is worthy of our thinking and learning.

The mobile component seems to be just a mobile-related component, but it itself involves state machines, synchronization solutions, physical modules, detailed processing of different mobile states, animations, and the calling relationship with other components (Actors). , Enough to spend a while to study. This article will analyze its implementation principles as much as possible from the basic principles of mobile, detailed processing of mobile states, and mobile synchronization solutions, and then help everyone quickly understand and better use mobile components. Finally, some ideas for the realization of special mobile modes are given for your reference.

two. Basic principles of mobile implementation

2.1 Mobile components and player characters

The movement of the character is essentially to change the coordinate position reasonably, and the essence of the movement of the character in the UE is to modify the coordinate position of a certain root component. Figure 2-1 is our common component composition of a Character. It can be seen that we usually use CapsuleComponent (capsule body) as our root component, and the coordinates of Character are essentially the coordinates of its RootComponent, Mesh grid and other components Will follow the capsule and move. When the mobile component is initialized, the capsule body is set as the UpdateComponent of the mobile base component, and subsequent operations are all calculating the position of the UpdateComponent.

Protocol Buffers C   tutorial - Programmer Sought

Figure 2-1 The component composition of a default Character

Of course, we don’t have to set the capsule body to UpdateComponent. For DefaultPawn (observer), his SphereComponent is used as UpdateComponent, and for vehicle object AWheeledVehicle, his Mesh grid component is used as UpdateComponent by default. You can define your UpdateComponent yourself, but your custom component must inherit USceneComponent (in other words, the component must have world coordinate information), so that it can implement its movement logic normally.

2.2 Mobile component inheritance tree

There is not only one mobile component class. Through an inheritance tree, it gradually expands the capabilities of mobile components. From the simplest provision of mobile functions, to the correct simulation of mobile effects in different mobile states. As shown in Figure 2-2

Protocol Buffers C   tutorial - Programmer Sought

Figure 2-2 Class diagram of mobile component inheritance relationship

There are four mobile component classes. The first is UMovementComponent. As the base class of mobile components, it implements the basic mobile interface of SafeMovementUpdatedComponent(). You can call the interface function of UpdateComponent to update its position.

bool UMovementComponent::MoveUpdatedComponentImpl( const FVector& Delta, const FQuat& NewRotation, bool bSweep, FHitResult* OutHit, ETeleportType Teleport)
{
    if (UpdatedComponent)
    {
        const FVector NewDelta = ConstrainDirectionToPlane(Delta);
        return UpdatedComponent->MoveComponent(NewDelta, NewRotation, bSweep, OutHit, MoveComponentFlags, Teleport);
    }


    return false;
 }
  

From the above figure, you can see that the type of UpdateComponent is UScenceComponent. The component of UScenceComponent type provides basic location information-ComponentToWorld, and also provides an interface InternalSetWorldLocationAndRotation() to change the location of itself and its subcomponents. The UPrimitiveComponent class directly inherits from UScenceComponent, adding rendering and physical information. Our common Mesh components and capsules are inherited from UPrimitiveComponent, because we want to achieve a real movement effect, we may always be in contact with an Actor in the physical world, and we need to render our moving animation while moving. To show it to the players.

The next component is UNavMovementComponent, which is more of the ability to provide AI pathfinding, and includes basic movement status, such as whether it can swim, whether it can fly, etc.

The UPawnMovementComponent component has begun to be able to interact with the player. The front is the basic mobile interface, and the player operation cannot be realized without manual call. UPawnMovementComponent provides AddInputVector(), which can receive player input and modify the position of the controlled Pawn according to the input value. It should be noted that in UE, Pawn is a controllable game character (also can be controlled by AI), and his movement must be coordinated with the specific component UPawnMovementComponent, so this is also the origin of the name. The general operation process is that the player binds a button operation through the InputComponent component, and then calls the AddMovementInput interface of Pawn when the button responds, and then calls the AddInputVector() of the mobile component. After the call is completed, the operation will be consumed through the ConsumeMovementInputVector() interface Enter the value of to complete a move operation.

Finally came the UCharacterMovementComponent, the main focus of the mobile component. This component can be said to be the integration of Epic’s years of game experience. It accurately handles various common movement status details and achieves a relatively smooth synchronization solution. Various position corrections and smoothing processing have achieved the current mobile effect, and we don’t need to write our own code to use this highly completed mobile component. It can be said that it is really suitable for first-person and third-person RPG games. .

In fact, there is a more commonly used mobile component, UProjectileMovementComponent, which is generally used to simulate the movement of bows and arrows, bullets and other projectiles. However, this article will not focus on this.

2.3 Brief analysis of mobile component related class relations

The previous analysis mainly focused on the mobile component itself, here is a more comprehensive overview of the entire framework of mobile. (Refer to Figure 2-3)

Protocol Buffers C   tutorial - Programmer Sought

Figure 2-3 Related class diagram of mobile framework

In an ordinary three-dimensional space, the simplest movement is to directly modify the coordinates of the character. Therefore, as long as our character has a component that contains coordinate information, it can move through the basic mobile component. However, as the complexity of the game world has deepened, we have added a walkable ground and an ocean that can be explored in the game. We found that the movement becomes complicated. The player can walk on the ground under his feet, so he needs to constantly detect ground collision information (FFindFloorResult, FBasedMovementInfo); if the player wants to swim in the water, he needs to detect the volume of water (GetPhysicsVolume( ), Overlap event, also requires physics); the speed and effect in the water are very different from those on land, so write the two states separately (PhysSwimming, PhysWalking); when moving, the animation actions match, then update When you are in the position, update the animation (TickCharacterPose); what to do when you encounter obstacles when moving, how to deal with being pushed by other players (there are related processing in MoveAlongFloor); the game content is too little, I want to add some NPCs that can find their own way, and Need to set up the navigation grid (involving FNavAgentProperties); if a player is too boring, let everyone play online (simulating mobile synchronization FRepMovement, client-side mobile correction ClientUpdatePositionAfterServerUpdate).

Looking at it this way, it’s not easy to be an excellent mobile component. But no matter what, UE basically does it for you. Through the above description, you now have a general understanding of the handling of mobile components in all aspects, but you may not be able to start when you encounter specific problems, so let’s continue to analyze.

three. Detailed handling of each movement state

In this section, we will focus on the component UCharacterMovementComponent to analyze in detail how he handles player characters in various moving states. First of all, it must start with Tick. The state is detected and processed every frame. The state is distinguished by a movement mode MovementMode, which is modified to the correct movement mode when appropriate. There are 6 modes of movement by default. The basic commonly used modes are walking, swimming, falling, and flying. There is a walking mode provided for AI agents, and finally there is a custom movement mode.

Protocol Buffers C   tutorial - Programmer Sought

Figure 3-1 Mobile processing flow in stand-alone mode

3.1 Walking

The walking mode can be said to be the basis of all movement modes, and it is also the most complicated one of them. In order to simulate the real-world movement effect, there must be a physical object under the player’s feet that can support and not fall, just like the ground. In the mobile component, this ground is recorded by the member variable FFindFloorResult CurrentFloor. At the beginning of the game, the mobile component will set the default MovementMode according to the configuration. If it is Walking, it will find the current ground through the FindFloor operation. The initialization stack of CurrentFloor is shown in Figure 3-2 (Character Restart() will cover Pawn’s Restart()):

Protocol Buffers C   tutorial - Programmer Sought

Figure 3-2

Let’s analyze the process of FindFloor first. FindFloor essentially finds the ground under the feet through the Sweep detection of the capsule, so the ground must have physical data, and the channel type must be set to respond to the player’s Pawn with Block. There are some small details here. For example, when we are looking for the ground, we only consider the location near the foot and ignore the objects near the waist; Sweep uses a capsule instead of radiographic detection, which is convenient for processing inclined plane movement and calculating standing Radius, etc. (Refer to Figure 3-3, Normal and ImpactNormal in HitResult may not be the same in the Sweep detection of the capsule). In addition, the current movement of Character is based on the capsule body, so an Actor without a capsule body component cannot use UCharacterMovementComponent normally.

Protocol Buffers C   tutorial - Programmer Sought

Figure 3-3

Can players stand on the ground if they find the ground? Not necessarily. This again involves a new concept PerchRadiusThreshold, which I call the perch radius, which is the standing radius. The default value is 0, and the mobile component will ignore the related calculation of the standing radius. Once this value is greater than 0.15, it will make further judgments to see if the current ground space is enough for the player to stand on.

The previous preparation work is completed, and now officially enters the walking displacement calculation. This piece of code is calculated in PhysWalking. In order to perform smoothly, UE4 divides a tick movement into N segments (the time of each segment cannot exceed MaxSimulationTimeStep). When processing each segment, first record the current position information and ground information. In TickComponent, the current acceleration is calculated according to the player’s key duration. Then CalcVelocity() calculates the speed based on the acceleration, and also considers ground friction, whether it is in the water, etc.

// apply input to acceleration
Acceleration = ScaleInputAcceleration(ConstrainInputAcceleration(InputVector));

After calculating the speed, call the function MoveAlongFloor() to change the coordinate position of the current object. Before actually calling the mobile interface SafeMoveUpdatedComponent(), a special situation is simply handled-the player walks along the slope. Normally in the walking state, the player will only move forwards, backwards, left, and right, and there will be no movement speed in the Z direction. What if you encounter a slope? If the slope can be walked, the ComputeGroundMovementDelta() function will be called to calculate a new parallel and slope speed based on the current horizontal speed. This can simply simulate the effect of walking along the slope, and generally speaking when going uphill The player’s horizontal velocity should be reduced, which can be handled automatically by setting bMaintainHorizontalGroundVelocity to false.

Now it seems that we can simulate a mobile process perfectly, but if you think about it, there is another situation that has not been considered. That is how to deal with obstacles? According to our usual game experience, encountering obstacles must be a failure to move, and it may slide a little along the wall. This is indeed the case in UE. During the process of character movement (SafeMoveUpdatedComponent), there will be a collision detection process. Because the UPrimitiveComponent component has physical data, this operation is handled in the function UPrimitiveComponent::MoveComponentImpl. The following code will detect whether obstacles are encountered during the movement, and will return HitResult if obstacles are encountered.

FComponentQueryParams Params(PrimitiveComponentStatics::MoveComponentName, Actor);
FCollisionResponseParams ResponseParam;
InitSweepCollisionParams(Params, ResponseParam);
bool const bHadBlockingHit = MyWorld->ComponentSweepMulti(Hits, this, TraceStart, TraceEnd, InitialRotationQuat, Params);

After receiving the HitResult returned by SafeMoveUpdatedComponent(), the collision obstacle will be handled in the following code.

If Hit.Normal has a value in the Z direction and can still walk, it means that this is an inclined plane that can be moved up, and then the player is allowed to move along the inclined plane

Determine whether the current collider can be stepped on, if possible, try to step on it. If it is not stepped on during the process, it will also call SlideAlongSurface() to slide along the collision.

// UCharacterMovementComponent::PhysWalking
else if (Hit.IsValidBlockingHit())
{
    // We impacted something (most likely another ramp, but possibly a barrier).
    float PercentTimeApplied = Hit.Time;
    if ((Hit.Time > 0.f) && (Hit.Normal.Z > KINDA_SMALL_NUMBER) && IsWalkable(Hit))
    {
        // Another walkable ramp.
        const float InitialPercentRemaining = 1.f - PercentTimeApplied;
        RampVector = ComputeGroundMovementDelta(Delta * InitialPercentRemaining, Hit, false);
        LastMoveTimeSlice = InitialPercentRemaining * LastMoveTimeSlice;
        SafeMoveUpdatedComponent(RampVector, UpdatedComponent->GetComponentQuat(), true, Hit);
        const float SecondHitPercent = Hit.Time * InitialPercentRemaining;
        PercentTimeApplied = FMath::Clamp(PercentTimeApplied   SecondHitPercent, 0.f, 1.f);
    }


    if (Hit.IsValidBlockingHit())
    {
        if (CanStepUp(Hit) || (CharacterOwner->GetMovementBase() != NULL && CharacterOwner->GetMovementBase()->GetOwner() == Hit.GetActor()))
        {
            // hit a barrier, try to step up
            const FVector GravDir(0.f, 0.f, -1.f);
            if (!StepUp(GravDir, Delta * (1.f - PercentTimeApplied), Hit, OutStepDownResult))
            {
                UE_LOG(LogCharacterMovement, Verbose, TEXT("- StepUp (ImpactNormal %s, Normal %s"), *Hit.ImpactNormal.ToString(), *Hit.Normal.ToString());
                HandleImpact(Hit, LastMoveTimeSlice, RampVector);
                SlideAlongSurface(Delta, 1.f - PercentTimeApplied, Hit.Normal, Hit, true);
            }
            else
            {
                // Don't recalculate velocity based on this height adjustment, if considering vertical adjustments.
                UE_LOG(LogCharacterMovement, Verbose, TEXT("  StepUp (ImpactNormal %s, Normal %s"), *Hit.ImpactNormal.ToString(), *Hit.Normal.ToString());
                bJustTeleported |= !bMaintainHorizontalGroundVelocity;
            }
        }
        else if ( Hit.Component.IsValid() && !Hit.Component.Get()->CanCharacterStepUp(CharacterOwner) )
        {
            HandleImpact(Hit, LastMoveTimeSlice, RampVector);
            SlideAlongSurface(Delta, 1.f - PercentTimeApplied, Hit.Normal, Hit, true);
        }
    }
}

The basic movement processing is completed, and immediately after the movement, it will be judged whether the player has entered the water or the Falling state, and if it is, it will immediately switch to the new state. To
Since the player may constantly switch from Walking, Swiming, Falling and other states in a frame, there will be an iteration to record the number of moves in the current frame before each movement. If the limit is exceeded, the movement simulation will be cancelled. behavior.

3.2 Falling

The Falling state is also the most common state other than Walking. As long as the player is in the air (whether jumping or falling), the player will be in the Falling state. Similar to Walking, in order to show a smoother performance, Falling’s calculation also divides a Tick movement into N segments (the time of each segment cannot exceed MaxSimulationTimeStep). When processing each paragraph, first calculate the player’s horizontal speed controlled by input, because the player can also be affected by the player’s control while in the air. Subsequently, obtain the gravity calculation speed. The acquisition of gravity is a bit interesting, you will find that it is acquired through Volume,

float UMovementComponent::GetGravityZ() const
{
    return GetPhysicsVolume()->GetGravityZ();
}
APhysicsVolume* UMovementComponent::GetPhysicsVolume() const
{
    if (UpdatedComponent)
    {
        return UpdatedComponent->GetPhysicsVolume();
    }
    return GetWorld()->GetDefaultPhysicsVolume();
}

Volume will take GlobalGravityZ from WorldSetting. Here is a reminder. We can modify the code to achieve different gravity of different Volumes and achieve custom gameplay. Note that even if we are not in any volume, he will bind a default DefaultVolume to our UpdateComponent. Then why should there be a DefaultVolume? Because in a lot of logic processing, you need to get the DefaultVolume and related data inside. For example, DefaultVolume has a TerminalLimit, which cannot exceed the set speed when calculating the descending speed by gravity. We can change the speed limit by modifying this value. By default, many properties in DefaultVolume are initialized through the Physics-related configuration in ProjectSetting. Refer to Figure 3-4,

Protocol Buffers C   tutorial - Programmer Sought

Figure 3-4

Calculate the current new FallSpeed ​​through the acquired Gravity (calculated in NewFallVelocity, the calculation rule is very simple, that is, simply use the current speed-Gravity*deltaTime). Then calculate the displacement and move according to the current and previous frame speed, the formula is as follows

FVector Adjusted = 0.5f*(OldVelocity   Velocity) * timeTick;
SafeMoveUpdatedComponent( Adjusted, PawnRotation, true, Hit);

After we calculated the speed and moved the player before, we also need to consider the problem of mobile collision.

First caseIt is a normal landing. If the player finds that it collides with a terrain that can stand after calculation, then directly call ProcessLanded to perform the landing operation (this judgment is mainly based on the height of the collision point, and the wall can be filtered out).

Second caseIt is to encounter a platform in the process of jumping, and then check whether the player’s coordinates and the current collision point are in an acceptable range (IsWithinEdgeTolerance), if yes, execute FindFloor to re-check the ground, and if detected, execute the landing process.

The third caseIt is the wall and other things that cannot be stepped on. If an obstacle is encountered during the falling process, HandleImpact will be executed first to give the touched object a force. Then call ComputeSlideVector to calculate the displacement of the sliding. Since the speed of the player will change after the collision with the obstacle, recalculate the speed at this time, and adjust the position and direction of the player again. If the player has a horizontal displacement at this time, LimitAirControl will also be used to limit the player’s speed. After all, the player cannot freely control the character in the air. To extend the third situation further, there may be a collision adjustment and another wall is encountered. The processing of Falling here allows the player to find a suitable position on the two walls. But it still can’t solve the situation where the player is caught on two slopes but can’t land (or constantly switching between Waling and Falling). If we have time, we can try to solve this problem later. The solution can start with the ComputeFloorDist function under FindFloor. The purpose is to allow the player to find a walkable ground in this situation.

Protocol Buffers C   tutorial - Programmer Sought

Figure 3-5 Caught in the gap causes constant switching

3.2.1 Jump

When it comes to Falling, I have to mention the basic operation of jumping. The following roughly describes the basic flow of jump response,

1. Binding trigger response event

void APrimalCharacter::SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent)
{
    // Set up gameplay key bindings
    check(PlayerInputComponent);
    PlayerInputComponent->BindAction("Jump", IE_Pressed, this, &ACharacter::Jump);
    PlayerInputComponent->BindAction("Jump", IE_Released, this, &ACharacter::StopJumping);
}
void ACharacter::Jump()
{
    bPressedJump = true;
    JumpKeyHoldTime = 0.0f;
}


void ACharacter::StopJumping()
{
    bPressedJump = false;
    ResetJumpState();
}

2. Set bPressedJump to true as soon as the button responds. The frame loop of TickComponent calls ACharacter:: CheckJumpInput to immediately detect whether a jump operation is performed

  1. Execute the CanJump() function to process the relevant restriction logic in the blueprint. If the function is not rewritten in the blueprint, ACharacter::CanJumpInternal _ Implementation() will be executed by default. This is the basis for controlling whether the player can jump, such as crouching and not jumping, and swimming. In addition, there is a JumpMaxHoldTime which means that the player will not trigger a jump after pressing a key over this value. JumpMaxCount represents the number of segments the player can perform jumps. (Such as double jump)

  2. Execute the CharacterMovement->DoJump (bClientUpdating) function, execute the jump operation, enter Falling, and set the jump speed to JumpZVelocity. This value cannot be less than 0.

  3. Determine whether const bool bDidJump = canJump && CharacterMovement && DoJump; is true. Do some other related operations.

  4. Do Jump count and related event distribution

3. After a PerformMovement is over, ClearJumpInput will be executed, and bPressedJump will be set to false. But JumpCurrentCount will not be cleared so that you can continue to process multiple jumps.

4. When the player releases the button p, bPressedJump will be set to false, clearing the related state. If the player is still in the air, JumpCurrentCount will not be cleared. Once bPressedJump is false, no jump operations will be processed.

5. If the player presses the jump button in the air, he will also enter ACharacter::CheckJumpInput. If JumpCurrentCount is less than JumpMaxCount, the player can continue to perform the jump operation.

Protocol Buffers C   tutorial - Programmer Sought

Figure 3-6

3.3 Swiming

There are three essential differences in each state:

  1. Speed ​​difference

  2. The degree of gravity

  3. Inertia

The performance of swimming is a state of inertia of movement (will not stop immediately after letting go), less affected by gravity (slowly falling or not moving in the water), and slower than usual (showing water resistance). The default detection logic for whether the player is in the water is also relatively simple, that is, to determine whether the Volume where the current updateComponent is located is a WaterVolume. (Pull a PhysicsVolume in the editor and modify the attribute WaterVolume)

The CharacterMovement component has a buoyancy configuration Buoyancy, and the final buoyancy can be calculated according to the degree of player diving into the water (ImmersionDepth returns 0-1). After that, we need to calculate the speed. At this time, we need to get the friction force in Volume, and then pass it into CalcVelocity, which reflects the effect of slower movement of the player in the water. Then calculate the buoyancy in the Z direction to calculate the speed in that direction. As the player dives, you will find that the player’s speed in the Z direction is getting smaller and smaller. Once the whole body is submerged in the water, the gravity speed in the Z-axis direction Will be completely ignored.

// UCharacterMovementComponent::PhysSwimming
const float Friction = 0.5f * GetPhysicsVolume()->FluidFriction * Depth;
CalcVelocity(deltaTime, Friction, true, BrakingDecelerationSwimming);
Velocity.Z  = GetGravityZ() * deltaTime * (1.f - NetBuoyancy);


// UCharacterMovementComponent::CalcVelocity Apply fluid friction
if (bFluid)
{
    Velocity = Velocity * (1.f - FMath::Min(Friction * DeltaTime, 1.f));
}

Protocol Buffers C   tutorial - Programmer Sought

Figure 3-7 A character floating in the water volume

After the speed is calculated, the player can move. Here UE wrote a separate interface Swim to perform the movement operation. At the same time, he considered that if the player leaves the water volume after the movement and exceeds the water surface too much, he has the opportunity to force the player to adjust to the water surface position, and the performance will be better.

What’s more next, you may have guessed it, which is to deal with the situation where a collision obstacle is detected while moving. Basically the logic is similar to the previous one. If you can step on it (StepUp()), adjust the player’s position and step on it. If you can’t step on it, give the obstacle a force, and then slide a distance along the obstacle surface (HandleImpact, SlideAlongSurface).

How to deal with the inertial performance of water movement? In fact, it’s not because of any special processing in the water, but when calculating the speed, there are two incoming parameters different from those of Walking. One is Friction which means friction, and the other is BrakingDeceleration which means the reverse speed of braking. To

When the acceleration is 0 (indicating that the player’s input has been cleared), the incoming friction in the water is much smaller than the ground friction (0.15:8), and the braking speed is 0 (Walking is 2048), so ApplyVelocityBraking is When processing, it seems to brake immediately when walking, but it seems to have moving inertia in situations such as Swim and fly.

// Only apply braking if there is no acceleration, or we are over our max speed and need to slow down to it.
if ((bZeroAcceleration && bZeroRequestedAcceleration) || bVelocityOverMax)
{
    const FVector OldVelocity = Velocity;


    const float ActualBrakingFriction = (bUseSeparateBrakingFriction ? BrakingFriction : Friction);
    ApplyVelocityBraking(DeltaTime, ActualBrakingFriction, BrakingDeceleration);


    //Don't allow braking to lower us below max speed if we started above it.
    if (bVelocityOverMax && Velocity.SizeSquared() < FMath::Square(MaxSpeed) && FVector::DotProduct(Acceleration, OldVelocity) > 0.0f)
    {
        Velocity = OldVelocity.GetSafeNormal() * MaxSpeed;
    }
}

3.4 Flying

Finally talked about the last movement state, if you want to debug this state, you can modify the DefaultLandMovementMode to Flying in the character’s movement component. To

Flying is similar to other state routines, and is relatively simpler. First calculate Acceleration based on the previous input, and then start to calculate the current speed based on the friction. After the speed is calculated, SafeMoveUpdatedComponent is called to move. If you encounter an obstacle, first see if you can step on it, if not, handle the collision and slide along the obstacle surface.

//UCharacterMovementComponent::PhysFlying
//RootMotion Relative
RestorePreAdditiveRootMotionVelocity();


if( !HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity() )
{
    if( bCheatFlying && Acceleration.IsZero() )
    {
        Velocity = FVector::ZeroVector;
    }
    const float Friction = 0.5f * GetPhysicsVolume()->FluidFriction;
    CalcVelocity(deltaTime, Friction, true, BrakingDecelerationFlying);
}
//RootMotion Relative
ApplyRootMotionToVelocity(deltaTime);

There is a phenomenon about the flying state that you may have questions. When I set the default movement method to Flying, the player can slide a distance (with inertia) after releasing the keyboard. But when using the GM command, why is it like the Walking state, it stops immediately after releasing the button? To

Its real-time code does a special treatment for cheat flying. After the player releases the button, the acceleration becomes 0. At this time, the player’s speed is forced to be set to 0. So the performance of using GM is different from the actual performance.

3.5 FScopedMovementUpdate delayed update

FScopedMovementUpdate is not a state, but an optimized movement program. Because when you look at the engine code, you may see the following code before executing the move:

// Scoped updates can improve performance of multiple MoveComponent calls.
{
    FScopedMovementUpdate ScopedMovementUpdate(UpdatedComponent, bEnableScopedMovementUpdates ? EScopedUpdate::DeferredUpdates : EScopedUpdate::ImmediateUpdates);


    MaybeUpdateBasedMovement(DeltaSeconds);


    //...Other logic processing, no specific code is given here


    // Clear jump input now, to allow movement events to trigger it for next update.
    CharacterOwner->ClearJumpInput();
    // change position
    StartNewPhysics(DeltaSeconds, 0);


         //...Other logic processing, no specific code is given here


    OnMovementUpdated(DeltaSeconds, OldLocation, OldVelocity);
} // End scoped movement update

Why put the moving code inside this brace, and what is FScopedMovementUpdate? Carefully recall our previous specific movement processing logic. In a frame, we may reset or modify our movement many times due to illegal movement and obstacles. If you just simply modify the position of the capsule body, it is actually nothing, but in fact, we also need to modify the position of the sub-components, update the physical volume, update the physical position, etc., and the movement data in the calculation process is actually useless. Only the last mobile data is needed.

Therefore, using FScopedMovementUpdate can lock the movement of objects such as physics and other objects within its scope, and then update after the movement is truly completed. (Wait until FScopedMovementUpdate is destructed before processing) There are many places where FScopedMovementUpdate is used, and basically there will be logic related to mobile rollback.

To be continued

Protocol Buffers C   tutorial - Programmer Sought

Game development

Reply to “gamebook” to get game development books

Reply to “C Interview” to get C /game interview experience

Reply to “Introduction to Game Development” to get the article on introduction to game development

Reply to “operating system” and get operating system related books

5 Understanding the Protocol Buffer API

Let’s take a look at the generated code to understand what classes and functions the compiler has created for you. If you look at the student.pb.h file generated by the compiler protoc for us, you will find that you get a class that corresponds to each message written in the student.proto file.

  // required uint64 id = 1;
  inline bool has_id() const;
  inline void clear_id();
  static const int kIdFieldNumber = 1;
  inline ::google::protobuf::uint64 id() const;
  inline void set_id(::google::protobuf::uint64 value);

  // required string name = 2;
  inline bool has_name() const;
  inline void clear_name();
  static const int kNameFieldNumber = 2;
  inline const ::std::string& name() const;
  inline void set_name(const ::std::string& value);
  inline void set_name(const char* value);
  inline void set_name(const char* value, size_t size);
  inline ::std::string* mutable_name();
  inline ::std::string* release_name();
  inline void set_allocated_name(::std::string* name);

  // optional string email = 3;
  inline bool has_email() const;
  inline void clear_email();
  static const int kEmailFieldNumber = 3;
  inline const ::std::string& email() const;
  inline void set_email(const ::std::string& value);
  inline void set_email(const char* value);
  inline void set_email(const char* value, size_t size);
  inline ::std::string* mutable_email();
  inline ::std::string* release_email();
  inline void set_allocated_email(::std::string* email);

  // repeated .tutorial.Student.PhoneNumber phone = 4;
  inline int phone_size() const;
  inline void clear_phone();
  static const int kPhoneFieldNumber = 4;
  inline const ::tutorial::Student_PhoneNumber& phone(int index) const;
  inline ::tutorial::Student_PhoneNumber* mutable_phone(int index);
  inline ::tutorial::Student_PhoneNumber* add_phone();
  inline const ::google::protobuf::RepeatedPtrField< ::tutorial::Student_PhoneNumber >& phone() const;
  inline ::google::protobuf::RepeatedPtrField< ::tutorial::Student_PhoneNumber >* mutable_phone();

As you can see, the getter function has exactly the same name as the field name and is in lower case, and the setter functions all start with the set_ prefix. In addition, there is a function with has_ ​​prefix. For each single (required or optional) field, if the field is set (set) value, the function will return true. Finally, there is a clear_ prefix function for each field, which is used to reset (un-set) the field to an empty state.

However, the numeric field id has only the basic read and write functions as described above, and the name and email fields have some additional functions because they are strings — functions with a prefix of mutable_ return a direct pointer to string .

In addition, there is an additional setter function. Note: You can even call mutable_email() when email has not been set, and it will be automatically initialized to an empty string. In this example, if there is a single message field, then it will also have a function with a mutable_ prefix, but no function with a set_ prefix.

There are also special functions for repeated fields-if you look at the functions of the repeated field phone, you will find that you can:(1) Get the _size of the repeated field (in other words, how many phone numbers are associated with this Person).

(2) Get a specified phone number by index.

(3) Update an existing phone number through the specified index.

(3) Add another phone number to the message, and then you can edit it (duplicate scalar types have a function with add_ prefix, allowing you to pass in new values).

For more information on how the compiler generates special fields, please see the articleC generated code reference。

About Enums and Nested Classes. The generated code contains a PhoneType enumeration, which corresponds to the enumeration in the .proto file. You can think of this type as Student::PhoneType, whose values ​​are Student::MOBILE and Student::

The compiler also generated a nested class called Student::PhoneNumber. If you look at the code, you will find that the “real” class is actually called Student_PhoneNumber, but a typedef inside Student allows you to treat it like a nested class. The only difference caused by this is that if you want to forward-declare the class in another file, you cannot forward-declare the nested type in C , but you You can make a forward declaration to Student_PhoneNumber.

About Standard Message Methods. Each message (message) also contains a series of other functions to check or manage the entire message, including:

bool IsInitialized() const; //Check if all required fields are set.

 void CopyFrom(const Person& from); //Overwrite the internal value of the caller's message with the value of the external message.

 void Clear(); //Reset all items to empty state.

 int ByteSize() const; //Message byte size

About Debug API.

string DebugString() const; //Output the message content in a readable way

 string ShortDebugString() const; //The function is similar to DebugString(), there will be less blank when output

string Utf8DebugString() const; //Like DebugString(), but do not escape UTF-8 byte sequences.

void PrintDebugString() const;  //Convenience function useful in GDB. Prints DebugString() to stdout.

These functions and the I/O functions that will be mentioned in the following chapters implement the Message interface, and they are shared by all C protocol buffer classes. For more information, please check the articlecomplete API documentation for Message。

About parsing & serialization (Parsing and Serialization).

Finally, each protocol buffer class has functions to read and write the message type of your choice. They include:

bool SerializeToString(string* output) const; //Serialize the message and store it in the specified string. Note that the content inside is binary, not text; we just use string as a convenient container.

 bool ParseFromString(const string& data); //Parse the message from the given string.

 bool SerializeToArray(void * data, int size) const //Serialize the message to an array

 bool ParseFromArray(const void * data, int size) //parse message from array

 bool SerializeToOstream(ostream* output) const; //Write the message to the given C   ostream.

 bool ParseFromIstream(istream* input); //Parse the message from the given C   istream.

These functions are just a few functions for parsing and serialization. Please refer againMessage API referenceTo see the complete list of functions.

note: Protocol buffers and object-oriented design The protocol buffer classes are usually just pure data storage (like structures in C ); they are not first-class citizens in the object model. If you want to add richer behavior to the generated class, the best way is to encapsulate it in the application.

If you do not have control over the design of .proto files, it is also a good idea to encapsulate protocol buffers (for example, if you reuse a .proto file from another project). In that case, you can use encapsulated classes to design interfaces to better suit the specific environment of your application: hide some data and methods, expose some convenient functions, and so on.

Оцените статью
Huawei Devices
Добавить комментарий