[UNITY][Ruby’s Adventure]#7 会話ダイアログ、音声、ビルド

前回までの記事

この記事はUNITYの公式チュートリアル『Ruby's Adventure』を日本語で解説している記事です。

↓↓記事一覧↓↓

#1 UNITY導入~キャラクター移動

#2 タイルマップ~物理演算

#3 HP設定~回復とダメージ~敵の配置

#4 アニメーションの設定と適用

#5 攻撃とカメラ追従

#6 パーティクルとUI(HPゲージ)

#7 会話ダイアログ~音声~ビルド ←いまここ

(番外編)うまく動かない場合の対処法

会話ダイアログを追加しよう

実際のチュートリアルページはこちら

喋るカエルを配置しよう

さて、続いては会話を実装したいと思います。

ProjectウインドウのSprites→Characterフォルダから、カエルの画像を▶ボタンで展開し、現れた3つのスプライトをShiftキーで全て選択し、そのままHierarchyウインドウ内へドラッグ&ドロップし、ファイルの保存先はAnimationフォルダを選び、「Jambi」と名付けてください。

こうすると、自動的にアニメーションを再生するオブジェクトとして生成されます。

次に、次のようにオブジェクトの下準備をしてください。

  • わかりにくいのでオブジェクト名を「Jambi」にする
  • Box Collider 2Dを適用、キャラクターの下半分あたりに設定
  • アニメーションが早いと感じる場合はAnimationウインドウでSample Rateを4とかに変更
  • Inspectorウインドウの右上のlayer→Add layerから「NPC」というレイヤーを作成して割り当てる。

こんな感じですね。

 

Rubyに「話しかける」処理を追加しよう

次に、RubyControllerスクリプトのUpdate()内の最後に下記のように書き加えてください。

if (Input.GetKeyDown(KeyCode.X))
{
    RaycastHit2D hit = Physics2D.Raycast(rigidbody2d.position + Vector2.up * 0.2f, lookDirection, 1.5f, LayerMask.GetMask("NPC"));

    if (hit.collider != null)
    {
        Debug.Log("Raycast has hit the object " + hit.collider.gameObject);
    }
}

初めに「if (Input.GetKeyDown(KeyCode.X))」とあるので、Xキーが「話すボタン」になります。複数のデバイスで操作したい場合は別の記述が必要となる点にご注意ください。

次に「RaycastHit2D hit = Physics2D.Raycast(rigidbody2d.position + Vector2.up * 0.2f, lookDirection, 1.5f, LayerMask.GetMask("NPC"));」とあります。長いですね…。

まず「RaycastHit2D hit」で、話しかける対象の当たり判定を取得する変数を定義しています。

Raycast」は「光線」を意味し、イメージとしては「光線を飛ばして当たったところで何かを処理する」コードです(実際に光る線が出るわけではありません)。今回は「Rubyから光線を飛ばして、当たったところにカエルがいたら会話を開始する」感じにします。

次の「Physics2D.Raycast」で「光線の飛ばし方」を変数に入れています。括弧()内の引数は「(光線の発射地点, 発射方向, 最大光線距離, 光線を当てる対象)」です。

光線の発射地点に書かれた「rigidbody2d.position + Vector2.up * 0.2f」は、歯車の時と同じですね。Rubyの上半身当たりから発射されるようにしています。

発射方向に書かれた「lookDirection」も、歯車と同じくRubyの見ている方向です。

最大光線距離には「1.5f」とあるので、これ以上離れると話せなくなります。

光線を当てる対象に書かれた「LayerMask.GetMask("NPC")」ですが、これは先ほど作成したレイヤー名です。

次に「if (hit.collider != null)」について、「hit」は先ほどの変数ですね。「hit.collider」と書くと、光線が当たった当たり判定(コライダー)を取得できます。で、それがカラッポ(null)でない場合(!=)なので、このif文は「光線に何かが当たった場合」に処理することを書くメソッドです。

で、その処理の内容に光線の当たった対象のオブジェクト名をコンソールウインドウに表示させるDebug.Logを書いています。

 

以上で試動してみると、Xキーでデバッグログが吐き出されていることが解ります。

