目次
手順
STEP1:プロジェクトビューに敵の画像を登録する
左図のように、Assets フォルダに「Enemies」フォルダを作成します。さらに、Enemies フォルダの中に敵キャラクター名のフォルダを作成します。
左図では、「Slime(スライム)」のフォルダを作成しました。これ以降、敵キャラクターはスライムを追加するものとして説明します。
アセットストアでダウンロードしてきた敵の画像を、先ほど作った敵の名前のフォルダにコピペします。
アセットストアから画像をインポートする方法はこちらの記事が参考になります。お気に入りの画像を探してみましょう。
STEP2:敵をゲーム画面に配置する
プレイヤーを配置する前に、Unity エディタが「シーン」ビューを表示していることを確認してください。「ゲーム」ビューの場合はオブジェクトの追加ができません。
また、「シーン」ビュー内の「ツールパレット」のいずれかを選択しておきます。「タイルパレット」を選択していると意図せずタイルマップを編集してしまうことがあるためです。
次の手順でヒエラルキーに敵を入れるオブジェクトを作成します。
① ヒエラルキーの空いているところで右クリックをし、「空のオブジェクトを作成」を選択。名前を「Enemies」とする。
② 「Enemies」を右クリックをし、「空のオブジェクトを作成」を選択。名前を「Slimes」とする。
次の手順で敵をゲーム画面に配置します。
① プロジェクトの Enemies フォルダから、敵の画像をヒエラルキーにドラッグアンドドロップする
② オブジェクト名を「Slime1」に変更する
③ ソートレイヤー「Enemies」を作成し、前面に表示させる
「ツールパレット」を使って、敵の大きさや最初の位置を調整します。
STEP3:敵に重力をつける
Unity には、重力を計算してくれるコンポーネント(部品)があります。ここでは、敵にそのコンポーネントを付けて、重力を取り入れたいと思います。
重力がない世界では、敵は右図のようにずっと宙に浮いたままになります。
① ヒエラルキーの中から「Slime1」を選択します
② インスペクタービューの下にある「コンポーネントを追加」をクリックします
③ 「Physics 2D」を選択します
Physics とは「物理」の意味です
④ 「Rigidbody 2D」を選択します
Rigidbody とは「剛体」の意味です。重力の他に摩擦などの計算も可能です。
下記の左図のように、「Slime1」のインスペクターに「Rigidbody 2D」が表示されていれば OK です。ただしこのままだと、地面をすり抜けて落ちていきます。
そこで次に「Slime1」に当たり判定を設定します。
※「Ground」の当たり判定は「② プレイヤーの作成」で設定しています。
① 「Slime1」のインスペクタービューの下にある「コンポーネントを追加」をクリックします。
② 「Physics 2D」⇒「Capsule Collider 2D」を選択します。
※敵の形に合わせて選択してください。(例)四角に近い場合は、「Box Collider 2D」が適切です。
③ 「Capsule Collider 2D」の中の「コライダーの編集」をクリックします。
④ 緑色の点の部分をつかんでスライムの形に合わせます。
⑤ 上のように、敵が地面に着地できれば、重力の設定は完成です。
STEP4:敵に、プレイヤーとの当たり判定をつける
方針:1つの GameObject に複数の当たり判定を設定するときは、当たり判定の数だけ子オブジェクトを作成する
左図のスライムの場合、頭の当たり判定は、プレイヤーに踏まれたかどうかを検知するために使用します。また体の当たり判定は、プレイヤーが横からスライムと当たったかどうか検知するために使用します。
複数の当たり判定を持つ場合、どちらの当たり判定にあたったのか調べるのは難しいです。当サイトでは、当たり判定の数だけ子オブジェクトを作成する方法をおススメしています。
それでは、具体的な手順を説明していきます。
下記のように、ヒエラルキー上の「Slime1」に空のゲームオブジェクト「ColliderHead」「ColliderBody」を追加します。「ColliderHead」はプレイヤーに踏まれたかどうかの当たり判定(敵の負け)に使います。「ColliderBody」はプレイヤーと横方向で当たったかどうかを判定(プレイヤーの負け)するために使います。
また、スクリプトから GameObject を扱いやすくするために、「Slime1」にタグ「Enemies」を付けます。タグは、インスペクタービューの上部にあります。
ColliderBody(敵の体の当たり判定)を設定
① ヒエラルキーの中から「ColliderBody」を選択します
② インスペクタービュー内のタグに「ColliderEnemyBody」を作成し、選択します。
※タグをつけることで、当たり判定を区別することができます。
③ インスペクタービューの下にある「コンポーネントを追加」をクリックします
④ 「Physics 2D」を選択します
⑤ 「Capsule Collider 2D」を選択します
⑥ 「Capsule Collider 2D」の中の「トリガーにする」にチェックを入れます。
※何かと当たったとき、物理演算を無視し、スクリプトのみ実行させることができます。
⑦ 「Capsule Collider 2D」の中の「コライダーの編集」をクリックします。
⑧ 敵の当たり判定の範囲が表示されます。点の部分をつかんでスライムの体の形に合わせます。
⑨ 上図のように、敵の体の当たり判定を設定します。
ColliderHead(敵の頭の当たり判定)を設定
① ヒエラルキーの中から「ColliderHead」を選択します
② インスペクタービュー内のタグに「ColliderEnemyHead」を作成し、選択します。
③ インスペクタービューの下にある「コンポーネントを追加」をクリックします
④ 「Physics 2D」を選択します
⑤ 「Capsule Collider 2D」を選択します
⑥ 「Capsule Collider 2D」の中の「トリガーにする」にチェックを入れます。
※何かと当たったとき、物理演算を無視し、スクリプトのみ実行させることができます。
⑦ 「Capsule Collider 2D」の中の「コライダーの編集」をクリックします。
⑧ 敵の当たり判定の範囲が表示されます。点の部分をつかんでスライムの頭の形に合わせます。
⑨ 上図のように、頭と体の当たり判定が設定できました。
STEP5:プログラムの作成
スクリプトファイルを作成し、ゲームオブジェクトと紐付ける
① プロジェクトビューの中の「Scripts」フォルダを右クリック ⇒ 「作成」 ⇒ 「C# スクリプト」と選択し、スクリプトファイルを作成します。
② ファイル名を、「SlimeController」とします。
私は、「Enemies」と「Slime」フォルダを作成し、その中に「SlimeController」を格納しました。
③ 作成したスクリプトファイルを「Slime1」に紐づけます。下図のようにファイルを「Slime1」の「インスペクタービュー」の空いているスペースにドラッグアンドドロップします。
④ 成功すれば、「インスペクタービュー」内に「SlimeController」が表示されます(上図の青枠)。
①~④ と同様の手順で、次の内容を実施します。SlimeColliderBodyController ファイルと SlimeColliderHeaderController ファイルを作成します。前者は、ゲームオブジェクト「ColliderBody」に、後者はゲームオブジェクト「ColliderHead」に紐づけます。
プログラム解説(スライムが、プレイヤーに頭を踏まれたときの挙動)
下記のように、チカチカと点滅してからスライムが消えるようにします。
SlimeColliderHeadController.cs
using UnityEngine;
public class SlimeColliderHeadController : MonoBehaviour
{
private SlimeController slimeController;
private void Start()
{
GameObject slimeObj = transform.parent.gameObject;
slimeController = slimeObj.GetComponent<SlimeController>();
}
private void Update()
{
}
private void OnTriggerEnter2D(Collider2D collider)
{
var player = collider.gameObject.GetComponent<PlayerController>();
if (player != null)
{
slimeController.Dead();
}
}
}
■5行目:slimeController(変数)
親オブジェクトのスクリプトを実行するために必要です
10行目のコードで、親オブジェクトのスクリプトを取得します。
■17行目:OnTriggerEnter2D(メソッド)
スライムの頭部分に何かが当たったときに実行されます。
■19~23行目
もし、当たった相手がプレイヤーなら、22行目が実行されます。
22行目は、親のオブジェクト(SlimeController)の Dead メソッドを呼び出しています。
EnemiesState.cs
public enum EnemiesState
{
Idle,
Dead
}
■1行目:EnemiesState(enum 型)
複数の定数を一つの型で定義することができます
左のコードでは、敵の状態(アイドル状態、負け)を定義しています。
SlimeController.cs
using UnityEngine;
public class SlimeController : MonoBehaviour
{
[SerializeField]
private SpriteRenderer slimeRenderer;
private const float BlinkingCycle = 0.5f;
private const float DestroyTime = 1.5f;
private double elapsedTime;
private EnemiesState state = EnemiesState.Idle;
private void Start()
{
}
private void Update()
{
if (state == EnemiesState.Dead)
{
elapsedTime += Time.deltaTime;
Dying();
}
}
public void Dead()
{
state = EnemiesState.Dead;
elapsedTime = 0;
Destroy(gameObject, DestroyTime);
}
private void Dying()
{
float cos = Mathf.Cos((float)(2 * Mathf.PI * elapsedTime / BlinkingCycle));
Color color = slimeRenderer.color;
color.a = cos;
slimeRenderer.color = color;
}
}
■6行目:slimeRenderer(インスタンス変数)
Sprite Renderer の Color(今回は透明度)を変えることで点滅させます。Unity 側で、ヒエラルキー上の「Slime1」をアタッチ(紐づけ)する必要があります。
■8行目:BlinkingCycle(定数)
点滅の周期(秒)です。
■9行目:DestroyTime(定数)
プレイヤーに踏まれてから消えるまでの時間(秒)です。
■11行目:elapsedTime(インスタンス変数)
24行目が最初に実行されてからの経過時間(秒)です。
■13行目:state(インスタンス変数)
敵の状態を表す Enum 型の変数です。初期状態は Idle としました。
■20行目:Update(メソッド)
state の値が Dead の場合、25行目の Dying メソッドを実行します。
■29行目:Dead(メソッド)
プレイヤーに頭を踏まれたタイミングで、子オブジェクト(SlimeColliderHeadController)から呼び出されます。
■36行目:Dying(メソッド)
三角関数(コサイン)を利用して透明度を計算、スプライトに反映させます。
少し難しかったかもしれませんが、ここまでできると敵を倒せるようになります。
プログラム解説(プレイヤーが、スライムの体に当たったときの挙動)
下記のように、敵の体にぶつかるとプレイヤーが下に落ちていくようにします。
PlayerState.cs
public enum PlayerState
{
Idle,
Run,
Jump,
Fall,
Dead
}
■1行目:PlayerState(enum 型)
複数の定数を一つの型で定義することができます
左のコードでは、プレイヤーの状態(アイドル状態、走っている、ジャンプ、落下、負け)を定義しています。
GameObjectTags.cs
public enum GameObjectTags
{
Player,
Enemies,
ColliderEnemyBody,
ColliderEnemyHead
}
■1行目:GameObjectTags(enum 型)
複数の定数を一つの型で定義することができます
左のコードでは、ゲームオブジェクトを見分けるためのタグ(プレイヤー、敵、敵の体との当たり判定、敵の頭との当たり判定)を定義しています。
PlayerController.cs
using UnityEngine;
public class PlayerController : MonoBehaviour
{
[SerializeField] private float scale;
[SerializeField] private float jump;
[SerializeField] private float groundCheckRadius;
[SerializeField] private float groundCheckOffsetY;
[SerializeField] private float groundCheckDistance;
[SerializeField] private LayerMask groundLayer;
private Rigidbody2D rbody2D;
private float axisH;
private bool goingJump = false;
private PlayerState nowState = PlayerState.Idle;
private PlayerState oldState = PlayerState.Idle;
private void Start()
{
rbody2D = GetComponent<Rigidbody2D>();
transform.localScale = new Vector2(scale, scale);
}
private void Update()
{
if (nowState == PlayerState.Dead)
{
return;
}
axisH = Input.GetAxisRaw("Horizontal");
if (axisH < 0.0f)
{
transform.localScale = new Vector2(-1 * scale, scale);
}
else if (0.0f < axisH)
{
transform.localScale = new Vector2(scale, scale);
}
if (Input.GetButtonDown("Jump"))
{
goingJump = true;
}
}
private void FixedUpdate()
{
if (nowState == PlayerState.Dead && oldState != PlayerState.Dead)
{
GetComponent<CapsuleCollider2D>().enabled = false;
rbody2D.AddForce(new Vector2(0, 5), ForceMode2D.Impulse);
oldState = PlayerState.Dead;
}
if (nowState == PlayerState.Dead)
{
return;
}
if (axisH != 0.0f)
{
rbody2D.velocity = new Vector2(axisH * 3.0f, rbody2D.velocity.y);
}
if (OnGround() && goingJump)
{
Vector2 jumpPw = new Vector2(0, jump);
rbody2D.AddForce(jumpPw, ForceMode2D.Impulse);
goingJump = false;
}
}
public void Dead()
{
nowState = PlayerState.Dead;
}
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.gameObject.tag.Equals(GameObjectTags.ColliderEnemyBody.ToString()))
{
Dead();
}
}
private bool OnGround()
{
return Physics2D.CircleCast((Vector2)transform.position + (groundCheckOffsetY * Vector2.up), groundCheckRadius, Vector2.down, groundCheckDistance, groundLayer);
}
private void OnDrawGizmos()
{
Gizmos.color = Color.green;
Gizmos.DrawWireSphere((Vector2)transform.position + (groundCheckOffsetY * Vector2.up) + (Vector2.down * groundCheckDistance), groundCheckRadius);
}
}
スライムとの接触に関するプログラムのみ説明します。
■17、18行目:nowState、oldState(変数)
プレイヤーの状態の変化によって処理を分岐させるために利用します。
■28行目:nowState == PlayerState.Dead
プレイヤーが負けた場合は、ユーザーの入力を受け付けないようにします。
■51行目:if 文
プレイヤーの状態が初めて Dead になったときに、if 文の中が実行されます。
■53行目:
CapsuleCollider2D を無効化させています。こうすることで地面との当たり判定が無効化され、プレイヤーはゲーム画面の下に落ちていきます。
■54行目:
敵に当たったときに、プレイヤーが少しだけ上に上がるように力を加えています。
■55行目:
oldState を Dead にすることで if 文の中が二回実行されないようにしています。
■81行目:OnTriggerEnter2D(メソッド)
トリガー設定している当たり判定に入った場合に実行されます。当たった相手のタグ名が取得できるので何に当たったのか識別することができます。
■83行目
「ColliderEnemyBody」という名前でタグ付けされたゲームオブジェクトに当たったときに、プレイヤーが負けるようにしています。
以上で「敵の作成」の解説を終わります。お疲れ様でした。