ゴマちゃんフロンティア

気まぐれと勢いで作るUnityゲーム開発日記です。

【開発日記】ボス戦専用エリアの作成

time 2017/11/05

というわけで、今回は「ボス戦専用エリア」に関するお話になります!

敵を倒すタイプのアクションゲームには例外なく存在する要素であり、ステージの締めとしても重要な部分です。今回はボスとの戦闘前後の「エリアに入った時」や「ボス撃破後」の処理を考えてみます。

ちなみに登場させるボスの方をまともに作っていたりしますが、小物よりははっきりと大型のボスの方が好みです。
当初「草原と森」的な雰囲気のステージを序盤に据えようと思っていたので、「カブトムシっぽいもの」を作っていました。

…とてもカブトムシには見えないですが、本記事の説明用に何か欲しいので、とりあえずのハリボテとしてこいつを使います。

アクションゲームにおけるボスエリアについて

ほとんどのアクションゲームにおいて、ステージ最後に待ち構えるボスと対決する専用のエリアがあります。形状は様々ですが、共通するのは以下の点ではないでしょうか。

  • 入ると何かしらのイベントが発生しボスが登場
  • ↑と関連して、プレイヤーキャラクターがイベント位置まで自動的に移動
  • 一度入るとボスを倒すまで出られなくなる

特に「ボスを倒すまで出られなくなる」は重要です。敵前逃亡できてしまったら緊張感ないですよね。「マリオ」とか「ゼルダ」とかの一部ボス戦は出られるケースもありますが、その場合はボスの体力もリセットされて仕切り直しになることがほとんどです。
「ボスを倒した後の処理」も重要です。これを怠るとゲームとして成立しなくなります。

