【Unity】壁の移動処理で悩む(モーション導入)

前回更新から少し時間が経ってしまったんですが、
ようやくアンドロイド君にモーション付きの壁登りが
実装できたので、記事にしてみようと思います。

なかなか思うモーションが作れなくて、
あれでもないこれでもないとAnimationと格闘しておりました。
正直、時間をかけた割には、出来栄えは酷いです。(T-T) グスッ


まず、アンドロイド君を壁登りさせる為に上方向の移動処理を考えてみます。
【Unity】壁の移動処理で悩む
この記事で書いた内容を応用しながら作って行こうと思います。

壁に張り付いた状態を確認する為にLinecastを設定します。
ActionTest

private bool frontRay; //前方Ray

//Rayの設定
private void SetLinecast()
{
//LineCast用座標設定
Vector2 frontP1 = transform.position + (transform.right * 1.1f);
Vector2 frontP2 = transform.position + (transform.right * 1.1f) - (transform.up * 2.15f);

//前方センサー
frontRay = Physics2D.Linecast(frontP1, frontP2, wallLayer);

Debug.DrawLine(frontP1, frontP2, Color.red);
}

実際にRayを張ると
壁登り②.jpg
アンドロイド君の前にある赤い線がLinecastになります。
これで壁の接触を感知します。

アンドロイド君は、BoneにColliderを設定してるので、
このまま壁に当たると腕や足の位置で壁までの距離が安定しないので、
壁用のColliderを用意します。

足元に入れてるColliderの壁版と考えてもらえれば分かり易いです。
壁登り①.jpg
壁に接触した際、このColliderで壁との距離を一定にします。
ActionTest

public GameObject wallCollider; //壁用コライダー

void Update()
{
SetLinecast();

//フロントColliderアクティブ判定
if (frontRay)
wallCollider.SetActive(true);
else
wallCollider.SetActive(false);
}

Updateの冒頭でアクティブ判定を作って、接触時にONすればOKです。

続いて、接触時の状態でAnimationを変化させます。

接触する状態は、床を走ってる時、ジャンプ中の二つになります。
今回の壁登りは、ジャンプで壁を登って行く処理なので、
床に着地してる時は、止まっている状態をみせたいので、
壁登り③.jpg
壁に手を添えて立ち尽くした感じのAnimationを用意します。

後は、床に接触中で壁に接触となるので、
ActionTest

//アクションナンバーの設定
private enum actionNum
{
stop = 0,
run = 1,
jump = 2,
fall = 3,
sliding = 4,
wallAttach = 5,
wallJump = 6,
fallDown=7,
overCome=8,
}

void Update()
{
SetLinecast();

//フロントColliderアクティブ判定
if (frontRay)
wallCollider.SetActive(true);
else
wallCollider.SetActive(false);

//床の接触判定
if (bodyUnder)
FloorAction();
else
VerticalMotion();
}


//床に接触してる時の処理
private void FloorAction()
{
if (overComeMode == false)
footCollider.SetActive(true);

if (Input.anyKeyDown)
KeySelect();

//壁に接触したらstopモーション再生
if (frontRay)
{
AnimationChange(actionNum.stop);
return;
}

if (runSpeed < speedMax)
SpeedController(true);

AnimationChange(actionNum.run);
}

Update内が複雑になってきたので、FloorAction関数を用意して、
床に接触してる時の処理を分離しました。

まず、床に接触してる時はジャンプできないと困るので、
Input.anyKeyDownの後に設定する必要があります。

また、床に接触してる時に速度をコントロールしてるので、
この部分が呼び出されないように、前でstopモーションを呼び出してReturnさせます。
これで、stopモーションが呼び出されてもジャンプも出来て、
移動速度が上がらない状態にできます。

Floor上にいる時の処理がつくれたので、壁に張り付いた時の処理を作ります。

前回、球で作ったジャンプ処理を導入するのですが、
球を壁ジャンプさせる際に、左斜めに力を加える事で、
壁から浮き上がるような処理をさせていました。

ただ、アンドロイド君を浮き上がるように見せるのは、
Animationでできるので、ジャンプとしては、真上に上がる処理でOKになります。

なので、通常ジャンプの処理を利用しようと思います。
まず、Animationが必要になるので、
壁に張り付いてる状態
壁ジャンプの状態

二つのクリップを作成します。
壁登り④.jpg
壁に張り付いてる状態です。
キーフレームは、一つで問題ないです。
0秒に張り付いた状態を設定します。
壁登り⑤.jpg
壁をジャンプの状態です。
壁用Colliderから少し離れた位置で態勢を作っておきます。
Colliderが壁と接触する事で本体は浮いた感じに見えます。
これもキーフレームは一つで問題ないです。
壁登り⑥.jpg
Animationが作れたら、各状態から対応したトランジションを設定します。

かなり複雑になってきたので、簡単に説明すると
Jump→WallAttach
Fall→WallAttach
run→stop
WallAttach⇔WallJump
こんな感じでトランジションを組みます。

ちなみにWallAttachが張り付きで、WallJumpが壁登りです。

Animationの準備ができたので、ジャンプの処理を対応させていきます。
ActionTest

private bool jump; //ジャンプフラグ
private const float jumpPow = 400f; //ジャンプ力
private const float climbPow = 525f; //壁登り力

