アクションゲーム等によくある床が上下に動いたり、ベルトコンベアーのようにキャラクターを押し戻すような機能をUnityで作成していきたいと思います。
床から床へと飛び移っていくようなアクションゲームでこの機能があるとより楽しくなりそうですね♪
逆に操作性が悪いとストレスが発生する原因ともなりかねませんが・・・・(^_^;)
キャラクターをCharacterControllerで動かしている弊害
キャラクターをCharacterControllerで動かしている場合は外部からの力を受けないし、こちらから相手に影響を与える事が出来ません(Rigidbodyを取り付けていない場合)。
Rigidobody+CapsuleColliderで動かしている場合はRigidbodyコンポーネントを持っているので、外部からの力、こちらからの力を働かせる事が出来ます。
CharacterControllerを使ってキャラクターを動かしている場合は相手方でOnTriggerEnterやOnCollisionEnter等で検知をする事が出来ません。
(相手方のコライダのIsTriggerにチェックが入ってない物理的な判定をしている場合)
つまりベルトコンベアーを作成してキャラクターを検知しようと思っても検知が出来ないんです。
空間に見えないコライダを設置しIsTriggerにチェックを入れて検知するという事も出来ますが、全部に検知エリアを作るのは大変です。
こりゃまいりましたね・・・・((+_+))
これではキャラクターが床と接触しているかどうか?を検知出来ません。
しかし、CharacterControllerコンポーネントを持っているキャラクターは何かと接触した時にOnControllerColliderHitというメソッドが呼び出されます。
キャラクター側でこのOnControllerColliderHitメソッドを作成し、ベルトコンベアーの床の上に乗ったかどうかを検知するようにします。
ここまではCharacterContllerを持ったキャラクターの接触判定について見てきました。
上下に動く床を作成する
ベルトコンベアーの処理はとりあえず置いておいて、まずは上下に動く床の作成からしてみましょう。
↑のように空のゲームオブジェクト(MoveBlocks)、子要素にCubeを2つ作成します。
ゲームオブジェクトを動かすスクリプトを設定するのはMoveBlocksで子要素にはスクリプトは設定しません。
親要素が動くと子要素も相対的に動くのでCubeとCube(1)は同じように動く事になります。
↑がMoveBlocksのインスペクタです。
MoveBlocksのtransform.positionの値を直に変更してオブジェクトを動かす事も出来ますが、transform.positionで動かす場合は
物体に力を加えて動かすのではなくパラメータ値を変更して細かいワープをしているという感じになります。
なので、他のゲームオブジェクトからの影響を受けたい、または与えたい場合はtransform.positionの更新で動かさない方がいいかもしれません。
そもそも動く物体の場合はRigidbodyを取りつけて動かした方がいいみたいです。
MoveBlocksには新しくMoveBlockスクリプトを作り取り付けてください。スクリプトは後で作成します。
↑が実際の床の画像です。
階段にして上下に動くようにします。
床を上下に動かす為のスクリプトMoveBlockを作成
それでは床を上下に動かすスクリプトMoveBlockを作成していきましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | using UnityEngine; using System.Collections; public class MoveBlock : MonoBehaviour { private Rigidbody rigid; private Vector3 defaultPos; void Start () { rigid = GetComponent<Rigidbody>(); defaultPos = transform.position; } void FixedUpdate() { rigid.MovePosition(new Vector3(defaultPos.x, defaultPos.y + Mathf.PingPong(Time.time, 2), defaultPos.z)); } } |
MoveBlockのRigidbodyコンポーネントを取得してrigidフィールドに参照を入れます。
FixedUpdate関数はUpdate関数と似たものでフレーム毎に呼ばれる関数ですが、固定フレームレートで呼ばれるみたいです。
Rigidbody等で物体を動かす時はこちらを使用した方がいいようです。
RigidbodyのMovePosition関数で指定した位置に移動するようにしています。
指定している位置はX座標、Z座標はMoveBlockの最初の位置を、Y座標は
1 2 3 | defaultPos.y + Mathf.PingPong(Time.time, 2) |
の値を設定しています。
Mathf.PingPongは卓球のピンポンと同じで第1引数に指定した値を、0から第2引数で指定した値の間で設定します。
つまり上限までいったら跳ね返った値を返します。
例えば第1引数を4にすれば0が返ってきて、3にすれば1が値として返ってきます。
今回の場合Time.timeとUnity実行開始時からの時間を設定しているので、時間の経過と共に0~2の値が繰り返されるという事になります。
これでスクリプトが出来上がったので床の動きを確認してみましょう。
↑のように初期位置から上に2m上がり、その後に最初の位置まで戻るというのを繰り返しています。
キャラクターを上下に動く床にジャンプさせて登らせてみましょう。
ジャンプ機能に関しては↑の記事を参考にしてください。
↑のようにキャラクターが床にジャンプして登れるようになりました。
しかし、残念ながら床の下からジャンプをすると床をすり抜けて床の上に移動してしまいます。
現状では対処法が見つからないので、キャラクターを動く床の下に移動出来なくさせたり、床の下だとジャンプ出来ないようにするしかないかもしれません。
単純に左側の床の下にいて上から床がせまってくるとキャラクターと床が衝突し、居所をなくしたキャラクターが床の上に移動してしまいますね・・・(^_^;)
Rigidbody+CapsuleColliderでキャラクターを動かしている場合はブロックの影響を受けてキャラクターがはじかれるので問題はなさそうですが、CharacterControllerの場合は相手の力の影響を受けないのでこういう風になってしまうのかもしれません。
衝突の影響を受けたいならRigid+Capsuleに変更するのがいいのかも?
Rigidbody+Capsuleコライダでキャラクターを動かすには
↑を参照してください。
とりあえずこれで上下に動く床の作成が完了しました。
ベルトコンベアーの床を作成する
次はベルトコンベアーのような床を作成したいと思います。
ヒエラルキー上で右クリック→3D Object→Cubeで平べったい床を作成します。
名前をBeltConveyorにします。
また、BeltConveyorにはBlockレイヤーを設定します(新しいレイヤーを作成し設定してください)。
BeltConveyorを選択した状態でUnity画面の左上の方にある↑のボタンがLocalになっている事を確認します。
↑がBeltConveyorです。
青い矢印の方向にキャラクターを動かすので、床を回転させてキャラクターを動かしたい方向に青い矢印が来るようにBeltConveyorを回転します。
キャラクター操作スクリプトにベルトコンベアーを検知する処理を追加
キャラクターの移動スクリプトMoveBlockCharaを作りベルトコンベアーの上に乗った時の処理を記述します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | using UnityEngine; using System.Collections; public class MoveBlockChara : MonoBehaviour { private CharacterController characterController; private Animator animator; private Vector3 velocity = Vector3.zero; [SerializeField] private float walkSpeed = 1.5f; [SerializeField] private float jumpPower = 5f; // 動く床の上にいるかどうか private bool onTheFloor = false; // 動く床の動いていく方向 private Vector3 floorMoveDirection; // Use this for initialization void Start () { characterController = GetComponent <CharacterController> (); animator = GetComponent <Animator> (); } // Update is called once per frame void Update () { if (characterController.isGrounded) { velocity = Vector3.zero; var input = new Vector3 (Input.GetAxis ("Horizontal"), 0f, Input.GetAxis ("Vertical")); if (input.magnitude > 0f) { animator.SetFloat ("Speed", input.magnitude); transform.LookAt (transform.position + input); velocity = transform.forward * walkSpeed; } else { animator.SetFloat ("Speed", 0f); } if(Input.GetButtonDown ("Jump") && !animator.GetCurrentAnimatorStateInfo (0).IsName ("Jump") && !animator.IsInTransition (0) ) { onTheFloor = false; animator.SetBool ("Jump", true); velocity.y += jumpPower; } // ベルトコンベアーに乗っていたら力を加える if (onTheFloor) { velocity += floorMoveDirection; } } velocity.y += Physics.gravity.y * Time.deltaTime; characterController.Move (velocity * Time.deltaTime); } void OnControllerColliderHit(ControllerColliderHit col) { Debug.DrawLine (transform.position + Vector3.up * 0.1f, transform.position + Vector3.up * 0.1f + Vector3.down * 0.2f, Color.blue); // 他のコライダと接触していたら下向きにレイを飛ばしてBlockかどうか調べる if(Physics.Linecast (transform.position + Vector3.up * 0.1f, transform.position + Vector3.up * 0.1f + Vector3.down * 0.2f, LayerMask.GetMask ("Block"))) { var beltConveyor = col.gameObject.GetComponent<BeltConveyor> (); if(beltConveyor != null) { floorMoveDirection = beltConveyor.ConveyorVelocity(); onTheFloor = true; } else { onTheFloor = false; } } else { onTheFloor = false; } } } |
少しづつ見ていきます。
キャラクターが接地している時にonTheFloorでベルトコンベアーに乗っているか判断し、乗っていればベルトコンベアーの速度分をキャラクターの速度に追加します。
1 2 3 4 5 6 | // ベルトコンベアーに乗っていたら力を加える if (onTheFloor) { velocity += floorMoveDirection; } |
次にキャラクターが他のゲームオブジェクトと接触している時の呼ばれるOnControllerColliderHitに処理を書いていきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | void OnControllerColliderHit(ControllerColliderHit col) { Debug.DrawLine (transform.position + Vector3.up * 0.1f, transform.position + Vector3.up * 0.1f + Vector3.down * 0.2f, Color.blue); // 他のコライダと接触していたら下向きにレイを飛ばしてBlockかどうか調べる if(Physics.Linecast (transform.position + Vector3.up * 0.1f, transform.position + Vector3.up * 0.1f + Vector3.down * 0.2f, LayerMask.GetMask ("Block"))) { var beltConveyor = col.gameObject.GetComponent<BeltConveyor> (); if(beltConveyor != null) { floorMoveDirection = beltConveyor.ConveyorVelocity(); onTheFloor = true; } else { onTheFloor = false; } } else { onTheFloor = false; } } |
他のコライダと接触していたら、キャラクターの位置から下向きにレイを飛ばしBlockレイヤーが設定されたゲームオブジェクトであればそのゲームオブジェクトからBeltConveyorスクリプトを取得します。
取得出来たらConveyorVelocityメソッドを呼び出し、ベルトコンベアーの速度を取得しfloorMoveDirectionに保持し、onTheFloorフラグをtrueにしておきます。
それ以外の時はonTheFloorをfalseにし、ベルトコンベアーに乗っていないという事にします。
ベルトコンベアーの流れている方向を返すスクリプトBeltConveyorを作成する
それではBeltCoveyorスクリプトを作成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | using UnityEngine; using System.Collections; public class BeltConveyor : MonoBehaviour { // ベルトコンベアーのスピード [SerializeField] private float speed = 1f; // ベルトコンベアーの速度を返す public Vector3 ConveyorVelocity () { return transform.forward * speed; } } |
↑のようにBeltConveyorの向きにスピードをかけて返しているだけです。
これでベルトコンベアーの機能が出来たので、Unityの実行ボタンを押して確認してみましょう。
↑のようにキャラクターがベルトコンベアーの上に乗ると左側に移動するようになりました。
ベルトコンベアーの流れている方向を視覚的にわかりやすくする
しかしこのままだとベルトコンベアーがどちらの方に動いているのかわかりません。
そこでベルトコンベアーにマテリアルを設定し、そのマテリアルを切り替える事でベルトコンベアーが動いているように見せかけましょう。
↑が最初のテクスチャです。
↑の2つの画像のように線が流れるような画像を2つ作ります。
BeltConveyorオブジェクトの角度と画像の方向を合わせる必要があります。
合わない場合は画像を回転させて再度Unityに取り込むか、BeltConveyorの動く向きを変えるかどちらかをしてください。
滑らかな動きにしたい場合は画像をもう少し増やしてください。
わたくしの作った適当な画像をサンプルとして使ってもかまいません。
画像が出来たらUnityに取り込み、ProjectのAssetsフォルダの上で右クリック→Create→Materialを選択します。
MoveFloorという名前のマテリアルを作ります。
次にMoveFloorのAlbedoにさきほど取り込んだMoveFloor1のテクスチャ画像を指定します。
ベルトコンベアーのマテリアルを変更する処理を追加
それでは、ベルトコンベアースクリプトのBeltConveyorにマテリアルを変更する処理を追加しましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | using UnityEngine; using System.Collections; public class BeltConveyor : MonoBehaviour { // 変更するテクスチャの配列 [SerializeField] private Texture[] textures; // メッシュレンダラー private MeshRenderer mesh; // テクスチャを変更するまでの時間 private float changeTime = 0.3f; // テクスチャを変更してからの時間 private float nowTime = 0f; // 次のテクスチャの番号 private int textureNum = 1; // ベルトコンベアーのスピード [SerializeField] private float speed = 1f; void Start () { mesh = GetComponent<MeshRenderer>(); } void Update () { nowTime += Time.deltaTime; // マテリアルの変更時間がきたので変更 if(nowTime >= changeTime) { nowTime = 0f; mesh.material.mainTexture = textures[textureNum]; textureNum++; } // マテリアル配列の最後まで来たら最初に戻す if(textureNum >= textures.Length) { textureNum = 0; } } // ベルトコンベアーの速度を返す public Vector3 ConveyorVelocity () { return transform.forward * speed; } } |
変更するテクスチャはインスペクタで設定出来るようにします。
テクスチャの変更は一定時間が来たら変更しています。
テクスチャの変更には
1 2 3 | mesh.material.mainTexture |
を変える事で出来るようになります。
これでスクリプトが出来たのでBeltConveyorオブジェクトを選択しインスペクタでマテリアル等の設定をしてください。
BeltConveyorは↑のようになりました。
実行して確認
これで設定が完了したので、Unityを実行して確認してみましょう。
↑が実行結果です。
ベルトコンベアーがどちらに流れているかわかるようになりました!
画像が汚いのであまり流れてる感が出てないような気もしますが・・・・。
また、錯覚で右に流れているように見える事も・・・・さすがにそれはなさそうです・・・。
錯覚するベルトコンベアーを作成する
さきほど錯覚で右に流れているように見える事もと書きましたが、実際にそれを行ってみましょう。
本当は錯覚させたいのではなくもう少し綺麗な絵にしたいという事だったんですが・・・(^_^;)
↑のように3色で塗られた床を3パターン用意しUnityに取り込みます(上の画像はダウンロードして使って頂いてかまいません)。
記事の背景色と1色がかぶっているので解り辛くなってますね・・。
Unityに取り込んだらMoveFloorのAlbedoにMoveFloor3を設定します。
ここまではさきほどもやったので問題ないと思います。
次にマテリアルの設定を変更します。
MoveFloorマテリアルのインスペクタを開きTilingのYを3にします。
Tilingの数を3にしたので、マテリアルには元の画像がY方向に同じパターンが3つ並ぶようになります。
(向きによってはXの値を変更する必要があるかも)
その為、1つのパターンを作りマテリアルの設定でそのパターンを連続させる事が出来ます。
それでは作成したマテリアルをBeltConveyorスクリプトのTexturesにMoveFloor3~MoveFloor5までのテクスチャを設定し、動きを確認してみます。
↑のように同じ模様でテクスチャが張られるようになりました。
1つのパターンをループしてテクスチャとして使いたい時にTilingを使うと便利ですね。
今度は錯覚で左にも右にも動いているように見えます・・・・。
これはどうやって左に流れてるように見せればいいでしょうかね・・・・(^_^;)
とりあえずこれで上下に動く床とベルトコンベアーの機能が完成しました。