そのあたりを踏まえてレッツコーディング (`・ω・´)

入った際のイベント処理

エリアの入り口にBoxColliderを配置し、スクリプトのOnTriggerEnter()で判定します。
やることは「出入口を通れるようにする」「プレイヤーキャラクターの自動移動」「ボスの登場イベント実行」の3つです。これらを同時に実行するのは変なので、実行順序にも注意して組み立てます。
ちょっと長いですが、順番に説明していきます。

using DG.Tweening;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BossArea : MonoBehaviour
{
    public float fade_time;

    // エリアに入ったプレイヤーのオブジェクト
    protected GameObject enterPlayerObject;

    // プレイヤーの自動移動先
    public Transform eventPlayerMovePoint;

    // 登場させるボスのプレハブとオブジェクト
    public GameObject bossPrefab;
    protected GameObject boss;

    // ボスの出現位置
    public Transform bossAppearsPoint;

    public bool complete_player_movement;

    protected void OnTriggerEnter(Collider c)
    {
        if (c.gameObject.tag == "Player") {
            enterPlayerObject = c.gameObject;
            StartCoroutine(doEnterEvent());
        }
    }

    /// <summary>
    /// プレイヤーがボスエリアに入った際のイベント
    /// </summary>
    protected IEnumerator doEnterEvent()
    {
        IAutoMovement autoMovement = enterPlayerObject.GetComponent<IAutoMovement>();

        // プレイヤー移動を無効化
        enterPlayerObject.GetComponent<IPlayerCharacter>().switchCharacterMovement(false);

        // 塞ぐオブジェクトを非アクティブ化
        switchActiveBlockingObject(false);

        yield return new WaitForSeconds(fade_time);

        // 指定座標を超えたらtrueを返して止める
        yield return new WaitUntil(() => {
            autoMovement.onAutoMovementUpdate();
            return complete_player_movement;
        });

        // ボスの出現
        boss = Instantiate(bossObject, bossAppearsPoint.position, Quaternion.identity);
        BossEnemy bossEnemy = boss.GetComponent<BossEnemy>();
        StartCoroutine(bossEnemy.onInstantiate(this));
    }

    /// <summary>
    /// ボス登場イベント完了後に呼び出される関数
    /// </summary>
    public void completeBossAppearsEvent()
    {
        // プレイヤー移動を有効化
        enterPlayerObject.GetComponent<IPlayerCharacter>().switchCharacterMovement(true, true);
        switchActiveBlockingObject(true);
    }
}

最初に出入口を非アクティブにし、プレイヤーが通れるようにします。1秒ほどのフェード後に通れるようにするため、次の実行まではフェード時間分待機させます。(フェード処理は後述します)

プレイヤーの自動移動は以前紹介した記事がありますが、今回のケースでは使い物にならなかったので、新しく考え直してみました。
キモはWaitUntil()で、「関数内でtrueを返すまで待機する」という挙動ができるようになります。これとプレイヤーを自動的に一定速度で移動させる処理を組み合わせ、特定の位置まで移動するまで待機させます。
その「特定の位置に移動したか判定」には、移動先に適当なコライダーを置いて検知するのが確実です。このためだけにスクリプトを書くのは面倒ですが、我慢して記述します。

using UnityEngine;
[RequireComponent(typeof(BoxCollider))]
public class EventPlayerMovePoint : MonoBehaviour
{
    public void OnTriggerEnter(Collider c)
    {
        IAutoMovement autoMovement = c.GetComponent<IAutoMovement>();
        if (autoMovement != null) {
            BossArea bossArea = transform.GetComponentInParent<BossArea>();
            bossArea.complete_player_movement = true;
        }
    }
}

これでコライダーに触れるとcomplete_player_movementがtrueになり、待機が解除されます。

プレイヤーの移動完了に合わせてボスを登場させます。ここではStartCoroutine()BossEnemyというクラスのonInstantiate()を呼び出し、登場アクション完了後にcompleteBossAppearsEvent()を呼ばせます。onInstantiate()で自身の参照 (this) を渡しているのはそのためです。
登場アクション後はプレイヤーを操作可能にし、ボスもロジックに合わせた行動を行わせます。(今回は割愛します)

全部合わせるとこんな感じ。
キャプチャの関係で少しカメラからはみ出ていますが、雰囲気は伝わる…はず!

「入ったら出られなくなる」処理

「入った際のイベント」中に、エリアの出入口をオブジェクトでふさいでしまうのが手っ取り早いです。塞ぐためのオブジェクトがイベント中にアクティブとなる処理を加えます。

ただし実際には「塞ぐ箇所が1つとは限らない」ため、管理するための空オブジェクトを作成し、塞ぐオブジェクトを子として設定します。親オブジェクトはインスペクターに設定して参照しても問題ありませんが、ボスエリア用オブジェクトから見た子オブジェクトなので、Transform.Find()でさくっと取得してしまいます。

スクリプトからforeachで子オブジェクトのアクティブを順次切り替えていきます。ただ切り替えるだけでは見栄えがよろしくないので、フェードインも入れてみました。
今回DOTweenを使用したのは、フェード完了後に実行する関数をラムダ式で記述できるためです。iTweenで同じことをやる場合、oncompletetargetに文字列で関数名を指定した上で、その関数を定義しなければならないため冗長な感じです。同一オブジェクト内で完結させたい場合はDOTweenの方がスマートに書けます。

/// <summary>
/// エリアを塞ぐオブジェクトの有効/無効を切り替える
/// </summary>
protected void switchActiveBlockingObject(bool flag)
{
    Transform BlockingTransforms = transform.Find("BlockingObjects");

    foreach (Transform BlockingTransform in BlockingTransforms) {
        GameObject blockingObject = BlockingTransform.gameObject;
        Material material = blockingObject.GetComponent<Renderer>().material;

        Sequence sequence = DOTween.Sequence();
        sequence.OnComplete(() => blockingObject.GetComponent<Collider>().enabled = flag);

        sequence.Append(
            DOTween.ToAlpha(
                () => material.color,
                c => material.color = c,
                flag ? 1f : 0,
                fade_time
            )
        );

        sequence.Play();
    }
}

また、部屋状になっていないエリアの場合は「見えない(=Rendererを外した)オブジェクト」を塞ぐ用として使用しましょう。周りが崖になっている場合は不要かもしれませんが、カメラ外に飛び出してしまわないように設定しておくと安心かも。

「ボスを倒すと出られるようになる」処理

単にボスの参照を取得しておき、体力がなくなったらふさいでいるオブジェクトを非アクティブにするだけ。入る際と同様の処理なので簡単です。
ボス制御用クラスから呼び出すための関数を用意しておきます。

/// <summary>
/// ボス死亡時に呼び出される関数
/// </summary>
public void onDiedBoss()
{
    switchActiveBlockingObject(false);
}

「出られるようになる」とは言いますが、ゲームによってはそのままステージクリアかもしれないですね。

あとがき

そんなわけで、開発中のゲームの「ボス専用エリア周りの処理」について紹介しました。
この手の「順番に処理を行っていく」系はよくありますが、スクリプトで書こうとすると面倒だったりします。コルーチンやDOTweenのSequenceとかを上手く利用してテンポよく作っていきたいところです。

スポンサーリンク

down

コメントする



ツイッター