会話ダイアログを追加しよう

さて、続いてカエルに会話ダイアログを追加しましょう。

Hierarchyウインドウ内のオブジェクトJambiを右クリック→UI→Canvasを選択し、「DialogCanvas」とでも名付けます。

で、そのオブジェクトのコンポーネントCanvasのRender ModeWorld Spaceに変更し、Order in Layerを10に変更します。

「イベントカメラが指定されていないけど大丈夫?」という警告が表示されますが、大丈夫です。

次に、Rect TransformからPositionを全て0に、Widthを300、Heightを200に、Scaleを全て0.01に変更します。

次に、HierarchyウインドウでオブジェクトDialogCanvasを右クリックしてUI→Imageを追加、そのSource ImageにProjectウインドウのUIフォルダから会話ダイアログ画像をアタッチ、Inspectorウインドウのアンカー編集ボタンを押して、Altキーを押しながら右下の四角形をクリックしてアンカー全体に画像を拡げます。

↑HPゲージの時と同じですね。

できたら、ImageオブジェクトのなかにTextMeshProオブジェクトを作って、サイズを良い感じに変えつつ、話す内容や文字の装飾を編集したりしましょう。今回は「Hey! Help me fix all those broken robots!」なんてセリフを入力してみます。

 

会話システムをスクリプトで制御しよう

さて、作ったダイアログを会話システムにしてみましょう。

Scriptsフォルダ内で「NonPlayerCharacter」というC#スクリプトを作り、オブジェクトJambiのコンポーネントに追加したうえで、次のように書き換えてください。

public float displayTime = 4.0f;
public GameObject dialogBox;
float timerDisplay;

void Start()
{
    dialogBox.SetActive(false);
    timerDisplay = -1.0f;
}

void Update()
{
    if (timerDisplay >= 0)
    {
        timerDisplay -= Time.deltaTime;

        if (timerDisplay < 0)
        {
            dialogBox.SetActive(false);
        }
    }
}

public void DisplayDialog()
{
    timerDisplay = displayTime;
    dialogBox.SetActive(true);
}

public float displayTime = 4.0f;」は、ダイアログ表示時間の変数です。時間で消えるようにします。publicにしているのでInspectorウインドウで編集できます。

public GameObject dialogBox;」は、ダイアログボックスを取得するための変数です。publicにすることで、Inspectorウインドウでオブジェクトをアタッチできるようにしています。

float timerDisplay;」は、「displayTime」のための計測タイマーですね。

Start()内の「dialogBox.SetActive(false);」で、スタート時にダイアログボックスを非表示にしています。

timerDisplay = -1.0f;」で、タイマーの初期値を0未満にしています。まあ、このあたりはUpdate()内の条件次第です。

で、Update()内の「if (timerDisplay >= 0)」でタイマー作動時の処理、その内容の「timerDisplay -= Time.deltaTime;」でタイマーを減少=カウントダウンしています。

さらに、「if (timerDisplay < 0)」でタイマー終了時の処理、その内容の「dialogBox.SetActive(false);」でダイアログボックスを非表示にしています。

最後の「public void DisplayDialog()」は、ダイアログボックスを表示させるメソッドですね。

timerDisplay = displayTime;」でタイマーをダイアログ表示時間にしています。

で、「dialogBox.SetActive(true);」でダイアログボックスを表示させています。

簡単な構造ですね。

 

次に、RubyControllerスクリプトの「if (Input.GetKeyDown(KeyCode.X))」内の、

さらに「if (hit.collider != null)」のなかに、

次のコードを追加してください。

    NonPlayerCharacter character = hit.collider.GetComponent<NonPlayerCharacter>();

    if (character != null)
    {
        character.DisplayDialog();
    }

さて、「NonPlayerCharacter character = hit.collider.GetComponent<NonPlayerCharacter>();」ですが、これは敵や歯車のスクリプトでも同じようなことをやりましたね。「hit」はこの直前で定義していた、光線が当たった時の処理が入っている変数です。つまり、光線が当たったオブジェクトにあるスクリプト「NonPlayerCharacter」を取得して変数「character」に入れています。カエルだけに効果を発揮するコードですね。

