20190524のUnityに関する記事は13件です。

NavMeshを使った巡回するNPCのつくりかた

こんにちは、Oculus Questが届かなくてメンタルが不安定なZeniZeniです。
毎度ゲームを作ってると、巡回するNPCとか、一定範囲をウロチョロする敵とかを作りたいなーと思うときがあります。
そんな気持ちで巡回するNPCのつくり方を調べたりすると、基本的なやり方はわかっても、じゃあどう実装するのが楽で汎用的なのかがいまいちわからなかったので、自分で考えてみました。

個人的にNavMechを使った実装が最適解だと思うので、NavMeshを使っていきます。
NavMeshの基本的な使い方はこちらのおもちゃラボさんのサイトがとても分かりやすかったです。

作製するコード

DestinationController
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class DestinationController : MonoBehaviour
{
    //初期位置
    private Vector3 startPosition;
    //目的地
    [SerializeField] private Vector3 destination;

    [SerializeField] private Transform[] targets;

    [SerializeField] private int order = 0;

    public enum Route {inOrder, random}
    public Route route;


    void Start()
    {
        // 初期位置を設定
        startPosition = transform.position;
        SetDestination(transform.position);
    }

    public void CreateDestination()
    {
        if(route == Route.inOrder)
        {
            CreateInOrderDestination();
        }else if(route == Route.random)
        {
            CreateRandomDestination();
        }
    }

    //targetsに設定した順番に作成
    private void CreateInOrderDestination()
    {
        if(order < targets.Length-1)
        {
            order++;
            SetDestination(new Vector3(targets[order].transform.position.x, 0, targets[order].transform.position.z));
        }
        else
        {
            order = 0;
            SetDestination(new Vector3(targets[order].transform.position.x, 0, targets[order].transform.position.z));
        }
    }

    // targetsからランダムに作成
    private void CreateRandomDestination()
    {
        int num = Random.Range(0, targets.Length);
        SetDestination(new Vector3(targets[num].transform.position.x, 0, targets[num].transform.position.z));
    }

    // 目的地の設定
    public void SetDestination(Vector3 position)
    {
        destination = position;
    }

    // 目的地の取得
    public Vector3 GetDestination()
    {
        return destination;
    }
}
NpcMovement
using UnityEngine;
using UnityEngine.AI;

[RequireComponent(typeof(NavMeshAgent))]
[RequireComponent(typeof(DestinationController))]
public class NpcMovement : MonoBehaviour
{

    private NavMeshAgent navAgent = null;
    [SerializeField] private DestinationController destinationController;


    void Start()
    {
        navAgent = GetComponent<NavMeshAgent>();
        destinationController = GetComponent<DestinationController>();
        navAgent.SetDestination(destinationController.GetDestination());
    }

    void Update()
    {
        if(Vector3.Distance(transform.position, destinationController.GetDestination()) < 1.5f)
        {
            destinationController.CreateDestination();
            navAgent.SetDestination(destinationController.GetDestination());
        }
    }

}

使い方

巡回させたいフィールドのNavMeshの設定をします。
そして、巡回させたい3DモデルにNpcMovement.csをアタッチします。すると自動でDestinationControllerとNavMeshAgentもアタッチされます。
bandicam 2019-05-24 23-31-07-466.jpg
巡回させたい地点に何かしらGameObjectを設置して、それをDestinationControllerのTargetsに入れます。
それらの巡回地点をElement0から順番に巡回させたい場合はRouteをInOrderに、ランダムに巡回させたい場合はRandomにしてください。

後はNavMeshAgentで移動速度とかを調整すれば完了です。

開発、執筆にあたり、下記のサイト様を参考、引用させていただきました。

【Unity】NavMeshで経路に沿って動かす - おもちゃラボ

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unityでステージ解放を実装する

概要

 自作のゲームに複数のステージを登場させたいときにステージの解放はどうすれば実装できるのか、その一例をご紹介したいと思います。
今回は「一つ前のステージをクリアしたら次を解放する」という条件で解放していきます。

前提

 ステージ選択画面が存在し、ボタンクリックで各ステージへ行けるようになっている状態を前提とします。

スクリーンショット 2019-05-20 14.31.17.png

ステージ解放をしよう

1. 解放前のボタンを作ろう

qii.jpg

 上のように解放前後でボタンの色が変化するようにしたいので、まずはボタンを二つ用意するところから始めます。

 Command + D でステージボタンを複製 (Duplicate)します。
 名前を変更します。(今回はs2pとします)

betz.jpg

 ボタンの色を変更します(今回はグレー)

gott.jpg

 *ステージボタンを複製しているので、クリックするとゲームシーンへ移行するよう関連付けされていると思いますが、それは削除しましょう。

kesu.jpg

 これで解放前のボタンができました。
 関連付けをしていないので、クリックしても何も起こりません。

おまけ: 解放前のボタンにポップアップ的なものをつくる

 もちろんこのままでも大丈夫ですが、クリックした時に「解放されてないよ!」と表示されるポップアップを作ると分かりやすいかもしれません。

 Hierarchy 上で Create > UI > Panel でパネルを作成、大きさや位置、色を調節します。

スクリーンショット 2019-05-20 14.55.56.png

 テキストボックスを作成します。「まだ解放されていないよ!」と入力しました。
 ポップアップを消すボタンを作成します。テキストには「Back」と入力しました。
 できたのがこちら↓

スクリーンショット 2019-05-20 14.56.30.png

 ポップアップを消す「Back」ボタンを作動させるため、以下のスクリプトを書いて関連付けします。

MadaScript.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;



public class MadaScript : MonoBehaviour {


    public GameObject Popup;



    // Use this for initialization
     void Start () {

    }

    // Update is called once per frame

     void Update () {    

    }



     public void Appear(){
        Popup.SetActive (true);
    }


   //Appear関数が呼び出されるとポップアップが表示される

     public void Delete(){
        Popup.SetActive (false);
    }


   //Delete関数が呼び出されるとポップアップが非表示になる

}

 解説: SetActiveという関数を使うと、script上でGameObjectの表示・非表示(アクティブな状態にするか否か)の切り替えができます。普段はobjectを選択した際の左上のチェックボックスから変更できます。

check.jpg

 最後に、Panel のチェックボックスをはずします。(普段から表示されていなくていいので)
 これで解放前のボタンをクリックするとポップアップが出現し、Backボタンで消えるようになりました!

2. スクリプトを書こう 

 いよいよステージ解放をしていくにあたり、それを管理する変数を作ります。
 この変数の値によって、現在どのステージまで解放されているのか把握できるようにします。

 前提の状態から変更・追加するのは主に以下の2つになります:
 1. ステージ解放を管理するスクリプトを新たに作成
 2. ゴールした際のスクリプトに変数を代入する部分の追加(各ステージ)

解放の流れ・仕組み

 以下のような流れでゲームが読み込まれます。

nagareeのコピー.jpg

ステージ解放スクリプトの作成・関連付け

 今回はHelloScript という名前にしました。
 ここでstage_numの値を変更していきます!

