Ue4

「UE4での探索」ゲームキャラクターの動きの原理(パート1)



Exploring Ue4game Character Movement Principle



序文

前の記事 主に誰もがゲームの「役割」を広めるためにどのように動くか。今日の記事では、UnrealEngineがキャラクターの動きをどのように処理するかについて非常に詳細に分析します。ただし、記事の内容は比較的深く、読者は特定のゲーム開発経験を持っている必要があります。エンジンのソースコードと併せて理解することをお勧めします。



記事が非常に長いので、2つに分けて、ゆっくりと集めて読むことができます。

元のリンクを知っている:https://zhuanlan.zhihu.com/p/34257208



パートI

1。モバイルコンポーネントの意味を深く理解する

ほとんどのゲームでは、プレーヤーの動きが最も基本的な操作です。いわゆる「プレイヤー」がいない場合でも、制御またはAI制御できるオブジェクトがいくつか存在する必要があります。



UE4が提供するGamePlayフレームワークは、開発者に完璧なモバイルソリューションを提供します。 UEはコンポーネント化された設計アイデアを採用しているため(つまり、さまざまな機能を特定のコンポーネントに分割してカプセル化する)、このモバイルソリューションのコア機能はすべてモバイルコンポーネントに渡されて完了します。移動ロジックは、ゲームの複雑さに応じて異なる方法で処理されます。シンプルなトップビューRTSタイプのゲームの場合、一人称RPGゲームの基本的な座標の動きしか提供されない可能性があり、プレーヤーは空に入る可能性があります。ダイビング飛行の場合、必要な動きはより複雑です。しかし、どのような種類であっても、UEは基本的にそれを実現するのに役立ち、初期のFPSゲーム開発の経験からも恩恵を受けました。

エンジンによって提供される基本的な動きは、必ずしも私たちの目標を達成するわけではなく、私たちのデザインを制限するべきではありません。たとえば、軒と光の壁、魔法の宇宙船の超重力、スプリングシューズ、ジェットパックの飛行制御などです。これらの効果により、移動ロジックを自分でさらに処理する必要があります。それに基づいて変更したり、独自の移動モードをカスタマイズしたりできます。いずれにせよ、これらの操作にはモバイルコンポーネントの細心の調整が必要であるため、モバイルコンポーネントの実装原則を深く理解する必要があります。

さらに、オンラインゲームでは、モバイルの取り扱いがより複雑になります。さまざまなクライアントのプレーヤーにスムーズなモバイルパフォーマンスを体験させる方法は?少し遅れてキャラクターが「テレポート」しないようにするにはどうすればよいですか? UEによるこの側面の処理は、私たちの思考と学習に値します。

モバイルコンポーネントは単なるモバイル関連のコンポーネントのようですが、それ自体には、ステートマシン、同期ソリューション、物理モジュール、さまざまなモバイル状態の詳細な処理、アニメーション、および他のコンポーネント(アクター)との呼び出し関係が含まれます。 、勉強するのに十分な時間。この記事では、モバイルの基本原則、モバイル状態の詳細な処理、およびモバイル同期ソリューションから可能な限り実装の原則を分析し、すべての人がモバイルコンポーネントをすばやく理解してより適切に使用できるようにします。最後に、特別なモバイルモードを実現するためのいくつかのアイデアが参考のために提供されています。

二。モバイル実装の基本原則

2.1モバイルコンポーネントとプレイヤーキャラクター

キャラクターの動きは本質的に座標位置を合理的に変更することであり、UEでのキャラクターの動きの本質は特定のルートコンポーネントの座標位置を変更することです。図2-1は、キャラクターの一般的なコンポーネント構成です。通常、ルートコンポーネントとしてCapsuleComponent(カプセル本体)を使用し、Characterの座標は基本的に、そのRootComponent、メッシュグリッド、およびその他のコンポーネントの座標であることがわかります。カプセルに続いて移動します。モバイルコンポーネントが初期化されると、カプセル本体がモバイルベースコンポーネントのUpdateComponentとして設定され、後続の操作はすべてUpdateComponentの位置を計算します。

