【Unity】2Dシューティングを作ってみる話 #30
記事をご覧いただき、誠にありがとうございます。
投稿主の無能です。
前回は、ゲーム全体の調整をして、各プラットフォーム向けにビルドしてゲームを実行して確認しました。
今回はショットのパワーを実装したいと思います。
ショットのパワーを変化させる
敵・味方共にショットのパワーが1のままで変化が無いので、ショットのパワーを実装したいと思います。
この実装によって、敵・味方のHPを考えたりすることもゲームの調整に入ってくるので、よりゲーム性が出てくると思います。
では早速やっていきましょう。
パワーが変化したショットのオブジェクトを用意する
これは敵やアイテムと同じ考え方で、色違いのショットでパワーが変化したことをゲームのプレイヤーに知らせます。
ですので、まずは色違いのショットのオブジェクトを準備します。
ショットの画像を二つ複製し、それぞれstronger・strongestとします。
ショットの画像を二つ複製し、それぞれstronger・strongestとする
そしてPlayerBulletプレハブも二つ複製し、こちらもstronger・strongestとします。
PlayerBulletプレハブも二つ複製し、こちらもstronger・strongestとする
PlayerBulletプレハブの画像を、先程複製した画像のstronger・strongestを設定します。
stronger
strongest
後は色を変えればそれぞれのショットの出来上がりです。
無能はstrongerを緑に、strongestを赤にしました。
stronger
strongest
もし全然違うタイプのショットにしたい場合は、画像をインポートしてください。
その際にはColliderの適用を忘れずに行ってください。
また3段階以上を用意する場合も、プレハブを複製して想定する分だけショットを準備してください。
これでプレイヤーのショットのパワーが変化したオブジェクトの準備が出来ました。
次は敵のショットのパワーが変化したオブジェクトの準備をします。
敵のショットのパワーが変化したオブジェクトを準備する
敵のショットは、変わらず丸い弾にしておいて、色違いで強さを判別します。
敵の弾の画像は無いので、そのままプレハブを複製します。
複製したものをプレイヤーと同様にstronger・strongestにします。
敵の弾のプレハブを複製してリネームする
プレイヤーと同様にstrongerは緑、strongestは赤にします。
敵のstronger
敵のstrongest
これで敵のショットも準備出来ました。
敵の方は、このままスクリプトでSotPowerを変数に追加すれば実装出来そうなのですが、プレイヤーの方はと言えばショットのパワーが変化するきっかけがありません。
やられてリスポーンする時は最弱のショットになることは確定ですが、生存している中でショットのパワーを変化させるにはどうしたら良いでしょうか。
パッと思い付くのは、アイテムでショットパワーを強化するアイテムが出現し、それを取得するとプレイヤーのショットパワーが増加する、と言うのが良さそうです。
という事でショットパワーを加算するアイテムを作ります。
アイテムのオブジェクトの作成自体の流れは、他のアイテムと同様です。
無能はピンクにしました。
画像をインポート
画像の大きさを調整
アイテムのプレハブを複製して、この画像を設定して、アイテムの種類を決めれば準備完了です。
アイテムを複製してリネーム
画像とアイテムの種類を設定
これでお膳立てが出来ました。
次にスクリプトで処理を追記していくのですが、まずは手数が少なくて済むItemControllerから攻めていきます。
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; // 残機の増加量
[SerializeField] int shotPowerAdd = 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, addShotPower }; // 内部処理用(アイテムの効果の種類)
#endregion
#endregion
#region Methods
#region Start
void Start()
{
// カプセルのリジッドボディを取得
capsuleRB = GetComponent<Rigidbody2D>();
// ゲームマネージャーを取得
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;
// ショットパワー加算の場合
case (int)itemEffectType.addShotPower:
// プレイヤーのショットパワーを増加
player.PlayerShotPowerChanged(shotPowerAdd);
// 自身を破棄
Destroy(gameObject);
// switch文を抜ける
break;
// 該当なしの場合
default:
// switch文を抜ける
break;
}
// 取得時に効果音を再生
AudioSource.PlayClipAtPoint(SE_GetItem, Camera.main.transform.position, getItemVol);
}
#endregion
#endregion
}
では見ていきましょう。
メンバ変数のitemEffectTypeにaddShotPowerを追加して、分岐を増やしました。
それに応じてItemEffect関数のswitch文にaddShotPowerの分岐を増やし、プレイヤーのショットパワーを増加させ、アイテム自身を破棄します。
ここまではいつもの流れなので簡単ですね。
では次にPlayerControllerでショットパワーを増加させるように追記します。
このようになります。
PlayerController.cs
using System.Collections;
using System.Collections.Generic;
using TMPro;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.Rendering.UI;
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; // シールドの現在の耐久値
int playerShotPowerMax = 3, playerShotPowerMin = 1; // ショットパワーの最大値,最小値
int countCtrl = 1; // カウント調整用
#endregion
public static int PlayerShotPower { set; get; } = 1; // ショットの強さ
public static int PlayerBodyDamage { get; } = 3; // プレイヤー本体に衝突した時のダメージ
#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, PlayerShotPower);
}
// 発射間隔をリセット
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, int sPower)
{
// 弾を生成
GameObject bulletClone = Instantiate(playerBullet[sPower - countCtrl], 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.EnemyBodyDamage, collision);
}
// 敵の弾の場合
else if (collision.gameObject.CompareTag("EnemyBullet"))
{
// ダメージの算出
PlayerHPChanged((int)calcType.damage, EnemyController.EnemyShotPower, 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;
// ショットの強さを戻す
PlayerShotPower = playerShotPowerMin;
// プレイヤーの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
#region PlayerShotPowerChanged
// ショットパワーを変更する
public void PlayerShotPowerChanged(int shotPowerChange)
{
// ショットパワーの変更が無い場合
if (shotPowerChange == 0)
{
// 処理を返す
return;
}
// ショットパワーがプラスの場合
else if (shotPowerChange > 0)
{
// ショットパワーを加算
PlayerShotPower += shotPowerChange;
// ショットパワーが最大値を超える場合
if (PlayerShotPower > playerShotPowerMax)
{
// ショットパワーを最大値にする
PlayerShotPower = playerShotPowerMax;
}
}
// ショットパワーを減らす場合
else if (shotPowerChange < 0)
{
// ショットパワーを減算する
PlayerShotPower -= shotPowerChange;
// ショットパワーが最小値未満になる場合
if (PlayerShotPower < playerShotPowerMin)
{
// ショットパワーを最小値にする
PlayerShotPower = playerShotPowerMin;
}
}
}
#endregion
#endregion
}
では見ていきましょう。
メンバ変数で変化したのは、playerBulletが配列になったことです。
一種類ではなく、扱う弾の種類が増えたので当たり前ですね。
それから内部用として、ショットパワーの最小値・最大値、カウント調整用のcountCtrlを用意しました。
そして外部アクセスのために、プレイヤーの弾のパワーとプレイヤー本体が敵に与えるダメージを用意しました。
プレイヤーのショットパワーは、ショットパワーを変更するためsetを、プレイヤーのショットの強さをダメージにするためgetを用意しています。
プレイヤー本体のダメージは読み取りだけになるので、get-onlyにしています。
因みにstatic変数は、ゲーム全体の変数として扱われるので、Inspectorビューには表示されない事だけ覚えておいてください。
Update関数内のショットを呼び出す関数には、プレイヤーのショットパワーを伝える引数を新たに追加しています。
そしてPlayerShot関数で弾をクローン生成する際に、配列に変更したので配列の順序からcountCtrlで1引いた数を生成します。
これはダメージの計算にも関わってくる部分なので、ショットの生成時にcountCtrlで1を引いて配列にあわせています。
そしてOnTriggerEnter2D関数内を、EnemyCotrollerとあわせて修正しています。
そして新たにPlayerShotPowerChanged関数を作成し、こちらでショットパワーの変更の処理を行っています。
ショットパワーの加算値が0の場合は処理を返します。
1以上の場合はショットパワーを加算し、0未満の場合はショットパワーを減算します。
何れも最大値・最小値を超えないようにif文で条件分岐させています。
ここまででプレイヤー側のショットパワーの変更について出来たので、次は敵のショットパワーを追記します。
プレイヤーにショットパワーを実装したので、敵の方はプレイヤーとは異なり予め決まっているので簡単ですね。
EnemyCotroller.cs
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
[RequireComponent(typeof(Rigidbody2D))]
public class EnemyController : MonoBehaviour
{
#region var-EnemyType
[Header("敵の種類")][SerializeField] int enemyType = 0;
#endregion
#region var-EnemyMove
[Header("敵の移動スピード")][SerializeField] float moveSpeed = 1f;
#endregion
#region var-EnemyHP
[Header("敵のHP")]
[SerializeField] int enemyHP = 1;
#endregion
#region var-EnemyShot
[Header("敵の弾の発射")]
[SerializeField] GameObject[] enemyBullet; // 敵の弾
[SerializeField] Transform enemyFirePos; // 発射ポイント
[SerializeField] float shotSpeed = 5f; // 弾の速度
[SerializeField] float shotDelay = 2f; // 弾の発射の閾値
[SerializeField] int shotPower = 1; // 敵の弾の強さ
#endregion
#region var-EnemyPerformance
[Header("加算する点数")][SerializeField] int addScore = 100;
[Header("爆発エフェクト")][SerializeField] GameObject enemyExplosion;
#endregion
#region var-Controller
[Header("ゲームマネージャー")] GameManager gameManager;
[Header("プレイヤーコントローラー")] PlayerController player;
#endregion
#region var-Internal
Rigidbody2D enemyRB; // 敵のリジッドボディ
float shotInterval = 0; // 弾の発射管理用
int enemyHPMin = 0, enemyHPMax = 10; // 敵HPの最小値,最大値
int countCtrl = 1; // カウント調整用
enum enemyMoveType { normal, playerAimAttack, playerFollowY } // 敵の動きの種類
enum enemyDamageType { heal, damaged } // ダメージの種類
#endregion
#region var-ExternalAccess
public static int EnemyShotPower { set; get; } = 1; // 弾のダメージ
public static int EnemyBodyDamage { get; } = 3; // 本体のダメージ
#endregion
#region Start
void Start()
{
// 敵のリジッドボディを取得
enemyRB = GetComponent<Rigidbody2D>();
// ゲームマネージャーを取得
gameManager = GameObject.FindWithTag("GameManager").GetComponent<GameManager>();
// プレイヤーコントローラーを取得
player = GameObject.FindWithTag("Player").GetComponent<PlayerController>();
// ショットパワーを代入
EnemyShotPower = shotPower;
}
#endregion
#region Update
void Update()
{
// 弾の発射 ---
// deltaTimeを加算
shotInterval += Time.deltaTime;
// 発射間隔が閾値未満の場合
if (shotInterval < shotDelay)
{
// 処理を中断
return;
}
// 弾の発射関数を呼ぶ
EnemyShot(enemyFirePos, EnemyShotPower);
// 発射間隔をリセット
shotInterval = 0;
// --- ここまで
}
#endregion
#region FixedUpdate
void FixedUpdate()
{
// 敵を動かす
EnemyMove(enemyType);
}
#endregion
#region EnemyMove
// 敵を動かす
void EnemyMove(int moveType)
{
// moveTypeで分岐
switch (moveType)
{
// ノーマルの場合
case (int)enemyMoveType.normal:
// 敵に左方向のベクトルを持たせる
enemyRB.velocity = new Vector2(-moveSpeed, 0);
// swtich文を抜ける
break;
// プレイヤーに向かって特攻の場合
case (int)enemyMoveType.playerAimAttack:
// 敵にプレイヤーの位置に向かうように動かす
enemyRB.velocity = new Vector2(-moveSpeed + player.transform.position.x, player.transform.position.y - transform.position.y);
// swtich文を抜ける
break;
// プレイヤーの上下運動に合わせて動く場合
case (int)enemyMoveType.playerFollowY:
// 敵をプレイヤーの上下の位置にあわせて動かす
enemyRB.velocity = new Vector2(-moveSpeed, player.transform.position.y - transform.position.y);
// swtich文を抜ける
break;
// 該当なしの場合
default:
// swtich文を抜ける
break;
}
}
#endregion
#region OnTriggerEnter2D
// 接触判定
void OnTriggerEnter2D(Collider2D collision)
{
// プレイヤーに接触した場合
if (collision.gameObject.CompareTag("Player"))
{
// ダメージの算出
EnemyHPChanged((int)enemyDamageType.damaged, PlayerController.PlayerBodyDamage, collision);
}
// プレイヤーの弾に接触した場合
else if (collision.gameObject.CompareTag("PlayerBullet"))
{
// ダメージの算出
EnemyHPChanged((int)enemyDamageType.damaged, PlayerController.PlayerShotPower, collision);
// スコアを加算
gameManager.ScoreAdded(addScore);
}
// ObjectDestroyerに接触した場合
else if (collision.gameObject.CompareTag("ObjectDestroyer"))
{
// 自身を破棄
Destroy(gameObject);
}
}
#endregion
#region EnemyShot
// 弾の発射
void EnemyShot(Transform firePos, int sPower)
{
// 弾を生成
GameObject bulletClone = Instantiate(enemyBullet[sPower - countCtrl], firePos.position, Quaternion.identity);
// 弾のRigidbodyに速度ベクトルをつける
bulletClone.GetComponent<Rigidbody2D>().velocity = new Vector2(-shotSpeed, 0);
}
#endregion
#region EnemyExplosiion
// 爆発演出
void EnemyExplosion(Collider2D collision)
{
// 自身を破棄
Destroy(gameObject);
// 爆発エフェクトを生成
Instantiate(enemyExplosion, transform.position, Quaternion.identity);
}
#endregion
#region EnemyHPChanged
// ダメージの算出
void EnemyHPChanged(int damageType, int damage, Collider2D collision)
{
// ダメージの種類で分岐
switch (damageType)
{
// HP回復の場合
case (int)enemyDamageType.heal:
// 回復分をHPに加算
enemyHP += damage;
// HPの最大値以上に鳴る場合
if (enemyHP >= enemyHPMax)
{
// HPを最大値にする
enemyHP = enemyHPMax;
}
// switch文を抜ける
break;
// ダメージの場合
case (int)enemyDamageType.damaged:
// ダメージ分を減算
enemyHP -= damage;
// HPが最小値以下に鳴る場合
if (enemyHP <= enemyHPMin)
{
// 爆発演出
EnemyExplosion(collision);
}
// switch文を抜ける
break;
// 該当なしの場合
default:
// switch文を抜ける
break;
}
}
#endregion
}
では見ていきましょう。
敵の方は今までHPと言う概念は持っていませんでしたので、ショットパワーを実装する際にHPを持たせるようにしました。
メンバ変数にenemyHPとenemyHPMin・MaxというHP関連の変数を持たせます。
あとはプレイヤーと同じくBulletを配列にしてcountCtrlで調整し、ショットパワーの最小値・最大値を持たせました。
敵のパラメータを変更しやすいようにshotPowerを持たせています。
Start関数ではshotPowerをEnemyShotPowerに代入しています。
敵のショットパワーは敵が生成された時点で固定になっているので、Start関数でEnemyShotPowerに代入することでInspectorビューでの変更がしやすくなります。
static変数は共通でクラス名.変数で何処からでもアクセス可能になりますが、そのような「共通である」という一つの考え方からInspectorビューに表示されないようになっています。
そのためshotPowerでInspectorビューから変更可能にしておき、Start関数で代入するというわけです。
Update関数ではプレイヤーと同様にショットパワーを伝える引数を追加し、EnemyShot関数でcountCtrlで配列の数の調整をしています。
変化があるのはEnemyHPChanged関数が新たに追加されたことと、OnTriggerEnter2D関数でEnemyHPChanged関数が呼ばれています。
これらはプレイヤーと同様HPの変化を処理しています。
プレイヤーとの違いはコンティニュー処理とアイテム処理が無い事です。
一応回復する処理も追記していますが、現段階では敵が回復する処理は実装していません。
時間経過で敵のHPが回復する処理を実装したりすると面白いかも知れません。
では追加された項目を設定します。
ItemGeneratorにShotPowerUpを追加
プレイヤーのBulletに弾の種類を追加
今回はEnemy_Normalのショットパワーを2、PlayerAimAttackを1、PlayerFollowYを3にしました。
Normalのショットパワーを2
PlayerAimAttackのショットパワーを1
PlayerFollowYのショットパワーを3
ではゲームを実行して確認します。
今回確認することは
敵がそれぞれ設定した弾を発射するか
それぞれが発射した弾は設定通りのダメージになっているか
になります。
敵がそれぞれ設定した弾を発射するか:OK(1/3)
敵がそれぞれ設定した弾を発射するか:OK(2/3)
敵がそれぞれ設定した弾を発射するか:OK(3/3)
発射された弾の計算も大丈夫でした。
(スクリーンショットは割愛します)
これでショットの強さを実装出来ました。
まとめ
今回は
敵・味方共にショットのパワーを実装した
という事をやりました。
次回は、ステージボスを実装したいと思います。
では、また次回!





















コメント
コメントを投稿