たくあんポリポリ

勉強したことを載せていきます。最近、技術系の記事はZennに書いています。(https://zenn.dev/chittai)

【Unity】ゲームにキャラクター選択機能を追加する

前に作成したゲームにキャラクター選択機能を追加しました。ちなみに、モデルにはVRMを使用していますが、アップロードしたりする機能は今回は特につけてないです。処理としてはキャラクターを複数配置してその切替を行っているだけです。

c-taquna.hatenablog.com

キャラクター切り替え機能

f:id:c_taquna:20190209024637j:plain:w250
f:id:c_taquna:20190209024657j:plain:w250

キャラクター切り替え機能のイメージです。キャラクターが切り替えられるだけで自由度が格段に上がったように感じるし、よりゲームっぽくなったなと感じます。

使用したツール / 環境

  • Unity 2017.3.1f1
  • UniRx
  • UniVRM

どんな仕組みか

ここではどんな仕組みで実現しているのか説明します。

キャラクターの切り替え方法

まず、どうやってキャラクターを切り替えているか説明します。眼の前にあるこのCharacterSelectと書かれたCnavas上に2つの丸い画像があります。ちなみに、これはボタン(UI)です。ここにポインタをあわせてトリガーを引くと、処理が実行されてポインタを合わせたキャラクターが選択できます。
f:id:c_taquna:20190209024924j:plain:w250

キャラクター選択時の処理

まず、一番最初にも記載しましたが、すごく簡単な仕組みで実現しています。やり方としては、シーン上にキャラクターのモデルを配置して、キャラクター選択がされた方のモデルをアクティブにしています。ちなみに、UniRXを使用しています。

キャラクターは以下の様に配置しています。

f:id:c_taquna:20190209025027j:plain:w250
AliciaSolidが左の金髪の女の子で、VRoid_testが右側の黒髪の女の子です。

ボタンを押した時に、以下の処理を行います。ここでは、ReactivePropertyを定義して、この値が変更になるたびに変更処理が実行されるようになります。基本、AliciaSolidを初期パラメータとして入れています。

public ReactiveProperty<string> charaName = new ReactiveProperty<string>("AliciaSolid");
public ReactiveProperty<string> beforeCharaName = new ReactiveProperty<string>("AliciaSolid");

public static string SelectedCharacter { get; set; } = "AliciaSolid";


これが今回のキャラクター選択機能のメイン部分です。詳細は後述します。

var aliciaStream = _aliciaButton.OnClickAsObservable().Select(_ => "AliciaSolid");
var vroidStream = _vroidButton.OnClickAsObservable().Select(_ => "VRoid_test");

var characterChangeComponents = GetComponent<CharacterSelectComponents>();

// View → Model
// キャラクターのボタンを押したらReactivePropertyの更新をする
var aliciaStream = _aliciaButton.OnClickAsObservable().Select(_ => "AliciaSolid");
var vroidStream = _vroidButton.OnClickAsObservable().Select(_ => "VRoid_test");

var characterChangeComponents = GetComponent<CharacterSelectComponents>();

// View → Model
// キャラクターのボタンを押したらReactivePropertyの更新をする
aliciaStream.Subscribe(name => characterChangeComponents.charaName.Value = name);
vroidStream.Subscribe(name => characterChangeComponents.charaName.Value = name);

// Model → View
// 表示するモデルを変更する
characterChangeComponents.charaName.SkipLatestValueOnSubscribe().Subscribe(name => 
{
    var beforeCharaObject = FindModelObject(characterChangeComponents.beforeCharaName.Value);
    beforeCharaObject.SetActive(false);

    var charaObject = FindModelObject(name);
    charaObject.SetActive(true);
    characterChangeComponents.beforeCharaName.Value = characterChangeComponents.charaName.Value;

    // シーン間で選択されたキャラクターの情報を保持するための変数
    CharacterSelectComponents.SelectedCharacter = characterChangeComponents.charaName.Value;

});


_aliciaButtonと_vroidButtonはキャラ変更様に作成したボタンUIです。インスペクターから指定します。ボタンが押されたらそのボタンに紐づく文字列を流します。今回の場合、左側の金髪の女の子の絵が書かれているボタンを押すと”AliciaSolid”が流れ、もう片方は”VRoid_test”です。

var aliciaStream = _aliciaButton.OnClickAsObservable().Select(_ => "AliciaSolid");
var vroidStream = _vroidButton.OnClickAsObservable().Select(_ => "VRoid_test");


次の処理はこちらです。aliciaStream.SubscribeとvroidStream.Subscribeがあります。上記のコードと突き合わせると、ボタンUIを押すと、ReactivePropertyの値が更新されます。そうすると、どうなるでしょうか。

aliciaStream.Subscribe(name => characterChangeComponents.charaName.Value = name);
vroidStream.Subscribe(name => characterChangeComponents.charaName.Value = name);


そうすると、この処理が動きます。この処理は上のコードを見てもらえればよいですが、現在表示されているモデルを消して、新しいモデルを表示します。

characterChangeComponents.charaName.SkipLatestValueOnSubscribe().Subscribe(処理略)


そして下記の処理が選択されたキャラクターをstatic変数に入れる処理です。他のシーンでは一番最初にこの変数を読み込んで、どっちのモデルを有効化するか判断しています。

 CharacterSelectComponents.SelectedCharacter = characterChangeComponents.charaName.Value;


このように、ボタン(UI)にポインタを当ててトリガーを引く→アクティブなキャラクターを変更する→選択されたキャラクターの情報をstaticでもっておくという一連の処理をしています。そうすることで、シーンをまたいでも選択されたキャラクターの情報を持ち続けることができます。ただ、この場合、毎回シーンが変わるごとにどっちのキャラクターが選択されているか読み込む必要があります。

そうなると各シーンでモデルが重複するかつ、キャラクターに関係するスクリプトが各シーンに存在したりしていてとにかくいろんなものが重複重複と・・・・かなりめんどくさいことになってます。

他にやり方ないの?

DontDestroyOnLoadを使用する方法がとりあえず考えられるかなと。このDontDestroyOnLoadというのは、一度読み込むとシーンを遷移してもオブジェクトを破棄しません。そのため、キャラクターをDontDestroyOnLoadで作成すれば、シーン毎にキャラクターのモデルを配置する必要がなくなります

ただ、今回はキャラクターの配置やアクションが各シーンで違っていたので、ちょっと使いづらかったかなと。あまりシーン間で状態が変わらないような静的なオブジェクトであれば使いやすいかなと。あと、DontDestroyOnLoadはオブジェクトが一番最初に登場するシーンから始めないといけないので、使用してしまうと途中のシーンからテストとかができなくてちょっとめんどくさいかも。

感想

シーン間でキャラクターのオブジェクトを持つ良い方法ってないかな~
あと、作成してから少し時間がたってしまった、、、