今回はキャラクターが乗ると沈んでいき、降りると元の位置に戻るブロックを作成していきます。
実際の機能は以下のような感じです。
キャラクターがブロックに乗っている間は沈んでいくので高い所に上る為には完全に沈む前にジャンプしなければいけません。
沈むブロックの作成
まずは沈むブロックを作成していきます。
沈むブロックゲームオブジェクトの作成
沈むブロックはUnityのプリミティブ図形であるCubeを使って作成します。
ヒエラルキー上で右クリックから3DObject→Cubeを選択し、名前をSinkingBlockとします。
元のCubeの基点はCubeの中心にあるのでTransformのYを0.5にし、SinkingBlockが地面に丁度乗るような感じで配置します。
SinkingBlockにはインスペクタのAdd ComponentからPhysics→Rigidbodyを取り付けます。
SinkingBlockはスクリプトから動かすのでIsKinematicにチェックを入れます。
またSinkingBlockが動くのは上下のみなので、ConstraintsのFreeze PositionのXとZ、Freeze RotationのXYZ全てにチェックを入れ移動や回転に制限を加えます。
ブロックを沈ませるスクリプトの作成
新しくSinkingBlockという名前のスクリプトを作成し、SinkingBlockゲームオブジェクトに取り付けます。
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 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class SinkingBlock : MonoBehaviour { private Rigidbody rigidBody; // ボックスの初期位置 private Vector3 defaultPosition; // ボックスの移動速度 private Vector3 velocity; // 飛ばすレイの距離 [SerializeField] private float rayDistance = 1f; // 自身のコライダ private Collider myCollider; // 接触を無視するコライダ [SerializeField] private Collider floorCollider; // 戻るスピード [SerializeField] private float returnSpeed = 0.1f; // 沈むスピード [SerializeField] private float sinkingSpeed = 2f; // キャラクターがブロックに乗っているかどうか private bool characterIsOnBoard; [SerializeField] private Vector3 blockSize = Vector3.one; // Start is called before the first frame update void Start() { rigidBody = GetComponent<Rigidbody>(); defaultPosition = rigidBody.position; myCollider = GetComponent<Collider>(); // 自身のコライダとフロアのコライダの接触をしないようにする Physics.IgnoreCollision(myCollider, floorCollider, true); } // Update is called once per frame void Update() { // BoxとPlayerレイヤーを持つコライダと接触するか確認し、プレイヤーが乗っていれば下向き、 if (!characterIsOnBoard) { if (Physics.CheckBox(transform.position + Vector3.up * rayDistance, blockSize * 0.52f, Quaternion.identity, LayerMask.GetMask("Player"))) { velocity = Vector3.down * sinkingSpeed; characterIsOnBoard = true; } else { velocity = Vector3.up * returnSpeed; } // キャラクターが乗っている時 } else { // キャラクターが乗っているとされている時に、ボックスとキャラクターが接触しているか確認し、していなければ乗っていないに変更 if (!Physics.CheckBox(transform.position + Vector3.up * rayDistance, blockSize * 0.52f, Quaternion.identity, LayerMask.GetMask("Player"))) { characterIsOnBoard = false; } } } private void FixedUpdate() { // 移動が上向き(元に戻る向き)で初期位置以上の場合は移動させない if (velocity.y > 0f && rigidBody.position.y >= defaultPosition.y ) { // 完全に移動させる rigidBody.MovePosition(defaultPosition); return; } rigidBody.MovePosition(rigidBody.position + velocity * Time.fixedDeltaTime); } } |
defaultPositionはSinkingBlockの初期位置を入れます。
velocityはブロックの移動速度です。
rayDistanceはキャラクターがブロックの上にいるかどうかを判定する時のレイの長さです。
myColliderはブロック自身のコライダを入れます。
floorColliderはこのブロックとの接触を無視したいコライダ(今回の場合は床)を設定します。
returnSpeedはブロックを元に戻す時の速さを設定します。
sinkingSpeedは沈む速さを設定します。
characterIsOnBoardはキャラクターが自身のブロックに乗っているかどうかです。
blockSizeはブロック(Cube)の上にキャラクターがいるかどうか判定する時に使用するボックスのサイズを設定します。
Startメソッドでは自身のゲームオブジェクトからRigidbodyの取得をし、その後に自身の位置をdefaultPositionに入れています。
その後、自身のブロックに設定してあるコライダを取得しmyColliderに入れます。
SinkingBlockゲームオブジェクトに設定されているのはBox Colliderですが、Box ColliderはColliderから継承して作られたものなので、親であるCollider型のフィールドや変数等に入れることが出来ます。
その後Physics.IgnoreCollisionメソッドを使って第1引数のmyColliderと第2引数のfloorColliderの接触を第3引数のbool値に設定します。
この場合はtrueを設定しているので、ブロックと床のコライダが接触しないようにしています。
今回は指定したコライダが接触しないようにスクリプトで指定しましたが、UnityメニューのEdit→Project SettingsのPhysicsのLayer Collision Matrixのチェックを外してレイヤー毎の衝突判定の有無を切り替えることも出来ます。
UpdateメソッドではcharacterIsOnBoardがfalseに時(キャラクターがブロックに乗っていない時)にCheckBoxを使って自身の位置からrayDistance分移動した位置にボックスを作りそれとPlayerレイヤーを持つゲームオブジェクトと接触するかどうかを判定します。
第1引数がボックスの中心位置、第2引数がボックスの各軸の半分を指定し、第3引数は回転、第4引数でレイヤーマスクを指定しています。
第2引数にはボックスの軸の半分のサイズを指定するので一つの軸が1であれば0.5を指定するところですが、ブロックの端の方に立つと接触と判定されないことがあるので少し大きめの0.52を設定しました。
それでもブロックの端の方に立つとブロックが沈むでもなく浮かぶでもなくという無の状態が発生することがあります。
Playerレイヤーと接触していればSinkingBlockゲームオブジェクトの上にキャラクターがいるのでvelocityに下向きの値であるVector3.down(new Vector3(0, -1, 0)と同じ)にsinkingSpeedを掛けたものを入れます。
それ以外の時はVector3.up(new Vector3(0, 1, 0)と同じ)returnSpeedを掛けたものを入れます。
characterIsOnBoardがtrueの時(キャラクターがブロックに乗っている時)はキャラクターがブロックの上から降りたりジャンプして離れた時のチェックをします。
これは先ほどのPhysics.CheckBoxの判定に!を付けて接触していない時になります。
FixedUpdateは固定フレームレート(一定時間毎)に呼ばれるので、ここでvelocity.yが0より大きい時(移動が上向き)かつ現在のSinkingBlockのYの位置がdefaultPositionのYの位置以上の時は、元の位置に戻ろうとしていて、既に元の位置を越えているのでRigidbodyのMovePositionメソッドを使って初期位置に設定しなおして、returnを使ってそれ以降の処理をしないようにします。
それらの条件が成立しない場合はその後の処理に進み、現在の位置にvelocityの1フレーム分の値を足して少しずつ移動させます。
ここら辺はもっと頭のいいやり方がありそうですね。(´Д`)
キャラクタースクリプトの作成
キャラクターにはAnimatorコンポーネントにアニメーターコントローラーを設定し、CharacterControllerコンポーネントを取り付けコライダの調整が済んでいるとします。
キャラクターのスクリプトに少し工夫が必要です。
CharacterControllerを使った接地の確認ではisGroundedプロパティを用いて行うと簡単に接地しているかどうか確認出来、接地している時だけジャンプ処理をするようにしていました。
しかし、キャラクターがSinkingBlockゲームオブジェクトの上に乗るとキャラクターを乗せたままSinkingBlockが沈んでいく事になります。
この場合見た目上はあんまりわかりませんが、小刻みにisGroundedプロパティがfalseになってしまいます。
そこで一旦接地と判断したらvelocityのyに大きい値を入れ、地面からなるべく離れないようにします。
新しくSinkingBlockCharacterというスクリプトを作成し、キャラクターに取り付けます。
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 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class SinkingBlockCharacter : MonoBehaviour { private CharacterController characterControrller; private Animator animator; // 速度 private Vector3 velocity; // 移動スピード [SerializeField] private float speed = 2f; // ジャンプ力 [SerializeField] private float jumpPower = 6f; // 下向きの力 [SerializeField] private float addForcePower = 50f; // Start is called before the first frame update void Start() { characterControrller = GetComponent<CharacterController>(); animator = GetComponent<Animator>(); } // Update is called once per frame void Update() { // 入力値の計算 var input = new Vector3(Input.GetAxis("Horizontal"), 0f, Input.GetAxis("Vertical")); // 接地時の処理 if (characterControrller.isGrounded) { // 接地時に地面と離れるのを避ける為下向きの力を入れる(落下が速いブロックの場合はaddForcePowerを高くする) velocity = Vector3.down * addForcePower; // ジャンプ if (Input.GetButtonDown("Jump")) { velocity.y = jumpPower; } } // 入力値があれば移動値に重力加速度を足して速度を計算 if (input.magnitude > 0f) { transform.LookAt(transform.position + input.normalized); velocity = transform.forward * speed + new Vector3(0f, velocity.y, 0f); animator.SetFloat("Speed", input.magnitude); } else { animator.SetFloat("Speed", 0f); } velocity.y += Physics.gravity.y * Time.deltaTime; characterControrller.Move(velocity * Time.deltaTime); } } |
speedは移動の速さ
jumpPowerはジャンプ力
addForcePowerは接地と判断した時に下向きに加える力
です。
StartメソッドではCharacterControllerコンポーネントとAnimatorコンポーネントを取得します。
Updateメソッドでは入力値を計算しinputに入れます。
CharacterControllerのisGroundedプロパティがtrueであればその後の処理を行います。
velocityにはVector3.downにaddForcePowerをかけ、一旦接地と判断したら下向きに大きな速度を加えます。
Jumpボタンを押したらcurrentJumpValueとvelocity.yにjumpPowerを入れます。
入力値があればキャラクターの向きを変えます。
キャラクターの前方にspeedをかけたものに速度のY方向の値をvelocityに入れます。
最後に重力を速度に加味し、CharacterControllerのMoveメソッドで移動させます。
複数の沈むブロックを配置してみる
機能が出来たので確認に使うサンプルを作ります。
まずはキャラクターのLayerに新しくPlayerレイヤーを作成し、設定します。
次にヒエラルキー上で右クリックから3D Object→Planeを選択し、名前をFloorとします。
TransformのScaleのXYZをそれぞれ3としました。
次にSinkingBlockを複製し、全部で5つの沈むブロックを作成しますが、高さを変更し配置してみます。
各TransformのScaleのYを変更して高さを高くし、PositionのYを変更して、全体像が地面の上にくるようにします。
Sinking BlockのRay DistanceにはそれぞれのScaleのYの半分の値より少し小さい値を設定し、中心位置からの長さで調整します。
Floor ColliderにはヒエラルキーのFloorゲームオブジェクトをドラッグ&ドロップしてコライダを設定します。
Return SpeedやSinking Speedを調整し、Block Sizeではプレイヤーを検知するブロックサイズを設定します。
一部SinkingBlockレイヤーを設定していますが、別のやり方で作っている時に使っただけなので、今回はDefaultのままで大丈夫です。
これで機能が出来ました。
終わりに
キャラクターがブロックに乗ると沈み、降りると元に戻る機能は簡単に出来そうでしたが少し手こずりました。
もっと改良出来そうな気もしますが、とりあえずはこれで・・・・(´Д`)