ねのぷろ! ~げーむ・ぷろぐらみんぐ・ぶろぐ〜

プログラミングやゲームについてだらだら書きます。https://twitter.com/nenowawon

動画 You Tubeチャンネル移動

こんばんは。ネノワヲンです。 この度、新ゲームを作成することにしました。

進捗をYou Tubeに上げていこうかと思ったのですが、 アカウントの情報を忘れてしまったのでチャンネル移動することにしました。

↓新チャンネル

www.youtube.com

新ゲームの最初の動画を近々公開予定です。 それではまた。

Unity マルチシーン シーンを動的に読み込む

こんにちは。ネノワヲンです。今回は

nenowawon.hatenablog.com

の続きになります。

初めに

実行形式では、初めから複数のシーンを読み込んでおくことができません。

そのため、ゲームが起動してから必要なシーンを読み込む必要があります。

動的にシーンを追加する

SceneManagerのLoadSceneは、シーンの読み込み方法を選択することができます。

// そのシーンだけ読み込む(いつもの)
SceneManager.LoadScene(sceneIndex);
// ↑と同じ
SceneManager.LoadScene(sceneName, LoadSceneMode.Single);

// LoadSceneModeをAdditiveにするとシーン追加になる
SceneManager.LoadScene(sceneName, LoadSceneMode.Additive);

活用例

実際に私が使った手法です。

f:id:nenowawon:20190623145523p:plain
シーン名を登録しておく

メインとなるシーンに、↑のようにシーン名を登録しておきます。

Startで、登録しておいたシーンを追加で読み込みます。

private async void Start()
    {
        // サブシーンを読み込む
        foreach (var sceneName in subSceneNames)
        {
            await SceneController.AddSceneAsync(sceneName);
        }

        // ステージを読み込む
        await SceneController.AddSceneAsync(GameParameterManager.Instance.StageSceneName);

        // ステージのマップを読み込む
        await SceneController.AddSceneAsync(StageSceneManager.Instance.StageMapSceneName);

        // シーン読み込み完了イベントを発行
        OnLoadSceneCompleted.OnNext(Unit.Default);

        OnLoadSceneCompleted.Dispose();
    }

SceneControllerは自作クラスです。詳しくは

nenowawon.hatenablog.com

をご覧ください。やっていることは動的にシーンを追加しているだけです。

今回は、ゲームに必要なUI、ステージマップ、ステージの敵を組み合わせています。

このようにすることで、違うステージを使いたいときには読み込むシーンを変えることで対応できます。

f:id:nenowawon:20190623150630p:plain
必要に応じて差し替えたり追加できる

Unity 自作シーン遷移クラス

こんにちは。ネノワヲンです。 今回は、私が使っている自作シーン管理クラスを晒していこうと思います。

コード

using UnityEngine;
using UnityEngine.SceneManagement;

public class SceneController
{
    /// <summary>
    /// 指定したシーンに飛ぶ
    /// </summary>
    /// <param name="sceneName"></param>
    public static void JumpScene(string sceneName)
    {
        SceneManager.LoadScene(sceneName);
    }

    /// <summary>
    /// 指定したシーンに飛ぶ
    /// </summary>
    /// <param name="sceneIndex"></param>
    public static void JumpScene(int sceneIndex)
    {
        SceneManager.LoadScene(sceneIndex);
    }

    /// <summary>
    /// 指定したシーンを追加で生成
    /// </summary>
    /// <param name="sceneName"></param>
    public static void AddScene(string sceneName)
    {
        if (IsSceneLoaded(sceneName)) { return; }

        SceneManager.LoadScene(sceneName, LoadSceneMode.Additive);
    }

    /// <summary>
    /// 指定したシーンに飛ぶ(非同期)
    /// </summary>
    /// <param name="sceneName"></param>
    public static AsyncOperation JumpSceneAsync(string sceneName)
    {
        return SceneManager.LoadSceneAsync(sceneName);
    }

    /// <summary>
    /// 指定したシーンを追加で生成(非同期)
    /// </summary>
    /// <param name="sceneName"></param>
    public static AsyncOperation AddSceneAsync(string sceneName)
    {

        if (IsSceneLoaded(sceneName)) { return null; }

        return SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Additive);
    }

    /// <summary>
    /// 現在のシーンを読み直す(リトライ)
    /// </summary>
    public static void ReloadSceneAsync()
    {
        JumpScene(SceneManager.GetActiveScene().buildIndex);
    }

    /// <summary>
    /// そのシーンが読み込み済みかどうか調べる
    /// </summary>
    /// <param name="sceneName"></param>
    /// <returns></returns>
    public static bool IsSceneLoaded(string sceneName)
    {
        // 現在読み込まれているシーン数だけループ
        for (int i = 0; i < SceneManager.sceneCount; i++)
        {
            // 読み込み済みかどうか調べる
            if (SceneManager.GetSceneAt(i).name.Equals(sceneName))
            {
                Debug.LogWarning($"シーン名:{sceneName}は既に読み込まれています");
                return true;
            }
        }
        return false;
    }
}

