20210125のUnityに関する記事は7件です。

toio SDK for UnityでMMLを再生する

概要

toio SDK for UnityのMIDI note number再生機能で、MML(Music Macro Langauge)を再生しました。

GitHub repository

https://github.com/zurachu/toio-mml/

WebGL sample

toioキューブ実機が必要です。
3台まで接続可能にしています(当方2台しか持っていないので3台は動作未確認)
https://zurachu.github.io/toio-karuta/

Google Chrome推奨、あと別タブに切り替えるとパフォーマンスが落ちて発音のタイミングがズレズレになるので注意です。

動画(Twitter)

技術情報

MMLのパース

C#で書かれたMMLPlayer(パーサ)を教えていただいたので、こちらを利用しました。
https://github.com/Enichan/textplayer

Unity projectのAssets内に入れたら、問題なくビルド通ります。

TextPlayer.MML.MMLPlayerを継承したclassを作成して、PlayNote()をoverrideし、受け取ったNoteからCube.SoundOperationに変換してtoioキューブに再生指示を出します。

ToioCubeMmlPlayer.cs
public class ToioCubeMmlPlayer : MMLPlayer
{
    // (中略)

    protected override void PlayNote(Note note, int channel, TimeSpan time)
    {
        var durationMs = (int)note.Length.TotalMilliseconds;
        var volume = (byte)(note.Volume * 255);
        var noteNumber = (byte)(noteMap[note.Type] + note.Octave * noteNumberPerOctave);
        if (note.Sharp)
        {
            noteNumber++;
        }

        var operations = new Cube.SoundOperation[]
        {
            new Cube.SoundOperation(durationMs: durationMs, volume: volume, note_number: noteNumber)
        };

        cube.PlaySound(1, operations);
    }
}

Cube.SoundOperationの同時指定数は最大59ですが、MMLPlayerによって、必要なタイミングで1音の指定が送られる→1音ぶんのCube.SoundOperationを送る、で良くなっているので、Operationの最大数を考慮する必要は無いです。
また、所謂「テンポずれ」問題も起こらないです。
一方で、都度toioキューブと通信を行うため、同時に他の通信を行うとラグが出る可能性はあります。

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

Unity公式のLocalizationを軽めに使う

概要

LocalizationというpackageがUnity公式で提供されています(preview)
まだ情報が少なかったため、とりあえず軽くプロジェクトに入れてみる方法をメモしておきます
導入などは公式がわかりやすいです

環境

Unity2020.2.1f1
Localization 0.9.0-preview

軽く使ってみる

uGUIのTextをローカライズするだけだったらInspectorからLocalizeStringEventをアタッチして、表示する文字を選択するだけです
今回は動的に変更したい時にどうするかをメインに記載します
※StringだけでSpriteなどには触れません

var tableName = "TableName";// 作成したテーブル名を設定
var key = "FOO_KEY";// 取得したい文字のキー

var stringTable = await new LocalizedStringTable {TableReference = tableName}.GetTable().Task;
var stringResult = GetEntry(key).GetLocalizedString();

裏でアセットをロードしたりするためかawait必須になっています
StringTableはSerializeFieldでInspectorから指定できるため、awaitを回避できますが、毎回指定するのは面倒です

細かいところは無視して使う例

まずはコードだけ

public static class Localize
{
    private static StringTable StringTable;

    public static async UniTaskVoid LoadStringTableAsync()
    {
        StringTable = await new LocalizedStringTable {TableReference = "StringTable"}.GetTable().Task;
    }

    public static string GetLocalizedString(string key)
    {
        return StringTable.GetEntry(key).GetLocalizedString();
    }
}

使い方は下記です
とりあえず入れてみようという気になって知見が増えると嬉しいです

// アプリ起動時や言語選択時にロードしておく
// キャンセルなどはうまいことやる
await Localize.LoadStringTableAsync();

// 下記でよしなに取得できる
Localize.GetLocalizedString("BAR_KEY");
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

オブジェクトのテクスチャを送信して、画像として保存する

Unityでホワイトボードのようなもの作ってた時に使ってた。

大まかな流れ

1.対象のGameObjectを取得。
2.保存したいTextureを取得。
3.取得したTextureをバイト配列に変換。
4.サーバ上のphpに送信する。
5.phpで受信したバイト配列を画像に戻して保存する。
→受信側のphpは以前書いた記事そのまんまなので以下参照
pythonで画像を送信、phpで保存する
以上。