図2-1デフォルトのキャラクターのコンポーネント構成

もちろん、カプセル本体をUpdateComponentに設定する必要はありません。 DefaultPawn(オブザーバー)の場合、彼のSphereComponentはUpdateComponentとして使用され、車両オブジェクトAWheeledVehicleの場合、彼のメッシュグリッドコンポーネントはデフォルトでUpdateComponentとして使用されます。 UpdateComponentは自分で定義できますが、カスタムコンポーネントは、移動ロジックを正常に実装できるように、USceneComponentを継承する必要があります(つまり、コンポーネントにはワールド座標情報が必要です)。

2.2モバイルコンポーネントの継承ツリー

モバイルコンポーネントクラスは1つだけではありません。継承ツリーを通じて、モバイルコンポーネントの機能を徐々に拡張します。モバイル機能の最も単純な提供から、さまざまなモバイル状態でのモバイル効果の正しいシミュレーションまで。図2-2に示すように

図2-2モバイルコンポーネントの継承関係のクラス図

4つのモバイルコンポーネントクラスがあります。 1つ目はUMovementComponentです。モバイルコンポーネントの基本クラスとして、SafeMovementUpdatedComponent()の基本的なモバイルインターフェイスを実装します。 UpdateComponentのインターフェイス関数を呼び出して、その位置を更新できます。

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 }

上の図から、UpdateComponentのタイプがUScenceComponentであることがわかります。 UScenceComponentタイプのコンポーネントは、基本的な場所情報(ComponentToWorld)を提供し、それ自体とそのサブコンポーネントの場所を変更するためのインターフェイスInternalSetWorldLocationAndRotation()も提供します。 UPrimitiveComponentクラスはUScenceComponentから直接継承し、レンダリングと物理情報を追加します。一般的なメッシュコンポーネントとカプセルはUPrimitiveComponentから継承されます。これは、実際の移動効果を実現したいため、物理世界のアクターと常に接触している可能性があり、移動しながら移動アニメーションをレンダリングする必要があるためです。プレイヤーに見せるため。

次のコンポーネントはUNavMovementComponentです。これは、AIパスファインディングを提供する機能であり、泳ぐことができるかどうか、飛ぶことができるかどうかなどの基本的な移動ステータスが含まれています。

UPawnMovementComponentコンポーネントは、プレーヤーと対話できるようになりました。フロントは基本的なモバイルインターフェースであり、手動での呼び出しなしではプレイヤーの操作は実現できません。 UPawnMovementComponentはAddInputVector()を提供します。これは、プレーヤーの入力を受け取り、入力値に従って制御されたポーンの位置を変更できます。 UEでは、ポーンは制御可能なゲームキャラクターであり(AIでも制御可能)、ポーンの動きは特定のコンポーネントUPawnMovementComponentと調整する必要があるため、これが名前の由来でもあることに注意してください。一般的な操作プロセスでは、プレーヤーはInputComponentコンポーネントを介してボタン操作をバインドし、ボタンが応答したときにPawnのAddMovementInputインターフェイスを呼び出してから、モバイルコンポーネントのAddInputVector()を呼び出します。呼び出しが完了すると、操作はConsumeMovementInputVector()インターフェイスを介して消費されます。の値を入力して、移動操作を完了します。

最後に、モバイルコンポーネントの主な焦点であるUCharacterMovementComponentが登場しました。このコンポーネントは、Epicの長年のゲーム経験の統合であると言えます。さまざまな一般的な移動ステータスの詳細を正確に処理し、比較的スムーズな同期ソリューションを実現します。さまざまな位置修正とスムージング処理により、現在のモバイル効果が実現されています。この高度に完成したモバイルコンポーネントを使用するために、独自のコードを作成する必要はありません。一人称・三人称のRPGゲームに最適と言えます。 。

実際、より一般的に使用されるモバイルコンポーネントであるUProjectileMovementComponentがあります。これは、弓や矢、弾丸、その他の発射物の動きをシミュレートするために一般的に使用されます。ただし、この記事ではこれに焦点を当てません。

