C++ ときどき ごはん、わりとてぃーぶれいく☆

USAGI.NETWORKのなかのひとのブログ。主にC++。

地上を歩けて、空も飛べるキャラクター( ACharacter )の C++ な作り方

C++/TPSのテンプレプロジェクトを作ってソースを眺めると「地上を歩けるキャラクター」の作り方がわかります。そこで、追加要素として「この ACharacter を空も飛べるようにしたい」と思っても、エディターで “Can Fly” をチェックONするだけではまったく飛べないので、どうしたらよいか整理します。

歩く、飛ぶの切り替え方法

ACharacter::GetCharacterMovementUCharacterMovementComponent を取得して、 SetMovementMode に移動モードの定数を放り込む。

// --> 飛行移動モード
GetCharacterMovement()->SetMovementMode( MOVE_Flying );
// --> 歩行移動モード
GetCharacterMovement()->SetMovementMode( MOVE_Walking );

歩く、飛ぶの他に今回の記事でも使う落下なんかもある。他にも泳ぐとかあるけど今回は使わないので説明略。

// --> 落下移動モード
GetCharacterMovement()->SetMovementMode( MOVE_Falling );

ジャンプをトリガーにして飛ばす

  1. ジャンプ中にもう一度ジャンプ入力が来たら飛ばす。
  2. 飛行中にジャンプ入力が来たら落下させる。
  // AtpsCharacter::Jump をオーバーライド
  virtual void Jump() override;
void AtpsCharacter::Jump()
{
  // 1. 落下移動モードでジャンプ入力 --> 飛行移動モードへ移行
  if ( GetCharacterMovement()->IsFalling() )
    GetCharacterMovement()->SetMovementMode( MOVE_Flying );
  // 2. 飛行移動モードでジャンプ入力 --> 落下移動モードへ移行
  else if ( GetCharacterMovement()->IsFlying() )
    GetCharacterMovement()->SetMovementMode( MOVE_Falling );
  // その他の移動モード(歩行など)でジャンプ入力 --> ジャンプ
  else
    Super::Jump();
}

これで AtpsCharacter は最低限、歩いて飛べるキャラクターになる。

上昇/降下の入力を追加して飛ばす

  1. 飛行モードに対応したキャラクターは上昇/下降も入力で制御したい
  2. 歩行移動モードからジャンプしなくても、上昇の入力ですぐに飛行移動モードにしたい
  3. 落下中に上昇の入力でも飛行移動モードにしたい

(1)の実装には先だってエディターでプロジェクトの設定の入力から適当なAxis(例えば "MoveTop")とキーなどを割り当てておく。その上で、 void AtpsCharacter::SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) 内で、

PlayerInputComponent->BindAxis( "MoveTop", this, &AtpsCharacter::MoveTop);

としつつ、 AtpsCharacter::MoveTop も実装する:

// for tpsCharacter.h
  void MoveTop( float Value );
// for tpsCharacter.cpp
// TPSテンプレプロジェクトで始めれば既に定義済みのはずの MoveRight, MoveForward を参考にほぼそのまま軸の向きだけ変えればよい
void AtpsCharacter::MoveTop( float Value )
{
  if ( ( Controller != NULL ) && ( Value != 0.0f ) )
  {
    // TODO: このあとここに (2), (3) 用のコードを追加

    // find out which way is right
    const FRotator Rotation = Controller->GetControlRotation();
    const FRotator YawRotation( 0, Rotation.Yaw, 0 );

    // get right vector 
    const FVector Direction = FRotationMatrix( YawRotation ).GetUnitAxis( EAxis::Z );
    // add movement in that direction
    AddMovementInput( Direction, Value );
  }
}

ここまでで飛行中に上昇/下降は可能になる。続けて、↑の MoveTop の実装を少し工夫して (2), (3) も実現できる。

    // TODO: このあとここに (2), (3) 用のコードを追加

この部分を次のように実装する。

    // 2. 歩行移動モードからジャンプしなくても、上昇の入力ですぐに飛行移動モードにしたい
    // 3. 落下中に上昇の入力でも飛行移動モードにしたい
    if ( Value > 0 && !GetCharacterMovement()->IsFlying() )
      GetCharacterMovement()->SetMovementMode( MOVE_Flying );

飛行中に足場に接触したら歩行移動モードにする

この機能を実装しようとすると嵌りそうなちょっとした罠がある。

  • TickGetCharacterMovement()->IsFlying()GetCharacterMovement()->IsMovingOnGround() をチェックして・・・
    • IsMovingOnGroundMODE_Walking では意図通りに機能するけど MODE_Flying では常に false になるので使えません。
  • ACharacter::Landed( const FHitResult& )override して GetCharacterMovement()->IsFlying() なら・・・
    • MODE_Flying では Landed(ブループリント的には OnLanded ) がそもそも発生しないので使えません。

と、言うわけで、この機能の実装は素直な当たり判定で処理する事になる。

  // ACharacter::NotifyHit を override
  virtual void NotifyHit
  ( class UPrimitiveComponent* MyComp
  , AActor* Other
  , class UPrimitiveComponent* OtherComp
  , bool bSelfMoved
  , FVector HitLocation
  , FVector HitNormal
  , FVector NormalImpulse
  , const FHitResult& Hit
  ) override;
// 実装
void AtpsCharacter::NotifyHit
( class UPrimitiveComponent* MyComp
, AActor* Other
, class UPrimitiveComponent* OtherComp
, bool bSelfMoved
, FVector HitLocation
, FVector HitNormal
, FVector NormalImpulse
, const FHitResult& Hit
)
{
  Super::NotifyHit( MyComp, Other, OtherComp, bSelfMoved, HitLocation, HitNormal, NormalImpulse, Hit );

  // 飛行移動モードかつ接触した物体から受ける法線が上向きから45[deg]未満ならば歩行移動モードへ移行する
  if ( GetCharacterMovement()->IsFlying() && acosf( FVector::DotProduct( HitNormal, FVector::UpVector ) ) < PI / 4 )
    GetCharacterMovement()->SetMovementMode( MOVE_Walking );
}

ちなみに、この機能を実装しないと、地上に接しても空中飛行中と同じように移動しても構わないのでは?と思うかもしれない。それはそれで摩擦などを与えれば問題無いのかもしれないけれど、素直に地表では歩行モードに切り替えた方が自然な挙動となる場合が多いと思います。床がツルッツルの濡れた氷上という設定ならいいかもしれないけどね。

また、もし、固定角度ではなく、歩行移動モードで歩行可能な角度の場合にしたければ、 PI / 4 の部分を FMath::DegreesToRadians( GetCharacterMovement()->GetWalkableFloorAngle() ) にします。

こうして作った AtpsCharacter のプレイ動画

youtu.be

あとは飛行中にそれっぽいメッシュやスケルタルアニメーションに切り替えたり、用途に併せて細かな挙動付けや調整を施せば実用できると思います。

参考

  1. UCharacterMovementComponent::SetMovementMode | Unreal Engine API Reference UCharacterMovementComponent | Unreal Engine API Reference

  2. ACharacter::Landed | Unreal Engine API Reference
  3. ACharacter::OnLanded | Unreal Engine API Reference
  4. AActor::NotifyHit | Unreal Engine API Reference
  5. GetWalkableFloorAngle | Unreal Engine Blueprint API Reference
  6. Making character class fly? - UE4 AnswerHub