はじめに
ユニティーを使用した2Dアクションゲームの作り方のメモです。ゲーム制作の参考にしていただければ嬉しいです。トグルボタンをクリックすると記事が表示されます。小さくて見ずらい画像はクリックで拡大できます。
使用素材の準備
プレイヤーや床、敵などの画像を用意します。
私はこちらのアセットを使用しています。
packageManagerからアセットをインポート、もしくは用意した画像をUnityのフォルダにドラッグ&ドロップしておきます。
おすすめのアセット
- Unity初心者にオススメ!使えるエディター拡張ツール
プレイヤーを作成する
Hierarchウィンドウを右クリック→2DObject→Sprites→Squareを作成します。
名前はPlayerに変更しました。

追加すると緑色の四角い枠が付いてこれが当たり判定になります。

床を作成する
Hierarchウィンドウを右クリック→2DObject→Sprites→Squareを作成します。
名前はFloorに変更しました。
inspector画面のSpriteに用意した床用の画像をドラッグ&ドロップします。

Transformを調整して床を少し下げて横に伸ばしておきます。


プレイヤーに重力がついているので上から下に落ちていき、プレイヤーと床に当たり判定がついているので、Unityを実行するとプレイヤーが床に当たって止まるようになります。

敵の作成
2Dobject→Sprite→Squareを作成します。
名前はEnemyにしました。

inspector画面からSquareの画像を変更します。
変更したい画像をドラッグ&ドロップします。

addcomponentからBoxColider2dとrigidbody2dを追加して当たり判定などを追加します。