2.3モバイルコンポーネント関連のクラス関係の簡単な分析

以前の分析は主にモバイルコンポーネント自体に焦点を当てていましたが、ここではモバイルのフレームワーク全体のより包括的な概要を示します。 (図2-3を参照)

図2-3モバイルフレームワークの関連クラス図

通常の3次元空間では、最も簡単な動きは、キャラクターの座標を直接変更することです。したがって、キャラクターに座標情報を含むコンポーネントがある限り、基本的なモバイルコンポーネントを移動できます。ただし、ゲームの世界の複雑さが増すにつれて、ゲーム内で探索できる歩行可能な地面と海を追加しました。動きが複雑になることがわかりました。プレイヤーは足元で地面を歩くことができるため、プレイヤーが水中で泳ぎたい場合は、地面の衝突情報(FFindFloorResult、FBasedMovementInfo)を常に検出する必要があり、水の量を検出する必要があります(GetPhysicsVolume()、Overlap event 、また物理学が必要です)水中での速度と効果は陸上のものとは非常に異なるため、移動するときは2つの状態(PhysSwimming、PhysWalking)を別々に記述し、アニメーションアクションを一致させてから、更新します。アニメーション(TickCharacterPose)移動中に障害物に遭遇したときの対処方法、他のプレイヤーによるプッシュへの対処方法(MoveAlongFloorには関連する処理があります)ゲームの内容が少なすぎるため、独自の方法を見つけることができるNPCをいくつか追加したいと思います、およびプレーヤーが退屈すぎる場合は、ナビゲーショングリッド(FNavAgentPropertiesを含む)を設定する必要があります。全員がオンラインでプレイできるようにします(モバイル同期FRepMovement、クライアント側のモバイル修正ClientUpdatをシミュレートします) ePositionAfterServerUpdate)。

このように見ると、優れたモバイルコンポーネントになるのは簡単ではありません。しかし、何があっても、UEは基本的にあなたのためにそれを行います。以上の説明により、モバイルコンポーネントの取り扱いについては、あらゆる面で一般的に理解できましたが、特定の問題が発生したときに開始できない場合があるため、分析を続けましょう。

三。各運動状態の詳細な取り扱い

このセクションでは、コンポーネントUCharacterMovementComponentに焦点を当てて、さまざまな移動状態でプレイヤーキャラクターをどのように処理するかを詳細に分析します。まず第一に、それはティックで始まらなければなりません。状態はフレームごとに検出され、処理されます。状態は、移動モードMovementModeによって区別されます。これは、必要に応じて正しい移動モードに変更されます。デフォルトでは6つの移動モードがあります。一般的に使用される基本的なモードは、ウォーキング、水泳、落下、飛行です。 AIエージェント用に提供されている歩行モードがあり、最後にカスタム移動モードがあります。

図3-1スタンドアロンモードでのモバイル処理フロー

3.1ウォーキング

歩行モードはすべての移動モードの基本であると言え、最も複雑なモードでもあります。現実世界の動きの効果をシミュレートするには、地面のように、落下しないで支えられる物理的なオブジェクトがプレーヤーの足の下にある必要があります。モバイルコンポーネントでは、このグラウンドはメンバー変数FFindFloorResultCurrentFloorによって記録されます。ゲームの開始時に、モバイルコンポーネントは構成に応じてデフォルトのMovementModeを設定します。ウォーキングの場合は、FindFloor操作で現在の地面を見つけます。 CurrentFloorの初期化スタックを図3-2に示します(キャラクターRestart()はPawnのRestart()をカバーします):

図3-2