全体のソースコード

SendPic.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using UnityEngine.Networking;

public class SavePic : MonoBehaviour
{
    Texture2D texture;
    byte[] picData;

    public void SendPic()
    {
    //1.対象のGameObjectを取得。
    //Find()だったり、FindGameObjectWithTagだったり、インスペクターから直接指定したりなどなど
        GameObject _wb = GameObject.FindGameObjectWithTag("Whiteboard");

    //2.保存したいTextureをGameObjectから取得し、
    //3.取得したTextureをバイト配列に変換。
        texture = (Texture2D)_wb.GetComponent<Renderer>().material.mainTexture;

    //SendDataを呼び出す(コルーチンというらしい)
        StartCoroutine(SendData(picData));
    }
    IEnumerator SendData(byte[] postData)
    {
    //4.サーバ上のphpに送信する
        String url = "https://hagehoge/pic_save.php";//httpにandroid端末から送る場合はパーミッション関連で何か必要だったような・・・
        var request = new UnityWebRequest(url, "POST");
        request.uploadHandler = (UploadHandler)new UploadHandlerRaw(postData);
        request.downloadHandler = (DownloadHandler)new DownloadHandlerBuffer();
        request.SetRequestHeader("Content-Type", "image/png");

        yield return request.Send();
    }
}

実際やるとどんな感じなの

お絵かきして送信する。
image.png

WinSCP(FTPツール)で見てみるとこんな感じ。ちょっと細くなってるのは落書きに使ったPlaneは1.5倍に引き伸ばされてたため。
image.png

参考

・Photon Unity Networking 2 (PUN2) のRPCとRaiseEventを使ってテクスチャデータを送信する
https://nabla-tech-lab.hatenablog.com/entry/2019/05/15/180000

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

unityでアセットが正常に読み込まれなくなった?

[Assets]->[Reimport All]

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

Unity 子オブジェクトのチェック(非アクティブ含む)・Component削除

全Prefabの子オブジェクトからTextMeshProを含むものを探し出す

// Resources内からPrefabを全検索
var allPrefabs = AssetDatabase.FindAssets("t:Prefab", new string[] { "Assets/Resources" });

foreach (var guid in allPrefabs)
{
    var path = AssetDatabase.GUIDToAssetPath(guid);

    GameObject currentObject = AssetDatabase.LoadAssetAtPath<UnityEngine.GameObject>(path);

    // trueを入れると非アクティブも含めて全て取得
    var textMeshProUGUIArray = currentObject.GetComponentsInChildren<TextMeshProUGUI>(true);

    if (textMeshProUGUIArray.Length > 0)
    {
            // ※0より大きければデータ存在する。
    }
}



指定したComponentが親オブジェクトにアタッチされている場合、削除する。

// Resources内からPrefabを全検索
var allPrefabs = AssetDatabase.FindAssets("t:Prefab", new string[] { "Assets/Resources" });


foreach (var guid in allPrefabs)
{
    var path = AssetDatabase.GUIDToAssetPath(guid);

    GameObject currentObject = AssetDatabase.LoadAssetAtPath<GameObject>(path);

    // アタッチされているかをチェック
    if (currentObject.GetComponent<アタッチ済みクラス名>() != null)
    {
        // Trueを入れると削除可能になる。
        DestroyImmediate(currentObject.GetComponent<アタッチ済みクラス名>(),true);
    }
}

// 変更を保存する
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();


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

【Unity】Prefab/Nested Prefabs/Prefab Variant の違いについてまとめる

初めに

Preafab Variant という名前は知っているが、使ったことないので Prefab 全体について勉強した内容をまとめた記事です。

Prefab とは?

作成済みの GameObject をテンプレートとして保存し、複製して使用することができる機能です。

例えば、体力や攻撃力などをスクリプトなどで設定した Enemy オブジェクトを作成したとします。
そして Enemy オブジェクトを Prefab 化します。

そうすると、 Enemy オブジェクトを簡単に複製することができます。(インスタンス化という。)
もちろん何個でも複製可能です。

インスタンス化したオブジェクトは、スクリプトなどで設定した値を変更することも可能です。

Prefab 化する方法

作成した GameObject を Hierarchy にドラッグ&ドロップすれば作成できます。
Prefab化.gif

