はじめに
今回は演出に関連する改善を行いました。
- ゲームタイトル(百鬼討伐録)の追加
- ゲーム開始画面の「TOUCH TO START」を点滅
- Waveごとに背景画像の切り替え
- 敵キャラ/ボス撃破時のエフェクトを追加
ここまでの成果
ゲームの仕様
新しく追加した仕様には「(新)」を付けています。
変更した仕様に「(変)」を付けています。
- プレイヤーHP設定:初期HPは3
- プレイヤーHP表示:画面右上に「HP:X」を常時表示する
- (新)ゲームタイトル表示:開始前画面にゲームタイトルを表示する
- (変)ゲーム開始:開始前に「TOUCH TO START」を点滅表示し、タップで開始する
- 敗北条件:HPが0になった時点で「GAME OVER TOUCH TO RESTART」UIを表示し、ゲームを停止する
- 操作方法:WASDキー または 十字キー(上下左右の移動)
- 攻撃方法:自動発射
- 敵キャラ実装数:100体(e001~e100)
- ボス実装数:9体(b001~b009)
- 敵キャラHP
- e001〜e010:HP 1 , e011〜e020:HP 10 , e021〜e030:HP 15 , e031〜e040:HP 20 , e041〜e050:HP 25 ,,,
- 敵キャラの移動:画面上から下方向に直進する
- スコア:画面左上に「Score:X」を常時表示する
- e001~e010:100点 , e011~e021:200点 , e021~e031:300点 ,,,
- (新)背景仕様:Wave開始時に、Waveに対応する背景画像へ切り替える(フェード演出あり)
- (新)撃破エフェクト:敵キャラ撃破時にエフェクトを表示する
- (新)撃破エフェクト:ボス撃破時にエフェクトを表示する
- (新)エフェクト一覧
- ステータス:アイテムの取得で強化できるステータス
- 弾速:上限無し
- 連射速度:発射間隔は0.05秒未満にならないように設計
- 攻撃力:上限無し
- 移動速度:最大10(初期2から上昇、上限あり)
- HP:上限は3
- アイテム一覧
- Waveの仕様
- Waveは 1~100 まで進行
- Waveは基本的に以下の流れ
- 敵キャラが湧くフェーズ
- Waveボスが湧くフェーズ
- Waveボスを倒すと次のWaveに進む
- 各Waveの撃破ノルマ:10+Wave×2(最大で100)
- 敵キャラの同時出現上限:5+(Wave/10)
- 各Waveの敵キャラの出現ID範囲:(Wave-5)~Wave
- Wave30の場合、敵キャラの出現IDの範囲は25~30
- 通常ボスを設定
- Wave番号の敵キャラが通常ボス
- 通常ボスのサイズ:敵キャラの2倍
- 通常ボスのHP:敵キャラHP×Wave数×5
- 特別ボスを設定
- 出現するWave:5の倍数のWave
- 出現するボスID:b00{Wave/5}
- ボスの基本HP:1
- ボスのHP倍率:基本HP×ボス回数×10
- (新)背景画像の仕様
- 47都道府県における各地域の特徴を加味した背景画像
- 地域ごとに背景画像を2枚づつ用意
- 東京、京都、大阪の主要都市は背景画像を3枚づつ用意
背景切り替え
Waveと背景画像を「1対1」で対応
Waveごとに雰囲気を変えるために背景画像を変えています。コードで「if(wave==….)」あるいは「switch(wave)」のようにするとコードが冗長になります。「Wave番号 → 背景Sprite」 のようにデータ化しています。
また、背景画像のファイル名一覧のCSVを作成しています。WaveBackgroundDataには、CSVか読み込んだファイル名を、backgroundSpritesのリストとして持たせ、Wave1ならリスト[0]、Wave2なら[1]… という対応で管理しています。
<背景データを作る主要素>
| カテゴリ | コード/アセット | 役割 |
| 背景リスト本体 | Assets/Scripts/Data/WaveBackgroundData.cs | Wave→Spriteの入れ物 |
| 背景設定アセット | Assets/WaveBackgroundConfig.asset | 実際に背景Spriteが並ぶリスト |
| CSV→アセット投入 | Assets/Scripts/Editor/BackgroundImporter.cs | CSVの「ファイル名」列からSpriteを読み込み、リストへ追加 |
<実際のコード(入れ物)>
// Assets/Scripts/Data/WaveBackgroundData.cs
[CreateAssetMenu(fileName = "WaveBackgroundData", menuName = "Game/Wave Background Data")]
public class WaveBackgroundData : ScriptableObject
{
public List backgroundSprites;
}
<実際のコード(CSVから反映)>
// Assets/Scripts/Editor/BackgroundImporter.cs
var fileName = cols[3].Trim(); // 4列目がファイル名
var sprite = Resources.Load("Sprites/backgrounds/" + fileName.Replace(".png", ""));
if (sprite != null) backgroundData.backgroundSprites.Add(sprite);
Waveごとに背景画像の切り替え
背景画像の切り替えは、「いつ切り替えるか」 と 「どう切り替えるか(演出)」を分離させています。
- 切り替えタイミング:WaveManager が「Wave開始」「Waveクリア→次Wave確定」のタイミングで呼ぶ
- 切り替え演出:BackgroundManager が「フェードで差し替える」処理だけを担当する
<背景切り替えの主要素>
| カテゴリ | コード内の要素 | 役割 |
| 切り替え指示 | WaveManager | Waveに応じて背景変更を呼ぶ |
| 背景演出 | BackgroundManager | フェードでSpriteを差し替える |
| 2枚構成 | currentRenderer / transitionRenderer | “パッ”と変わるのを防ぎ、前面をフェードさせる |
<実際のコード(切り替えタイミング)>
// Assets/Scripts/WaveManager.cs (Start)
if (BackgroundManager.Instance != null)
{
BackgroundManager.Instance.ChangeBackgroundForWave(1);
}
// Assets/Scripts/WaveManager.cs (WaveClearSequence 内)
int nextWave = currentWave + 1;
if (BackgroundManager.Instance != null)
{
BackgroundManager.Instance.ChangeBackgroundForWave(nextWave);
}
<実際のコード(フェードで切り替え)>
// Assets/Scripts/BackgroundManager.cs
public void ChangeBackgroundForWave(int waveIndex)
{
int dataIndex = waveIndex - 1;
Sprite nextSprite = backgroundData.backgroundSprites[dataIndex];
if (nextSprite != null && currentRenderer.sprite != nextSprite)
{
StartCoroutine(FadeRoutine(nextSprite));
}
}
private IEnumerator FadeRoutine(Sprite nextSprite)
{
transitionRenderer.sprite = nextSprite;
transitionRenderer.color = new Color(1, 1, 1, 0);
float timer = 0f;
while (timer < fadeDuration)
{
timer += Time.deltaTime;
float alpha = Mathf.Clamp01(timer / fadeDuration);
transitionRenderer.color = new Color(1, 1, 1, alpha);
yield return null;
}
currentRenderer.sprite = nextSprite;
transitionRenderer.color = new Color(1, 1, 1, 0);
transitionRenderer.gameObject.SetActive(false);
}
撃破時のエフェクト
敵とボスの撃破時に文字をエフェクトで表示させています。
- 敵撃破時のエフェクト:文字(滅)と小さな赤い丸がはじける(パーティクル)
- ボス撃破時のエフェクト:文字(封)のみ
<撃破エフェクトの主要素>
| カテゴリ | コード内の要素 | 役割 |
| 敵撃破 | EnemyController.Die() | “滅”生成+パーティクル生成+撃破数加算 |
| ボス撃破 | BossController.Die() | “封”生成+WaveManager.OnBossDefeated() |
| 文字エフェクト寿命 | OneShotSpriteEffect | 上昇+フェードアウト+時間でDestroy |
<実際のコード(敵:“滅”+パーティクル)>
// Assets/Scripts/EnemyController.cs
protected virtual void Die()
{
if (isDead) return;
isDead = true;
if (ScoreManager.Instance != null) ScoreManager.Instance.AddScore(scoreValue);
if (WaveManager.Instance != null)
{
WaveManager.Instance.AddZakoKill();
}
// 撃破エフェクト(ParticleSystem)
SpawnDeathEffect();
// 文字エフェクト("滅")
if (defeatTextPrefab != null)
{
Instantiate(defeatTextPrefab, transform.position, Quaternion.identity);
}
Destroy(gameObject);
}
// Assets/Scripts/EnemyController.cs
private void SpawnDeathEffect()
{
if (deathEffectPrefab == null) return;
var ps = Instantiate(deathEffectPrefab, transform.position, Quaternion.identity);
// 色合わせ(必要ならTintに追従)
Color c = matchSpriteColor && spriteRenderer != null ? spriteRenderer.color : effectColor;
var main = ps.main;
main.startColor = c;
ps.Play();
Destroy(ps.gameObject, main.startLifetime.constantMax + main.duration + 0.2f);
}
<実際のコード(ボス:“封”)>
// Assets/Scripts/BossController.cs
protected override void Die()
{
if (isDead) return;
isDead = true;
if (ScoreManager.Instance != null)
{
ScoreManager.Instance.AddScore(scoreValue * 2);
}
if (WaveManager.Instance != null)
{
WaveManager.Instance.OnBossDefeated();
}
// 文字エフェクト("封")
if (defeatTextPrefab != null)
{
Instantiate(defeatTextPrefab, transform.position, Quaternion.identity);
}
Destroy(gameObject);
}
<実際のコード(文字エフェクト:1回表示して消す)>
// Assets/Scripts/OneShotSpriteEffect.cs
void Update()
{
timer += Time.deltaTime;
transform.position += Vector3.up * moveSpeedY * Time.deltaTime;
if (fadeOut && spriteRenderer != null)
{
float alpha = Mathf.Lerp(startColor.a, 0f, timer / lifetime);
spriteRenderer.color = new Color(startColor.r, startColor.g, startColor.b, alpha);
}
if (timer >= lifetime)
{
Destroy(gameObject);
}
}
以上です。
コメント