まず、FindFloorのプロセスを分析しましょう。 FindFloorは基本的に、カプセルのスイープ検出を通じて足の下の地面を検出するため、地面には物理データが必要であり、チャネルタイプはプレーヤーのPawn withBlockに応答するように設定する必要があります。ここにいくつかの小さな詳細があります。たとえば、地面を探すときは、足の近くの場所だけを考慮し、腰の近くのオブジェクトは無視します。スイープは、X線写真の検出の代わりにカプセルを使用します。これは、傾斜面の動きの処理や立ち半径の計算などに便利です。 (図3-3を参照してください。HitResultのNormalとImpactNormalは、カプセルのスイープ検出で同じではない場合があります)。さらに、キャラクターの現在の動きはカプセルボディに基づいているため、カプセルボディコンポーネントのないアクターはUCharacterMovementComponentを通常は使用できません。

図3-3

地面を見つけた場合、プレイヤーは地面に立つことができますか?必ずしも。これもまた、新しい概念PerchRadiusThresholdを含みます。これは、私がとまり木半径と呼んでいます。これは、立っている半径です。デフォルト値は0で、モバイルコンポーネントは関連する立ち半径の計算を無視します。この値が0.15を超えると、現在のグラウンドスペースがプレーヤーが立つのに十分であるかどうかを確認するためにさらに判断が行われます。

前回の準備作業が完了し、正式に歩行変位計算に入りました。このコードはPhysWalkingで計算されます。スムーズに実行するために、UE4はティックの動きをN個のセグメントに分割します(各セグメントの時間はMaxSimulationTimeStepを超えることはできません)。各セグメントを処理するときは、最初に現在位置情報と地上情報を記録します。 TickComponentでは、現在の加速度はプレーヤーのキーの持続時間に従って計算されます。次に、CalcVelocity()は加速度に基づいて速度を計算し、地面の摩擦、水中にあるかどうかなども考慮します。

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

速度を計算した後、関数MoveAlongFloor()を呼び出して、現在のオブジェクトの座標位置を変更します。モバイルインターフェースSafeMoveUpdatedComponent()を実際に呼び出す前に、特別な状況が単純に処理されます。プレーヤーは斜面に沿って歩きます。通常、歩行状態では、プレイヤーは前後左右に移動するだけで、Z方向の移動速度はありません。斜面に遭遇した場合はどうなりますか?傾斜を歩くことができる場合は、ComputeGroundMovementDelta()関数が呼び出され、現在の水平速度に基づいて新しい平行速度と傾斜速度が計算されます。これは、斜面に沿って歩く効果を簡単にシミュレートでき、一般的に上り坂を登るときは、プレーヤーの水平速度を下げる必要があります。これは、bMaintainHorizo​​ntalGroundVelocityをfalseに設定することで自動的に処理できます。

これで、モバイルプロセスを完全にシミュレートできるように見えますが、考えてみると、考慮されていない別の状況があります。それが障害に対処する方法ですか?私たちの通常のゲーム経験によれば、障害物に遭遇することは動かないことであるに違いありません、そしてそれは壁に沿って少し滑るかもしれません。これは確かにUEの場合です。キャラクターの移動(SafeMoveUpdatedComponent)のプロセス中に、衝突検出プロセスが発生します。 UPrimitiveComponentコンポーネントには物理データがあるため、この操作は関数UPrimitiveComponent :: MoveComponentImplで処理されます。次のコードは、移動中に障害物に遭遇したかどうかを検出し、障害物に遭遇した場合はHitResultを返します。

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

SafeMoveUpdatedComponent()によって返されるHitResultを受け取った後、衝突障害物は次のコードで処理されます。

Hit.NormalがZ方向の値を持ち、それでも歩くことができる場合、これは上に移動できる傾斜面であり、プレーヤーは傾斜面に沿って移動できることを意味します。

現在のコライダーを踏むことができるかどうかを判断し、可能であれば、踏んでみてください。プロセス中に踏まれない場合は、SlideAlongSurface()を呼び出して、衝突に沿ってスライドします。

// 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 } else if ( Hit.Component.IsValid() && !Hit.Component.Get()->CanCharacterStepUp(CharacterOwner) ) { HandleImpact(Hit, LastMoveTimeSlice, RampVector) SlideAlongSurface(Delta, 1.f - PercentTimeApplied, Hit.Normal, Hit, true) } } }

