##ここまでの進捗
- 背景がループするようにした。
- 普通の敵の動作を作成し、その敵が3秒ごとに生成される。
- 瞬間移動する敵の動作を作成し、5秒ごとに生成する。
- プレイヤーが画面の範囲外に行かないようにした。
- 敵とプレイヤーが衝突したらプレイヤーが消滅する。
- 分散攻撃の敵を実装する。
- タイトルシーンとエンディングシーンを追加する。
- プレイヤーがダメージ受けると点滅して数秒だけ無敵状態になる
- プレイヤーが横に移動すると機体が傾くアニメーションをつける
- エフェクトとBGMを追加する。
##今後やること
- ボスキャラの動作を実装する。
この記事では赤い部分を作成した。
いよいよボスの実装に入る
##難易度Extremeの内容
それぞれの敵キャラを強化したものをボスにする
画面の一番上にボスの体力バーを表示する
###画面の上部にボスの体力バーを設置した
これを参考にして作ったので今回は説明を少し端折る
qiita/SliderでHPバーを作る
ヒエラルキーで右クリックして[UI]→[Slider]を選択する
SliderのHandle Slide Areaはsliderのつまみの部分なので消す
sliderオブジェクトのInteractableはバーの長さをユーザーが変更できてしまうのでチェックを外す
Fill AreaとFillのRect transformの値をすべて0にする
Slider HpBar;
void Start() {
BossMaxHp = 50;
HpBar = hp.GetComponent<Slider>();
HpBar.maxValue = BossMaxHp;
HpBar.value = BossMaxHp;
}
これによってSliderでボスのHPを設定して、ボスのOnOnTriggerEnter2D内でHpBar.valueの値を減らすことで体力が減ることを表す
###構成
ボスA
↓
ボスB(ノーマルタイプのボス)
↓
ボスC(テレポートのボス)
↓
ボスD(分散攻撃のボス)
↓
ラスボス
すべてのボスの移動は画面上部をランダムに移動して、攻撃するときだけ止まる。
ボスはプレイヤーの弾を受けたら、攻撃をうけたことが分かるように点滅するようにする
###ボスA
ここのボスはまだ簡単な攻撃を行うようにする
[攻撃1]ランダムの方向に攻撃する
[攻撃2]正面に太いビームを撃つ
###ボスB
[攻撃1]分身を出してそれぞれの分身は固定の位置で攻撃してくる
[攻撃2]追尾してくる弾を撃つ
###ボスC
最初は攻撃が通らず、2つのオブジェクトを破壊しないと攻撃が通らない
オブジェクトが破壊されるまでは基本的に瞬間移動してからプレイヤーに向けた弾を撃つ
[攻撃1]瞬間移動した後にプレイヤーに向けた弾を撃つ(5回くらい)
[攻撃2]5回程度瞬間移動して弾を置いていき、移動し終わったらプレイヤー方向に弾を飛ばす
[攻撃3]瞬間移動で爆弾を置いていき爆弾が爆発すると3方向に弾が飛んでいく
###ボスD
[攻撃1]分散攻撃を連続でやってくる
[攻撃2]全方位にいくつもの弾幕(らせん状 or 等間隔)をはる(言葉で説明しずらい)
[攻撃3]弾が2段階くらい分裂する
###ラスボス
ラスボスに入る前にライフを全快にして画面が赤く点滅する
ボスAの攻撃を進化させた以下の攻撃と今までのボスの攻撃をいくつかやってくる
[攻撃1]正面にビームを撃つ & ランダムの方向に攻撃する
[攻撃2]分身を出してそれぞれの分身は固定の位置で攻撃してくる
[攻撃3]瞬間移動で爆弾を置いていき爆弾が爆発すると3方向に弾が飛んでいく
[攻撃4]全方位の弾幕を2つ出す
[攻撃5]瞬間移動で弾を置いていき、移動し終わったらプレイヤーに向けた弾を撃ち、弾も途中で分裂する
##ボスA
###ランダムの方向に攻撃する
分散攻撃を利用する
分散の数を5にしてランダムでその5方向から1方向選んで発射する
public void Attack6(Transform bossA, int NwayCount) {
float r = Random.Range(1, NwayCount + 1);
float angle = -(NwayCount + 1) * 5 / 2 + r * 5;
Instantiate(BossProjectilePrefab, bossA.position, Quaternion.Euler(new Vector3(0.0f, 0.0f, angle)));
}
ほとんど分散攻撃のプログラムと変わらない
引数に分散の数とボスのtransformをいれて、ランダムの方向を決めて発射する
###正面にビームを撃つ
ビームのスプライトはないので自分で以下のように作成した。
ボスの撃つ弾から四角く切り抜いた
これをある程度伸ばしてビームのスプライトとした
public void Attack7(Transform bossA) {
Instantiate(BeamProjectilePrefab, new Vector3(bossA.transform.position.x - 0.32f, bossA.transform.position.y, 0), bossA.rotation);
Instantiate(BeamProjectilePrefab, new Vector3(bossA.transform.position.x + 0.33f, bossA.transform.position.y, 0), bossA.rotation);
Instantiate(BossBeamPrefab, new Vector3(bossA.transform.position.x + 0.03f, bossA.transform.position.y - 3.0f, 0), bossA.rotation);
}
あまり難しいことはなく、プレファブを出現させているだけ
ちょっとしょぼいけどこれもちゃんと実装できてよかった
###行動パターン
(移動×3回 → 通常攻撃) × 5回 → 乱数で攻撃1か攻撃2のどちらかを選択し攻撃する
これを繰り返す
##ボスB
###分身を出してそれぞれの分身は固定の位置で攻撃してくる
if (c == 0) {
//3つの分身を出す
for (int i = 0; i < 3; i++) {
float rand_x = Random.Range(-3.0f, 3.0f);
float rand_y = Random.Range(0.0f, 4.5f);
Instantiate(AvatarPrefab, new Vector2(rand_x, rand_y), Quaternion.identity);
}
c = 1;
}else if (c == 1) {
//分身が全滅するまで本体はランダムの方向に弾を撃つ
if (GameObject.FindGameObjectsWithTag("Avatar").Length > 0) {
float r = Random.Range(1, 6);
float angle = -15 + r * 5;
Instantiate(BossProjectilePrefab, bossA.position, Quaternion.Euler(new Vector3(0.0f, 0.0f, angle)));
AudioSource.PlayClipAtPoint(BossShotSE, transform.position);
} else if (GameObject.FindGameObjectsWithTag("Avatar").Length == 0) {
AttackFlag = 0;
c = 0;
}
}
上がプログラムとなり、変数cによって分身を発生させる処理を1回だけ行うようにしてあとは分身は生成された場所で弾を撃ち続け、本体はランダムの方向に弾を撃つようにする
###追尾してくる弾を撃つ
発射してから1.0秒間だけプレイヤーに向けて進む弾
ボスからプレイヤーへのベクトル = ボスBのposition - プレイヤーのposition
これを正規化したものを6倍することによって速度を保っている
public void Track(Transform t) {
Vector2 vec = player.transform.position - t.position;
rb.velocity = vec.normalized*6;
}
簡単によけられてしまうかもしれないけどできた!
###行動パターン
基本的にはボスAと同じ
(移動×3回 → 通常攻撃) × 5回 → 乱数で攻撃1か攻撃2のどちらかを選択し攻撃する
##ボスC
ボスCの特徴として2つのシールドを破壊しないと本体には攻撃が通らないようになっている
シールドは2つ破壊されてから60秒経つと復活する
###瞬間移動した後にプレイヤーに向けた弾を撃つ(5回くらい)
瞬間移動はtransform.Translateで移動する
(瞬間移動 → 攻撃) ×5 → 0.5秒静止
ここはあまりかからなかった
この動画だと瞬間移動しないけど本来ちゃんとする
###5回程度瞬間移動して弾を置いていき、移動し終わったらプレイヤー方向に弾を飛ばす
弾を置いたらプレイヤーの向きになるようにする
瞬間移動は上の攻撃1と同じ
移動先に弾を設置して1.0秒経ったらそれぞれの弾がプレイヤーに向かって飛んでいく
speed=3.0f;
//プレイヤーまでの方向ベクトルを取得
Vector3 dir = player.transform.position - transform.position;
//角度を取得
float angle = Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg;
Vector3 euler = new Vector3(0, 0, angle + angleOffset);
transform.rotation = Quaternion.Euler(euler);
rb.velocity = dir.normalized * speed;
上は現在地からプレイヤーまでの向きを求めてその向きに進むスクリプト
Mathf.Atan2で現在地からプレイヤーの位置までの角度をラジアンで表している
Mathf.Rad2Degはラジアンから度に変換している
角度の表し方は右を0度として半時計周りに増えていく、追跡する弾は最初真下を向いているので90度プラスすることで基準の方向を0度の方向にする必要がある
ここの説明は以下を参照したほうが分かりやすい
Hatena Blog/あるオブジェクトを指定の方向に向ける
rotationは度ではなくてQuaternionという構造で表されているのでVector3のeulerをQuaternion.Eulerで変換することで向きを変える
このようになった
###瞬間移動で爆弾を置いていき爆弾が爆発すると3方向に弾が飛んでいく
瞬間移動と爆弾を置く操作は上の攻撃と同じ
置かれた爆弾は1.0秒後に3方向に分散する弾を出す
###行動パターン
(瞬間移動 → 通常攻撃) × (2つのオブジェクトが壊れるまで) → 乱数で攻撃1か攻撃2か攻撃3のどちらかを選択し攻撃する → 時間経過(60秒)したら2つのオブジェクトが復活してループ
##ボスD
###分散攻撃を連続でやってくる
分散攻撃は以前のものを利用するが、分散の中央の弾がプレイヤーの方向になるようにする
分散の数はランダムで3か5になるようにした
###らせん状に弾が飛んでいく
この攻撃も分散攻撃を改良していく
分散数を300などにしてそれぞれの弾の間隔も小さくして、次の弾を発射するまでの待機時間を0.02秒にした
これを実装するためにこの攻撃は関数ではなくてコルーチンを用いた
//らせん状に弾が飛んでいく
IEnumerator Attack14(int c) {
for(int i = 1; i < c; i++) {
float angle = -(c + 1) * 10 / 2 + i * 10;
NwayShot(angle);
yield return new WaitForSeconds(0.02f);
}
AttackFlag = 0;
}
それっぽくなって結構感動した
てか分散攻撃が汎用性高すぎて驚いてる
###弾を発射したら数秒後に2段階弾が分裂する
弾を発射した後に少し時間経過したら分裂させたかったのでコルーチンを採用した
Instantiateで作成したインスタンスを変数として保持しておき、waitForSecondsで待機させた後に分裂させた
分裂先の方向も7方向からランダムにすることで分裂できる範囲を広くさせた
//弾を発射したら数秒後に2段階分裂する
IEnumerator Attack15(int NwayCount) {
//最初の弾(p1として保持)
float r1 = Random.Range(1, NwayCount + 1);
float angle1 = -(NwayCount + 1) * 5 / 2 + r1 * 5;
GameObject p = Instantiate(BossProjectilePrefab_slow2, transform.position, Quaternion.Euler(new Vector3(0.0f, 0.0f, angle1)));
AudioSource.PlayClipAtPoint(BossShotSE, transform.position);
yield return new WaitForSeconds(0.2f);
//最初の弾からの1段階の分裂(分裂した弾をp2として保持)
float r2 = Random.Range(1, NwayCount + 1);
float angle2 = -(NwayCount + 1) * 5 / 2 + r2 * 5;
GameObject p2=Instantiate(BossProjectilePrefab_slow2, p.transform.position, Quaternion.Euler(new Vector3(0.0f, 0.0f, angle2)));
AudioSource.PlayClipAtPoint(BossShotSE, transform.position);
yield return new WaitForSeconds(0.5f);
//1段階で分裂したそれぞれの弾が分裂(p1とp2からそれぞれ分裂)
float r3 = Random.Range(1, NwayCount + 1);
float angle3 = -(NwayCount + 1) * 5 / 2 + r3 * 5;
Instantiate(BossProjectilePrefab_slow2, p.transform.position, Quaternion.Euler(new Vector3(0.0f, 0.0f, angle3)));
AudioSource.PlayClipAtPoint(BossShotSE, transform.position);
float r4 = Random.Range(1, NwayCount + 1);
float angle4 = -(NwayCount + 1) * 5 / 2 + r4 * 5;
Instantiate(BossProjectilePrefab_slow2, p2.transform.position, Quaternion.Euler(new Vector3(0.0f, 0.0f, angle4)));
AudioSource.PlayClipAtPoint(BossShotSE, transform.position);
}
###行動パターン
(移動×3回 → 通常攻撃) × → 乱数で攻撃1か攻撃2か攻撃3のどちらかを選択し攻撃する
##ラスボス
今までのボスの攻撃を組み合わせたりして改良したものが多いので、説明を省くものが多い
###正面にビームを撃つ & ランダムの方向に攻撃する
ビームはボスAのビームを利用して、同時にランダムの方向への攻撃をするようにした
これは既存のものを組み合わせただけなので説明は省く
###分身を出してそれぞれの分身は固定の位置で攻撃してくる
これもボスBの攻撃において分身の数を4体にしただけ
###瞬間移動で弾を置いていき、少し経つとプレイヤーに向けて弾が飛んでいく
ボスCの攻撃2を利用して画面の右側と左側に弾を設置して時間が経つと、それぞれの弾がプレイヤーの方向に向かっていき途中で分裂する
弾が分裂するのもボスDの攻撃3を用いて、発射された弾に対してそれぞれ適用した
###全方位の弾幕を2つ出す
ボスDのらせん状の弾幕を2つに増やした
###瞬間移動で爆弾を置いていき、移動し終わったら弾を撃ち、弾も途中で分裂する
ボスCの攻撃3を利用して画面の左右に爆弾を置き、それぞれから弾を発射したときにその弾に対してボスDの攻撃3の弾が分裂する動作を適用した
これですべての実装が終わった
このゲームが最初に作ったゲームになるが、最初とはいえ2Dのゲームでもこんなに作るのが大変だとは思わなかった
次は3Dのゲームで何か作れればと思っている