Unityで2D STGを作る(3日目)
- ボスの被ダメージ処理を追加
- IPlayerShotインタフェースを作成し、自機弾、ボムに実装させる
- ボスは被弾時にGameObjectからIPlayerShotを取得してダメージを取得
- ボスの残ライフ表示バー作成
// 残ライフに応じてバーの長さを調整 private void updateLifeBarLength() { float maxLife = modeLifeMap[restModeNum]; float per = life / maxLife; Vector3 newScale = new Vector3(per, lifeBarObj.transform.localScale.y, lifeBarObj.transform.localScale.z); lifeBarObj.transform.localScale = newScale; Debug.Log("newScale:" + newScale); } // 画像はpositionを中心にして描画されるので、画像の大きさに応じて位置をずらす private void updateLifeBarPos() { float leftX = GameManager.getLeftByScreen() + 50; float barWidth = lifeBarObj.GetComponent<SpriteRenderer>().bounds.size.x; Vector3 worldPos = GameManager.toWorldPoint(leftX + barWidth / 2, 0); lifeBarObj.transform.position = new Vector3(worldPos.x, lifeBarObj.transform.position.y, lifeBarObj.transform.position.z); Debug.Log("newPos:" + lifeBarObj.transform.position); }
- ボスの残形態数を表すバーを作成
public void addRestModeBar(int index) { // 画像サイズを使いたいので一旦インスタンス化 GameObject icon = gameManager.instantiate("LifeBar", lifeBarObj.transform.position); // 残り形態のバーの配置領域 float baseLeft = GameManager.getLeftByScreen() + 10; float baseRight = GameManager.getLeftByScreen() + 40; float areaWidth = baseRight - baseLeft; // 配置領域に収まるよう長さを調整 // バーの長さ×残り形態 + スペース×(残り形態-1)が上記に収まるように // barWidth * restModeNum + space * (restModeNum - 1) = areaWidth; // barWidth = (areaWidth - space * (restModeNum - 1)) / restModeNum; float space = 2; float barWidth = (areaWidth - space * (restModeNum - 1)) / restModeNum; // scaleを調整してバーの長さを調整 float originalWidth = icon.GetComponent<SpriteRenderer>().bounds.size.x; float scale = barWidth / originalWidth; icon.transform.localScale = new Vector3(scale, icon.transform.localScale.y, icon.transform.localScale.z); // indexに応じて配置 float startX = baseLeft + barWidth / 2; float spacing = barWidth + space; float x = startX + (index - 1) * spacing; icon.transform.position = new Vector3(GameManager.toWorldPoint(x, 0).x, icon.transform.position.y, icon.transform.position.z); restModeBarIcons.Push(icon); }
- ボスのモード切替を追加
void OnTriggerEnter2D(Collider2D collider) { // 自機、自機弾以外とはそもそも接触判定をしないので考慮不要 // 自機と当たった場合は自機側で処理するので何もしない if (collider.CompareTag("Player")) { return; } // 無敵中は終わり if (isInvincible()) { return; } // ボムか自機弾と当たったらダメージを受ける IPlayerShot shot = collider.GetComponent<IPlayerShot>(); life -= shot.GetDamage(); // ライフバーの長さ調整 updateLifeBarLength(); updateLifeBarPos(); if (life <= 0) { if (restModeNum == 0) { // TODO 時間をおいてから life = 0; clearAllEnemyShot(); // ゲームクリア画面へ遷移 SceneManager.LoadScene("EndScene"); } else { // 次のゲージへ restModeNum--; life = modeLifeMap[restModeNum]; updateLifeBarLength(); updateLifeBarPos(); loseRestModeBar(); BossState nextState; switch (restModeNum) { case 1: nextState = new Spell1State(this); break; case 0: nextState = new Spell2State(this); break; default: throw new Exception("illegal restModeNum:" + restModeNum); } state = new ConnectState(this, nextState); // 敵弾はクリア clearAllEnemyShot(); } } Debug.Log("hit Boss"); } private class ConnectState : BossState { private Boss1 boss; private BossState nextState; public ConnectState(Boss1 boss, BossState nextState) { this.boss = boss; this.nextState = nextState; } public void Update() { // モード切替時とその後の100Fは無敵 boss.invincibleCount = 100; float moveX = 0; float moveY = 0; float speed = 4; //初期位置に移動 // TODO スムーズに移動するように Vector2 baseWorldPos = new Vector2(boss.gameManager.getCenter().x, boss.getBaseY()); if (Math.Abs(baseWorldPos.x - boss.transform.position.x) > speed * 2) { if (baseWorldPos.x < boss.transform.position.x) { moveX = -speed; } else { moveX = speed; } } if (Math.Abs(baseWorldPos.y - boss.transform.position.y) > speed * 2) { if (baseWorldPos.y < boss.transform.position.y) { moveY = -speed; } else { moveY = speed; } } boss.transform.Translate(moveX, moveY, 0); // 移動が終わったら次のステートへ if (moveX == 0 && moveY == 0) { boss.state = nextState; } } }
- キー操作で移動するように、ショットは押しっぱなしで発射するように、低速移動を追加
- 開発中は特定キー押下で無敵になるように
Application.isEditorで判断できる - Resouces.loadの結果をキャッシュするように
必要ないかもしれないが、開発時にResource ID out of range in GetResourceのエラーが出たので
public GameObject instantiate(String prehabName, Vector3 worldPos) { String name = "Prefabs/" + prehabName; GameObject prehab; if (prehabCache.ContainsKey(name)) { prehab = prehabCache[name]; } else { prehab = (GameObject)Resources.Load(name); prehabCache.Add(name,prehab); } return (GameObject)Instantiate(prehab, worldPos, Quaternion.identity); }
- 敵弾の画像サイズに応じて当たり判定も調整されるように
public void setSprite(string spriteName) { SpriteRenderer renderer = GetComponent<SpriteRenderer>(); renderer.sprite = gameManager.loadSprite(spriteName); // XXX 画像は縦横同じサイズという想定 float spriteSize = renderer.bounds.size.x; // 画像サイズに応じて当たり判定を調整 if (spriteSize != SIZE) { float scale = spriteSize / (float)EnemyShot.SIZE; GetComponent<CircleCollider2D>().radius = GetComponent<CircleCollider2D>().radius * scale; } }
- ボスの通常弾幕を作成
private class Normal1State : BossState { private Boss1 boss; private GameManager gameManager; private float startAngle = 270; //真下 private Vector2 moveVector; public Normal1State(Boss1 boss) { this.boss = boss; this.gameManager = boss.gameManager; } private int count; public void Update() { // 0~100は何もしない if (count < 100) { } // nWay弾を角度を変えて定期的に撃つだけ else if (count < 200) { if (count % 15 == 0) { int n = 16; for (int i = 0; i < n; i++) { float angle = startAngle + (360.0f / n) * i; float speed = 10; Vector2 moveVector = GameManager.getMoveVector(speed, angle); GameObject enemyShot = gameManager.createEnemyShot("EnemyShot", boss.transform.position); enemyShot.GetComponent<EnemyShot>().init(gameManager, new StraightShotStrategy(boss.gameManager, enemyShot, moveVector)); } startAngle += 5; } } // 200~300は何もしない if (count < 300) { } // 300になったらランダムな方向に移動開始 else if (count == 300) { float speed = 0.5f; float angle = UnityEngine.Random.Range(0, 360); moveVector = GameManager.getMoveVector(speed, angle); // ゲーム領域外に近寄らなすぎないように調整 moveVector = boss.fixMoveVector(moveVector); } else if (count <= 400) { // 300~400は移動しながらショット boss.transform.Translate(moveVector); if (count % 15 == 0) { int n = 16; for (int i = 0; i < n; i++) { float angle = startAngle + (360.0f / n) * i; float speed = 10; Vector2 moveVector = GameManager.getMoveVector(speed, angle); GameObject enemyShot = gameManager.createEnemyShot("EnemyShot", boss.transform.position); enemyShot.GetComponent<EnemyShot>().init(gameManager, new StraightShotStrategy(boss.gameManager, enemyShot, moveVector)); } startAngle -= 5; } } count++; // 400になったら最初に戻る if (count == 400) { count = 0; } } }
- ボスのスペカ1を作成
体力半分以上:nWay弾が画面端近くに行ったら戻ってくる(実際は指定フレーム数で戻るようにしてるだけで細かな調整によるもの)
体力1/4以上:回転するnWay弾が画面端近くに行ったら戻ってくる(時計周りと反時計回りの繰り返し)
体力1/4未満:時計回りと反時計周りのnWay戻り弾を同時に発射+ゆっくりとした自機狙い弾
private class Spell1State : BossState { private Boss1 boss; private GameManager gameManager; private float maxLife; private float prevLife; private Spell1SubState subState; public Spell1State(Boss1 boss) { this.boss = boss; this.gameManager = boss.gameManager; maxLife = boss.life; prevLife = boss.life; subState = new FirstState(boss); } private interface Spell1SubState { void Update(); } //体力半分以上 private class FirstState : Spell1SubState { private int count; private float startAngle = 270; //真下 private Vector2 moveVector; private Boss1 boss; private GameManager gameManager; public FirstState(Boss1 boss) { this.boss = boss; this.gameManager = boss.gameManager; } public void Update() { // 0~100は何もしない if (count < 100) { } // nWay弾を角度を変えて定期的に撃つだけ else if (count < 200) { if (count % 20 == 0) { int n = 16; const int stopStartCount = 75; const int stopCount = 45; const int slowCount = stopStartCount * 3 / 10; const float returnSpeedRate = 0.8f; for (int i = 0; i < n; i++) { float angle = startAngle + (360.0f / n) * i; float speed = 9f; Vector2 moveVector = GameManager.getMoveVector(speed, angle); GameObject enemyShot = gameManager.createEnemyShot("EnemyShot", boss.transform.position); enemyShot.GetComponent<EnemyShot>().init(gameManager, new ReturnStraightShotStrategy(boss.gameManager, enemyShot, moveVector, stopStartCount, stopCount, slowCount, returnSpeedRate, ImageManager.GRAY_SPHERE_48)); enemyShot.GetComponent<EnemyShot>().setSprite(ImageManager.BLACK_SPHERE_48); } startAngle += 6; } } // 200~300は何もしない if (count < 300) { } // 300になったらランダムな方向に移動開始 else if (count == 300) { float speed = 0.5f; float angle = UnityEngine.Random.Range(0, 360); moveVector = GameManager.getMoveVector(speed, angle); // ゲーム領域外に近寄らなすぎないように調整 moveVector = boss.fixMoveVector(moveVector); } else if (count <= 400) { // 300~400は移動しながらショット boss.transform.Translate(moveVector); if (count % 20 == 0) { int n = 16; const int stopStartCount = 75; const int stopCount = 45; const int slowCount = stopStartCount * 3 / 10; const float returnSpeedRate = 0.8f; for (int i = 0; i < n; i++) { float angle = startAngle + (360.0f / n) * i; float speed = 9f; Vector2 moveVector = GameManager.getMoveVector(speed, angle); GameObject enemyShot = gameManager.createEnemyShot("EnemyShot", boss.transform.position); enemyShot.GetComponent<EnemyShot>().init(gameManager, new ReturnStraightShotStrategy(boss.gameManager, enemyShot, moveVector, stopStartCount, stopCount, slowCount, returnSpeedRate, ImageManager.GRAY_SPHERE_48)); enemyShot.GetComponent<EnemyShot>().setSprite(ImageManager.BLACK_SPHERE_48); } startAngle -= 6; } } count++; // 400になったら最初に戻る if (count == 400) { count = 0; } } } //体力半分以下、1/4以上 private class SecondState : Spell1SubState { private int count; private float startAngle = 270; //真下 private Vector2 moveVector; private Boss1 boss; private GameManager gameManager; // private int straightAngleSign = 1; public SecondState(Boss1 boss) { this.boss = boss; this.gameManager = boss.gameManager; } public void Update() { // 0~100は何もしない if (count < 100) { } else if (count < 250) { if (count % 35 == 0) { int n = 16; const float speed = 7.5f; //反時計回り const float angleSpeed = -1; const int stopStartCount = 90; const int stopCount = 45; const int slowCount = 25; const float returnSpeedRate = 0.8f; for (int i = 0; i < n; i++) { float initialAngle = startAngle + (360.0f / n) * i; GameObject enemyShot = gameManager.createEnemyShot("EnemyShot", boss.transform.position); enemyShot.GetComponent<EnemyShot>().init(gameManager, new ReturnRollingShotStrategy(boss.gameManager, enemyShot, speed, initialAngle, angleSpeed, stopStartCount, stopCount, slowCount, returnSpeedRate, ImageManager.GRAY_SPHERE_48)); enemyShot.GetComponent<EnemyShot>().setSprite(ImageManager.BLACK_SPHERE_48); } startAngle += 6; } } // 250~350は何もしない if (count < 350) { } // 350になったらランダムな方向に移動開始 else if (count == 350) { float speed = 0.5f; float angle = UnityEngine.Random.Range(0, 360); moveVector = GameManager.getMoveVector(speed, angle); // ゲーム領域外に近寄らなすぎないように調整 moveVector = boss.fixMoveVector(moveVector); } else if (count <= 500) { // 350~500は移動しながらショット boss.transform.Translate(moveVector); if (count % 30 == 0) { int n = 16; const float speed = 7.5f; //時計回り const float angleSpeed = 1; const int stopStartCount = 90; const int stopCount = 45; const int slowCount = 25; const float returnSpeedRate = 0.8f; for (int i = 0; i < n; i++) { float initialAngle = startAngle + (360.0f / n) * i; GameObject enemyShot = gameManager.createEnemyShot("EnemyShot", boss.transform.position); enemyShot.GetComponent<EnemyShot>().init(gameManager, new ReturnRollingShotStrategy(boss.gameManager, enemyShot, speed, initialAngle, angleSpeed, stopStartCount, stopCount, slowCount, returnSpeedRate, ImageManager.GRAY_SPHERE_48)); enemyShot.GetComponent<EnemyShot>().setSprite(ImageManager.BLACK_SPHERE_48); } startAngle += 6; } } count++; // 500になったら最初に戻る if (count == 500) { count = 0; straightAngleSign = -straightAngleSign; } } } private class ThirdState : Spell1SubState { private int count; private float startAngle = 270; //真下 private Vector2 moveVector; private Boss1 boss; private GameManager gameManager; public ThirdState(Boss1 boss) { this.boss = boss; this.gameManager = boss.gameManager; } public void Update() { // 0~100は何もしない if (count < 100) { } // 100になったらランダムな方向に移動開始 else if (count == 100) { float speed = 0.5f; float angle = UnityEngine.Random.Range(0, 360); moveVector = GameManager.getMoveVector(speed, angle); // ゲーム領域外に近寄らなすぎないように調整 moveVector = boss.fixMoveVector(moveVector); } else if (count < 300) { // 100~300は移動しながらショット boss.transform.Translate(moveVector); if (count % 30 == 0) { int n = 12; const float speed = 5.5f; const float angleSpeed = 0.5f; const int stopStartCount = 135; const int stopCount = 45; const int slowCount = 30; const float returnSpeedRate = 0.8f; for (int i = 0; i < n; i++) { float initialAngle = startAngle + (360.0f / n) * i; // 見栄えを考慮して描画順序を調整 if (i % 2 == 0) { createRetrunRollingShot(speed, initialAngle, angleSpeed, stopStartCount, stopCount, slowCount, returnSpeedRate, ImageManager.RED_SPHERE_48, ImageManager.GRAY_SPHERE_48); createRetrunRollingShot(speed, initialAngle, -angleSpeed, stopStartCount, stopCount, slowCount, returnSpeedRate, ImageManager.BLUE_SPHERE_48, ImageManager.GRAY_SPHERE_48); } else { createRetrunRollingShot(speed, initialAngle, -angleSpeed, stopStartCount, stopCount, slowCount, returnSpeedRate, ImageManager.BLUE_SPHERE_48, ImageManager.GRAY_SPHERE_48); createRetrunRollingShot(speed, initialAngle, angleSpeed, stopStartCount, stopCount, slowCount, returnSpeedRate, ImageManager.RED_SPHERE_48, ImageManager.GRAY_SPHERE_48); } } startAngle -= 6; } } count++; // 定期的に自機狙い弾を放つ if (count % 200 == 150) { float speed = 0.75f; float angle = GameManager.getPlayerAngle(boss.transform.position); Vector2 moveVector = GameManager.getMoveVector(speed, angle); GameObject enemyShot = gameManager.createEnemyShot("EnemyShot", boss.transform.position); enemyShot.GetComponent<EnemyShot>().init(gameManager, new StraightShotStrategy(boss.gameManager, enemyShot, moveVector)); } // 500になったら最初に戻る if (count == 500) { count = 0; } } private GameObject createRetrunRollingShot(float speed, float initialAngle, float angleSpeed, int stopStartCount, int stopCount, int slowCount, float returnSpeedRate, String spriteName, String returnSpriteName) { GameObject enemyShot = gameManager.createEnemyShot("EnemyShot", boss.transform.position); enemyShot.GetComponent<EnemyShot>().init(gameManager, new ReturnRollingShotStrategy(boss.gameManager, enemyShot, speed, initialAngle, angleSpeed, stopStartCount, stopCount, slowCount, returnSpeedRate, returnSpriteName)); enemyShot.GetComponent<EnemyShot>().setSprite(spriteName); return enemyShot; } } public void Update() { float firstLife = maxLife / 2; float secondLife = maxLife / 4; if (prevLife >= firstLife && boss.life < firstLife) { subState = new SecondState(boss); } else if (prevLife >= secondLife && boss.life < secondLife) { subState = new ThirdState(boss); } subState.Update(); prevLife = boss.life; } }
スペカ1のラストのキャプチャ