基本移動処理が完了し、移動直後にプレイヤーが水状態に入ったか落下状態に入ったかを判断し、入った場合はすぐに新しい状態に切り替わります。に
プレーヤーはフレーム内で歩行、水泳、落下などの状態を絶えず切り替える可能性があるため、各移動の前に現在のフレームの移動数を記録するための反復があります。制限を超えると、動作シミュレーションはキャンセルされます。動作。

3.2落下

落下状態は、ウォーキング以外の最も一般的な状態でもあります。プレイヤーが空中にいる限り(ジャンプするか落下するかに関係なく)、プレイヤーは落下状態になります。ウォーキングと同様に、よりスムーズなパフォーマンスを示すために、フォーリングの計算でもティックの動きがN個のセグメントに分割されます(各セグメントの時間はMaxSimulationTimeStepを超えることはできません)。各段落を処理するときは、最初に入力によって制御されるプレーヤーの水平速度を計算します。これは、プレーヤーが空中にいる間もプレーヤーの制御の影響を受ける可能性があるためです。続いて、重力計算速度を取得します。重力の獲得は少し興味深いです、あなたはそれがボリュームを通して獲得されるのを見つけるでしょう、

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

ボリュームは、WorldSettingからGlobalGravityZを取得します。ここにリマインダーがあります。コードを変更して、さまざまなボリュームのさまざまな重力を実現し、カスタムゲームプレイを実現できます。ボリュームがない場合でも、デフォルトのDefaultVolumeをUpdateComponentにバインドすることに注意してください。では、なぜDefaultVolumeが必要なのですか?多くのロジック処理では、DefaultVolumeと関連データを内部に取得する必要があるためです。たとえば、DefaultVolumeにはTerminalLimitがあり、重力によって下降速度を計算するときに設定速度を超えることはできません。この値を変更することで制限速度を変更できます。デフォルトでは、DefaultVolumeの多くのプロパティは、ProjectSettingのPhysics関連の構成によって初期化されます。図3-4を参照してください。

図3-4

取得した重力を介して現在の新しいFallSpeedを計算します(NewFallVelocityで計算され、計算ルールは非常に単純です。つまり、現在の速度-Gravity * deltaTimeを使用するだけです)。次に、変位を計算し、現在および前のフレーム速度に従って移動します。式は次のとおりです。

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

前に速度を計算してプレーヤーを動かした後、モバイル衝突の問題も考慮する必要があります。

最初のケース 通常の着陸です。計算後に立つことができる地形と衝突した場合は、直接ProcessLandedを呼び出して着陸操作を行います(この判断は主に衝突点の高さに基づいており、壁をフィルターで取り除くことができます)。

2番目のケース ジャンプの過程でプラットフォームに遭遇し、プレーヤーの座標と現在の衝突点が許容範囲内にあるかどうかを確認します(IsWithinEdgeTolerance)。そうであれば、FindFloorを実行して地面を再確認し、検出された場合は実行します。着陸プロセス。

3番目のケース 踏むことができないのは壁などです。落下プロセス中に障害物に遭遇した場合、HandleImpactが最初に実行され、触れられたオブジェクトに力が与えられます。次に、ComputeSlideVectorを呼び出して、スライドの変位を計算します。障害物との衝突後、プレイヤーの速度が変化するため、この時点で速度を再計算し、プレイヤーの位置と方向を再度調整してください。この時点でプレーヤーに水平方向の変位がある場合、LimitAirControlを使用してプレーヤーの速度を制限します。結局のところ、プレイヤーは空中でキャラクターを自由に制御することはできません。 3番目の状況をさらに拡張するために、衝突調整が行われ、別の壁に遭遇する場合があります。ここでの落下の処理により、プレイヤーは2つの壁の適切な位置を見つけることができます。しかし、それでもプレーヤーが2つの斜面で捕らえられても着陸できない(または常にウェリングとフォーリングを切り替える)状況を解決することはできません。時間があれば、後でこの問題の解決を試みることができます。ソリューションは、FindFloorの下のComputeFloorDist関数から開始できます。目的は、プレイヤーがこの状況で歩ける地面を見つけられるようにすることです。

