- 投稿日:2021-01-25T21:44:49+09:00
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)
#toio でmml再生するやつ、Qiita用に再収録。 pic.twitter.com/5fMeVUGt5P
— ヅラChu (@zurachu) January 25, 2021技術情報
MMLのパース
C#で書かれたMMLPlayer(パーサ)を教えていただいたので、こちらを利用しました。
https://github.com/Enichan/textplayerUnity projectのAssets内に入れたら、問題なくビルド通ります。
TextPlayer.MML.MMLPlayerを継承したclassを作成して、PlayNote()をoverrideし、受け取ったNoteからCube.SoundOperationに変換してtoioキューブに再生指示を出します。
ToioCubeMmlPlayer.cspublic 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キューブと通信を行うため、同時に他の通信を行うとラグが出る可能性はあります。
- 投稿日:2021-01-25T20:45:43+09:00
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");
- 投稿日:2021-01-25T18:49:42+09:00
オブジェクトのテクスチャを送信して、画像として保存する
Unityでホワイトボードのようなもの作ってた時に使ってた。
大まかな流れ
1.対象のGameObjectを取得。
2.保存したいTextureを取得。
3.取得したTextureをバイト配列に変換。
4.サーバ上のphpに送信する。
5.phpで受信したバイト配列を画像に戻して保存する。
→受信側のphpは以前書いた記事そのまんまなので以下参照
pythonで画像を送信、phpで保存する
以上。全体のソースコード
SendPic.csusing 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(); } }実際やるとどんな感じなの
お絵かきして送信する。
↓
WinSCP(FTPツール)で見てみるとこんな感じ。ちょっと細くなってるのは落書きに使ったPlaneは1.5倍に引き伸ばされてたため。
参考
・Photon Unity Networking 2 (PUN2) のRPCとRaiseEventを使ってテクスチャデータを送信する
https://nabla-tech-lab.hatenablog.com/entry/2019/05/15/180000
- 投稿日:2021-01-25T11:22:28+09:00
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();
- 投稿日:2021-01-25T10:25:04+09:00
【Unity】Prefab/Nested Prefabs/Prefab Variant の違いについてまとめる
初めに
Preafab Variant という名前は知っているが、使ったことないので Prefab 全体について勉強した内容をまとめた記事です。
Prefab とは?
作成済みの GameObject をテンプレートとして保存し、複製して使用することができる機能です。
例えば、体力や攻撃力などをスクリプトなどで設定した Enemy オブジェクトを作成したとします。
そして Enemy オブジェクトを Prefab 化します。そうすると、 Enemy オブジェクトを簡単に複製することができます。(インスタンス化という。)
もちろん何個でも複製可能です。インスタンス化したオブジェクトは、スクリプトなどで設定した値を変更することも可能です。
Prefab 化する方法
作成した GameObject を Hierarchy にドラッグ&ドロップすれば作成できます。
Prefab をインスタンス化する方法
Project から Hierarchy にドラッグ&ドロップすればインスタンス化できます。
インスタンスから Prefab を上書きする方法
インスタンス化したオブジェクトから、Prefab に対して上書き(Override)することができます。
Override するには、 まずインスタンスの Inspector から編集します。
そのあとは、二種類の上書き方法があります。1. Override したプロパティを右クリック → "Apply to Prefab 'Prefab名'"
2. Overridesドロップダウンから Override したプロパティを選択して Apply ボタンを押下
3. Overridesドロップダウンから Apply All ボタンを押下※ 言葉通り、上2つとは異なり、変更したプロパティの全てを上書きします。
インスタンスで上書きできるもの
- プロパティの値の変更
- コンポーネントの追加/削除
- GameObjectの追加
インスタンスで上書きできないもの
- GameObjectの削除
- 階層構造を変更できない
- Open Prefab から直接編集する必要がある
直接 Prefab を編集する
Hierarchy から編集する方法の他に、 Prefab モードから編集することもできます。
Preafab モードを表示する方法は、2種類あります。1. Project から Prefab を選択 → Open Prefab ボタンを押下
2. Hierarchy から Prefab を選択 → 「>」を押下
Prefab モードでの変更は、 Auto Save にチェックが付いていれば、自動で保存されます。
チェックが付いていなければ、自分で Save ボタンを押すことで保存できます。
Nested Prefabs とは?
名前の通りですが、 Prefab は入れ子にすることができます。
Prefab の階層内に別の Prefab が存在している状態です。スクショは、 "Enemy1" Prefab に "Enemy2" Prefab が入れ子になっています。
この状態で、 Apply All ボタンを押下すると、以下のように、Prefab の中に Prefab が入っている状態で Prefab 化することができます。
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 を用意します。
EnemyManager の内容は以下です。
(例なので最小限しか書いておらず、特に機能はないです。hp と attack を定義しているだけです。)EnemyManager.csusing UnityEngine; public class EnemyManager : MonoBehaviour { [SerializeField] int hp; [SerializeField] int attack; }Preafab Variant でザコ敵のシャドウを作成
Prefab 化した BaseEnemy を Hierarchy から Project にドラッグ&ドロップします。
そうすると、新しい Prefab を作成するか、 Prefab Variant を作成するかダイアログで問われます。
今回は Prefab Variant を選択します。
あとは、 Prefab を変更するように、 Prefab Variant を編集します。
名前は "ShadowVariant" 、HP = 10とAttack = 2に変更します。
ちなみに、 Prefab Variant に適用した変更を Prefab にも上書きしたい場合は、 Prefab と同じ手順で上書きすることができます。Preafab Variant でそこそこ強い敵ギガースを作成
せっかくなので上とは違う Prefab Variant の作成方法でやってみます。
Prefab を右クリック > Create > Prefab Variant で作成できます。
名前は "GigasEnemy"、HP = 20000とAttack=5000に変更します。
Prefab とのインスタンスの接続解除
インスタンス化したが、 Prefab との紐付けを解除する方法もあります。
右クリック > Unpack Prefab で、紐付けを解除できます。ちなみに 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
- 投稿日:2021-01-25T06:37:44+09:00
【Unity(C#)】お互いに正面で顔を合わせているのを内積(Vector3.Dot)で判定を行う
この記事は、こちらの記事を参考にお互いが正面向き合ってる状態で、判定する内容になります。
内積、Vector3.Dot、normarizedについての説明は、参考記事内にあるため省略します。
【Unity(C#)】Rayではなく内積(Vector3.Dot)で視線判定を行う記述が増えてしまう為、お互いの頭のゲームオブジェクトは今回Inspecterからアタッチしてます。
概ね頭のある場所は、キャラクターオブジェクト>Root>Hips>Spine_1>Spine_2>Neck>Headと言った感じの階層です。
取り敢えず、どんなもんか気になる人向けに先にコードを出します。コード
LookMatch.csusing 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{ //距離が離れている時の処理を記述 } } }デモ ~顔が合うと?マークを表示する~
以上の状態であると?マークが表示されるようになります。
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離れても判定する事に・・・顔が向き合っていれば胴はどうでもいい