HelloScript.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class HelloScript : MonoBehaviour {

    public int stage_num; // スコア変数
    public GameObject ni;
    public GameObject san;
    public GameObject yon;
    public GameObject go;

    // Use this for initialization
    void Start () {
        //現在のstage_numを呼び出す
        stage_num = PlayerPrefs.GetInt ("SCORE", 0);
    }

    // Update is called once per frame
    void Update () {
        //stage_numが2以上のとき、ステージ2を解放する。以下同様
        if (stage_num >= 2) {
            ni.SetActive (true);
        }

        if (stage_num >= 3) {
            san.SetActive (true);
        }

        if (stage_num >= 4) {
            yon.SetActive (true);
        }

        if (stage_num >= 5) {
            go.SetActive (true);
        }

    }
}

 スクリプトを作成したらステージ選択sceneに空のobjectを作成して、関連付けを行いましょう!
 空のobjectはHierarchy > Create > Create Emptyで作ります。

kan.jpg

ゴールでのstage_numの変更

 ゴールした時に値が変更されるようにするために、以下のスクリプトを作成、ゴールのオブジェクトにドラッグします。

Kaihou3Script.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Kaihou3Script : MonoBehaviour {


    // Use this for initialization
    void Start () {

    }

    // Update is called once per frame
    void Update () {

    }

    void OnTriggerEnter(Collider other){
        if (other.gameObject.name == "Player") {
            PlayerPrefs.SetInt ("SCORE", 3);
            PlayerPrefs.Save ();
        }
    }
}

3. 完成!!

 これで完成です。
 ステージ選択sceneのスクリプトを使うと、解放の状況によって図鑑が解放されるなどの応用もできます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unityのリッチテキストはエスケープできない

前提

  • スクリプトから動的に内容を変化させるuGUIのTextで、内容によって部分的にスタイルをコントロールできるようにしたかったので、リッチテキスト(⇒Unityマニュアル) を使用しました。
    • 例えば、左寄せで「ABC」と「X ABC」を切り替えたいような要求です。

ハマったこと

  • ユーザが入力可能な文字列を挿入したら、文字列中にタグがあって化けてしまいました。
  • タグをエスケープしようとしたのですが、Unityのリッチテキストにはエスケープ方法がありませんでした。
  • <\< に置き換えれば、確かにタグとして認識されなくなるのですが、バックスラッシュも表示されてしまいます。

やりたいこと

  • 有効なタグにならないようにしたい。
  • なるべく入力に近い表示にしたい。

回避した方法

  • 元の文字列stringに対して、string.Replace ("<", "< ").Replace (">", " >")を使うようにしました。

反省

  • ユーザ入力に由来する文字列を表示する場合は、リッチテキストは使わない方が良さそうです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unityでモブプロのまとめページ【AZML】

勉強会でUnityを使ったモブプログラミングを行っており、その活動内容のまとめページになります。活動自体に名前があった方がよさそうなので【AZML】と名付けてます。活動内容の詳細については以下の記事に記載しているので併せてご確認いただければ幸いです。

Unityでモブプログラミングやってみよう【発足】

2019年4月20日

3チームに分かれて以下に記載されている人数に分かれてモブプロをしました。発足したばかりなので、いろいろ難航したチームもあったり・・・。無事に3チームともゲームを作成することができました。

[B01_ColorPattern]:4人

【一日目】Unity でモブプログラミング[B01_ColorPattern]

[B02_Dase]:4人

【一日目】Unity でモブプログラミング[B02_Dase]

[B03_PostBird]:5人(ただしハンズオン)

【一日目】Unityでモブプログラミング[B03_PostBird]

2019年05年11日

元号が令和になってからの一発目の勉強会です。いきなりの人数激減によりチーム2人ずつのペアプログラミングで取り組むことになりました。

[C01_ToiletPaper]:2人(ペアプログラミング)

【二日目】Unity でモブプログラミング[C01_ToiletPaper]

[C01_ToiletPaper]:2人(ペアプログラミング)

【二日目】Unity でモブプログラミング[C02_NosePush]

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

uGUIで動的にサムネイルを生成する

やりたいこと

  • ステージ一覧で、ステージマップのサムネイルを動的に生成して張りつけたい。
    • 一覧される各要素は、動的に生成され、レイアウトグループで配置されます。
  • uGUIで、動的にSpriteを生成してImageに貼り付けます。

やったこと

  • サムネイルの生成は時間がかかるので、非同期に行います。
  • 個々のサムネイルを自律的に生成させます。
private IEnumerator MakeThumbnail () {
    this.Loading.SetActive (true); // クルクルを表示
    this.Thumbnail.gameObject.SetActive (false); // サムネを非表示
    yield return null;
    var size = Mathf.Max (this.Level.Width, this.Level.Height); // 縦横大きい方
    var dx = (size - this.Level.Width) / 2; // 中央寄せのため横のパディング
    var dy = (size - this.Level.Height) / 2; // 中央寄せのため縦のパディング
    this.Texture2D = new Texture2D (size, size); // テクスチャを生成
    for (var y = 0; y < size; y++) {
        for (var x = 0; x < size; x++) {
            var color = this.Level.Matrix [x - dx, y - dy].Color; // 色を取得 (範囲外は自動判別)
            this.Texture2D.SetPixel (x, size - 1 - y, color); // 色を描き込む
            yield return null;
        }
    }
    this.Texture2D.Apply ();
    this.Texture2D.filterMode = FilterMode.Point;
    this.Thumbnail.sprite = this.Sprite = Sprite.Create (this.Texture2D, new Rect (0, 0, size, size), new Vector2 (0.5f, 0.5f));
    this.Thumbnail.gameObject.SetActive (true); // サムネイルを表示
    this.Loading.SetActive (false); // クルクルを非表示
}

// 自分が消えるときには、作ったデータも消す
private void OnDestroy () {
    if (this.Texture2D) { Destroy (this.Texture2D); }
    if (this.Sprite) { Destroy (this.Sprite); }
}

環境

  • unity 2018.4.0f1
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Unity】他のSceneに変数などの情報を渡すカンタンな方法

まずは Scene について説明

Scene とはゲーム画面というとわかりやすいんでしょうか。よく具体例で言っているのはロールプレイングゲームですね。フィールド歩く「マップ画面」と敵に遭遇したら切り替わる「バトル画面」は、それぞれ違う画面として管理した方が良さそうじゃないですか。そういうときに Scene で「マップ Scene」や「バトル Scene」という形でそれぞれの画面ごとに作ることができます。

他の Scene に変数の情報とか渡したい

こう書かれてもピンと来ないかもしれませんので以下のような Scene で構成されているゲームがあるとします。

  • タイトル画面
  • ゲーム画面
  • クリア画面

やりたいことはゲーム画面でプレイした結果をクリア画面に渡したいんです!具体的にいうとゲーム画面でクリアしたかどうかを bool 値で判定して、その bool 値をリザルト画面に渡したい。

方法1:public で static な変数を使う