図3-5ギャップに引っ掛かると一定のスイッチングが発生する

3.2.1ジャンプ

落下に関しては、ジャンプの基本的な操作について言及する必要があります。以下は、ジャンプ応答の基本的な流れを大まかに説明しています。

1.バインディングトリガー応答イベント

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.ボタンが応答したらすぐにbPressedJumpをtrueに設定します。 TickComponentのフレームループはACharacter :: CheckJumpInputを呼び出して、ジャンプ操作が実行されたかどうかを即座に検出します

  1. CanJump()関数を実行して、ブループリント内の関連する制限ロジックを処理します。ブループリントで関数が書き直されていない場合、ACharacter :: CanJumpInternal _ Implementation()がデフォルトで実行されます。これは、しゃがんだりジャンプしたりしないなど、プレーヤーがジャンプできるかどうかを制御するための基礎です。さらに、JumpMaxHoldTimeがあります。これは、この値を超えてキーを押した後、プレーヤーがジャンプをトリガーしないことを意味します。 JumpMaxCountは、プレーヤーがジャンプを実行できるセグメントの数を表します。 (ダブルジャンプなど)

  2. CharacterMovement-> DoJump(bClientUpdating)関数を実行し、ジャンプ操作を実行し、Fallingと入力して、ジャンプ速度をJumpZVelocityに設定します。この値は0以上にする必要があります。

  3. const bool bDidJump = canJump && CharacterMovement && DoJumpがtrueであるかどうかを判別します。他の関連する操作を実行します。

  4. ジャンプカウントと関連するイベントの配布を行う

3. PerformMovementが終了すると、ClearJumpInputが実行され、bPressedJumpがfalseに設定されます。ただし、JumpCurrentCountはクリアされないため、複数のジャンプを引き続き処理できます。

4.プレーヤーがボタンpを離すと、bPressedJumpがfalseに設定され、関連する状態がクリアされます。プレイヤーがまだ空中にいる場合、JumpCurrentCountはクリアされません。 bPressedJumpがfalseになると、ジャンプ操作は処理されません。

5.プレイヤーが空中でジャンプボタンを押すと、ACharacter :: CheckJumpInputも入力します。 JumpCurrentCountがJumpMaxCountより小さい場合、プレーヤーはジャンプ操作を実行し続けることができます。

図3-6

3.3水泳

各州には3つの本質的な違いがあります。

  1. 速度の違い

  2. 重力の程度

  3. 慣性

水泳のパフォーマンスは、動きの慣性の状態(手放した直後に停止しない)であり、重力の影響を受けにくく(ゆっくりと落下するか、水中で動かない)、通常よりも遅くなります(耐水性を示します)。プレーヤーが水中にいるかどうかのデフォルトの検出ロジックも比較的単純です。つまり、現在のupdateComponentが配置されているボリュームがWaterVolumeであるかどうかを判別します。 (エディターでPhysicsVolumeをプルし、属性WaterVolumeを変更します)

CharacterMovementコンポーネントには浮力構成の浮力があり、最終的な浮力は、プレーヤーが水中に飛び込む度合いに応じて計算できます(ImmersionDepthは0-1を返します)。その後、速度を計算する必要があります。このとき、摩擦力をボリュームで取得し、それをCalcVelocityに渡す必要があります。これは、水中でのプレーヤーの動きが遅いことの影響を反映しています。次に、Z方向の浮力を計算して、その方向の速度を計算します。プレイヤーが潜るにつれて、Z方向のプレイヤーの速度がどんどん小さくなっていることがわかります。全身が水中に沈むと、Z軸方向の重力速度は完全に無視されます。

// 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)) }

図3-7水量に浮かぶキャラクター

速度が計算された後、プレーヤーは移動できます。ここで、UEは、移動操作を実行するための別個のインターフェースSwimを作成しました。同時に、移動後に水量を離れて水面を超えすぎると、水面の位置に合わせるように強制する機会があり、パフォーマンスが向上すると考えた。