Prefab をインスタンス化する方法

Project から Hierarchy にドラッグ&ドロップすればインスタンス化できます。
インスタンス化.gif

インスタンスから Prefab を上書きする方法

インスタンス化したオブジェクトから、Prefab に対して上書き(Override)することができます。

Override するには、 まずインスタンスの Inspector から編集します。
そのあとは、二種類の上書き方法があります。

1. Override したプロパティを右クリック → "Apply to Prefab 'Prefab名'"
image.png
2. Overridesドロップダウンから Override したプロパティを選択して Apply ボタンを押下
image.png
3. Overridesドロップダウンから Apply All ボタンを押下

※ 言葉通り、上2つとは異なり、変更したプロパティの全てを上書きします。
image.png

インスタンスで上書きできるもの

  • プロパティの値の変更
  • コンポーネントの追加/削除
  • GameObjectの追加

インスタンスで上書きできないもの

  • GameObjectの削除
  • 階層構造を変更できない
    • Open Prefab から直接編集する必要がある

変更しようとすると ↓ のダイアログが表示される。
image.png

直接 Prefab を編集する

Hierarchy から編集する方法の他に、 Prefab モードから編集することもできます。
Preafab モードを表示する方法は、2種類あります。

1. Project から Prefab を選択 → Open Prefab ボタンを押下
image.png
2. Hierarchy から Prefab を選択 → 「>」を押下
image.png

Prefab モードでの変更は、 Auto Save にチェックが付いていれば、自動で保存されます。
image.png
チェックが付いていなければ、自分で Save ボタンを押すことで保存できます。
image.png

Nested Prefabs とは?

名前の通りですが、 Prefab は入れ子にすることができます。
Prefab の階層内に別の Prefab が存在している状態です。

スクショは、 "Enemy1" Prefab に "Enemy2" Prefab が入れ子になっています。
image.png

この状態で、 Apply All ボタンを押下すると、以下のように、Prefab の中に Prefab が入っている状態で Prefab 化することができます。
image.png

Prefab Variant とは?

Unity 公式には、

プレハブバリアントは、一揃いの事前定義されたプレハブのバリエーションを使用したい場合に便利です。

どういうことかというと、「構成はほぼ一緒だが、一部の設定だけがベースの Prefab と異なる」といった場合に便利ということです。
例えば、 BaseEnemy という Prefab から、HPと攻撃力が異なる様々な種類の敵オブジェクトを作りたいときなど便利です。

Unity 2018.3からの新機能らしいです。

実際に使ってみます。

今回は HP と Attack というプロパティを持つ BaseEnemy (Prefab) を元に、以下の Prefab Variant を作成するという例でやってみます。

  • 名前:シャドウ(ザコ敵)
    • HP: 10
    • Attack: 2
  • 名前:ギガース(そこそこ強い敵)
    • HP: 20000
    • Attack: 5000

BaseEnemy (Prefab) を用意

上で記載した Prefab 化を元に、BaseEnemy という名前の Prefab を用意します。
image.png
EnemyManager の内容は以下です。
(例なので最小限しか書いておらず、特に機能はないです。hp と attack を定義しているだけです。)

EnemyManager.cs
using UnityEngine;

public class EnemyManager : MonoBehaviour
{
    [SerializeField] int hp;
    [SerializeField] int attack;
}

Preafab Variant でザコ敵のシャドウを作成

Prefab 化した BaseEnemy を Hierarchy から Project にドラッグ&ドロップします。
そうすると、新しい Prefab を作成するか、 Prefab Variant を作成するかダイアログで問われます。
今回は Prefab Variant を選択します。
プレファブバリアント.gif
あとは、 Prefab を変更するように、 Prefab Variant を編集します。
名前は "ShadowVariant" 、HP = 10とAttack = 2に変更します。
image.png
ちなみに、 Prefab Variant に適用した変更を Prefab にも上書きしたい場合は、 Prefab と同じ手順で上書きすることができます。

Preafab Variant でそこそこ強い敵ギガースを作成

せっかくなので上とは違う Prefab Variant の作成方法でやってみます。
Prefab を右クリック > Create > Prefab Variant で作成できます。
image.png
名前は "GigasEnemy"、HP = 20000とAttack=5000に変更します。
image.png

Prefab とのインスタンスの接続解除