private void FixedUpdate()
{
if (jump)
{
JumpMotion();
}
}

//ジャンプ処理
private void JumpMotion()
{
float tempPow = (playerRB.velocity.y < 0) ?
Mathf.Abs(playerRB.velocity.y) * 50f + climbPow
: jumpPow;

footCollider.SetActive(false);
playerRB.AddForce(Vector2.up * tempPow);
jump = false;
}

三項演算子で書いたので分かりにくいですかね~
少し説明すると、
床上で止まっている時と壁に張り付いてる時を切り分けるには、
Velocityを利用すれば判断できます。

床上に止まっていれば、velocityは0になります。
壁に張り付いてる時は、落下するのでマイナスになります。
なので、 (playerRB.velocity.y < 0) ?としておけば、張り付いてる事が判断できます。

壁の場合、落下速度が上がるとジャンプ力が足りなくなるので、
球の時に考えた、落下速度に合わせた力加減を導入してみました。

通常と壁でジャンプ力を変えたのは、
壁を少しスムーズに上がって行く感じにしたかったので、
壁ジャンプ力を少し大きくしました。

これでジャンプの処理が作れたので、Animationの切り替えを作ります。

Animationの切り替えは、
ジャンプの上昇・落下判定と同じでvelocityから読み取ります。
上昇中ならWallJumpを、落下中ならWallAttachを再生すればOKでっす。

と言う事で、上昇落下判定がジャンプと共有できるので、中に組み込んでいきます。
ActionTest

//落下・上昇チェック
private void VerticalMotion()
{
//落下中なら
if (playerRB.velocity.y < 0)
{
//壁に接触してるか
if (frontRay)
{
if (Input.anyKeyDown)
KeySelect();

AnimationChange(actionNum.wallAttach);
}
else
AnimationChange(actionNum.fall);
}

//上昇中なら
else if (playerRB.velocity.y > 0)
{
if (frontRay == false)
SpeedController(false);

//壁に接触してるか
if (frontRay)
AnimationChange(actionNum.wallJump);
else
AnimationChange(actionNum.jump);
}
}

まず、関数名をAirMotionとしてましたが、
壁に張り付いても呼び出すので、VerticalMotionに変更しました。

Velocityの+-量に合わせて、Animationを切り替えます。

ジャンプのキー入力は、張り付いてる時だけ入力を受け付けたいので、
落下中の判定内にキー入力を用意します。

これで一応、処理が作れたのでPLAYしてみます。
壁登り⑦.jpg
あら??壁に刺さって止まってしまう… (T-T) グスッ

球の時は、移動速度2で処理をしていました。
壁のFrictionは、0.002に設定してるのですが、速度が7.6となってるので、
早すぎると摩擦の影響を受けて、下に落ちなくなってます。

これでは意味がないので、Frictionを0にしたいのですが、
壁に接触してる時の動きが滑り過ぎる感じになるので、
少し残しておきたいんですよね~ ( ̄~ ̄;) ウーン

なので、移動速度を調整してみます。
ActionTest

private void FixedUpdate()
{
MoveProcessing();

if (jump)
{
JumpMotion();
}
}

//移動処理
private void MoveProcessing()
{
float tempSpeed = (frontRay) ? 1f : runSpeed;
playerRB.velocity = new Vector2(tempSpeed, playerRB.velocity.y);
}

面倒だったので、壁に接触してる際は1を代入すると言う処理にしました。
これならrunSpeedの調整など必要なくなるので簡単でいいです。
(;^o^) \(ToT )あんたほんとにそれでいいの

今度こそ上手く行くこと願ってPLAYでっす。
上手く上下するようになりました。ヽ(´▽`)/~♪

こうなるとフィールドを作ってPLAYしたくなるのが心情!
高台やスライディングできる隙間、障害物を用意してPLAYしてみます。
壁登り⑧.jpg
ああ!
スライディングでLinecastが反応して止まってしまいます(T-T) グスッ

忘れてました。
スクリプトを修正します。
ActionTest

//Rayの設定
private void SetLinecast()
{
//前方センサー
if (sliding == false)
frontRay = Physics2D.Linecast(frontP1, frontP2, wallLayer);
}

単純にスライディングフラグがOFFの時だけLinecastを張る様にします。

今度こそ上手く行くように願って…

なんとか形になりました。ヽ(´▽`)/~♪

一通りのアクションは作れたので、追いかけてくる敵を作れば、
一応をゲームとしては、成り立ってくれそうな感じでっす。

ただ、不満点も多々あります。
・壁を登り切った時に大きく飛び上がる
・スラディングで障害物にぶつかると転倒までラグがある
・結局Boneに設定したColliderの意味がなくなってる

この辺りは、今後の課題になりそうです。

まだまだモーションやギミックなんかも導入したいのですが、
処理が複雑になりすぎる事や追加すると必ずバグが出たりします。

一からアクションを作るのって難儀ですね~(T^T) ヒック

まだまだ追加したい部分があるので、新しく作れたら記事にしたいと思います。
ただ、思うように処理が作れないので、更新はかなり遅れると思いますが…
(°-°;)ヾ(-_- ;) シモシモ・・

それまで暫くお待ちくださいませ。
(TωT)/~~~ BYE BYE

この記事へのコメント