2020/8/24エンジニア

    [Unity]PCVRとスマホで対戦できるゲーム制作[Prat.3]

    GoogleSpreadSheetを使ったVive対スマホ

    執筆者 / イチクマ

    はじめに

    前回のつづきです。(全3回中3回目)

    最後となる今回はスマホ側の実装を説明していきます。

    この記事の内容

    1. スプレッドシートでデータを共有する

    2. VR側のゲーム画面を実装する

    3. スマホ側のゲーム画面を実装する(いまここ)

    用意するもの

    今回はAndroidのみを対象としていますので、端末を用意します。

    また前回同様にAssetStoreからそれっぽいものをピックアップしました。

    SD Martial Arts Girl Xia-Chan:職業:モンク

    小人の見た目を作る

    インポートしたSD Martial Artsから「Chara_4Hero」を使用します。

    今回は複数人が参加することを想定しているため、

    すごく簡単なキャラクターメイクを用意していきます。

    当初は複数の職業を用意することで、見た目だけでキャラクターの差別化を図ろうと考えていましたが、

    アセットが揃わなかったので、RGBの色変更にしました。

    色変更をマテリアルに仕込みます、以下は変更前です。

    Materialを新規作成し、Albedoテクスチャを外し、Emissionにチェックを入れます。

    以下が変更後です。

    Emissionの色を変えることで色変更します。

    前回ステージ作成で使用したアセットを使用して、背景を作ります。

    キャラクターとカメラを適当に配置したのが以下です。

    (イメージは闘技場の入口みたいな感じです)

    またUIとして以下を追加します。

    ・色調整用のスライダー

    ・画面遷移ボタン

    ・名前入力

    ・コメント入力

    (名前とコメントは雰囲気だけなので、使用しません。)

    色変更するためのスクリプトは以下です。

    [SerializeField] SkinnedMeshRenderer mesh;
    [SerializeField] Slider sld_r;
    [SerializeField] Slider sld_g;
    [SerializeField] Slider sld_b;

    void Update ()
    {
        Color col = new Color(sld_r.value, sld_g.value, sld_b.value);
        mesh.material.SetColor(“_EmissionColor”, col);
    }

    スライダーの値を0~1までに限定しています。

    取得したスライダーの値をそのままEmissionのRGB値としています。

    完成イメージは以下のようになります。

    次の画面に行くボタンが押されたときに

    スプレッドシートに状態と色情報を更新します。

    何番目のプレイヤーなのかを判別する際には

    状態欄を上から順に確認して”ready”になった番号をしようします。

    ClearSD();
    SetSD_Pos(1, 3);
    SetSD_Value(0, 0, “play”);
    SetSD_Value(1, 0, “Andoid”);
    SetSD_Value(2, 0, “悶々モンク”);
    SetSD_Value(3, 0, “0xFFFFFF”);
    SendDataAsync(“シート名”,null);

    戦闘画面を作る

    巨人の時に作ったステージを複製して使います。

    巨人はそのまま中央に配置し、それを取り囲むように小人を配置します。

    カメラは巨人を見上げるようなアングルにします。

    また移動と攻撃はボタン押しによる操作にしたいのでUI画面を作ります。

    データの同期をきっちりするつもりはないので、

    大まかな移動手段として、巨人を中心にぐるぐる回るようにします。

    移動ボタンは前後左右で、前後はボタンを押すごとに入れ替わるようにします。

    攻撃は前にいるときは近接攻撃、後ろにいるときは遠距離攻撃といったように、

    現在の立ち位置からアイコンが変わるような作りにしました。

    移動関連のスクリプト抜粋は以下です。

    //カメラ更新
    private void UpdateCamera()
    {
        if( cam == null) { return; }
        cam_pos = atk.transform.position;
        float distance = 4;
        float setangle = atk_now_angle – 20.0f;

        cam_pos.x += distance * Mathf.Sin(Mathf.Deg2Rad * setangle);
        cam_pos.z += distance * Mathf.Cos(Mathf.Deg2Rad * setangle);
        cam_pos += new Vector3(0.0f, 2.0f, 0.0f);
        cam.transform.position = cam_pos;
        cam.transform.LookAt(def.transform);
    }

    // 巨人更新
    private void UpdateDefenter()
    {
        if(def_movetime_max > 0.0f )
        {
            def_movetime_now += Time.deltaTime;
            if( def_movetime_now >= def_movetime_max ){
                def_movetime_now = def_movetime_max;
                def.SetPosRot(def_pos_tgt, def_rot_tgt);
                return;
            }
            float rate = def_movetime_now / def_movetime_max;

            Vector3 setpos = Vector3.Lerp(def_pos_bak, def_pos_tgt, rate);
            Vector3 setrot = Vector3.Lerp(def_rot_bak, def_rot_tgt, rate);
            def.SetPosRot(setpos, setrot);
        }
    }

    //小人操作
    private void UpdateAttacker()
    {
        switch( atk_state )
        {
            case 1:
                {
                    float max_time = 0.2f;
                    move_time += Time.deltaTime;
                    if (move_time >= max_time) move_time = max_time;

                    float rate = move_time / max_time;
                    Vector3 cmdangle = new Vector3(0.0f, 0.0f, 180.0f * rate);
                    command_root.transform.localEulerAngles = cmdangle;

                    if (move_time >= max_time)
                    {
                        atk_state = 2;
                        move_time = 0.0f;
                    }
                }
                break;
            case 2:
                {
                    //float max_time = 1.0f;
                    float max_time = 0.2f;
                    move_time += Time.deltaTime;
                    if (move_time >= max_time) move_time = max_time;
                    float rate = move_time / max_time;
                    atk_now_angle = atk_bak_angle + atk_add_angle * rate;
                    atk_now_distance = atk_bak_distance + atk_add_distance * rate;

                    this.SetAttackerPosition();// atk_add_angle );
                    if (move_time >= max_time)
                    {
                        move_time = 0.0f;
                        atk_state = 3;
                    }
                }
                break;
            case 3:
                {
                    float max_time = 0.3f;
                    move_time += Time.deltaTime;
                    if (move_time >= max_time) move_time = max_time;

                    float rate = move_time / max_time;
                    Vector3 cmdangle = new Vector3(0.0f, 0.0f, 180 + 180.0f * rate);
                    command_root.transform.localEulerAngles = cmdangle;

                    if (move_time >= max_time)
                    {
                        atk_state = 4;
                        move_time = 0.0f;
                    }
                }
                break;
    }
    }

    完成イメージは以下のようになります。

    スプレッドシートを更新

    巨人と同様にスプレッドシートのデータ設定関数を使っていきます。

    更新するデータは3~6行目(図の青枠)のいずれかとなります。

    キャラクターメイキングで取得した番号をもとに設定していきます。

    ボタン入力で移動が終了したときに

    キャラクターの位置、角度を送ります。

    ClearSD();
    SetSD_Pos(5, 3);
    SetSD_Value(0, 0, Pos.x);
    SetSD_Value(1, 0, Pos.y);
    SetSD_Value(2, 0, Pos.z);
    SetSD_Value(3, 0, Rot.x);
    SetSD_Value(4, 0, Rot.y);
    SetSD_Value(5, 0, Rot.z);
    SendDataAsync(“シート名”,null);

    攻撃ボタンを押したときは状態を”attack”にします。

    アニメーション操作やUI操作については長くなるので割愛します。

    完成イメージは以下のようになります。

    まとめ

    スプレッドシートを使った簡易対戦ゲームを説明してきました。

    攻撃が当たったときのダメージ処理などは実装していませんが、

    スマホで横からちょっかいを出している感じには出来たと思います。

    コンテンツの試作段階でイメージ作成に苦労されている方がいれば、

    Unityとスプレッドシートを合わせた活用例として

    時間短縮の一助となればうれしいです。

    参考リンク

    ブログ一覧へ戻る