インスタンス化したが、 Prefab との紐付けを解除する方法もあります。
右クリック > Unpack Prefab で、紐付けを解除できます。

image.png

ちなみに Prefab Variant を Unpack Prefab すると、 Prefab になり、
Prefab を Unpack Prefab すると、全ての Prefab の紐付けを解除できます。

Prefab Variant を Unpack Prefab Completely すると、一気に全ての紐付けを解除できます。
また、子階層に Prefab がいる場合も、 Unpack Prefab Completely すると、一気に紐付けを解除できます。

終わりに

最初は Prefab Variant について知りたくて、調べていました。
そのついでに Prefab とか改めて調べ直したのですが、結構新しいことを知ることができてよかったです。
Unity の基礎をもう少し深堀りしてもいいかなーと思いました。

ちなみに、敵の元ネタはキンハーです。

参考文献

https://dkrevel.com/makegame-beginner/prefab-nested/
https://light11.hatenadiary.com/entry/2019/01/20/205726
https://docs.unity3d.com/ja/2018.4/Manual/PrefabVariants.html

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

【Unity(C#)】お互いに正面で顔を合わせているのを内積(Vector3.Dot)で判定を行う

この記事は、こちらの記事を参考にお互いが正面向き合ってる状態で、判定する内容になります。
内積、Vector3.Dot、normarizedについての説明は、参考記事内にあるため省略します。
【Unity(C#)】Rayではなく内積(Vector3.Dot)で視線判定を行う

記述が増えてしまう為、お互いの頭のゲームオブジェクトは今回Inspecterからアタッチしてます。
概ね頭のある場所は、キャラクターオブジェクト>Root>Hips>Spine_1>Spine_2>Neck>Headと言った感じの階層です。
取り敢えず、どんなもんか気になる人向けに先にコードを出します。

コード

LookMatch.cs
using UnityEngine;
public class LookMatch : MonoBehaviour
{
    [SerializeField]private GameObject playerHead; //プレイヤーの頭
    [SerializeField]private GameObject targetHead; //対象となる相手の頭
    [SerializeField]private float distance;        //判定に必要な距離
    // Start is called before the first frame update
    void Start()
    {

    }
    // Update is called once per frame
    void Update()
    {
        //互いの距離が一定距離内だったら判定する。
        float mag = (targetHead.transform.position - playerHead.transform.position).magnitude;
        if (distance >= mag)
        {
            //ターゲットからこちらの方向へ正規化したベクトルを作成
            Vector3 targetToCharaDirection = (playerHead.transform.position - targetHead.transform.position).normalized;
            if (Vector3.Dot(targetToCharaDirection, playerHead.transform.forward.normalized) < -0.9      //正規化したベクトルの内積がプレイヤーの頭から一定以下
                && Vector3.Dot(targetToCharaDirection, targetHead.transform.forward.normalized) > 0.9    //正規化したベクトルの内積が相手の頭から一定以上 
                && targetHead.transform.InverseTransformPoint(playerHead.transform.position).z >= 0)    //対象にとって正面側にいるかどうか
            { 
                //顔が向き合っている時の処理内容を記述
            }else{
                //顔が向き合ってない時の処理内容を記述
            }

        }else{
            //距離が離れている時の処理を記述
        }
    }
}

デモ ~顔が合うと?マークを表示する~

顔が合う.gif
・一定距離にいて
・顔も向き合っている

以上の状態であると?マークが表示されるようになります。

InverseTransformPoint

Transform.InverseTransformPoint
ワールド空間からローカル空間へ position を変換します。

これにより、プレイヤーの頭が対象の頭にとってローカル空間にpositionが変換され
前方側(z >= 0 )にいる状態になります。
Vector3.Dot(targetToCharaDirection, targetHead.transform.forward.normalized) > 0.9  //正規化したベクトルの内積が相手の頭から一定以上
によりあまりいらない気もします。

magnitude

Vector3.magnitude
ベクトルの長さ(読み取り専用)
ベクトルの (x x+y y+z* z) の平方根の長さを返します。

今回の使い方としては、対象の頭の位置とプレイヤーの頭の位置を差し引いた時の距離の値が
distanceに設定した値より小さければ距離の判定を満たす形になっています。
そうしないと、数百m離れても判定する事に・・・

顔が向き合っていれば胴はどうでもいい

顔.PNG

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