機能

通常のシーン遷移、シーン追加があります。

また、非同期版ではこのように↓

// サブシーンを読み込む
foreach (var sceneName in subSceneNames)
{
        await SceneController.AddSceneAsync(sceneName);
}

// ステージを読み込む
await SceneController.AddSceneAsync(StageSceneName);

// ステージのマップを読み込む
await SceneController.AddSceneAsync(StageMapSceneName);

awaitでシーン読み込みを待機できます。

Unity Zenjectを用いたマルチシーン設計について

こんにちは。ネノワヲンです。

今回は、Zenjectを用いたマルチシーン設計についての話をしたいと思います。

Unityバージョン : 2018.4.2f1

目次

マルチシーン設計とは

Unityでは複数シーンを同時に読み込み、使用することができます。

今回は、シーンを1つの機能として使おう、というお話です。

何が嬉しいか

色々な場面で使いまわし、かつ他の機能と組み合わせて使うものと相性がいいです。

例えば、ゲーム中のUI。シーンを見ながらレイアウトを変更することができるうえ、そのUIを変更する場合はシーンを1つ変更すればよくなり、変更漏れなどが少なくなります。

Zenjectとは

github.com

簡単に言うと、あるクラスが持つ変数にインスタンスを入れてくれるライブラリです。

本来であればインスタンスを登録するのは自分でやる必要がありますが、Zenjectを使用することで自動化ができたりします。

やってみた

まず、1つのシーンのみで作ってみます。

f:id:nenowawon:20190616163119g:plain

今回は、プレイヤーの座標を画面に表示するシンプルなシーンを作りました。

f:id:nenowawon:20190616164425p:plain このように、必要なクラスをインスペクターから設定しています。

このシーンをマルチシーンに分割していきます。

マルチシーンにする

シーンを複数作成します。 今回はメインシーン以外のカメラやライトは必要ないので消します。

f:id:nenowawon:20190616164158p:plain
UIシーンを追加

UI用のオブジェクトをUIシーンに移します。

f:id:nenowawon:20190616170626p:plain
シーンが違うと参照できない!!
unityでは、実行時にしか他のシーンのオブジェクトを参照できません。

Zenjectを使用することで、この問題を解決できるようになります。

Zenjectをインストール

assetstore.unity.com Zenjectをアセットストアからダウンロードします。

SceneContextを作る

Zenjectでは、インジェクトをしてくれるコンテナとして、いくつかのContextが用意されています。 今回は、シーン単位でインジェクトを行うSceneContextを作成します。 f:id:nenowawon:20190616172324p:plain

ZenjectBinding

シーン内のコンポーネントを登録しておくことで、自動的にインジェクトを行ってくれるZenjectBindingという機能を使います。

f:id:nenowawon:20190616172838p:plain 登録したコンポーネントをインジェクトしてもらうために、変数に[Inject]属性を付けます。

/// <summary>
    /// プレイヤークラス
    /// </summary>
    [Inject]
    private PlayerProvider playerProvider;

f:id:nenowawon:20190616173449p:plain
Serializeフィールドなしでシーン上のオブジェクトを取得

[Inject]が付いたすべてのオブジェクトにインスタンスをインジェクトしてくれます。

また、コンポーネントは複数登録できます。 f:id:nenowawon:20190616173818p:plain

シーンを超えたBinding

SceneContextは親子関係を持つことができます。

子シーンは、親シーンのオブジェクトもインジェクトできます。

親シーンのcontextのContractNamesを設定します。

f:id:nenowawon:20190616174801p:plain
名前を設定

子シーンのcontextのParentContractNamesに親シーンのコンテキスト名を入れます。

f:id:nenowawon:20190616175120p:plain
シーン名ではないので注意
後の手順は同じです。
f:id:nenowawon:20190616175444p:plain
Mainシーンのオブジェクトを参照できている

活用例

追記: 続き書きました。

nenowawon.hatenablog.com

Unity Layerの管理について

こんにちは。ネノワヲンです。 今回は、Layer管理についてのお話です。