で、「if (character != null)」が、その変数がカラッポでないとき=光線が当たったオブジェクトにNonPlayerCharacterスクリプトが存在するとき=カエルに話しかけた時に処理するif文です。

その内容の「character.DisplayDialog();」で、先ほどNonPlayerCharacterスクリプト内に作ったDisplayDialog()を呼び出してるわけですね。

ここまで勉強したかたなら簡単なはずです!

 

オーディオを追加しよう

実際のチュートリアルページはこちらです

Rubyにオーディオソースを追加しよう

さて、ゲーム内を彩るチャプターはこれが最後です。

Hierarchyウインドウ内でカラのオブジェクトを作り、「BackgroundMusic」と名付け、コンポーネントAudio Sourceを追加しましょう。

↑こんな画面になっていると思います。

ここではBGMを流すので、まずはLoopにチェックをいれ、Projectウインドウ内のAudioフォルダにある「2D MUSIC LOOP.wav」をAudio Clipにアタッチしましょう。

これだけでBGMは完了です。

 

続いて、「主人公がきっかけで、主人公から一度だけ鳴る音」を設定します。なんの音かというと、

  • HP回復アイテムを取った時の音
  • 歯車を投げた時の音
  • Rubyがダメージを受けた時の音

の3つですね。

例えば「回復アイテム取得音を回復アイテムに設定」「歯車の投擲音を歯車に設定」とオブジェクトごとにAudio Sourceを追加しても良いのですが、これらの効果音は全て主人公の行動がきっかけとなるため、4つとも主人公にAudio Sourceを持たせようと思います。

ちなみに、例えばHP回復アイテムであるイチゴにAudio Sourceを持たせると、このチュートリアルでは音が再生されません。
なぜかというと、イチゴはRubyが取得したと同時にオブジェクトごと消えるDestroy)スクリプトになっているためです。これを解決するためにはイチゴを「Rubyが取得したら、音を再生し終えるまで非表示にはするけどオブジェクトを消さないでおいて、再生し終えたらオブジェクトを削除する」のような大変手間の掛かる(無意味な)スクリプトが必要となります。「消えると同時に音が鳴るオブジェクト」にはAudio Sourceを持たせないのが一番ですね。

で、オブジェクトRubyにコンポーネントAudio Sourceを追加します。1個だけでOKです。

次に、RubyControllerスクリプトを書き換えます。

まず最初のほうの定義にこれ

public AudioClip throwSound;
public AudioClip hitSound;
AudioSource audioSource;

を追加します。「public」で「AudioClip」を定義することによって、RubyのInspectorウインドウに複数の音声素材をアタッチできるようにしています。だからAudio Sourceは一つでOKなのですね。

次に、Start()内にこれ

 audioSource = GetComponent<AudioSource>();

を追加してコンポーネントを取得し、

次に、Start()など他のメソッドと同じ階層に並べる形で、これ

public void PlaySound(AudioClip clip)
{
    audioSource.PlayOneShot(clip);
}

を追加することで、PlaySound()というメソッドを呼び出して、括弧()内に先ほどの変数名を書けば音を再生されるようにします。ちなみに、「PlayOneShot」音が連続で鳴った時に重複させて再生できるコードです。

次に、Launch()内にこれ

PlaySound(throwSound);

を追加することで歯車発射と同時に音を再生し、

ChangeHealth()内の「if (amount < 0)」の最後にこれ

PlaySound(hitSound);

を追加することで、ダメージ減少時に被ダメージ音を再生するようにすればOKです。

新たなコードも少ないし、簡単な構造ですね。

できたら、InspectorウインドウでAudioClipを忘れずにアタッチしておきましょう(Audioフォルダ内にある「PlayerHit」と「ThrowCog」というファイルです)。

敵に近付くと聴こえる音を追加しよう

続いて、「主人公から離れた場所で鳴る音」を設定します。具体的には「敵が歩く音」ですね。

ところで、先ほどから追加している「Audio Source」は音の発信源です。では音はどこで聴いているのでしょうか?

これはデフォルトでMain Cameraに設定されています。

このAudio Listenerが音の聴こえる点です。

