Unityで会話やクエスト受注を実装するアセットの紹介

はじめに

Asset Storeから会話の処理を実装する「Dialogue System for Unity」というアセットを購入したので、その使い方を書きました。
使い方の部分の内容はほとんど公式クイックスタートの和訳のようなものなので、英語に自信がある人は公式をどうぞ。
目次を見るとステップの数が多くて大変そうに感じるかもしれませんが、各ステップはそれぞれ数秒~1分程で終わるくらいの分量なので意外と簡単に試せると思います。

目次

Dialogue System for Unityとは

Dialogue System for Unity」はアセットストアから購入可能な有料アセットです。

www.youtube.com

この動画のように様々な仕様の会話やクエストを作ることが出来ます。 どんなアセットなのか知りたい場合にはこちらも参考になります。

通常時の価格:65ドル、Plus/Proの割引価格:52ドル
現在行われているのマッドネスセール中なら32.5ドルで購入できます。(5月16日まで)

Dialogue System for Unityの使い方

Dialogue System for Unityはクイックスタートが用意されているのですが、英語のみの様なので、ここではその内容を書いていきます。(特別和訳が得意なわけではないので、訳が正確ではない部分もあるかもしれません。あらかじめご了承ください。)

クイックスタートのセクションでは次の内容を学べるそうです。

  • ダイアログデータベースを作成し、会話を書き込む
  • ダイアログ管理のゲームオブジェクトのセットアップ
  • 会話の始め方
  • オブジェクト同士の相互作用のさせ方
  • 会話中はプレイヤーの操作を無効にする方法

さらに細かいステップについてはクイックスタート以降のセクションで見ることが出来ます。

Step1 シーンの作成

新規シーンを作成する(File→New Scene)

f:id:ItsukiNamito:20190502231049p:plain

Step2 ダイアログマネージャをシーンに配置する

Assets/Plugins/Pixel Crushers/Dialogue System/Prefabs内にある”Dialogue Manager”をシーンにドラッグする

f:id:ItsukiNamito:20190502231830p:plain

Step3 ダイアログデータベースの作成

このゲームオブジェクトは既にさしあたり十分な対話のUIを用意してくれますが、会話の中身となるダイアログデータベースを作成する必要があります。

先ほどのゲームオブジェクトを選択し、インスペクターからInitial Databeフィールドの隣にある”Create”ボタンをクリックします。

f:id:ItsukiNamito:20190503120438p:plain

Step4 ダイアログエディターを開く

Step3で作成したダイアログデータベースに名前を付けたら、再度ダイアログマネージャーを選択します。

インスペクターを見るとStep3で”Create”だったボタンが”Edit”となっているのでそれをクリックしてダイアログエディターを開きます。
その左上にあるDialogue Systemのロゴをクリックしてもダイアログエディターを開くことができます。

Step5 会話の追加

ダイアログエディターの”Conversations”タブをクリックする

  • ”+”ボタンをクリックして新しい会話を追加する。
  • オレンジ色のStartノードで右クリックをして”Create Child Node"を選ぶ。インスペクターの”Dialogue Text”のフィールドに「Hello.」と書き込む。(ちゃんと”Dialogue Text”に書き込んでいるか”Title”などの他のフィールドに書き込んでいないか確認してください。)
  • ダイアログエディターに戻り、「Hello.」と書かれたグレーのノードを右クリックし、再度”Create Child Node”を選んでください。今度はDialogue Textフィールドに「Goodbye.」と書き込みます。
  • グレーのノードはNPCによって話されたもので、青いノードはプレイヤーによるものです。

以上を行うと下の図のようになっているはずです。

f:id:ItsukiNamito:20190503173402p:plain

Step6 シーンにプレイヤーとNPCを配置する

プレイヤーとして空のゲームオブジェクトを作成します。(GameObject→Create Empty)
NPCとしてキューブを作成します。(GameObject→3D Object→Cube)
空のゲームオブジェクトは"Player"、キューブは"NPC"へと名前を変更し、positionを(0,0,0)にセットしてゲームウィンドウで見えるようにします。(キューブでなくて自分のキャラクターを代わりに使っても構いません)

Step7 NPCにトリガーを追加する

Step6で作成したNPCを選択し、インスペクターから”Dialogue System Trigger"コンポーネントを追加します。

f:id:ItsukiNamito:20190503174532p:plain

Step8 NPCにアクションを追加する

Step7で追加した”Dialogue System Trigger"の”Actions”から”Add Action"をクリックし、”Start Conversation"を選択する。

f:id:ItsukiNamito:20190503175003p:plain

Step9 会話内容とその会話を行う役者の設定

"Conversation"のドロップダウンからStep5で作成していた会話を選択します。
"Conversation Actor"の項目に"Player"オブジェクトを、"Conversation Conversant"の項目に”NPC”オブジェクトを割り当てます。

f:id:ItsukiNamito:20190503180519p:plain

Step10 シーン起動時にトリガーが働くようにする