値の持ち方

レイヤーを、enumで管理しています。

   /// <summary>
    /// レイヤー名と番号
    /// </summary>
    public enum Layer
    {
        Ground = 8,
        Player = 9,
        HangObject = 10,
        AttackPoint = 11,
        Hook = 12,
        Enemy = 13,
        MiniMap = 14,
        PlayerFace = 15,
        Guide = 16,
        EnemyBase = 17,
        Goal = 18
    }

unityではレイヤーはintで使うので、直接intにできるenumだと管理がしやすいです。

LayerMaskの管理

/// <summary>
    /// フックの衝突レイヤー
    /// </summary>
    public const int hookCol = (1 << (int)Layer.Ground | 1 << (int)Layer.HangObject | 1 << (int)Layer.AttackPoint | 1 << (int)Layer.Enemy);

    /// <summary>
    /// カメラとの障害物判定のマスク
    /// </summary>
    public const int cameraBlockCheck = ~(1 << (int)Layer.Player | 1 << (int)Layer.Hook | 1 << (int)Layer.Enemy);

    /// <summary>
    /// 敵の本体の衝突レイヤー
    /// </summary>
    public const int enemyBaseCol = (1 << (int)Layer.Ground | 1 << (int)Layer.EnemyBase);

Raycastの際に使用するMaskの値もあらかじめ格納しておくことで、管理が楽になります。

C++ 衝突応答(めり込み防止)

こんばんは。ネノワヲンです。

やったこと

ソースコード

void Player::LateUpdate(float deltaTime)
{

    vector<RectangleCollider*> colliderList;

    // コライダーのリストを取得
    for (auto collider : DirectXRenderer::instance->m_ColliderList) {

        //テスト用オブジェクトにぶつかった場合
        if (collider->m_pGameObject->m_tag != Tag::TEST) { continue; }

        // 衝突判定をする
        if (!m_pCollider->CheckCollider(collider)) { continue; }

        // 判定するコライダーを格納する
        colliderList.emplace_back(collider);
    }

    XMFLOAT3 movePos = XMFLOAT3(0.0f, 0.0f, 0.0f);

    for (auto collider : colliderList) {
        // 衝突物の座標
        XMFLOAT3 checkColliderPos = collider->m_pGameObject->m_pTransform->m_pos;

        // 自分の頂点ごとのワールド座標
        RectangleVertex myRect = m_pCollider->GetRect(*m_pTransform);

        RectangleVertex collisionRect = collider->GetRect(*collider->m_pGameObject->m_pTransform);

        Transform transformTemp = *m_pTransform;

        bool isPositisionBackY = false;

        bool isPositisionBackX = false;

        // Y軸座標の移動があった場合
        if (m_CurrentMoveTemp.y > 0.0f || m_CurrentMoveTemp.y < 0.0f) {
            
            // Y軸座標を戻す
            transformTemp.m_pos.y -= m_CurrentMoveTemp.y;

            // 衝突判定をする
            if (!m_pCollider->CheckColliderTransform(collider, transformTemp)) {

                float moveYTemp = 0.0f;

                isPositisionBackY = true;

                OutputDebugString(_T("縦に戻す\n"));
            }
        }

        transformTemp = *m_pTransform;

        // X軸座標の移動があった場合
        if (m_CurrentMoveTemp.x > 0.0f || m_CurrentMoveTemp.x < 0.0f) {
            // X軸座標を戻す
            transformTemp.m_pos.x -= m_CurrentMoveTemp.x;

            // 衝突判定をする
            if (!m_pCollider->CheckColliderTransform(collider, transformTemp)) {

                float moveXTemp = 0.0f;

                isPositisionBackX = true;

                OutputDebugString(_T("横に戻す\n"));

            } // if
        } // if

        if (isPositisionBackX&&isPositisionBackY) {
            // 衝突したコライダーが他にある場合
            if (colliderList.size() > 0) {
                continue;
            }
        }

        // x座標を元に戻す
        if (isPositisionBackX) {
            movePos.x = -m_CurrentMoveTemp.x;
        }
        // y座標を元に戻す
        if (isPositisionBackY) {
            movePos.y = -m_CurrentMoveTemp.y;
        }

    } // for

    // 移動する
    Move(movePos);
}

今日はもう遅いので、概要に関しては改めて明日書こうと思います。

あとがき

f:id:nenowawon:20181121235128j:plain 情報を調べていたら自分のブログが出てくるという嬉しいのか悲しいのか分からない現象(嬉しい)