ということを念頭に置いて…まずは「敵が歩く音」を設定します。

敵PrefabにコンポーネントAudio Sourceを追加して、ProjectウインドウのAudioフォルダから「Robot Walking_Broken.wav」をAudio Clipにアタッチし、LoopONにして、

Spatial Blendを一番右の3Dに振ってください。

さて、こんな風に円が表示されますよね。これは「最大音量で」音が聴こえる範囲で、この円から離れていくと音が減衰していきます。では、音の聞こえる最長の範囲はどこでしょうか? シーンビューを縮小していくと…

範囲がとんでもないことになってますね。これを直します。

Inspectorウインドウの3D Sound SettingsからMax Distanceを500→10に変更してください。

これで実用的なサイズになりました。シーンビューの2Dボタンを押すと表示が3Dに切り替わり、範囲が球として確認可能です。

さて、実際に音を聴くのはAudio ListenerのついたMain Cameraです。↑の動画から解るように、カメラはゲーム全体の少し手前(z軸  -10)にあります。 つまり、ロボットが画面の中央にあっても、敵の音量範囲の端っこにカメラが位置しているため、音量が小さくなってしまいます。

しかし、カメラ位置を「z軸 0」に移動したりはできません。そこで、Audio Listenerを別のオブジェクトに設定します。

Main Cameraを右クリックしてカラの子オブジェクトを作成し、そのオブジェクトの位置を「z軸 10」にすると、親オブジェクトとの相対的な位置により「z軸 0」に子オブジェクトを配置することができます。

あとは、Main CameraからコンポーネントAudio Listenerを削除して、子オブジェクトにAudio Listenerを追加すればOKです。

 

ゲームを形にしよう!

実際のチュートリアルページはこちらです

 

さて、いよいよゲーム完成です!!!

…いや、全然ゲームにはなっていないのですが、このチュートリアルはここで終了です。Ruby's Adventureって、意外とゲームとして完成されたものにはならないで終わりなんですね…。

さて、まずはUNITY画面上部のEdit→Project Settings→Player画面を見てください。

  • Company Name … 会社名。ゲームで作成されたファイルが保存されるフォルダー名になったりします。
  • Product Name … ゲーム名/製品名。実行可能ファイル/バンドルに名前を付け(プラットフォームによって異なります)、会社名の下に場所を作成して、特にそのゲームに関連するすべてのものを保存するために使用されます。
  • Default Icon … デフォルトのアイコンです。モバイルのアプリアイコンやデスクトップのexeファイルのアイコンになります。
  • Default Cursor … デフォルトのカーソル。デスクトップ版を遊んでいる間、ゲーム起動中にシステムの矢印とは異なるカーソルが表示されるように設定できます。よくあるやつですね。

です。好きに設定しましょう。

次に、その下の枠はプラットフォームごとの詳細設定です。iOSなどのプラットフォーム開発モジュールをインストールしていればタブが増えているはずですが、とりあえずPC版の設定をしましょう。要点のみ解説します。

  • Icon … アイコンサイズごとに異なる画像を設定できます。

  • Resolution and Presentation … Resolutionは解像度です。このなかのRun In BackgroundをOFFにすると、プレイヤーがWebブラウザを開くなどしてゲーム画面が非アクティブになっている間、ゲームは一時停止されます。
  • Standalone Player OptionsのDisplay Resolution DialogがEnabled(有効)に設定されていると、ゲームを起動したときに解像度を選択できるようになります↓

 

↑こんなのが出ます。

続いて、UNITY画面上部のFile→Build Settingsを開いてください。

若干違うかもしれませんが、大体こんな感じのが出ると思います。

右側にある「Add Open Scenes」を押すと、現在開いているシーンをゲームに反映します。押さないとそのシーンはゲームに反映されないので、押しましょう。

左下の枠では、ゲームを実行するプラットフォームを選択できます。デフォルトではPCゲームになっていると思います。プラットフォームを追加でインストールするには、Unity Hubから「コンポーネントの追加」を選択しましょう。

設定が終わったら、右下のBuildボタンで完了です。

 

お疲れ様でした!

オマケ 完成コード