【Unity】2Dシューティングを作ってみる話 #28
記事をご覧いただき、誠にありがとうございます。
投稿主の無能です。
前回は、敵の種類を増やして敵毎に動きを付けました。
今回はプレイヤーにシールド機能を実装したいと思います。
プレイヤーのシールド機能
敵の種類が増えて手強くなってきたので、プレイヤーも強化したいと思います。
そこでシールド機能を付けて、敵の弾や敵本体からのダメージを防ぐ、または軽減するようにしたいと思います。
シールドを作成する
まずはシールドを作ります。
2DオブジェクトからCircleを作り、「PlayerShield」としてプレイヤーの子オブジェクトにします。
GameObject > 2D Object > Sprite > Circle でCircleを作成
「PlayerShield」という名前でプレイヤーの子オブジェクトにする
作成したPlayerShieldをPlayerと同じposition(0, 0, 0)にします。
Playerと同じ位置にする
当然ですが重なってPlayerが見えません。
そこでこのCircleの透明度を変更し、Player全体を包み込むようにします。
透明度は色相環のAというアルファ値で変更できます。
色相環のアルファ値で透明度を変更できる
ではシールドを青っぽくして、Player全体を包み込みます。
シールドを青っぽくしてアルファ値を調整する
シールドの大きさを調整
プレイヤー全体を包み込んだ
これでシールドっぽくなりました。
あとはシールドを非アクティブにしておけば準備OKです。
シールドを非アクティブにする
シールドを持つようになるきっかけを作る
シールドを非アクティブにしたので、どのようにしてアクティブにするかと言うと、シールドのアイテムを取得したらアクティブにします。
そうすればアイテムの価値がグッと出てきますね。
ということでシールドのアイテムを作りましょう。
アイテムはシールドが青っぽいので青にしました。
アイテムの素材をインポート
画像の大きさを設定
インポートした画像を設定
ではスクリプトでの実装になります。
アイテムの分岐を増やす方から追加するとこのようになります。
ItemController.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[RequireComponent(typeof(Rigidbody2D))]
public class ItemController : MonoBehaviour
{
#region Variables
#region var-CapsuleBase
[Header("アイテムの基本パラメーター")]
[SerializeField] float rotateSpeed = 10.0f; // アイテムの回転速度
[SerializeField] float dropSpeed = 1.0f; // アイテムの進行速度
[SerializeField] int itemType = 0; // アイテムの種類
#endregion
#region var-CapsuleIndividual
[Header("アイテム個別のパラメーター")]
[SerializeField] int healPoint = 5; // 回復アイテムの回復量
[SerializeField] int itemScore = 5000; // スコアアイテムのスコア増加量
[SerializeField] int remainingAdd = 1; // 残機の増加量
#endregion
#region var-SE
[Header("効果音関連")]
[SerializeField] AudioClip SE_GetItem; // アイテム取得時の効果音
[SerializeField] float getItemVol = 0.4f; // 取得時の効果音の音量
#endregion
#region var-Controller
[Header("コントローラー関連")]
[SerializeField] PlayerController player; // プレイヤーコントローラー
[SerializeField] GameManager gameManager; // ゲームマネージャー
Rigidbody2D capsuleRB; // アイテムのリジッドボディ
#endregion
#region others
Collider2D itemCollider; // アイテムのコライダー
#endregion
#region Internal
enum itemEffectType { heal, addScore, oneUp, shield }; // 内部処理用(アイテムの効果の種類)
#endregion
#endregion
#region Methods
#region Start
void Start()
{
// カプセルのリジッドボディを取得
capsuleRB = GetComponent<Rigidbody2D%gt;();
// ゲームマネージャーを取得
gameManager = GameObject.FindWithTag("GameManager").GetComponent<GameManager>();
// プレイヤーコントローラーを取得
player = GameObject.FindWithTag("Player").GetComponent<PlayerController>();
// アイテムのコライダーを取得
itemCollider = GetComponent<Collider2D>();
}
#endregion
#region Update
void Update()
{
// アイテムをプレイヤーの方向へ動かす
capsuleRB.velocity = new Vector2(-dropSpeed, 0);
// アイテムを回転させる
capsuleRB.transform.Rotate(0, 0, rotateSpeed);
}
#endregion
#region OnTriggerEnter2D
// 接触判定
private void OnTriggerEnter2D(Collider2D collision)
{
// 接触したオブジェクトのタグがPlayerだった場合
if (collision.gameObject.CompareTag("Player"))
{
// アイテムの効果を呼び出す
ItemEffect(itemType, itemCollider);
}
}
#endregion
#region ItemEffect
// アイテムの効果
void ItemEffect(int iType, Collider2D collision)
{
// 引数iTypeで分岐
switch (iType)
{
// 回復効果の場合
case (int)itemEffectType.heal:
// プレイヤーのHP表示を更新
player.PlayerHPChanged(iType, healPoint, collision);
// switch文を抜ける
break;
// スコア加算の場合
case (int)itemEffectType.addScore:
// スコアを加算する
gameManager.ScoreAdded(itemScore);
// 自身を破棄
Destroy(gameObject);
// switch文を抜ける
break;
// 1upの場合
case (int)itemEffectType.oneUp:
// 残機を増やす
player.RemainingCounter(remainingAdd);
// 自身を破棄
Destroy(gameObject);
// switch文を抜ける
break;
// シールドの場合
case (int)itemEffectType.shield:
// シールドを有効にする
player.PlayerShieldActivate();
// 自身を破棄
Destroy(gameObject);
// switch文を抜ける
break;
// 該当なしの場合
default:
// switch文を抜ける
break;
}
// 取得時に効果音を再生
AudioSource.PlayClipAtPoint(SE_GetItem, Camera.main.transform.position, getItemVol);
}
#endregion
#endregion
}
今回はenumのitemEffectTypeにshieldを追加しました。
ItemEffect関数のswitch文の分岐にshieldの場合を追加して、PlayerControllerのPlayerShieldActivate関数を呼んで、アイテム自身を破棄します。
いつものアイテムの処理ですね。
では次にPlayerControllerの方を追記するとこうなります。
PlayerController.cs
using System.Collections;
using System.Collections.Generic;
using TMPro;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.UI;
[RequireComponent(typeof(Rigidbody2D))]
public class PlayerController : MonoBehaviour
{
#region Player-Variables
#region var-Move
[Header("移動関連")]
[SerializeField] float moveSpeed = 1f; // プレイヤーの移動速度
[SerializeField] float marginX = 1f, marginY = 1f; // 余白x・y
#endregion
#region var-Shot
[Header("ショット関連")]
[SerializeField] GameObject playerBullet; // 弾のプレハブ
[SerializeField] float bulletSpeed = 10f; // 弾の速度
[SerializeField] Transform playerFirePos; // 発射ポイント
[SerializeField] float bulletDelay = 1f; // 発射の閾値
#endregion
#region var-HP
[Header("プレイヤーのHP")]
[SerializeField] int playerHPMax = 10; // プレイヤーのHP(初期値:10)
[SerializeField] int playerHPCurrent; // プレイヤーの現在のHP
[SerializeField] TMP_Text playerHPText; // プレイヤーのHP表示のテキスト
[SerializeField] Slider playerHPGauge; // プレイヤーのHP表示のゲージ
#endregion
#region var-Remaining
[Header("プレイヤーの残機")]
[SerializeField] TMP_Text remainingCount_Text; // プレイヤーの残機表示のテキスト
[SerializeField] GameObject[] remainingCount_Icon; // プレイヤーの残機表示のアイコン
[SerializeField] int remainingCountMax = 5; // プレイヤーの残機の最大数
[SerializeField] int remainingCountCurrent = 3; // プレイヤーの残機の現在値
[SerializeField] Transform respawnPos; // プレイヤーの復活地点
#endregion
#region var-Shield
[Header("シールド関連")]
[SerializeField] GameObject playerShield; // プレイヤーのシールド
[SerializeField] int ShieldHPMax = 5; // シールドの耐久値の最大値
#endregion
#region var-SE
[Header("効果音関連")]
[SerializeField] AudioClip SE_playerDamaged; // プレイヤー被弾時の効果音
[SerializeField] AudioClip SE_ShieldDamaged; // シールドが攻撃された効果音
[SerializeField] float playerDamagedVol = 0.4f, shieldDamagedVol = 0.4f; // 被弾時の効果音の音量(プレイヤー,シールド)
#endregion
#region var-Controllers
[Header("バーチャルスティック")][SerializeField] VariableJoystick joyStick;
[Header("爆発エフェクト")][SerializeField] GameObject playerExplosion;
[Header("ゲームマネージャー")][SerializeField] GameManager gameManager;
[Header("エネミーコントローラー")][SerializeField] EnemyController enemyController;
#endregion
#region base-Variables
Rigidbody2D playerRB; // プレイヤーのRigidbody
Vector2 moveDirection, min, max; // プレイヤーのベクトル,画面サイズの最小ベクトル/最大ベクトル
Vector2 playerPos; // プレイヤーの移動制限用
float bulletInterval; // 弾の発射間隔管理用
bool isShotPressed = false; // 発射ボタン管理用
#endregion
#region Internal-Variables
int playerHPMin = 0; // HPの最小値
enum calcType { heal, damage, display } // HP表示・計算のタイプ
int remainingCountMin = 0; // プレイヤーの残機の最小値
int changeRemaining = -1, remainingCountDisp = 0; // プレイヤーの残機減少用,表示用
int shieldHPCurrent, shieldHPMin = 0; // シールドの現在の耐久値
#endregion
#endregion
#region Method
#region Start
void Start()
{
// プレイヤーのRigidbodyを取得
playerRB = GetComponent<Rigidbody2D>();
// プレイヤーのHPを初期化
playerHPCurrent = playerHPMax;
// プレイヤーのHPを表示
PlayerHPDisplay();
// プレイヤーの残機を表示
RemainingCounter(remainingCountDisp);
}
#endregion
#region Update
void Update()
{
// 入力受付
InputProcess();
// バーチャルスティック処理 ---
// バーチャルスティックに何らかの入力が合った場合
// (joyStickが0では無い場合)
if (joyStick.Direction != Vector2.zero)
{
// moveDirectionをバーチャルスティックの入力にする
moveDirection = joyStick.Direction;
}
// --- ここまで
// 弾の発射 ---
// deltaTimeを加算
bulletInterval += Time.deltaTime;
// 発射間隔が閾値未満の場合
if (bulletInterval < bulletDelay)
{
// 処理を中断
return;
}
// スペースキーが押された場合/isShotPressedがtrueの場合
if (Input.GetKey(KeyCode.Space) || isShotPressed)
{
// 弾の発射関数を呼ぶ
PlayerShot(playerFirePos);
}
// 発射間隔をリセット
bulletInterval = 0;
// --- ここまで
}
#endregion
#region FixedUpdate
void FixedUpdate()
{
// プレイヤーを動かす
PlayerMove();
}
#endregion
#region InputProcess
// 入力受付
void InputProcess()
{
// 入力を正規化して受け取る
float x = Input.GetAxisRaw("Horizontal");
float y = Input.GetAxisRaw("Vertical");
// 入力を正規化する
moveDirection = new Vector2(x, y).normalized;
}
#endregion
#region PlayerMove
// プレイヤーを動かす
void PlayerMove()
{
// 移動制限
MoveClamp();
// プレイヤーを動かす
playerRB.velocity = moveDirection * moveSpeed;
}
#endregion
#region MoveClamp
// プレイヤーの移動制限
void MoveClamp()
{
// 最後の移動地点を取得
playerPos = transform.position;
// ビューポート座標からワールド座標を取得
min = Camera.main.ViewportToWorldPoint(Vector2.zero);
max = Camera.main.ViewportToWorldPoint(Vector2.one);
// 移動を制限する
playerPos.x = Mathf.Clamp(playerPos.x, min.x + marginX, max.x - marginX);
playerPos.y = Mathf.Clamp(playerPos.y, min.y + marginY, max.y - marginY);
// プレイヤーの位置を取得した最後の地点にする
transform.position = playerPos;
}
#endregion
#region PlayerShot
// 弾の発射
void PlayerShot(Transform firePos)
{
// 弾を生成
GameObject bulletClone = Instantiate(playerBullet, firePos.position, Quaternion.identity);
// 弾のRigidbodyに速度ベクトルをつける
bulletClone.GetComponent<Rigidbody2D>().velocity = new Vector2(bulletSpeed, 0);
}
#endregion
#region OnTriggerEnter2D
// 接触判定
private void OnTriggerEnter2D(Collider2D collision)
{
// 敵の場合
if (collision.gameObject.CompareTag("Enemy"))
{
// ダメージの算出
PlayerHPChanged((int)calcType.damage, EnemyController.bodyDamage, collision);
}
// 敵の弾の場合
else if (collision.gameObject.CompareTag("EnemyBullet"))
{
// ダメージの算出
PlayerHPChanged((int)calcType.damage, EnemyController.shotDamage, collision);
}
// プレイヤーの弾の場合
else if (collision.gameObject.CompareTag("PlayerBullet"))
{
// 処理を中断
return;
}
}
#endregion
#region PlayerExplosion
// 爆発演出
void PlayerExplosion(Collider2D collision)
{
// 接触した方を破棄
Destroy(collision.gameObject);
// 自身を非アクティブ
gameObject.SetActive(false);
// プレイヤーの位置に爆発オブジェクトを作成
Instantiate(playerExplosion, playerPos, Quaternion.identity);
}
#endregion
#region OnPointerDown
// 発射ボタンが押された時のイベント
public void OnPointerDown()
{
// 発射ボタンのフラグを変更
isShotPressed = true;
}
#endregion
#region OnPointerUp
// 発射ボタンが元に戻った時のイベント
public void OnPointerUp()
{
// 発射ボタンのフラグを変更
isShotPressed = false;
}
#endregion
#region Continue
// コンティニュー
void Continue()
{
// ゲームマネージャーのコンティニューUIを表示
gameManager.VisibleUI_Continue();
}
#endregion
#region PlayerHPDisplay
// プレイヤーのHP表示・更新
void PlayerHPDisplay()
{
// HPのテキストを表示・更新
playerHPText.SetText("PlayerHP : " + playerHPCurrent + " / " + playerHPMax);
// 現在のHPをゲージに反映
playerHPGauge.value = playerHPCurrent;
}
#endregion
#region PlayerHPChanged
// プレイヤーのダメージ算出
public void PlayerHPChanged(int cType, int damage, Collider2D collision)
{
// switchで条件分岐:対象→cType
switch (cType)
{
// cTypeが回復だった場合
case (int)calcType.heal:
// ダメージ分を加算する
playerHPCurrent += damage;
// 接触されたオブジェクトを破棄
Destroy(collision.gameObject);
// プレイヤーのHPが最大値以上の場合
if (playerHPCurrent >= playerHPMax)
{
// プレイヤーのHPを最大値にする
playerHPCurrent = playerHPMax;
}
// switch文を抜ける
break;
// cTypeがダメージだった場合
case (int)calcType.damage:
// シールドがアクティブになっている場合
if (playerShield.activeInHierarchy)
{
// シールドの耐久値を減算する
PlayerShieldIsActive(damage, collision);
}
else
{
// ダメージ分を減算する
playerHPCurrent -= damage;
}
// 接触したオブジェクトを消す
Destroy(collision.gameObject);
// プレイヤーのHPが0以下(最小値以下)だった場合
if (playerHPCurrent <= playerHPMin)
{
// 爆発演出
PlayerExplosion(collision);
// プレイヤーのHPを0(最小値)にする
playerHPCurrent = playerHPMin;
// 残機を更新
RemainingCounter(changeRemaining);
}
// それ以外の場合
else
{
if (playerShield.activeInHierarchy)
{
// 被弾時に効果音を再生(シールドへのダメージ)
AudioSource.PlayClipAtPoint(SE_ShieldDamaged, Camera.main.transform.position, shieldDamagedVol);
}
else
{
// 被弾時に効果音を再生(プレイヤーへのダメージ)
AudioSource.PlayClipAtPoint(SE_playerDamaged, Camera.main.transform.position, playerDamagedVol);
}
// switch文を抜ける
break;
}
// switch文を抜ける
break;
// cTypeが表示だった場合
case (int)calcType.display:
// switch文を抜ける
break;
// 該当なしの場合
default:
// HP表示の分岐へジャンプ
goto case (int)calcType.display;
}
// プレイヤーのHPを表示
PlayerHPDisplay();
}
#endregion
#region RemainingCountDisplay
// 残機表示・更新
void RemainingCountDisplay()
{
// 現在の残機が最大値を超えている場合
if (remainingCountCurrent > remainingCountMax)
{
// 残機を最大値にする
remainingCountCurrent = remainingCountMax;
}
// プレイヤーの残機表示のテキストを更新
remainingCount_Text.SetText("Remaining Count:" + remainingCountCurrent + "/" + remainingCountMax);
// 残機の最大数までループ
for (int i = 0; i < remainingCountMax; i++)
{
// 現在の残機数がi以下の場合
if (remainingCountCurrent <= i)
{
// 残機アイコンを非アクティブ
remainingCount_Icon[i].SetActive(false);
}
// それ以外の場合
else
{
// 残機アイコンをアクティブ
remainingCount_Icon[i].SetActive(true);
}
}
}
#endregion
#region RemainingCounter
// 残機カウントを更新
public void RemainingCounter(int addRemaining)
{
// 残機に変更がない場合(addRemainingが0の場合)
if (addRemaining == remainingCountDisp)
{
// 残機を表示する
RemainingCountDisplay();
}
// 残機を増やす場合(addRemainingがプラスの場合)
else if (addRemaining > remainingCountDisp)
{
// 残機を増やす
remainingCountCurrent += addRemaining;
// 残機が最大値を超える場合
if (remainingCountCurrent > remainingCountMax)
{
// 現在の残機を最大値にする
remainingCountCurrent = remainingCountMax;
}
}
// 残機を減らす場合(addRemainingがマイナスの場合)
else if (addRemaining < remainingCountDisp)
{
// 残機を減らす
remainingCountCurrent += addRemaining;
// 残機が最小値(0)以下の場合
if (remainingCountCurrent < remainingCountMin)
{
// 残機を最小値にする
remainingCountCurrent = remainingCountMin;
// コンティニュー処理
Continue();
}
// それ以外の場合
else
{
// プレイヤーを復活地点に移動
PlayerRespawn(respawnPos);
}
}
// 残機表示を更新
RemainingCountDisplay();
}
#endregion
#region PlayerRespawn
// プレイヤーの復活
void PlayerRespawn(Transform rePos)
{
// プレイヤーをアクティブ
gameObject.SetActive(true);
// プレイヤーを復活地点に移動
gameObject.transform.position = rePos.position;
// プレイヤーのHPを戻す
playerHPCurrent = playerHPMax;
// プレイヤーのHPを更新
PlayerHPDisplay();
}
#endregion
#region PlayerShieldActivate
// シールドをアクティブにする
public void PlayerShieldActivate()
{
// シールドをアクティブ
playerShield.SetActive(true);
// シールドの耐久値を最大にする
shieldHPCurrent = ShieldHPMax;
}
#endregion
#region PlayerShieldIsActive
// シールドのダメージ
void PlayerShieldIsActive(int shieldDamage, Collider2D collision)
{
// シールドで受けたダメージを減算
shieldHPCurrent -= shieldDamage;
// 耐久値が最小値(0)未満になった場合
if (shieldHPCurrent < shieldHPMin)
{
// 耐久値を超過した分をexcessDamageに代入
int excessDamage = shieldHPCurrent;
// シールドを非アクティブ
playerShield.SetActive(false);
// 超過したダメージ分はプレイヤーへダメージ
PlayerHPChanged((int)calcType.damage, -excessDamage, collision);
}
}
#endregion
#endregion
}
では見ていきましょう。
メンバ変数に
- シールドのゲームオブジェクト
- シールドの耐久値の最大値
- シールドの耐久値の最小値
- 現在のシールドの耐久値
- シールドがダメージを受けた時の効果音
- 効果音のボリューム
を追加しました。
PlayerHPChanged関数のダメージを受ける分岐で、シールドがアクティブになっている場合の条件分岐を追加しています。
activeInHierarchyについてはドキュメントで詳細を確認してください。
GameObject.activeInHierarchy - Unity Documentation
activeInHierarchyは名前からも推測できる通り、対象のゲームオブジェクトがHierarchyビュー上(シーン上)でアクティブかどうかbool値を返すものです。
シールドがアクティブになっているかで条件を分岐させ、シールドがアクティブであればシールドから、非アクティブであればプレイヤーからダメージを減算します。
そしてダメージを受けた時の効果音も、同様にシールドとプレイヤーで分けています。
PlayerShieldActivate関数は、アイテムによってシールドがアクティブになります。
シールドがアクティブになった際に耐久値を最大にしています。
そしてシールドの耐久値とダメージを計算するPlayerShieldIsActive関数では、まずシールドが受けたダメージを現在の耐久値から減算します。
その際にシールドの耐久値が0未満になった場合に、まずはシールドを非アクティブにします。
そしてダメージの超過分をexcessDamageに代入して、PlayerHPChanged関数で超過分のダメージをプレイヤーのHPから減算する処理にしています。
これで処理が正常に行われるか、追加された項目を設定して確認しましょう。
まずは追加分の素材のインポートです。
シールドのアイテム取得時の効果音も変更するので、シールドアイテム取得時とシールドへのダメージの二つになります。
素材をインポート
ShieldActiveをシールドアイテム取得時の効果音に設定します。
同時にアイテムの種類も設定します。
アイテムの種類と効果音を設定
後はアイテムリストへ追加します。
アイテムリストへシールドを追加
プレイヤーの追加された項目も設定します。
追加された項目を設定
ではゲームを実行して確認します。
今回確認することは
- アイテムを取得したらシールドがアクティブになる
- ダメージが耐久値(初期値は5に設定)を超えたら非アクティブになる
- 耐久値を超過したダメージがプレイヤーに入る
となります。
アイテムを取得したらシールドがアクティブになる:OK
ダメージが耐久値(初期値は5に設定)を超えたら非アクティブになる:OK
耐久値を超過したダメージがプレイヤーに入る:OK
これでシールドの実装が出来ました。
まとめ
今回は
- プレイヤーにシールドを実装した
という事をやりました。
次回は、ゲーム全体を調整してから、ここまでの状態を各プラットフォームへビルドしたいと思います。
以前ビルドした時に比べて大分様変わりしたので、そろそろ各プラットフォームで試してみましょう。
では、また次回!



















コメント
コメントを投稿