新しく敵用のC#スクリプトを作成します。
名前はEnemyにしました。
作成したスクリプトをEnemyにドラッグ&ドロップします。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Enemy : MonoBehaviour
{
[SerializeField, Header("移動速度")]
private float movespeed;
private Rigidbody2D rigidBody;
void Start()
{
rigidBody= GetComponent<Rigidbody2D>();
}
void Update()
{
Move();
}
private void Move()
{
rigidBody.velocity = new Vector2(Vector2.left.x * movespeed, rigidBody.velocity.y);
}
Enemyオブジェクトのinspector画面から移動速度を設定します。
私は5に変更しました。

Unity実行してEnemyが横に動くかチェックします。
プレイヤーのアクションについて
window→packageManagerからInputSystemをインポートします。
キーボードやコントローラーの入力に必要になります。

Playerのinspector画面のaddComponentからPlayerInputを追加します。

PlayerInputのCreateActionsをクリックします。

Inputactionの保存先を指定して保存します。
私は新しくActionsというフォルダを作成しました。

スクリプト開いて以下のコードを入力していきます。
using UnityEngine;
using UnityEngine.InputSystem;
public class Player : MonoBehaviour
{
[SerializeField,Header("移動速度")]
public float moveSpeed;
//入力された方向を入れる変数
private Vector2 inputDirection;
//移動方向入れる変数
private Rigidbody2D rigid;
void Start()
{
//PlayerのRigidbody2Dコンポーネントを取得する
rigid = GetComponent<Rigidbody2D>();
}
void Update()
{
Move();
}
private void Move()
{
//プレイヤーが入力した方向に横方向限定で移動速度分の力を加える
rigid.velocity = new Vector2(inputDirection.x * moveSpeed, rigid.velocity.y);
}
public void OnMove(InputAction.CallbackContext context)
{
//移動方向の入力情報がInputdirectionの中に入るようになる
inputDirection = context.ReadValue<Vector2>();
}
}
playerのMoveのところにプレイヤーオブジェクトをドラッグ&ドロップしてOnMoveを選択します。

playerスクリプトのMovespeedを変更します。
私は10に設定しました。

これでWASDキーを入力するとプレイヤーを移動させることができるようになります。

前回作成したインプットアクションファイルを開いてジャンプ用のアクションを作成します。
Playerを選択してActionsの+マークをクリックします。

すると新しいアクションが作成されます。
名前はJumpにしておきました。
作成したjumpの下のnobindingを選択します。

右側の項目のpathの参画のマークをクリックします。
この項目でJumpに何のボタンを設定するか決めることができます。
今回はスペースキーを押したらジャンプできるように設定したいので
一覧からkeyborad→bylocations→spaceキーを選択します。

設定したら上のほうにあるSaveassetをクリックして設定を保存します。
前回作成したPlayerScriptを開いて新たにジャンプ用の変数と関数を追加します。
[SerializeField, Header("ジャンプ速度")]
private float jumpSpeed;
public void Onjump(InputAction.CallbackContext context)
{
if (!context.performed)
{
return;
}
rigid.AddForce(Vector2.up * jumpSpeed, ForceMode2D.Impulse);
}
追加したらinspector画面からプレイヤーを選択してPlayerInputの設定をします。
eventsの項目にJumpが増えているので+ボタンをクリックしてplayerオブジェクトを設定してのnofunction→player→onjumpを設定します

最後にジャンプ速度を設定して完了です。
私は10に設定してみました。

再生してspaceキーを押すとジャンプできるようになっています。
このままだと無限にジャンプができるのでスクリプトで制御します。
playerスクリプトにbool変数を作成してtrueの時はジャンプができなくなるようにします。
private bool bjump;
private void OnCollisionEnter2D(Collision2D collision)
{
if (collision.gameObject.tag == "Floor")
{
bjump = false;
}
}
public void Onjump(InputAction.CallbackContext context)
{
if (!context.performed || bjump)
{
return;
}
rigid.AddForce(Vector2.up * jumpSpeed, ForceMode2D.Impulse);
bjump=true;
}
Floorにタグを設定して完了です。

playerのC#スクリプトを開きます。
OnCollisionEnter2Dの変更と新たにメゾットを作成します。
private void OnCollisionEnter2D(Collision2D collision)
{
if (collision.gameObject.tag == "Floor")
{
bjump = false;
}
if (collision.gameObject.tag == "Enemy")
{
HitEnemy(collision.gameObject);
}
}
private void HitEnemy(GameObject enemy)
{
float halfScaleY = transform.localScale.y/2.0f;
float enemyHalfScaleY = transform.localScale.y / 2.0f;
if (transform.position.y - (halfScaleY - 0.1f) >= enemy.transform.position.y + (enemyHalfScaleY - 0.1f))
{
Destroy(enemy);
}
}
プレイヤーが回転してしまう場合があるので
player→rigidbody→freezerotation.zにチェックを入れます。

Unityを再生して踏んだ時に敵が消えるかチェックします。
playerスクリプトに敵に衝突した場合ダメージを受ける処理を追加します。
新しくHP用の変数とダメージを受けるメゾットを作ります。
UpdateでHpが表示されるようになっています。
HitEnemyメゾットにelse文を追加します。
[SerializeField, Header("体力")]
private int hp;
void Update()
{
Move();
Debug.Log(hp);
}
public void Damage(int damage)
{
hp= Mathf.Max(hp - damage,0);
}
private void HitEnemy(GameObject enemy)
{
float halfScaleY = transform.localScale.y/2.0f;
float enemyHalfScaleY = transform.localScale.y / 2.0f;
if (transform.position.y - (halfScaleY - 0.1f) >= enemy.transform.position.y + (enemyHalfScaleY - 0.1f))
{
Destroy(enemy);
}
else
{
enemy.GetComponent<Enemy>().PlayerDamage(this);
}
}
Enemyスクリプトにダメージを与える処理を追加します。
[SerializeField, Header("攻撃力")]
private int attack;
public void PlayerDamage(Player player)
{
player.Damage(attack);
}
playerオブジェクトのinspector画面からHPを設定します。
私は10にしました
Enemyオブジェクトのinspector画面から攻撃力を設定します。
5に設定しました。
再生して敵にぶつかったときにconsoleウィンドウのDebug.logに残りのHPが表示されるようになります。

敵を踏んだ時にプレイヤーがジャンプする処理を作成します。
PlayerスクリプトのHitEnemyメゾットに処理を追加します。
private void HitEnemy(GameObject enemy)
{
float halfScaleY = transform.localScale.y/2.0f;
float enemyHalfScaleY = transform.localScale.y / 2.0f;
if (transform.position.y - (halfScaleY - 0.1f) >= enemy.transform.position.y + (enemyHalfScaleY - 0.1f))
{
Destroy(enemy);
rigid.AddForce(Vector2.up * jumpSpeed, ForceMode2D.Impulse);
}
else
{
enemy.GetComponent<Enemy>().PlayerDamage(this);
}
}
ジャンプ処理と同じようなものをif文の中に追加しました。
Unityを再生して敵を踏んだ時にジャンプするかチェックします。
HPバーの作成
プレイヤーの体力を表示するUIを作成してダメージを受けたらUIを減らすようにしていきます。
まずUIの画像を配置します。
UI→imageを作成

imageのinspector画面からimagecomponentを消去します。

imageにaddcomponentでhorizontallayoutGroupを追加します。

これを追加すると子オブジェクトを等間隔で並べることができます。
imageの名前はHPに変更しました。

HPの子オブジェクトにimageを作成します

名前はHpIconに変更しました。

HPを左上に設定してゲーム画面の左上にimageがに来るようにposを設定します。


Anchor Presetsを設定すると画面サイズが変更されても自動的に設定した位置に配置されるように設定できます。

HpIconをいくつかコピーしてHPのinspector画面のhorizontallayoutのspacingで間隔があくように調整します

調整したらHpIconは一旦全て消去しておきます。
C#スクリプトを作成します。名前はPlayerHP
PlayerスクリプトにGetHPメゾットを追加します。
using UnityEngine;
using UnityEngine.UI;
public class PlayerHP : MonoBehaviour
{
[SerializeField, Header("HPアイコン")]
private GameObject playerIcon;
private Player player;
private int beforeHP;
// Start is called before the first frame update
void Start()
{
//【FindObjectOfType】<>で指定したコンポーネントがついているオブジェクトを
//inspector画面から探してコンポーネントを取得する。
player = FindObjectOfType<Player>();
beforeHP = player.GetHP();
CreateHPIcon();
}
private void CreateHPIcon()
{
for (int i = 0; i < player.GetHP(); i++)
{
GameObject playerHPObj = Instantiate(playerIcon);
playerHPObj.transform.parent = transform;
}
}
// Update is called once per frame
void Update()
{
ShowHPIcon();
}
private void ShowHPIcon()
{
if (beforeHP == player.GetHP()) return;
Image[] icons = transform.GetComponentsInChildren<Image>();
for (int i = 0; i < icons.Length; i++)
{
icons[i].gameObject.SetActive(i < player.GetHP());
}
beforeHP = player.GetHP();
}
}
public int GetHP()
{
return hp;
}
HPに作成したPlayerHPをアタッチしてinspector画面からPlayerIconにprefabにしたHpIconを設定します。

再生するとHPの数だけハートが表示されます。
ダメージを受けると減っていきます。

GameOverの実装
UI→Panelを生成します。パネルの名前はGameOverにしました。

GameOverのColorを変更して黒にしました。

- 【Unity】TextMeshProで日本語入力する方法
UI→TextMeshProを作成します。初めて使用する場合下のような画面が出てくるのでImportTMPをクリックします。

画面上にNewTextというのが出てきます。

inspector画面のTextMeshProの入力欄からNewTextの部分を変更できるので、すべて消去してGameOverと入力します。

ここで文字を入力すれば表示したい文字に設定できます。

TextStyleをTitleに変更します。

Titleにすると少し文字が太くなり表示がでかくなります。

もう少し文字を大きくしたいのでFontSizeの数値を70くらいにします。

文字がシーン画面のTextMeshProの枠からはみ出る場合自動で改行されてしまうので枠のサイズを調整します。


Alignmentを下のように設定すると文字が中央によります。

TextMeshProはGameOverの子オブジェクトにしておきます。

このオブジェクトはゲーム開始時に非表示にしておきたいのでチェックを外して非アクティブにしておきます。

playerスクリプトを開いてコードを追加します。
public void Dead()
{
if (hp <= 0)
{
Destroy(gameObject);
}
}
//Damage関数で実行する
Dead();
敵に衝突したらDamageメゾットが呼ばれて体力ダメージが減って、体力が0になったらプレイヤーオブジェクトが消滅するようになります。
空のオブジェクトを作成します。名前はMainManagerにしました。

MainManagerのTransformはリセットしておきます。

新しくC#スクリプトを作成します。名前はMainManagerにしました。
作成したスクリプトをMainManagerオブジェクトにアタッチしておきます。

スクリプトを開いてコードを入力します。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MainManager : MonoBehaviour
{
[SerializeField, Header("ゲームオーバーUI")]
private GameObject _gameObject;
private GameObject _player;
// Start is called before the first frame update
void Start()
{
_player = FindObjectOfType<Player>().gameObject;
}
// Update is called once per frame
void Update()
{
ShowGameOver();
}
private void ShowGameOver()
{
if (_player != null) return;
_gameObject.SetActive(true);
}
}
MainManagerのinspector画面から先ほど作ったゲームオーバーUIをアタッチします。

体力が0になるとGameOverが表示されるようになります。
おすすめのアセット
2Dアクションゲーム開発の入門として「Corgi Engine – 2D + 2.5D Platformer」をお勧めします。このアセットは、2Dおよび2.5Dのプラットフォームゲームの作成を容易にするツールキットで、多くのプリセット機能とカスタマイズオプションを提供します。Unityのビルトイン、URP、HDRPレンダーパイプラインとの互換性も完備しており、幅広いデバイスでの使用が可能です。シンプルで直感的な設計のおかげで、初心者でもスムーズにプロジェクトをスタートできます。