最後に”Trigger"のドロップダウンを”On Start"をセットします。
こうすることで、トリガーはシーン起動時にアクションが働くようになります。

f:id:ItsukiNamito:20190503181013p:plain

Step11 実行して確認する

エディタ上部のプレイボタンを押して会話を確認してみましょう!

ダイアログシステムはNPCの「Hello.」というセリフを表示し、その少し後にプレイヤーの反応のメニューを表示します。(メニューには「Goodbye.」という選択肢が含まれています。)
これはテンプレートとなる標準のUIで、後から自分好みの見た目にカスタマイズすることが出来ます。

f:id:ItsukiNamito:20190503192317g:plain

次は会話をゲームプレイの中で開始させる方法を紹介します。

Step12 トリガーを”On Use”に切り替える

”Dialogue System Trigger"コンポーネントの”Trigger"のドロップダウンを”On Use"に戻します。
こうすることでトリガーは”On Use”メッセージを受け取った時にアクションが働くようになります。(この”On Use"メッセージは一般的にはプレイヤーのインタラクションを担うコンポーネントから呼ばれます。)

f:id:ItsukiNamito:20190503190423p:plain

Step13 Usableコンポーネントで話しかけられる距離を設定する

NPCに”Usable”コンポーネントを追加します。
こうすることでプレイヤーのインタラクションを担うコンポーネントNPCが”Usable"(使用可能)であることを教えることが出来ます。
(インタラクションの元となる)カメラの位置が5以上離れているため、"Max Use Distance"フィールドに30を設定します。

f:id:ItsukiNamito:20190503191211p:plain

Step14 プレイヤーにSelectorコンポーネントを追加する。

プレイヤーに”Selector”コンポーネントを追加し、”Select At"のドロップダウンを”Mouse Position"にセットします。

f:id:ItsukiNamito:20190503191814p:plain

Step15 実行してマウスによって会話が出来ることを確認する

そうしたら再度プレイボタンを押してシーンを起動します。
マウスカーソルをキューブの上に移動させると次の図のような黄色いメッセージが表示されます。
f:id:ItsukiNamito:20190503212318p:plain

これはプレイヤーのSelectorコンポーネントNPCのUsableコンポーネントを検出したという意味です。
この状態でスペースキーを押すか、マウスを右クリックすることでSelectorコンポーネントNPCに対して”OnUse”メッセージを送り、トリガーは会話を開始させます。

この”Selector”コンポーネント、そして似たコンポーネントである”Proximity Selector"コンポーネントはダイアログシステムを動作させる手段を提供します。

これらはとても拡張性が高く、マウスカーソルに限らず、他の様々な方法でオブジェクトを選択し会話が出来るようにカスタマイズすることが出来ます。
またSelector系のコンポーネントを使わず、好みで自作のインタラクションシステムやC#のコードからトリガーを実行することもできます。

Step16 会話中に余計な処理を止める

会話中にSelectorコンポーネントを止めることもできます。
往々にして会話中にはSelectorコンポーネントやプレイヤーの移動やカメラ制御のような処理を止めたくなるでしょう。

その一つとして”Dialogue System Events"コンポーネントを追加する方法があります。このコンポーネントはダイアログシステムの活動によって発生する様々なイベントを用意しています。

下のgifでは”On Conversation Start"イベント発生時(会話開始時)にSelectorコンポーネントを無効にし、”On Conversation End"イベント発生時(会話終了時)にSelectorコンポーネントを再度有効にしています。

f:id:ItsukiNamito:20190503223029g:plain

参考になりそうなサイト

www.asset-sale.net

おわりに

このクイックスタートを見ればわかるようにプログラミング無しでもある程度の会話システムを作れますし、拡張性も高そうなので個人的にはおススメできる十分値段分の価値のあるアセットだと感じました!

今自主制作しているゲームでも使っていくつもりなので今後見た目の変え方などの記事を書くこともあるかもしれません。

おまけ

今回の記事を書くにあたってStep3までのスクショはPrtScを使ってペイントで文字などを入れていたのですが、めんどくさいので調べていたらこんな記事を見つけました。

tsubakit1.hateblo.jp

Step5からは矢印や文字の感じがいい感じになっているのがわかると思いますが、上の記事で紹介されているMonosnapを使用しています。
ペイントを使う場合と異なり、自分の好きな範囲でスクショが撮れて矢印や文字を入れる手間も少なく、綺麗に仕上がるのでお勧めです。

ちなみにStep11や16にあるような動画もこのMonosnapで録画することが出来ます。(gifにするためにAfter EffectとPhotoshopは使ってますが…)

Unityでのサウンド処理のプログラム

はじめに

いつもUnityのサウンド関係は後回しにしがちで、しかも使い方を忘れて実装のたびに調べているので備忘録を兼ねてブログでまとめておこうと思いました。 サウンドプログラムに関して特に詳しいわけではないのですが、たぶん一週間ゲームジャムくらいであればこのくらいでも足りるひとは多いのではないかと思います。 プログラムはコピペすればDOTweenを入れたりAudio Mixerの設定をしたりしても10分をかからないくらいで使えるんじゃないかと思うので、よかったら使ってみてください。

追記

プログラムの内容は適宜修正や使い方の注意のコメントを加えていく予定です。

目次

サウンド処理のプログラム全文

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

public class SoundController : MonoBehaviour
{
    private static SoundController instance;
    public static SoundController Instance
    {
        get { return instance; }
        private set { instance = value; }
    }

    AudioSource[] audioSources = new AudioSource[2];

    // Audio Mixerとそのグループ
    [SerializeField]
    AudioMixer audioMixer;
    [SerializeField]
    AudioMixerGroup musicMixerGroup;
    [SerializeField]
    AudioMixerGroup seMixerGroup;

    private void Awake()
    {
        if (instance == null)
        {
            instance = this;
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            // 既にSoundControllerが存在するなら破棄する
            Destroy(this);
            return;
        }

        audioSources = GetComponents<AudioSource>();
        foreach(AudioSource a in audioSources)
        {
            a.outputAudioMixerGroup = musicMixerGroup;
        }
    }


    [SerializeField]
    float fadeDuration = 1.0f;              // フェードに掛ける時間

    int currentIndex = 0;                   // 現在使っているAudioSourceの番号
    bool isPlayingMusic = false;            // 現在音楽を再生しているか

    public void PlayMusic(AudioClip nextMusic, bool looping = true)
    {
        if (isPlayingMusic)
        {
            // 現在流れている音楽を消しつつ、次の音楽を再生する
            StartCoroutine(StopMusic(currentIndex));
            currentIndex = 1 - currentIndex; // 使うAudioSourceを入れ替える
            audioSources[currentIndex].clip = nextMusic;    // 次の音楽を設定
            audioSources[currentIndex].volume = 0.0f;       // 次の音楽のボリュームを0にする
            audioSources[currentIndex].DOFade(1.0f, fadeDuration);  // 次の音楽のボリュームをだんだん大きくする
            audioSources[currentIndex].Play();
        }
        else
        {
            currentIndex = 0;   // 0番目のAudioSourceを使う
            audioSources[currentIndex].clip = nextMusic;
            audioSources[currentIndex].volume = 1.0f;
            audioSources[currentIndex].Play();
        }

        isPlayingMusic = true;
    }

    // 指定されたIndexのAudioSourceを止める
    private IEnumerator StopMusic(int sourceIndex)
    {
        audioSources[sourceIndex].DOFade(0.0f, fadeDuration);
        yield return new WaitForSeconds(fadeDuration);
        audioSources[sourceIndex].Stop(); // もしfadeDuration以内の時間で何回も音楽を切り替える場合はここはコメントアウトするか、フェード中に音楽が切り替わったらコルーチンを止める処理を入れてください。
    }

    // 音楽を完全に止める
    public void StopAllMusic()
    {
        StartCoroutine(StopMusic(0));
        StartCoroutine(StopMusic(1));

        isPlayingMusic = false;
    }

    // 効果音を再生する
    public void PlaySE(AudioClip se, float lifetime = 10.0f)
    {
        GameObject seObject = new GameObject();
        seObject.AddComponent<AudioSource>();

        seObject.GetComponent<AudioSource>().outputAudioMixerGroup = seMixerGroup;
        seObject.GetComponent<AudioSource>().volume = 1.0f;
        seObject.GetComponent<AudioSource>().PlayOneShot(se);

        // 一定時間後に消滅するようにする
        Destroy(seObject, lifetime);
    }

    #region ボリューム関係

    public float musicVolume = 0.5f;     // 音楽のボリューム
    public float soundEffectVolume = 0.5f;        // 効果音のボリューム
    // 音楽のボリュームをオーディオミキサーに設定する
    public void SetMusicVolume(float value)
    {
        musicVolume = value;
        float decibel = 20.0f * Mathf.Log10(musicVolume);

        if (float.IsNegativeInfinity(decibel))
            decibel = -96f;

        audioMixer.SetFloat("MusicVolume", decibel);
    }

    // 効果音のボリュームをオーディオミキサーに設定する
    public void SetSoundEffectVolume(float value)
    {
        soundEffectVolume = value;
        float decibel = 20.0f * Mathf.Log10(soundEffectVolume);

        if (float.IsNegativeInfinity(decibel))
            decibel = -96f;

        audioMixer.SetFloat("SoundEffectVolume", decibel);
    }

    #endregion

    #region 色んな所で使う効果音などはSoundControllerで直接呼び出せるようにする。

    // クリックの効果音
    [SerializeField]
    AudioClip clickSE;
    public void PlayClickSE()
    {
        PlaySE(clickSE);
    }
    #endregion
}

使い方

1.DOTweenの導入

まずUnityのAsset StoreよりDOTweenというアセットをインポートします。 僕は有料版のDOTween Proを使用していますが、恐らく無料版でもこのプログラムに関しては問題ないと思いますので、そちらのURLを張っておきます。

assetstore.unity.com

2.SoundControllerクラスの作成

プロジェクトウィンドウから右クリック>Create>C# Scriptを選択し、SoundControllerという名前を付けてください。(もっといい名前があれば変えて結構です) 次にこのブログの”サウンド処理のプログラム全文”にあるスクリプトを全部コピーしたら、先ほど作成したSoundCotrollerをダブルクリックしてエディタで開き、Ctrl+AからのCtrl+Vでペーストしてください。

3.最初のシーンにSoundControllerコンポーネントを付けたオブジェクトを用意する

ゲームを開始したときに一番最初に読み込まれるシーン(タイトルなど)を開きます。 HierarchyウィンドウからCreate Emptyで空のゲームオブジェクトを作成し、Add Componentから先ほどのSoundControllerを1つ、AudioSourceを2つアタッチしてください。

f:id:ItsukiNamito:20190328202641p:plain
こんな感じにしてください。

4.Audio Mixerを用意する

WindowメニューからAudio Mixerを選択するか、Ctrl+8でAudio Mixerウィンドウを開いてください。

Audio Mixerウィンドウから次のような作業をお願いします。
1.Mixers欄の右にあるプラスボタンをクリックして、新規のMixerを追加してください。名前はMain等でいいと思います。
2.Masterグループを選択した状態でGroup欄の右にあるクリックを押してMusicグループとSEグループをMasterグループ直下に作成してください。(Musicグループ下にSEグループを作らないように気を付けてください。)MusicグループはBGMグループなど自分の分かりやすい名前を付けて構いません。
3.Musicグループを選択し、InspectorウィンドウのAttenuationのすぐ下にあるVolumeの文字の上で右クリックをし、「Expose 'Volume (of Music)' to script」クリックします。これでMusicのボリュームをスクリプトから変更できるようになったのですが、デフォルトの名前がMyExposedParamになっているので、Audio Mixerウィンドウの右上のExposed Parametersから名前をMusicVolumeなど分かりやすいように変更しておきましょう。
4.最後にSEグループを選び、Inspectorウィンドウから「Add Effect」ボタンをクリックして「Normalize」エフェクトを選びましょう。これで同時に沢山の効果音が鳴らされても、音割れがしなくなります。(たぶん)

f:id:ItsukiNamito:20190328205204p:plain
文字が小さくて見えない場合はクリックすると拡大画像が表示されます。

f:id:ItsukiNamito:20190328213017p:plain

5.SoundControllerコンポーネントに必要な情報を割り当てる

Projectウィンドウを開くとMainミキサーアセットがあります。これのトグルを開きます。 3の手順で用意したSoundControllerの付いたゲームオブジェクトをクリックし、ドラッグアンドドロップで下の図のように割り当てます。

f:id:ItsukiNamito:20190328214903p:plain
Projectウィンドウから赤線でつながっているInspectorウィンドウの項目へドラッグアンドドラッグしてください。

Sound Controllerの一番下にあるClick SEと書いてある場所にはボタンをクリックしたときの効果音のAudioClipを設定してください。

ここまでで準備は終わりです。

6.自分のスクリプトからSoundControllerのメソッドを呼び出す

後は自分のスクリプトからBGMを流したいタイミングでSoundController.Instance.PlayMusic(流したいBGMのAudioClip);を呼び出し、効果音を鳴らしたいタイミングでSoundController.Instance.PlaySE(流したい効果音のAudioClip);を呼び出せば使うことが出来ます。もしBGMを止めたい場合はSoundController.Instance.StopAllMusic();で止めることが出来ます。

BGMを流す簡単な例を書いておきます。

public class TitleManager : MonoBehavior
{
 public AudioClip titleBGM;
 void Start()
 {
  SoundController.Instance.PlayMusic(titleBGM);
 }
}

プログラムの解説(興味があれば読んでください)

private static SoundController instance;
public static SoundController Instance
{
 get { return instance; }
 private set { instance = value; }
}

private void Awake()
{
 if(instance == null)
 {
  instance = this;
  DontDestroyOnLoad(gameObject);
 }
 else
 {
  Destroy(this);
  return;
 }
~
}

SoundControllerはシングルトン的な使い方をしたかったので、instanceに唯一のインスタンスを保持しておき、void Awake()のタイミングで既にinstanceにインスタンスがあれば、自分は2つ目以降なので破棄、そうでなければ自分をinstanceに保持させています。もしSingletonMonobehaviorクラスなどを自作している場合には適宜書き換えて使ってください。
この作りの場合は音を鳴らしたい場合このSoundControllerを配置してあるシーンからテストする必要があります。もし、ゲームシーンの音の確認の為にタイトルから経由してくるのが大変な場合はこちらのサイトなどを参考に、Instance参照時にGameObjectの生成、SoundController,AudioSource2つのAddComponentなどを行う処理書いてください。

    int currentIndex = 0;                   // 現在使っているAudioSourceの番号
    bool isPlayingMusic = false;            // 現在音楽を再生しているか

    public void PlayMusic(AudioClip nextMusic, bool looping = true)
    {
        if (isPlayingMusic)
        {
            // 現在流れている音楽を消しつつ、次の音楽を再生する
            StartCoroutine(StopMusic(currentIndex));
            currentIndex = Mathf.Abs(currentIndex - 1); // 使うAudioSourceを入れ替える
            audioSources[currentIndex].clip = nextMusic;    // 次の音楽を設定
            audioSources[currentIndex].volume = 0.0f;       // 次の音楽のボリュームを0にする
            audioSources[currentIndex].DOFade(1.0f, fadeDuration);  // 次の音楽のボリュームをだんだん大きくする
            audioSources[currentIndex].Play();
        }
        else
        {
            currentIndex = 0;   // 0番目のAudioSourceを使う
            audioSources[currentIndex].clip = nextMusic;
            audioSources[currentIndex].volume = 0.0f;
            audioSources[currentIndex].DOFade(1.0f, fadeDuration);
            audioSources[currentIndex].Play();
        }

        isPlayingMusic = true;
    }

このSoundControllerでは音楽を切り替える際に、現在流れているBGMのボリュームを少しずつ小さくし、次に流れるBGMのボリュームを少しづつ大きくします。 クロスフェードっていう処理らしいです。 準備の段階でAudioSourceを二つ追加してもらった理由はこのBGMの切り替え時に前のBGMと次のBGMの両方が流れているタイミングがあるためです。

    // 指定されたIndexのAudioSourceを止める
    private IEnumerator StopMusic(int sourceIndex)
    {
        audioSources[sourceIndex].DOFade(0.0f, fadeDuration);
        yield return new WaitForSeconds(fadeDuration);
        audioSources[sourceIndex].Stop();
    }

sourceIndexで指定された方のAudioSourceを停止させます。この際フェード終了後にStopを呼んでいるため、フェード中にさらに音楽を切り替えると音楽が鳴らなくなってしまいます。
頻繁に流れる音楽が変わる場合、お手数ですが3行目のaudioSources[sourceIndex].Stop();はコメントアウトして使うか(ただこの場合常に2つAudioSourceが再生されている状態で、片方のボリュームが0だから一曲しか再生されていないように聞こえます。)、フェードアウト中に再生される場合はコルーチンを止める処理を別に描く必要があります。

    // 効果音を再生する
    public void PlaySE(AudioClip se, float lifetime = 10.0f)
    {
        GameObject seObject = new GameObject();
        seObject.AddComponent<AudioSource>();

        seObject.GetComponent<AudioSource>().outputAudioMixerGroup = seMixerGroup;
        seObject.GetComponent<AudioSource>().volume = 1.0f;
        seObject.GetComponent<AudioSource>().PlayOneShot(se);

        // 一定時間後に消滅するようにする
        Destroy(seObject, lifetime);
    }

効果音は先ほどの2つのAudio Sourceではなく新規にGameObjectを生成し、AudioSourceを追加して、そのAudioSourceから効果音を流させています。効果音が鳴り終わってしばらくした後(デフォルトでは10秒後)に自動的にそのGameObjectを破棄するようにしてあります。

    #region ボリューム関係

    public float musicVolume = 0.5f;     // 音楽のボリューム
    public float soundEffectVolume = 0.5f;        // 効果音のボリューム
    // 音楽のボリュームをオーディオミキサーに設定する
    public void SetMusicVolume(float value)
    {
        musicVolume = value;
        float decibel = 20.0f * Mathf.Log10(musicVolume);

        if (float.IsNegativeInfinity(decibel))
            decibel = -96f;

        audioMixer.SetFloat("MusicVolume", decibel);
    }

    // 効果音のボリュームをオーディオミキサーに設定する
    public void SetSoundEffectVolume(float value)
    {
        soundEffectVolume = value;
        float decibel = 20.0f * Mathf.Log10(soundEffectVolume);

        if (float.IsNegativeInfinity(decibel))
            decibel = -96f;

        audioMixer.SetFloat("SoundEffectVolume", decibel);
    }

    #endregion

これはコンフィグ画面でスライダーなどでボリュームを変更することを想定して用意しています。 SetMusicVolumeの引数がfloatになっていますが、ここに0~1の値を与えるとAudio Mixerのボリュームを変更してくれます。SetSoundEffectVolumeも同様です。 また、AudioMixerはボリュームがデシベルなので、0~1の値を変換する処理を行っています。

    #region 色んな所で使う効果音などはSoundControllerで直接呼び出せるようにする。

    // クリックの効果音
    [SerializeField]
    AudioClip clickSE;
    public void PlayClickSE()
    {
        PlaySE(clickSE);
    }
    #endregion

BGMや効果音を鳴らす際に基本的には鳴らす側で使いたいAudioClipを設定して、PlayMusicやPlaySEで呼び出すといった使い方を想定しています。 しかしクリック音のように色んな所から使われ、汎用的に呼び出される効果音に関してはSoundController側で設定するようにしてあります。こうすることでクリックの音を変えたいと思った時にいちいち鳴らしている場所を探してAudioClipを差し替えなくてもSoundControllerで一つ変えるだけで済みます。

参考にしたサイト

hecres.hatenablog.com

www.slideshare.net

おわりに

このプログラムはどなたでも使用していただいて構いません。音を付けたいけど一週間ゲームジャムで時間がないという場合や、Unity初心者でサウンドの処理をどうすればいいか分からないといった方に参考になればいいなと思っています。(僕もまだ初心者かもしれませんが…) また、サウンドの事分かるよ!という方など補足や訂正があればぜひ教えていただけるととても嬉しいです。

ちなみにこの内容では頻繁に音楽が切り替わる場合や3Dゲームなどで音の発生位置によって聞こえ方を変えるみたいなのには対応できないと思います。

おまけ

ブログの記事の最後で好きなものを宣伝することにしているのですが、今回はサウンド処理なので有名な方ですが一番好きな作曲家の浜渦正志さんについて紹介します。


SaGa Frontier II OST - Roman

残念ながら僕は音楽について表現する言葉は詳しくないのですが、浜渦さんの音楽はとても透明感が高くて(表現あってるのかな?)あまり激しくないので、リラックスするときや作業用BGMとしても疲れないのでとてもおススメです! たぶん浜渦さんの音楽で一番有名なのはFF13の閃光かな?


FF13 戦闘曲閃光 高音質

僕はこれらの音楽はCDやiTunesから購入して聞いてるのですがスクウェアエニックスゲーム音楽は買おうと思えば買えるものが多いのでとても助かっています。

自己紹介とこのブログについて

はじめに

はてなブログをはじめてみました。 知らない人に読んでもらった時のために、簡単な自己紹介とこのブログではどんなことを書いていくつもりなのかをこの記事では書いていこうと思います。

目次

自己紹介

2019年4月1日にゲーム会社にエンジニアとして入社しました。 現時点ではコンシューマ、アミューズメント、ソーシャルのどこに配属されるかは決まっていません。

ちなみにツイッターのアカウントなどで使用している伊月七三十は「いつき なみと」と読みます。
本名ではありませんが、一応本名を基にしています。

使用する言語はC,C++C#です。 普段はUnityを使ってゲームを作ることが多いですが、UE4DirectXを使うこともあります。 今のところ作ったゲームの一部はUnityRoomで公開しています。

unityroom.com

また2年くらい前にApp Storeでも作ったゲームを公開していたのですが、Appleのデバロッパプログラムのメンバーシップの有効期限が切れているのでたぶん今は利用できないと思います。

趣味はイラストや日本史、紅茶・お酒です。あと最近は競技プログラミングにハマっていて昨日ちょうどAtCoderで茶色になりました。いったん緑を目標にしています。

資格は応用情報とCGエンジニア検定 エキスパート、画像処理エンジニア検定 エキスパート、マルチメディア検定 エキスパートを持っています。

写真に嫌な思い出がありかなり苦手です。
カメラを向けられるだけでも強いストレスを感じるのですが、自意識過剰だと思われることも多く理解してもらえないことがあったり、言い出しにくい雰囲気を感じたりします。
なので、この自己紹介の記事を読んだら写真が本当に苦手な人もいるんだなと頭の片隅にでも入れておいてくだされば、僕のことは忘れても構いません。

一応自己紹介っぽい情報を書きましたが、他に気になることがあればコメントからでも気軽に質問してください。

このブログで提供する(かもしれない)情報

ブログにゲーム制作記なんてつけてしまいましたが、ゲーム制作に限らず創作活動やプログラミングなどの技術的な内容、ゲームへの考察について書く可能性はあります。
ただ、なんとなくゲーム制作をしている人に役に立つような情報を提供できる記事にしていきたいと考えているので、そういった人に興味を持ってもらいやすそうな名前にしました。
特に今後のゲーム制作で苦戦することがあった際にこういう情報があれば助かったのにという内容は積極的に記事にしていきたいと思います。

現時点で書いてみたいなと思っていることをいくつか書いておきます。
Unity、Unreal Engine、Godot、DirectX11、競技プログラミング、イラスト、Blender、読んだ参考書や小説のレビュー、Kuin

おわりに

もし質問やこれについて書いてほしいという要望があればコメント(知っていればTwitterやLINEでも)をいただければ極力対応しますので気軽にどうぞ。 これから有益だと思う情報は共有できるようにしていきたいと考えているのでよろしくお願いします!

それとブログなので好きなゲームやゲーム音楽、本、クリエイターの事について応援したい気持ちもあるのですが、それについてまとめた記事を書いても興味を持ってもらえるか怪しいし、情報過多なのではと考えています。
各記事について興味をもって読んでくれた人に対して最後に一つ書くくらいがちょうどいいかなと思うのでよほど不評でない限り続けようと考えています。

あと、正直に白状すると収益化には興味があるので、ブログの中にアフェリエイトを入れることがあります。
ただ、そのために自分が感じた以上に持ち上げたり、アフェリエイト目的の記事を書いたりすることはしないようにしようと考えています。ご理解いただけると幸いです。 と思っていたのですが、アフェリエイトを付けるには作り立てのブログではだめみたいですね。しばらくしたらもう一度リベンジしてみようと思います。 ちなみに収益化については期待しておらず、少し入ったらラッキーくらいの考えですが、もし収益化できた場合には主に参考書を買ったり、アセットを買ったりといったゲームの自主制作の費用に充てる予定です。

おまけ

一番好きなゲームについて書こうと思います。
遊んだことのある方も多いかもしれませんが、タクティクスオウガというゲームです。 ジャンルはシミュレーションRPGでシミュレーションとしてもかなり戦術性が高く面白いですが、 個人的に特に魅力的だと思うのはシナリオや世界観の方です。
世界観はヴァレリア島という島で起こった民族紛争をとても詳細に描いていて、登場人物の設定もかなり細かく考えられていて読んでいるだけでも楽しいですし、敵味方問わずに確かな信念を持っている人も多く、立場も踏まえると行動にかなり説得力があって魅力的な人が多いです。
そしてプレイヤーの選択によって物語が分岐するのですが、それによってよりゲームに参加している感じもあり、衝撃的な展開もあってかなり面白いです。

イラストや音楽のクオリティも高く、ゲーム内容とも良くあっているとても完成度の高いゲームだと思います。

元々はスーパーファミコンのゲームなのですが、ボリュームやプレイのテンポの良さからリメイク版の方がお勧めです。 リメイクが出たのがだいぶ前でPSPなのですが、PS StoreからVitaでも遊ぶことが出来ます。(PSPと違ってうるさくないので一番おすすめの遊び方)

一週間ゲームジャムでUnityでシミュレーションゲームを作ってみました。

はじめに

久しぶりにunityroomの一週間ゲームジャムに参加して、せっかくなので前からやろうと思っていたはてなブログも始めてみることにしました。 作ったのはこんなゲームです。 unityroom.com

f:id:ItsukiNamito:20190323212054p:plain

一週間ゲームジャムでは毎回参加する際に何か一つ以上新しいことに挑戦することにしているのですが、今回は

  • 今までに作ったことがないジャンルであるSLGを作る。
  • Asepriteでドット絵に挑戦する。
  • はてなブログで記事にする。(これは後付け)

に挑戦することにしてみました。

それと自己紹介の記事も書いてみたので、お前誰だよって感じの人はこちらもどうぞ。

itsukinamito.hatenablog.com

目次

制作編

今回はUnityのバージョンは2017.4.21f1を使っています。その他にバージョン管理でGithubを、ドット絵の作成にAsepriteを使用しました。

ドット絵の作成

ドット絵はAsepriteを使って作成しています。AsepriteはSteamで1480円で購入可能です。

store.steampowered.com

このソフト自体はだいぶ前に買ってあったのですが、ちょっと触ってほったらかしにしていたのでこちらの動画を見ながら作成しました。


【ドット絵】Aseprite(エースプライト)~初級者向け講座[質問形式]~

これはゲーム中でも使用しているツナの画像です。

f:id:ItsukiNamito:20190324005443p:plain

Unityで実装

作成したドットを使用してUnityで実装します。全て書くとかなりの量になってしまうので、この記事ではタイルマップと敵の簡単なAIについて取り上げます。

タイルマップ

タイルマップはVisualizerとOcean、Groundの3つのレイヤーで構成しました。

f:id:ItsukiNamito:20190324145130p:plain

OceanとGroundは名前の通り、海と陸地のタイルマップです。Visualizerは下の画像の様にユニット選択時に到達可能範囲を青く、攻撃可能範囲を赤く可視化させる際に使用しています。

f:id:ItsukiNamito:20190324150003p:plain

このVisualizerはあらかじめタイルを設置せず、ランタイム中にスクリプトから配置したり、取り除いたりしています。 まずプレイヤーのクリックを検知してユニットの周りに可視化タイルを配置するためのクラスを作成します。

~
using UnityEngine.Tilemaps;
using UnityEngine.EventSystems;

public class Visualizer : MonoBehaviour, IPointerClickHandler
{
 [SerializeField]
 Tilemap visualizerTilemap;

    [SerializeField]
    TileBase passibleTile;
    [SerializeField]
    TileBase attackableTile;

 // 到達可能な範囲を青く可視化させる
 void ShowPassibleTile(Vector3Int unitPos,int maxStep)
 {
  // 一マスごとに確認して青くする
  CheckPassible(unitPos,maxStep + 1);
 }

 // 到達できるか確認する
 void CheckPassible(Vector3Int pos,int remainStep)
 {
  if(posの位置のタイルがOceanかつ敵対するユニットが配置されていない。)
  {
   visualizerTilemap.SetTile(pos,passibleTile);
   --remainStep;
   if(remainStep == 0)return; // これ以上移動できない

   // まだ移動できるなら隣接するマスに到達可能か再帰的に確認していく
   CheckPassible(pos + Vector3Int.up, remainStep);
   CheckPassible(pos + Vector3Int.left, remainStep);
   CheckPassible(pos + Vector3Int.right, remainStep);
   CheckPassible(pos + Vector3Int.down, remainStep);
  }
 }

 // プレイヤーがクリックしたときに実行する
 public void OnPointerClick(PointerEventData data)
 {
  if(既に選択しているユニットがいる)
  {
   TileBase clickedTile = visualizerTilemap.GetTile(クリックされた地点);
   if(clickedTile == passibleTile)
   {
    // 移動処理
   }
   else if(clickedTile == attackableTile)
   {
    // 攻撃処理
   }
           // VisualizerTilemapにあるタイルを全て取り除き選択しているユニットがいない状態にする。
  }
  else if(クリックした地点にまだ行動できるユニットがいる)
  {
   その地点にいるユニットを選択状態にする処理
   ShowPassibleTile(ユニットの地点、ユニットの移動可能な距離);
  }
 }
~
}

色々省いているけどこんな感じの実装をしました。タイルマップも普段あまり触らないため、スクリプトからどう操作すればいいのかわかりませんでしたが、Unity Technologies社のGitHubリポジトリにある

GitHub - Unity-Technologies/2d-techdemos: Tech Demos for Unity 2D Features

が大変参考になりました。先ほどのコードにもありますが、tilemap.SetTile(position,tile)でpassibleTile(青のタイル)やattackableTile(赤のタイル)を第2引数に与えればそのタイルが配置され、nullを与えれば消すことが出来ます。後はtilemap.GetTile(position)でクリックした地点にpassibleTileがあれば移動、attackableTileがあってそこに敵がいれば攻撃とすればいいと思います。

敵のAI

敵のAIはストラテジーパターンで作成しています。ただ時間がなく間に合っていないので今回は敵のAIは1パターンしか用意していません。 まずは基底クラスを作成します。これはコンポーネントにする必要もないのでMonoBehaviorは継承させません。

public class BrainBase
{
 public void Update()
 {
  if(次に行動させるユニットが決まっている)
  {
   // ユニットの行動を決定する
   DecideAction();
  }
  else
  {
   // 次に行動させるユニットを決める
   DecideUnit();
  }
 }

 // 選択したユニットの行動を決定する(攻撃、移動、待機など)
 virtual protected void DecideAction()
 {
 }

 // 次に行動させるユニットを決める
 virtual protected void DecideUnit()
 {
 }

 // 継承先で汎用的に使えそうなプログラム色々
 ~
}

後はこのBrainBaseを継承して様々なパターンのAIを作成し、EnemyManager的なクラスを作成してBrainBaseの派生クラスのインスタンスを生成、保持します。敵のターンになった時に一気に全ての敵の行動が処理されるとプレイヤーが何が起こったのかわからなくなるので、コルーチンなどで一定時間ごとにBrainBase.Update()を実行すればいいです。僕は0.5秒に一回更新するようにしました。

公開編

今回はWebGLのビルドでつまづくこともなく、2日遅刻して提出しました 制作時からTwitterでマグロもののゲームを作っている人が多いのは気づいていたのですが、まさかツナの足軽で被る人がいるとは思ってませんでした。 以前参加した際にはランキングはついてなかったのですが、いつの間にかランキングが出るようになっていました。

unityroom.com

残念ながら僕のゲームはランキングには入れませんでした。 やっぱりランキングに入っているゲームは全体的に完成度の高いものばかりですね。

おわりに

初めてのブログ記事ということもあり文章が拙く読みにくかったかもしれませんが、ここまで読んでくださりありがとうございました。 書いていくうちに文章力も上がるんじゃないかなと思うので、今後も書き続けながら技術力も上げていって読んでくださった方に有益な記事を書けるようになっていったらなと思っています。 記事の内容に関して「ここが間違っている」とか「こういう方法もあるよ」とか「ここをもっと詳しく書いてほしい」とかあれば出来るだけ対応していくつもりなので、意見や質問があれば気軽にコメントをいただけると嬉しいです。

おまけ

この「おわりに」に何を書くか迷っていたのですが、自分の好きなものの宣伝をして少しでも貢献出来たらいいなと思ったので書かせてください。

もし著作権的にまずい等あればここの項目は削除します。

時々作業用BGMとしても使っているのですが、今回作ったのが足軽シミュレーションゲームなのでこれを選びました。 とは言っても今回作ったゲームとはシステムが似ているわけではないのですが…

太閤立志伝シリーズはかなり好きなシリーズで何年も新作を待ち続けているのですが、もうチームも解体されているとかであまり期待できなそうなので、実力をつけていつか自主制作で精神的続編にチャレンジしたい思っているゲームです。