public で static な変数を使えば他のスクリプトへの変数に参照できます。どこからでも変数を呼べるし格納もできるので Scene が違っていても変数に格納できます。以下にサンプルとなるソースを記載します。GamePlaying が実際に遊ぶゲーム画面にアタッチされており、GameResult がゲーム結果画面にアタッチされているモノとしてご確認ください。

▼【保存する場合のスクリプト】▼

using UnityEngine;

public class GamePlaying
{
    public static bool isClear = false;
    void Start () {
        //スタートからクリア判定を置く
        //isClear = true;
    }
}

▼【読み込みたい場合のスクリプト】▼

using UnityEngine;

public class GameResult
{
    void Start () {
        //ゲーム結果画面でGamePlayingの変数を読み込み
        if(GamePlaying.isClear){
            Debug.Log(" isClear が true なのでクリアしてる状態です");
        }
    }
}

共通で使用したい変数である isClear は、GameResult 側に宣言してもよいです。その場合は GamePlaying 側からはGameResult.isClearで参照することができます。

public で static な変数のリスクも知っておこう

上記のような形で public で static な変数を使用すれば、ほぼどこからでも参照できます。どこからでも参照できるというのは便利ではありますが、それはそれで意図しない場所から参照される場合もあります。特に人と作業される場合は「この変数は参照できるから利用しよう!」と変数に対して意図しない値を格納されたりすることもあるのでご注意ください。
「staticおじさん」という揶揄もあったりして多用しすぎるとよくない可能性もあるにはありますが、リスクなどを把握しながら上手く使い分けて行くことが重要だと思います。とりあえずは個人でやる分には上記の方法を使ってみてもイイかなと思っています悩んで停滞するよりは進めることの方が重要だと思うので。

方法2:DontDestroyOnLoad()を使用する

通常、他のSceneに切り替わった(遷移)場合は、遷移前のSceneのオブジェクトは全て破棄されます。DontDestroyOnLoad()を使用すれば、使用したSceneについて他のSceneに切り替わった(遷移する)場合でもオブジェクトが破棄されません。オブジェクト自体は残っているのでオブジェクトを検知して変数を参照したりオブジェクトを操作することが可能です。

▼【ゲーム画面にアタッチするスクリプト】▼

using UnityEngine;
using UnityEngine.SceneManagement;

public class GamePlaying : MonoBehaviour {

    public string test = "他のSceneで読み込まれるぜ!";

    void Start () {
        DontDestroyOnLoad (this);
    }

    //クリックしたら「GameResult」に遷移
    void Update () {
        if (Input.GetMouseButtonDown (0)) {
            SceneManager.LoadScene ("GameResult"); 
        }
    }
}

▼【結果画面にアタッチするスクリプト】▼

using UnityEngine;

public class GameResult : MonoBehaviour {

    void Start () {
        //オブジェクトを名前で探す
        GameObject resultObj = GameObject.Find ("ResultObject");
        //変数「test」を参照しDebug.Logに出力
        Debug.Log (resultObj.GetComponent<GamePlaying> ().test);

        //オブジェクトの位置情報を変更
        resultObj.transform.position = new Vector3 (
            resultObj.transform.position.x,
            resultObj.transform.position.y - 5,
            resultObj.transform.position.z
        );
    }
}

▼【ゲーム画面スクリーンショット】▼
DontDestroyOnLoad()-Playing.png

▼【結果画面スクリーンショット】▼
DontDestroyOnLoad()-Result.png

結果画面でtestという変数を参照していますが public な変数です。public を付けないと他からは参照できないのですが static でなくてもオブジェクトを通して参照できます。ちなみにざっくりと static について説明しますが static は静的なモノで最初に領域を確保されるため参照対象となる変数は1つしか存在しません。この説明だけだとわかりにくいですが、今回の場合を例にするとちょっとわかりやすいかも。
今回の場合はオブジェクトResultObjectの変数testを参照していますが、もし他のオブジェクトなら他のオブジェクトを探して、探し出したオブジェクトの変数testを参照できます。つまりオブジェクトの数だけ変数testが存在することになります。

▼【試しにstaticの変数を追加してみるが・・・】▼

using UnityEngine;
using UnityEngine.SceneManagement;

public class GamePlaying : MonoBehaviour {

    public string test1 = "他のSceneで読み込まれるぜ!";
    static public string test2 = "staticのテストだぜ";

    void Start () {
        DontDestroyOnLoad (this);
    }

    void Update () {
        if (Input.GetMouseButtonDown (0)) {
            SceneManager.LoadScene ("GameResult"); 
        }
    }
}

▼【GameObjectから参照しても参照できない】▼
TESTAAAA.png

方法3:永続的に保存する「PlayerPrefs」

プログラムおよびアプリ自体に値を保存してしまえば Scene を跨いでも受け取れるようになります。値を渡す目的だけで使うのは微妙かもしれません。ゲーム画面のスコアなどの記録したいモノであれば使ってもイイかなと。

以下にサンプルのソースを記載します。以下のことを気をつけてご利用いただければ幸いです。

  • 保存するときは必ずPlayerPrefs.Save()を忘れない
  • 保存や読み込みのキーは大文字小文字を区別するので注意

▼【保存する場合のスクリプト】▼

using UnityEngine;

public class GamePlaying
{
    void Start () {
        //「SCORE」というキーで、Int値の「20」を保存
        PlayerPrefs.SetInt("SCORE", 20);
        PlayerPrefs.Save();
    }
}

▼【読み込みたい場合のスクリプト】▼

using UnityEngine;