さらに、あなたはそれを推測したかもしれません、それは移動中に衝突障害物が検出された状況に対処することです。基本的に、ロジックは前のものと同様です。踏むことができる場合(StepUp())、プレイヤーの位置を調整して踏んでください。踏むことができない場合は、障害物に力を加えてから、障害物の表面に沿って距離をスライドさせます(HandleImpact、SlideAlongSurface)。

水の動きの慣性性能にどのように対処しますか?実際、それは水中での特別な処理によるものではありませんが、速度を計算するとき、ウォーキングのパラメーターとは異なる2つの入力パラメーターがあります。 1つは摩擦を意味するFrictionであり、もう1つはブレーキの逆速度を意味するBrakeingDecelerationです。に


加速度が0(プレーヤーの入力がクリアされたことを示す)の場合、水中での摩擦は地面の摩擦(0.15:8)よりもはるかに小さく、ブレーキ速度は0(歩行は2048)であるため、ApplyVelocityBrakingは次のようになります。処理中は歩くとすぐにブレーキがかかるように見えますが、水泳やフライなどの状況では移動慣性があるようです。

// 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() 0.0f) { Velocity = OldVelocity.GetSafeNormal() * MaxSpeed } }

3.4飛行

最後に最後の移動状態について説明しました。この状態をデバッグする場合は、キャラクターの移動コンポーネントでDefaultLandMovementModeをFlyingに変更できます。に


飛行は他の州のルーチンに似ており、比較的簡単です。最初に前の入力に基づいて加速度を計算し、次に摩擦に基づいて現在の速度の計算を開始します。速度が計算された後、SafeMoveUpdatedComponentが呼び出されて移動します。障害物に遭遇した場合は、最初にそれを踏むことができるかどうかを確認します。そうでない場合は、衝突を処理し、障害物の表面に沿ってスライドします。

//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)

飛行状態について疑問があるかもしれません。デフォルトの移動方法をFlyingに設定すると、プレーヤーはキーボードを離した後、距離を(慣性で)スライドできます。しかし、GMコマンドを使用すると、なぜ歩行状態のようになり、ボタンを離すとすぐに停止するのでしょうか。に


そのリアルタイムコードは、チートフライングのための特別な処理を行います。プレイヤーがボタンを離すと加速度が0になります。このとき、プレイヤーの速度は強制的に0に設定されます。そのため、GMを使用した場合のパフォーマンスは実際のパフォーマンスとは異なります。

3.5FScopedMovementUpdate遅延更新

FScopedMovementUpdateは状態ではなく、最適化された移動プログラムです。エンジンコードを見ると、移動を実行する前に次のコードが表示される場合があるためです。

// 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

移動コードをこの中括弧内に配置する理由と、FScopedMovementUpdateとは何ですか?以前の特定の動き処理ロジックを注意深く思い出してください。フレーム内では、違法な動きや障害物により、何度も動きをリセットまたは変更する場合があります。カプセル本体の位置を変更するだけでは、実際には何もありませんが、実際には、サブコンポーネントの位置の変更、物理ボリュームの更新、物理位置の更新など、および動きも必要です。計算プロセスのデータは実際には役に立たない。最後のモバイルデータのみが必要です。

したがって、FScopedMovementUpdateを使用すると、物理学やその他のオブジェクトなどのオブジェクトの移動をスコープ内でロックし、移動が実際に完了した後に更新できます。 (FScopedMovementUpdateが破棄されるまで待ってから処理してください)FScopedMovementUpdateが使用される場所は多く、基本的にモバイルロールバックに関連するロジックがあります。

つづく

ゲーム開発

「ゲームブック」に返信して、ゲーム開発の本を入手してください

「C ++インタビュー」に返信して、C ++ /ゲームインタビューの経験を積んでください

「ゲーム開発入門」に返信して、ゲーム開発入門に関する記事を入手してください。

「オペレーティングシステム」に返信して、オペレーティングシステム関連の書籍を入手してください