public class GameResult
{
    void Start () {
        //「SCORE」というキーで保存されているInt値を読み込み
        int resultScore = PlayerPrefs.GetInt("SCORE");
        Debug.Log("保存されている点数:" + resultScore )
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unity Editor拡張 ファイル保存ダイアログメモ

◆下記URLを参照
http://light11.hatenadiary.com/entry/2018/04/19/192748

■拡張子にtxtと書けば、テキスト形式で保存可能

■Assetsのところを未入力にしてもAssetsフォルダ内がデフォルトで表示される。
(Editor拡張の関数だからしょうがないけどここを何とかしたいな、
 Assetsより外に保存したい時は1階層上に上がる操作が必要)
 

using UnityEngine;
using UnityEditor;

public class SaveFilePanel : EditorWindow {

    private void OnGUI()
    {
        if (GUILayout.Button("保存")) {
            // 保存先のファイルパスを取得する
            var filePath = EditorUtility.SaveFilePanel("題名", "Assets", "保存したいファイル名", "拡張子");

            if (!string.IsNullOrEmpty(filePath)) {
                File.WriteAllText(filePath, "12234567890", Encoding.UTF8);
            }
        }
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unity 3Dで視野の辺縁部の影が消える (ハマったこと)

前提

  • unityで3Dを使用し、カメラを引いて遠くから見ています。

現象

  • 視野の縁に近付くにつれて、オブジェクトの影が消えてしまいます。

修正前

必要だったこと

  • Project Settings > Quality > Shadow Distanceを必要なだけ大きくします。

修正後

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unityで加算合成とかしたい

前提

  • unityでマテリアルとシェーダーの基本的な利用方法はここでは説明しません。
  • シェーダーのソースが利用できる必要があります。
  • この記事は、現時点での私の理解に留まり、必ずしも真実ではありません。

目的

  • 既存のシェーダーのブレンドモードを変更したい。
    • 例えば、もやっとした画像をPhotoshopの「覆い焼き(リニア)」みたいに被せて、光ってるみたいに見せたい。

手っ取り早い解決

  • 対象のオブジェクトに割り当てられたマテリアルを確認し、マテリアルが使用しているシェーダーを特定します。
    • 例えば、対象がuGUIのImageでマテリアルが明示されていないなら、Default UI Materialが割り当てられ、UI/Defaultシェーダが使われています。
  • シェーダーのソースを用意します。
  • ソースのファイル名を変更します。
    • 例えば、UI-Default.shaderUI-Default-Add.shaderに変更します。
  • ソースをプロジェクトに取り込みます。
  • ソースを開いて、冒頭のシェーダー名を変更します。
    • 例えば、以下のように変更します。
UI-Default-Add.shader
Shader "UI/Default"
{

UI-Default-Add.shader
Shader "UI/Add"
{
  • キーワードBlendを探し、後に続くふたつのキーワードを変更します。
    • 例えば、以下のように変更します。
UI-Default-Add.shader
Cull Off
Lighting Off
ZWrite Off
ZTest [unity_GUIZTestMode]
Blend SrcAlpha OneMinusSrcAlpha /////// 発見
ColorMask [_ColorMask]

UI-Default-Add.shader
Cull Off
Lighting Off
ZWrite Off
ZTest [unity_GUIZTestMode]
Blend One One /////// 加算合成
ColorMask [_ColorMask]
  • 新しいマテリアルを作り、書き換えたシェーダーを割り当てます。
  • 対象のオブジェクトに、マテリアルを割り当てます。

以上で、UI-Default.shaderの加算合成版ができました。

Blendの引数と効果

命令 上作用 下作用 効果 意味*
Blend SrcAlpha OneMinusSrcAlpha アルファブレンド 下画像を上画像の透明度を補うように薄くしたものと上画像をその透明度で薄くしたものを合成
Blend OneMinusDstColor Zero 反転 上画像に下画像の補色を乗算
Blend One One 加算 / 覆い焼き(リニア) 下画像に上画像を加算
Blend SrcAlpha One 加算+アルファ / スクリーン 下画像に上画像を透明度で薄くして加算
Blend OneMinusDstColor One スクリーン / 比較(明) 上画像に下画像の補色を乗算した上で下画像を加算
Blend Zero SrcColor 乗算 下画像に上画像を乗算

*私の理解

Blend 《第1引数》 《第2引数》は、「上画像に第1引数を作用させたものと、下画像に第2引数を作用させたものを合成する」というルールを定めるみたいだけど、この「作用」がどういうものかは、フラグメントシェーダーでプログラムされていて、たいていは「乗算」になっているようです。
つまり、多くの場合、Blend = 上画像 × 第1引数 + 下画像 × 第2引数となるものと受け取りました。
例えば、単純な乗算ならBlend = 上画像 × 0 + 下画像 × 上画像、加算なら、Blend = 上画像 × 1 + 下画像 × 1で実現できるわけです。

※私の勝手な解釈ですので、「そうじゃない」とかあれば、是非ご指摘ください。

技術情報

キーワード 意味
Zero (0, 0, 0, 0) 0%
One (1, 1, 1, 1) 100%
SrcColor (Sr/Kr, Sg/Kg, Sb/Kb, Sa/Ka) 新しい色
OneMinusSrcColor (1, 1, 1, 1) - (Sr/Kr, Sg/Kg, Sb/Kb, Sa/Ka) 新しい色の補色
DstColor (Dr/Kr, Dg/Kg, Db/Kb, Da/Ka) 現在の色
OneMinusDstColor (1, 1, 1, 1) - (Dr/Kr, Dg/Kg, Db/Kb, Da/Ka) 現在の色の補色
SrcAlpha (Sa/Ka, Sa/Ka, Sa/Ka, Sa/Ka) 新しいα
OneMinusSrcAlpha (1, 1, 1, 1) - (Sa/Ka, Sa/Ka, Sa/Ka, Sa/Ka) 新しいαの補色
DstAlpha (Da/Ka, Da/Ka, Da/Ka, Da/Ka) 現在のα
OneMinusDstAlpha (1, 1, 1, 1) - (Da/Ka, Da/Ka, Da/Ka, Da/Ka) 現在のαの補色
SrcAlphaSaturate (i, i, i, 1)
{i = Min (Sa / Ka, 1 - Da / Ka)}
「新しいα」と「現在のαの補色」の暗い方を不透明にしたもの
S (r, g, b, a) (0, 0, 0, 0)~(Kr, Kg, Kb, Ka) 新色(絶対値)
D (r, g, b, a) (0, 0, 0, 0)~(Kr, Kg, Kb, Ka) 現色(絶対値)

参考にしたページ

以下のページを参考にさせていただきました。
どうもありがとうございました。

愚痴

  • Markdownで、テーブルにnowrapって指定できないのかな?
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Unity]Shaderを書かずに、選択オブジェクトにハイライトをつける

 ゲーム制作においてハイライト表現は結構重要な要素です。3D空間内での「触った」「触ってない」という情報がプレイヤーに伝わらなければ、それはストレスの原因になるからです。今回はほぼコーディングなしでハイライト表現を実装します。

制作物のイメージ

仕上がったハイライトを先に掲載しておきます。こんなハイライトを作ります。highlight_image.png

制作環境

Unity 2019.1.0f2
Shader Graph ver.5.13.0(※デフォルトでver.5.7.2が入っていることがありますが、バージョンをあげないとエラーがでます)
Lightweight Render Pipeline(Assets > Create > Rendering > Lightweight Render Pipeline で辿ってください)

ShaderGraphでハイライトの縁を作る

highlight_image2.png
 Unity2019よりShader Graphというエディターが正式に実装されたことで、ノードベースでシェーダーを組むことができるようになりました。今回はこのエディターを使用することにします。
 プロジェクトを立ち上げたばかりではこのエディターは組み込まれていないので、自分でPackage Managerからインストールする必要があります。Package Managerはツールバーより Window > Package Manager で辿れます。バージョンは基本的に最新のものが選択されているはずですが、まれに最新になっていないことがあるので注意してください。
 ShaderGraphが導入できたら、Create > Shader > Unlit Graph と辿ってシェーダーを作成します。作成できたらダブルクリック等でシェーダーを開きます。
highlight_image3.png

 エディターが開いたら、以下のようにノードを組みます。今回はハイライト部分に特別凝ったことはしていないので、とてもシンプルに仕上がっています。ハイライト部分にもライティングを入れたい、ハイライトを点滅させたい、などといった場合は自分でノードを組みなおしてください。
 今回のハイライト表現の基本方針は “二度描き” です。ハイライトを入れたいオブジェクトを一回り大きく描画し、再度従来のマテリアルで描画する、というやりかたです。
 ノードはスペースで検索できます。

  • Positionノード は Input > Geometry
  • Multiplyノード は Math > Basic

から生成できます。
highlight_image4.png

なお、ノードが組み終わったら、必ずSave Assetしましょう。しないとマテリアルに反映されません。
highlight_image5.png

シェーダーに関しては以上です。以降さわることはありません。(便利な時代になりました...)

カスタムなScriptable Render Pipeline(SRP)に変更する

 次に“二度描き”を行えるように標準のSRPからカスタムなSRPに変更します。描画順序を変更するためには、Create > Rendering > Lightweight Render Pipeline > Pipeline Asset / Forward Renderer を辿って、SRP用のスクリプタブルオブジェクトを準備します。highlight_image6.png
 
 ファイルを準備しても、Unity側ではまだこのパイプラインを使用するようには設定されていません。なのでツールバーより Edit > Project Setting... を辿り、Graphics項目のScriptable Render Pipeline Settingを先ほど作成したPipeline Assetに変更します。
highlight_image7.png

 次はPipeline Asset側がカスタムなForward Rendererを使用するように設定します。AssetフォルダよりPipeline Assetを選択するとインスペクターに下図のようなパラメータ画面が表示されます。General > Data の項目を先ほど作成したForward Rendererに設定します。
highlight_image8.png

 下準備は完了しました。次はカスタムなForward Rendererに描画順序を設定していきます。

Custom Forward Rendererで描画順序を弄る

 AssetフォルダよりカスタムするForward Rendererを選択します。インスペクターよりRenderer Featuresを追加していきます。注意点としては、SRPではレイヤーグループ単位で描画順序を弄るため、ハイライトさせたいオブジェクトだけが属するレイヤーグループを準備する必要があります。そしてOverridesで二度描きでしようするマテリアルをShaderGraphで作成したシェーダーマテリアルに設定します。(※画像ではStencilにチェックが入っていますが、必要ありません)
highlight_image9.png
 この時点での描画結果は以下のようになります。元のオブジェクトより一回り大きく、ライティングなしの単色一色で描画されていることがわかります。
highlight_image10.png

 さて、ハイライトの縁になる部分は描画できましたが、これでは元のオブジェクトが何なのかわからないので、元のオブジェクトを描き直します。先ほどと同じようにRenderer Featuresを追加し、以下のように設定してください。
highlight_image11.png
 今回はオリジナルのマテリアルで再度描画してほしいので、Override用のマテリアルは設定しません。結果は以下の通りです。
highlight_image12.png
 これにてハイライトは完成です。あとはハイライトさせたいオブジェクトのレイヤーをハイライト用のレイヤーに変更するだけです。ね、簡単でしょ?

二度描きの欠点と回避方法

 完成と書きましたが、実は二度描きゆえの描画破綻が存在します。この描画破綻は、ハイライトの上(※正確にはハイライトとカメラの間に)に別のオブジェクトが存在する時に生じます。
highlight_gif.gif
 これを回避するには、ハイライト上に存在する可能性があるオブジェクトの描画を遅らせる必要があります。上図のGIFではプレーヤーオブジェクトがハイライトを遮っているので、それの描画を遅らせます。プレーヤー用のレイヤーを設定し、下図のように設定します。Renderer Featuresは上から描画が優先されるようなので、Playerレイヤーは一番下に設置します。
highlight_image13.png

highlight_gif2.gif
 これで描画破綻を回避できました。ね、簡単でしょ?

まとめ

 結構敷居が高かったハイライト表現がいとも簡単に実装できる!!Unityは今日も世界を救う!!

余談

 実は、ハイライト部分をメッシュとして動的に作成してしまうという方法もあります(←めっちゃめんどくさい)。その方法を用いたイメージも一応載せておきます。これは二度描きしていないので、ハイライト部分が距離に忠実に描画されています。ま、こういう方法もあるよ、ということです。
selecting.gif

参考文献

テラシュールブログ, 【Unity】LWRPで、壁で遮られて見えないキャラクターをシルエット表示するのが超簡単にできた, 2019/04/15,
http://tsubakit1.hateblo.jp/entry/2019/04/15/234935

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

歴史から知る「なぜ絵文字対応は面倒なのか」

はじめに

企画さんから、
「名前入力の際に絵文字を * でエスケープしてほしい(例:ハッピー:tada:→ハッピー*)」
という要望がきたので、それに対応するまでの流れをまとめたいと思います。(対応したとはとは言ってない)

Unicodeに絵文字が導入されるまで

こういった一部の文字をエスケープしたい場合、for文でcharをチェックするなり正規表現で弾くなりすると思いますが、絵文字の場合はそう上手くはいきません。

これには Unicode が絵文字を表示する仕組みが関係します。
絵文字が導入されるまでを時系列で振り返りながら、仕組みを知っていきましょう。

1990年代前半

制作当初、Unicode では1文字を2バイト固定で表現しようと計画されていました。
これによって、収録できる文字が最大65536文字になりますが、

  • Shift_JIS などの経験から可変長文字コードが面倒なことがわかっていたこと
  • 当時、中国・日本・韓国などの文字をまとめても20000個程度だったこと

などが理由で、問題ないだろうと判断されていました。
実際、Unicode 1.0 が策定された際は65536個以内に収まっていました。

これ以降、2バイト固定長、のちに UTF-16 と呼ばれるルールはC#やJavaなどのプログラミング言語に採用されるようになります。(char を2バイトとして扱う)

1990年代後半

当初、2バイト(65536文字)もあれば十分だろうと判断されていた Unicode ですが、技術者だけでなく言語学者も参画してもらったところ、65536文字では足りないことが明らかになります。

そこで、2文字1組(4バイト)として扱うサロゲートペアというルールが定められました。
これによって、扱える文字は増えたわけですが、当初定めていた2バイト固定長が崩れる形となり、UTF-16 は1文字2バイトまたは4バイトの可変長となってしまいます。

この結果、発生したのがUnicodeを使用したプロジェクトでのバグです。
当初、2バイトで1文字を扱っていたことで、サロゲートペアなどの4バイト文字がきた時に対処できなくなってしまいました。
しかし、サロゲートペアで表現する必要がある文字は一般人が知らないようなマイナー文字がほとんどだったため、特に対策されないプロジェクトが存在する形となりました。

2010年代前半

この状況に一石を投じたのが絵文字でした。

もともと日本の携帯電話などで浸透していた絵文字は、その扱いやすさから Emoji として世界でも扱われるようになります。(大統領からも Emoji を生み出したことに感謝の意を示されたらしいです)

絵文字は4バイトで表現されますが、上記の通り世界各国で使用されているため、技術者はサロゲートペアに対応せざるを得なくなりました。
これによって、今まで対応されていなかったマイナー文字も対応される形となり、結果的にバグ対策がなされる形となっていきます。

なぜ絵文字対応は面倒なのか

今回の記事のタイトルでもある、「なぜ絵文字対応は面倒なのか」
これは、

絵文字は4バイトで表現されますが、上記の通り世界各国で使用されているため、技術者はサロゲートペアに対応せざるを得なくなりました。

が理由となっていることがわかりました。
プログラミング言語的に置き換えると、charが2バイトで扱われている場合、絵文字などの4バイトは正しく1文字と判定されなくなるなどの問題が発生します。

なので、絵文字対応をするためには、この4バイト文字を正しく1文字扱いできればいいことがわかります。
幸い、IsSurrogateなどのサロゲートペアを検知する関数が各言語に存在するかと思われるので、そちらで判定すれば、上記の問題は解決します。

これで絵文字対応は完了...ということにはなりませんでした。
これには絵文字が導入されてからの歴史が関係します。

絵文字が導入されてから

もともとは日本の携帯電話だけにあったものが、iPhone の登場を筆頭に絵文字は世界的に人気となりました。
人気のコンテンツはより人の目を集め、それによって起こる問題も存在します。
主な例としては、人種差別やLGBT問題などが挙げられるでしょう。

参考記事:「絵文字に平等をサポートしてください」人種差別の指摘にゆれるUnicode

ただ、こういった「複数の肌色を用意してほしい」「男と男の絵文字も用意してほしい」といった要望は、日本の携帯電話での使用をベースに作られた絵文字には含まれていませんでした。

この解決策として、これまでの経緯を振り返ると、新たに4バイト文字として肌色別の絵文字を追加するという方法が考えられますが、これでは絵文字を検索する際に面倒になってしまいます。

そこで取られたのが、複数の絵文字を組み合わせて一つの文字を作るという方法でした。

複数の絵文字からなる絵文字

これまでの話では、たとえ1文字が2バイトであろうと4バイトであろうと1コードポイントで表現されていました。(異体字セレクタだと2コードポイントになりますが...)

ここでいうコードポイントとは、文字の集合の中でどこに配置されているかの位置になります。
これは、Unicode だと、U+○○○ のような形で表現され、単純計算で U+0000 ~ U+10FFFF が存在します。
例えば、「?」だと U+1F389 に該当します。

さて、先ほど挙げた「複数の絵文字からなる絵文字」ですが、これは複数のコードポイントから1文字として扱われます。

具体例を挙げます。
6AA54B84-84B9-403A-9C1F-6E46309D4F35.png

肌色違いなどの絵文字を表現する場合は、人の顔を表現する絵文字に色を表現する絵文字を追加することで表現できます。
また、この画像では2文字の絵文字を結合していますが、3文字4文字を結合して1文字を表現することもあります。(「?‍?‍?‍?」4人家族など)

この現象は簡単に確認ができます。実際に肌色違いを試してみましょう。

?+ ?=??

こちらは上記の画像の上の肌色違い生成式を文字に起こしたものです。
「?」をコピーして検索バーなどの適当の場所に貼り付けた後、「?」をコピーして貼り付けた後ろに「 ?」を貼り付けてください。
肌色違いが生成されたでしょうか?

このように、ある決まった文字が連続で続いた場合、1文字として扱う仕組みがあります。
それが書記素クラスタです。

なお、今回は絵文字を例に挙げていますが、書記素クラスタは「が」などにも利用されています。「か」に濁点の文字が結合されているのです。(1コードポイントの「が」も存在します)

# Ruby
irb(main):001:0> "が".length
=> 2

# C#
> WriteLine("が".Length);
2

Rubyはコードポイント、C#は2バイト単位でカウントするので、「が」は2バイト文字が2つ並んでいることがわかります。(U+304B U+3099

つまり、真に絵文字に対応するということは、こういった1コードポイントを超えた文字にも対応することになります。
これが面倒な理由になります。

各言語での挙動

1コードポイントを超えた文字に対応するためには、前述の通り書記素クラスタを用いる必要があります。
これが使われていなかったり、バージョン違いだったりすると正しく対応することができません。

言語別に挙動を見ていきましょう。

Ruby

まずは Ruby 2.3.8 を試してみます。

補足
Ruby のstring.lengthではコードポイントを数えてしまいます(例:肌色違いならU+1F471 U+1F3FBのため2文字扱い)。
なので、結合してできる肌色違いなどの文字を1文字扱いする正規表現\Xを使用しています。

irb(main):001:0> RUBY_VERSION
=> "2.3.8"
irb(main):002:0> "が".scan(/\X/).count
=> 1
irb(main):003:0> "?‍?‍?‍?".scan(/\X/).count
=> 7

どうやら正しく文字数をカウントすることができなかったようです。

これは Ruby 2.3.8 での正規表現の\Xがこれらの絵文字に対応していないことが原因となります。
\Xが絵文字に対応するのは Ruby 2.4.0 以降になります。

参考:https://docs.ruby-lang.org/ja/latest/doc/news=2f2_4_0.html

そのため、このバージョンで書記素クラスタを用いるためにはActiveSupportを使う必要があります。

irb(main):001:0> require 'active_support/multibyte/unicode'
=> true
irb(main):002:0> ActiveSupport::Multibyte::Unicode.unpack_graphemes("が").count
=> 1
irb(main):002:0> ActiveSupport::Multibyte::Unicode.unpack_graphemes("?‍?‍?‍?").count
=> 4

今度は、書記素クラスタを用いた文字数カウントになりますが、一部変更はかかっているものの、やはりおかしなカウントがされています。
これは、今回使用した ActiveSupport 5.0 が Unicode 8.0.0 以下のバージョンに基づいて文字数カウントを行なうためです。

正しくカウントするためには、Unicode 9.0.0 以降に対応した Ruby か ActiveSupport が必要になります。
なお、Unicode 9.0.0 に対応したのも Ruby 2.4.0 以降になります。

次は Ruby 2.6.2 での挙動を見ていきましょう。

irb(main):001:0> RUBY_VERSION
=> "2.6.2"
irb(main):002:0> "が".scan(/\X/).count
=> 1
irb(main):002:0> "?‍?‍?‍?".scan(/\X/).count
=> 1

Ruby 2.6.2 では正規表現\Xが絵文字に対応しており、Unicode 9.0.0 にも対応しているので、正しく絵文字をカウントできているようです。

C#

次は C# の挙動を見ていきましょう。

補足
C# のstring.Lengthでは char の数を数えています。(例:肌色違いならU+1F471 U+1F3FBで、どちらも4バイト文字なので、2+2で4文字扱い)
なので、4バイト文字や結合してできる肌色違いなどの文字を1文字扱いするStringInfoを使用します。

> System.Console.WriteLine(new System.Globalization.StringInfo("が").LengthInTextElements);
1
> System.Console.WriteLine(new System.Globalization.StringInfo("?‍?‍?‍?").LengthInTextElements);
7

結果が Ruby 2.3.8 で正規表現\Xを使用した時と同じ挙動ですね...
StringInfoのリファレンスを見てみましょう。

リファレンスを見た限り、特に絵文字に関する記述はありません。
ただ、

.NET Framework 4.6.2 では、文字の分類に基づくUnicode 標準、バージョン 8.0.0します。 .NET Framework 4.6.1 から .NET Framework 4 向けに基づくはUnicode 標準、バージョン 6.3.0します。 .NET Core でに基づくはUnicode 標準、バージョン 8.0.0します。

ということなので、どちらにせよStringInfoでも正しく絵文字を取り扱うことが難しそうです。

では、C#ではどうやって絵文字を扱うのか。
厳密にやるためには、外部のライブラリを頼る方法が一番かと思います。

++C++; // 未確認飛行 C さんのサイトで公開されているGraphemeSplitterを使うなどがいいかもしれません。

参考:https://ufcpp.net/blog/2017/10/graphemesplitter/

まとめ

絵文字の歴史と対応方法を紹介しました。
こういった絵文字の記事、閲覧環境によっては正しく表示されないことがあるので辛いところです...

おまけ

冒頭で企画さんから依頼されたもので、

「名前入力の際に絵文字を * でエスケープしてほしい(例:ハッピー:tada:→ハッピー*)」

こちらは最終的に、絵文字は入力を受け付けないという仕様に変更されたため、System.Globalization.UnicodeCategoryにて絵文字関連の文字は弾くという対応になりました。

今回はUnityでの絵文字対応で、最終的にはこういったコードに落ち着きました。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class NewBehaviourScript : MonoBehaviour
{
    [SerializeField] InputField _inputField;

    private void Start()
    {
        _inputField.onValidateInput += ValidateInput;
    }

    private char ValidateInput(string text, int charIndex, char addedChar)
    {
        var category = char.GetUnicodeCategory(addedChar);
        switch (category)
        {
            case System.Globalization.UnicodeCategory.Surrogate:
            /*
            case System.Globalization.UnicodeCategory.Control:
            case System.Globalization.UnicodeCategory.OtherNotAssigned:
            など、その他必要なものがあれば追加
            */
                return '\0';
        }

        return addedChar;
    }
}

参考文献

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UnityでマルチプレイVRを一番簡単に実現する方法

概要

「Unityで一番簡単に実現する方法」シリーズ第二弾です。

前回は「Unity でVRのテレポートを一番簡単な実装」ということで、VRテレポートシステムを作りました。今回はそれを引き続いて行きます。前回をやっていない方はそちらからやっていってください。

今回は前回やったVRテレポートを使って、多人数が同じ空間参加できるようにすることを実現しようと思います。多人数と言ってもMMO的な大規模ではなく、数人レベルで、かつ同じLAN内での利用を想定しています。

実装の順番としては

  1. Forge Networking インストール
  2. マッチング画面シーンのVR対応
  3. Playerオブジェクトの追従
  4. Network Contact Wizardを使ったコード自動生成
  5. Playerクラスの実装
  6. Player のPrefab化
  7. NetworkManager の修正
  8. GameLogic で Player の生成
  9. 確認

という段階を踏んでいきます。

Forge Networking インストール

ここではForge Networking というアセットを使って、マルチプレイを実現します。

f690d7de-56e1-44dc-ac61-fe2740deb2b8.jpg

Asset Store からダウンロードしてインポートしてください。

マッチング画面シーンのVR対応

スタートしていきなり多人数接続というわけにはいかず、ホストとして参加するか、そのホストにつないで参加するか、という選択画面が必要になります。それがいわゆるマッチングというものです。

マッチング画面シーンである

Assets/Bearded Man Studios Inc/Scenes/MultiplayerMenu.unity

を開いてください。このままではデスクトップ画面ですので、これをVR対応する必要があるのですが、ここでは手っ取り早く

  • 何も見えないけどボタンを押す(Connect)
  • ホストに繋がって次のシーンへ遷移する

という適当遷移として実装します。(本来であれば、ホストのIPアドレスを打って、ConnectというUI画面対応をしたほうが良いのですが、ここでは端折ります)

とりあえず、「File→Save As...」でこのシーンを「MultiplayerMenuVR.unity」とかに変更しておきましょう。

そして、

private void Start()
{
    ipAddress.text = "127.0.0.1";
    portNumber.text = "15937";
              :

のipAddress.text の部分のIPアドレスをホストとなるPCのIPアドレスにします。
IPアドレスは コマンドラインから ipconfig(Macならifconfig)で 調べてください。
私の場合は

    ipAddress.text = "192.168.1.12";

こんな感じになります。(皆さんそれぞれで違うはずです)
次に前回も使った

Assets/VRTK/Prefabs/CameraRig/UnityXRCameraRig/InputMappings/UnityXR.OpenVR.RightController.prefab

をシーンにD&D

Unity_2018_3_0f2_-_MultiplayerMenuVR_unity_-_MultiplayTest_-_Android__Metal_.png

そのUnityXR.OpenVR.RightControllerの中の項目を変更します。

  1. UnityXR.OpenVR.RightController>Trackpad>Press[9] を選択
  2. +ボタンをクリック
  3. シーン中の「Canvas」を空いている項目にD&D
  4. No Function から 「MultiplayMenu→Connect」に変更

Unity_2018_3_0f2_-_MultiplayerMenuVR_unity_-_MultiplayTest_-_Android__Metal_.png

これでPadボタンを押すとシーンが遷移するようになりました。

シーンを追加します。

  1. メニューから File → Build Settings...
  2. Add Open Scenes (現在開いているMultiplayMenuVRシーンを追加します)
  3. 追加したMultiplayMenuVRシーンを一番上にD&Dします。

File_と_Item-0_と_Item-0_と_Item-0_と_Item-0_と_Item-0.png

Playerオブジェクトの追従

前回作ったPlayerオブジェクトは動かしていませんでしたね。これを動かしていきましょう。
Gameシーンを再び開いてください。

プレイヤーとして作ったPlayer オブジェクトをカメラの動きに追従するスクリプトを作ります。
FollowCamera.cs というスクリプトを作って、以下のように書きます。

FollowCamera.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class FollowCamera : MonoBehaviour
{
    private Transform _camTrs;

    void Start()
    {
        _camTrs = GameObject.FindWithTag("MainCamera").transform;
    }

    // Update is called once per frame
    void Update()
    {
        this.transform.position = _camTrs.position;
        this.transform.rotation = _camTrs.rotation;
    }
}

これをPlayerオブジェクトにD&Dします。

Network Contact Wizardを使ったコード自動生成

次に、Forge Networkingの「Network Contact Wizard」というツールを使ってNetworkで流すデータを使うためのクラス群を作っていきます。こちらのツールを使えばそれらが自動で作ってくれるので大変便利です。

メニューから Window → Forge Networking → Network Contact Wizard を選択してWizardを立ち上げます

Window_と_Item-0.png

下記画面が出るので、Createをクリックしてください。

空白_Skitch_キャンバス.png

その画面で次のように設定してください。

  1. Name にはネットワークに流すデータ取りまとめの名前を入れます。ここでは "Player" と入れます
  2. +ボタンを押して項目を追加します。2つ作ります
  3. [position , VECTOR4, Interpolate, 0.15] と、[rotation, QUATERNION, Interpolate, 0.15] を項目に代入してください
  4. 「Save & Compile」をクリックして、クラス群を作ります

空白_Skitch_キャンバス.png

ちょっとすると、

Assets/Bearded Man Studios Inc/Generated/UserGenerated/

以下に

PlayerBehavior.cs 
PlayerNetworkObject.cs

というファイルが出来ているはずです。こちらが先程のWizardで自動的に生成されたファイルです。

Playerクラスの実装

ネットワークで位置と回転を同期するためのスクリプトを書きます。
Player.cs というファイルを作って、PlayerゲームオブジェクトにD&Dしてください。

Playerクラスは以下のようなスクリプトにしてください。

Player.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using BeardedManStudios.Forge.Networking.Generated;

// PlayerBehaviorは自動生成されて作られたスクリプトです。そこから継承させています。
public class Player : PlayerBehavior
{
    // Update is called once per frame
    void Update()
    {
        if (!networkObject.IsOwner)
        {
            // ホストじゃなければ、NetworkObjectから渡ってきたデータをそのまま渡す
            this.transform.position = networkObject.position;
            this.transform.rotation = networkObject.rotation;
            return;
        }

        // NetworkObjectに位置と回転を教えてあげる
        networkObject.position = this.transform.position;
        networkObject.rotation = this.transform.rotation;
    }
}

Player のPrefab化

Gameシーン中の、今出来上がったばかりのPlayerオブジェクトをPrefab化します。

  1. 適当なProjectの場所にD&Dすればよいだけです。
  2. Prefab化したシーン中のPlayerオブジェクトは消してしまいます。

Unity_2018_3_0f2_-_Game_unity_-_MultiplayTest_-_Android__Metal_.png

NetworkManager の修正

NetworkManagerというので、ネットワークに繋いだ際にPlayerの生成をしてくれるようです。そこにPlayerオブジェクトを登録しましょう。

Assets/Bearded Man Studios Inc/Prefabs/NetworkManager.prefab

を選択します。(Unity 2018以降の場合は「Open Prefab」クリックして中身を見ます)
このPrefabを直接いじっていきます。

そこの「Player Network Object」のSize を「1」にして、先程作ったPlayer PrefabをD&Dします。

Unity_2018_3_0f2_-_Game_unity_-_MultiplayForge_-_Android__Metal_.png

GameLogic で Player の生成

いよいよ最後のステップです。
NetworkManagerを介してPlayerオブジェクトを生成します。

「GameLogic」という空のゲームオブジェクトを作り、GameLogic.cs というスクリプトを作ります。

GameLogic.cs
using UnityEngine;
using BeardedManStudios.Forge.Networking.Unity;

public class GameLogic : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        NetworkManager.Instance.InstantiatePlayer();
    }
}

それをGameLogicゲームオブジェクトにD&Dします。
これでこのシーンに遷移したと同時にPlayerオブジェクトが生成されるということです。

確認

さあ、これで全部終わりました。
確認していきましょう。

まずはBuild & Run でOculus Go で実行しておきます。

インストールしてUnityロゴが表示されてアプリが立ち上がったら、ちょっと一旦何もせずにUnity Editorに戻りましょう。

GO02.gif

Unity Editor上でMultiplayMenuVRを開いて、Playします。

そして、UIの一番下に配置している「(h)Host(〜」をクリックします。

Unity_2018_3_0f2_-_MultiplayerMenuVR_unity_-_MultiplayTest_-_Android__Metal_.png

そして、今度はOculus Goでハンドデバイスのパッドを押して、ホストに繋ぎます。
すると、Oculus Go では普通にテレポート出来ていると思います。

GO04.gif

Unity Editorでは、Oculus Go で動いているプレイヤーの様子が第三者視点で見れるかと思います。

GO03.gif

もしOculus Go が2台ある場合は、そちらも同様に参加してみてください。
Unity Editor上に2台のプレイヤーの様子がわかるかと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unity2018.4.0f1でシリアル通信できない問題

はじめに

ArduinoをUnityとつなげていろいろしていて,LTS版が出たことを機にUnity2018に環境を移行させようとしたところエラーが発生したのでメモっときます.

環境

  • OS:Windows10
  • Unity2018.4.0f1
  • Arduino Uno

なお,UnityとArduinoの接続に関してはこちら「UnityとArduinoをシリアル通信」にお世話になっております.

エラー内容

いつものようにシリアル通信を行うためにAPI Compatibility Levelを.NET 2.0 Subnetから.NET2.0へ変更
(やり方は,こちらを参考にお願いします.)
(Unity2018.4.0f1ではAPI Compatibility LevelがOptimizationではなくConfigurationにある点に注意)

そして,「UnityとArduinoをシリアル通信」からお借りした「SerialHandler.cs」をAdd Componentしたところ...

Consoleに以下のような表示が

Assets\SerialHandler.cs(3,17): error CS0234: The type or namespace name 'Ports' does not exist in the namespace 'System.IO' (are you missing an assembly reference?)
Assets\SerialHandler.cs(20,13): error CS0246: The type or namespace name 'SerialPort' could not be found (are you missing a using directive or an assembly reference?)

erro1.PNG

また,「SerialHandler.cs」をVS2017で開くと,erro2.PNG

思いっきりエラーが出てます.

要約すると「System.IOにPertsってやつなんていないよ!」ってことなので,用意してあげたいと思います.

解決方法

調べたら,Microsoftの公式でUnityに.NETのdllファイルを新たに入れてあげる方法がありました.

「Unity で.NET 4.x を使用する」

上記リンクの「.NET との互換性を利用する」ってところを参考に行います.

  1. このリンクから「system.io.ports.4.6.0-preview5.19224.8」をダウンロード(2019/05/24現在のファイル名です.)して,拡張子を「.nupkg」から「.zip」へ変更し,解凍
  2. 解凍したフォルダ内の lib/netstandard2.0 に移動し、System.IO.Ports.dll をコピー
  3. Unity プロジェクトの Assets フォルダに、Plugins という名前の新しいフォルダを作成し,System.IO.Ports.dll ファイルをペースト
  4. さっきと同じAssetsフォルダに link.xml という以下のファイルを作成
link.xml
<linker>
  <assembly fullname="System.Core">
    <type fullname="System.Linq.Expressions.Interpreter.LightLambda" preserve="all" />
  </assembly>
</linker>

以上です.

結果

もう一回「SerialHandler.cs」をVS2017で開くと
safe1.PNG
エラー表示が消えました~

UnityのConsoleにも表示が消えていると思います!
いやー,無事解決できてよかった!

(追記)
実は, Edit > Project Setting > Player > Inspector > Other Settings > Configuration にある Scripting Runtime Version を .NET 3.5 Equivalent に変更してもエラーは消えます.
.NET 4 にこだわる必要がない方はそっちでもいいかもしれませんね (-_-;)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む