- 投稿日:2019-07-08T23:15:57+09:00
Oculus Questで物理キーボードを使用する
はじめに
Oculus Questで文字入力する場合には、通常ソフトウェアキーボードが表示されて入力することになるのですが、あまり入力しやすくはないように思います。
そこで物理キーボードを使えないか調べてみたところ、Bluetoothでキーボード接続を実現されている方がいて、やり方を公開されていました。
Oculus Quest も文章書き&開発マシンにするOculus Questをつけた状態では当然キーボードが見えないのですが、自分はブラインドタッチは無理だったので、キーボードのモデルを作って物理キーボードと位置合わせして入力してみました。
開発環境
Unity 2019.1.9f1
Oculus Integration for Unity - 1.37
TVLauncherGo 1.0
Logicool K380BK Bluetooth ワイヤレス キーボード動作例
キーボードのモデルの位置を、物理キーボードの位置と合わせて・・・
全然正しく入力できてません。
位置が微妙にずれているためか、思ったようにキーボードが押せていないです。所感
キーボードのモデルは写真をテクスチャとして張り付けただけですが、VR空間中でのサイズが物理キーボードのサイズと一致するようにする調整が若干面倒でした。後から気が付いたのですが、VR空間内で調整すれば簡単だったかも・・
このままではとても実用に耐えないので、やはりソフトウェアキーボードを使うことになりそうです。
- 投稿日:2019-07-08T20:22:37+09:00
Airtableでマスターデータを作ってUnityから読む
Airtable
ゲームのマスターデータ、何のツールで管理してますか?
Excel? GoogleSpreadsheet?
Airtableっていうめっちゃ便利なサービスがあります。GoogleSpreadsheetをマスターデータに使ってる!とか、DBみたいに使ってるならAirtableめっちゃオススメ
— きゅぶんず (@kyubuns) July 8, 2019
制約が必ず列単位でつくから「この行だけ設定違う?」ってなんないし
同じデータから別の見え方View作ったり出来るし
本当に求めていたものって感じhttps://t.co/JQMhyOQT5g pic.twitter.com/KkFd9UHzy7他テーブルの行の参照つっこんだり、画像突っ込んだりも出来る pic.twitter.com/uJ7aVoUJ1s
— きゅぶんず (@kyubuns) July 8, 2019マスターデータを作る
さっそくマスターデータを作ります。
「この列は数字だけ!」とかの制約がかけれたり、Filterがかけれたりしてめっちゃ便利!っていうかDBです。Unityから読む
めっちゃ分かりやすいAPIドキュメントがあります。
ApiKeyとBase(ドキュメントのID的なもの)もここから見れます。
https://airtable.com/api本家.netクライアント : ngocnicholas/airtable.net
をクローンしてちょっといじったやつ : kyubuns/airtable.net
今回はちょっといじったやつ前提で書きます。
AirtableApiClientディレクトリ以下をUnityに突っ込んで、Json.netもどこかから調達してきてください。
HttpUtility.UrlEncodeが無い!って言われたときは、この記事の一番下を見てください。あとはこれだけでデータが取れます。
using (var table = new AirtableBase(ApiKey, Base)) { var response = await table.ListRecords<Player>("Player"); foreach (var r in response.Records) { var player = r.Fields; Debug.Log($"{player.Lv} - {player.HP}"); } } public class Player { public int Lv; public int HP; }1回の呼び出しにつき100行までしか取ってこれないので、1テーブルが100行以上になりえるときはこんな感じ。
string offset = null; do { var response = await table.ListRecords<Player>("Player"); if (!response.Success) break; foreach (var r in response.Records) { var player = r.Fields; Debug.Log($"{player.Lv} - {player.HP}"); } } while (offset != null);注意点
The API is limited to 5 requests per second.
1秒間に5リクエストで制限かかるので、ユーザーが直接読むのには向いてなさそうです。
この制約がキツい場合はお問い合わせくださいって書いてあるので、有料プランだときっと上がるんでしょう。きっと。おまけ
HttpUtility.UrlEncodeが無い時用。
using System; namespace AirtableApiClient { public static class HttpUtility { public static string UrlEncode(string stringToEscape) { return Uri.EscapeDataString(stringToEscape); } } }
- 投稿日:2019-07-08T18:35:23+09:00
【Unity】入室可能なルームが存在するかチェックする方法【モノビットエンジン入門】
はじめに
どうもnisokaです。
前回に引き続き、オンライン開発の初心者がオンラインゲームを作る過程をお届けします。
対象読者さんはオンライン開発を始めたばかりの方ですので、かなり初心者向けです。以前の記事【モノビットエンジンでかんたんサーバ接続】にて、ルーム作成&入室のかんたんな流れとその方法について書きました。
今回はその内容を少しだけ発展させ、入室可能かどうかチェックする処理を作ります。環境は以下の通りです
win10 64bit, Unity2018.3.3f1, Monobit Unity Networking2.0 v2.6やること
- 入室可能なルームが存在するかチェックする
- 存在していたらランダム入室する
- 存在していなかったら新しく部屋を作る
実装方法
ルーム入室前のロビー入室が完了したタイミングでチェックを行いたいので、
下記のコールバックに処理を書きます。ロビーに入室したときに呼ばれるコールバックpublic void OnJoinedLobby() { // この中に処理を書く }チェックする処理
まず、IsEnableEnterRoom()で入室可能な部屋があるか調べます。
存在していたらMonobitNetwork.JoinRandomRoom()でランダム入室、存在していなかったらCreateRoom()でルームを作成しています。
System.Guid.NewGuid().ToString("N").Substring(0, 10).ToUpper()は10文字以内のランダムな文字列を作成しています。
MAX_ROOM_PLAYERS_COUNTはルームの最大入室人数です。チェックする処理if (IsEnableEnterRoom()) { // 存在したのでランダム入室する MonobitNetwork.JoinRandomRoom(); } else { // 存在しなかったので部屋を新しく作る string randomRoomName = System.Guid.NewGuid().ToString("N").Substring(0, 10).ToUpper(); CreateRoom(randomRoomName, MAX_ROOM_PLAYERS_COUNT); }入室可能な部屋があるか調べる
MonobitNetwork.GetRoomData()で存在するすべてのルームを取得し、クローズされていたらスルーします。
roomData.playerCount < roomData.maxPlayersは人数を比較し、入れる余地があったらTrueを返します。入室可能な部屋があるか調べる関数private bool EnableEnterRoom() { foreach (RoomData roomData in MonobitNetwork.GetRoomData()) { // クローズされている部屋だったら if(roomData.open == false) { // 次を見る continue; } if (roomData.playerCount < roomData.maxPlayers) { return true; } } return false; }ルームを作成する
MonobitNetwork.CreateRoom()でルーム作成要求をしています。
第2引数のRoomSettingsはルームカスタムパラメータと言って、公開/非公開や、オープン/クローズ、最大人数などを設定できます。ルームを作成する関数private bool CreateRoom(string roomName, int maxPlayer) { if (string.IsNullOrEmpty(roomName)) return false; if (maxPlayer <= 0) return false; // ルームカスタムパラメータ設定 RoomSettings roomSetings = new RoomSettings() { isVisible = true, isOpen = true, maxPlayers = (uint)maxPlayer, roomParameters = null, lobbyParameters = new string[] { } }; return MonobitNetwork.CreateRoom(roomName, roomSetings, null); }最後に
MUNを使ったルームマッチング機能の全容を書いた記事がまだないので、今後近いうちにお届けしたいと思います。
下記リンクにMUNのもっと詳しい使い方などが載っていますので、気になった方は是非そちらもご覧ください。参考リンク
- 投稿日:2019-07-08T15:52:48+09:00
バージョン番号(string)の比較 (C#)
やりたいこと
- バージョン番号のようなドット区切り十進表記の文字列を比較したい。
- 一括比較したいだけなので、
System.Version
クラスを使ってまではやりたくない。コード
自前実装
/// <summary>ドット区切り数字列を比較する</summary> public static int CompareVersionString (this string a, string b) { var aSeries = a.Split ('.'); var bSeries = b.Split ('.'); int aNum, bNum; for (var i = 0; i < aSeries.Length; i++) { if (i >= bSeries.Length) { return 1; } aNum = 0; int.TryParse (aSeries [i], out aNum); bNum = 0; int.TryParse (bSeries [i], out bNum); if (aNum > bNum) { return 1; } else if (aNum < bNum) { return -1; } } return (aSeries.Length == bSeries.Length) ? 0 : -1; }こっそりと
System.Version
を使う/// <summary>ドット区切り数字列を比較する</summary> public static int CompareVersionString (this string a, string b) { return (new System.Version (a)).CompareTo (new System.Version (b)); }
- 比較のためにだけVersionクラスを使います。(本末転倒)
using system;
を書かない辺りが「こっそり」です。使用例
#if UNITY_* Debug.Log ("1.1.12".CompareVersionString ("1.1.12.0")); #else Console.WriteLine ("1.1.12".CompareVersionString ("1.1.12.0")); #endif // -1
- 実際には
UNITY_*
という記述はできません。仕様
- 以下の説明では、ドットで区切られた個々の数字列を"位"と称します。
- 一般に、"メジャー"、"マイナー"、"ビルド"、"リビジョン"などと呼ばれるもののことです。
- 各"位"を数値化するルールは、
int.TryParse ()
に準じます。
- 数値化できなかった場合は
0
と見なします。- 双方の文字列の左端は、同じ大きさの"位"であるものと見なし、右に行くほど段階的に小さくなるものとします。
- 「左端は必ず"メジャー"で、次は"マイナー"である」ということです。
- "位"が存在しない場合は、
0
よりも"小さい"値と見なします。(上記の使用例を参照)- 比較の結果を、
(a < b) => -1
、(a == b) => 0
、(a > b) => 1
で返します。参考
- Unityのインスペクタで設定される"Version*"文字列(Application.version)をマスタデータとして扱いたかったので、こういうやり方をしています。
- 通常は、素直にVersionクラスを使うことをお勧めします。
public static readonly Version BundleVersion = new Version (Application.version);
- 投稿日:2019-07-08T14:31:04+09:00
リファクタリングに最適なクソコード
クソコード
練習などにお使いください
kusoko-do.rbusing System; using System.Collections; using System.Collections.Generic; using UnityEngine; using よくわからんやつ = UnityEngine.MonoBehaviour; using 真偽値 = System.Boolean; using 数値 = System.Int32; using 虚無 = System.Action; using 位置 = UnityEngine.Vector3; public class senpaikuso : よくわからんやつ { /* * SENPAIKUSO-零1 1秒毎に移動状態が切り替わるオブジェクト * start状態の時、x方向に零.1f/frameずつ進みます。 * ただし、1秒後に停止、2秒後には移動再開、3秒後は移動開始...のように1秒が経過する毎に移動/停止が切り替わる。 * なお、実行環境はアメリカで、日本語は使用できないものとする。 */ private const 真偽値 真 = true; private const 真偽値 偽 = false; private const 数値 一秒 = 60; private const 数値 零 = 0; private const 数値 Ⅲ = 3; private const 数値 Ⅱ = 2; private const 数値 Ⅰ = 1; private 位置 今いる場所 { get { return transform.position; } set { transform.position = value; } } private void Start() { 移動 = new 移動状態(); 移動.移動OK = 真; 移動.移動NG = 偽; 移動.待機OK = 偽; 移動.待機NG = 真; } private void Update() { int フレーム = 時間 = 時間 + Ⅰ; if (時間 == 一秒) { { 時間 = 零; if (移動.移動OK && 移動.待機NG) { 移動.移動OK = 偽; 移動.移動NG = 真; 移動.待機OK = 真; 移動.待機NG = 偽; } else if (移動.移動NG && 移動.待機OK) { 移動.移動OK = 真; 移動.移動NG = 偽; 移動.待機OK = 偽; 移動.待機NG = 真; } } } else { 今いる場所 += new 位置(移動速度 / Ⅲ, 零, 零); switch (移動.移動OK == 真) { case 真: switch (移動.待機NG == 真) { case 真: 今いる場所 += new 位置(移動速度 / Ⅲ, 零, 零); break; case 偽: break; } break; case 偽: break; } switch (移動.移動NG == 真) { case 真: switch (移動.待機OK == 真) { case 真: 今いる場所 -= new 位置(移動速度 / (Ⅲ * Ⅱ), 零, 零); break; case 偽: break; } break; case 偽: break; } switch (移動.待機OK == 真) { case 真: switch (移動.移動NG == 真) { case 真: 今いる場所 -= new 位置(移動速度 / (Ⅲ * Ⅱ), 零, 零); break; case 偽: break; } break; case 偽: break; } switch (移動.待機NG == 真) { case 真: switch (移動.移動OK == 真) { case 真: 今いる場所 += new 位置(移動速度 / Ⅲ, 零, 零); break; case 偽: break; } break; case 偽: break; } } } [Serializable] public struct 移動状態 { public 真偽値 移動OK; public 真偽値 移動NG; public 真偽値 待機OK; public 真偽値 待機NG; } [SerializeField] public float 移動速度 = 1; public class kotae { string 答え = "そんな物無いよ"; } [SerializeField] 移動状態 移動 = new 移動状態(); int 時間 = 零; }
- 投稿日:2019-07-08T11:45:32+09:00
MagicOnion のストリーミング接続を複数シーンで持ち越す場合のTips
はじめに
MagicOnion についてはこちらをご覧ください。
一般的なマルチプレイゲームでは、マッチング→メインのゲーム→リザルト といったふうに、複数のシーンでストリーミング接続を繋ぎっぱなしにする事が多いと思います。
その場合、1シーンで使う場合に比べ気をつける必要がある点がいくつかあるので、それぞれに対して自分がやっている対応を紹介します。Receiver
接続全体を1つのインターフェースにまとめることになるので、シーンごとに一部のメソッドしか使わないことになります。
一方、IHogeHubReceiver を実装するクラスでは当然すべてのメソッドを定義しなければいけません。
このままでは非常に扱いづらいのですべてのメソッドを UniRx の Observable に変換してしまうと良さそうです。
インターフェイスの明示的実装を使えば同名のPublicなプロパティが生やせます。例public interface IHogeHubReceiver { void OnHoge(); void OnFuga(int i); void OnPiyo(int i, int j); } public class HogeHubReceiver : IHogeHubReceiver { // 引数なしは UniRx.Unit に public IObservable<Unit> OnHoge => _onHoge; private Subject<Unit> _onHoge = new Subject<Unit>(); void IHogeHubReceiver.OnHoge() => _onHoge.OnNext(Unit.Default); // 1引数はそのまま public IObservable<int> OnFuga => _onFuga; private Subject<int> _onFuga = new Subject<int>(); void IHogeHubReceiver.OnFuga(int i) => _onFuga.OnNext(i); // 2引数以上はタプル化 public IObservable<(int i, int j)> OnPiyo => _onPiyo; private Subject<(int i, int j)> _onPiyo = new Subject<(int i, int j)>(); void IHogeHubReceiver.OnPiyo(int i, int j) => _onPiyo.OnNext((i, j)); }これを手動で書くのはめんどくさくてしょうがないのでエディタ拡張で自動生成しましょう。
StreamingHubReceiverCreator.csusing System; using System.Linq; using System.Text.RegularExpressions; using System.IO; using UnityEditor; /// <summary> /// StreamingHubReceiverのインターフェースから具象型を生成するエディタ拡張 /// </summary> public static class StreamingHubReceiverCreator { /// <summary> /// StreamingHubReceiverのインターフェースから具象型を生成する /// すべてのメソッドはIObservableに変換される /// </summary> [MenuItem("Assets/Create/StreamingHubReceiver")] private static void CreateStreamingHubReceiver() { var interfaceName = Selection.activeObject.name; var source = File.ReadAllText(AssetDatabase.GetAssetPath(Selection.activeObject)); // using追加 if (!source.Contains("using System;")) source = "using System;\r\n" + source; if (!source.Contains("using UniRx;")) source = "using UniRx;\r\n" + source; // クラス名変換 source = Regex.Replace(source, "interface I(.*Receiver)", "class $1 : I$1"); // 各メソッド変換 source = Regex.Replace(source, "( *)void (.*)\\((.*)\\);", m => { var space = m.Groups[1].Value; var name = m.Groups[2].Value; var privateName = "_" + Char.ToLower(name[0]) + name.Substring(1); var args = m.Groups[3].Value; var genericArgs = "Unit"; var argNames = "Unit.Default"; // 引数の個数に応じてデータ構造を変更 var x = Regex.Matches(args, "(?:(.+?[^,]) (.+?)(?:,|$))").Cast<Match>().ToArray(); if (x.Length == 1) { genericArgs = x[0].Groups[1].Value; argNames = x[0].Groups[2].Value; } else if (x.Length > 1) { // タプル化 genericArgs = "(" + args + ")"; argNames = "(" + string.Join(", ", x.Select(y => y.Groups[2].Value)) + ")"; } return $"{space}public IObservable<{genericArgs}> {name} => {privateName};\r\n" + $"{space}private Subject<{genericArgs}> {privateName} = new Subject<{genericArgs}>();\r\n" + $"{space}void {interfaceName}.{name}({args}) => {privateName}.OnNext({argNames});"; }); var fileName = Selection.activeObject.name.Substring(1) + ".cs"; var classPath = Directory.EnumerateFiles(".", fileName, SearchOption.AllDirectories).FirstOrDefault(); File.WriteAllText(classPath ?? "Assets/" + fileName, source); AssetDatabase.Refresh(); } /// <summary> /// 選択しているアイテムがスクリプトファイルかどうかを判別する /// </summary> /// <returns>スクリプトファイルかどうか</returns> [MenuItem("Assets/Create/StreamingHubReceiver", isValidateFunction: true)] private static bool ValidateCreateStreamingHubReceiver() => Selection.activeObject is MonoScript; }切断
接続が不要になったら切断するというは大事で、これをしないと Unity エディタがすぐフリーズします。
単独のシーンで使うならシーンで使うスクリプトの OnDestroy で切断すれば良いですが、
複数シーンの場合は、接続を管理するオブジェクトを作り DontDestroyOnLoad に設定しておくのが良いでしょう。public class ConnectionHolder : MonoBehaviour { public IHogeHub Client { get; private set; } public HogeHubReceiver Receiver { get; } = new HogeHubReceiver(); // さっき作ったやつ private Channel _channel; public static ConnectionHolder Create() { // DontDestroy化 var gameObject = new GameObject("ConnectionHolder"); DontDestroyOnLoad(gameObject); // 接続 var holder = gameObject.AddComponent<ConnectionHolder>(); holder._channel = new Channel("host:port", ChannelCredentials.Insecure); holder.Client = StreamingHubClient.Connect<IHogeHub, IHogeHubReceiver>(holder._channel, holder.Receiver); return holder; } private void OnDestroy() { Client.DisposeAsync(); _channel.ShutdownAsync(); } }
- はじめのシーンでは Create を呼ぶ
- 以降のシーンでは Find で探してくる
- 最後のシーンで Destroy する
というイメージです。
ついでに参加者の保持管理もこのクラスでやってしまうと後のシーンで使いやすくて便利です。
- 投稿日:2019-07-08T11:08:09+09:00
[Unity]エクセルでのデータ管理
はじめに
Unity1週間ゲームジャム「あつめる」でのExcelDataReader実践導入を例に記事を書いています。
また、管理クラスはUnity1週間ゲームジャム前に完成していましたがそちらも軽く説明を書いています。お題「あつめる」
https://unityroom.com/unity1weeks/13投稿作品「たこ焼きパーティー」
https://unityroom.com/games/takoyaki_partyソースコード
https://gitlab.com/blueback28/unityroom_takoyaki投稿作品での使用ライセンス等
Fee、自作ライブラリ
https://github.com/bluebackblue/fee_coreExcelDataReader
https://github.com/ExcelDataReader/ExcelDataReader/M+ FONTS
http://mplus-fonts.osdn.jp/効果音ラボ
https://soundeffect-lab.info/音楽:魔王魂
https://maoudamashii.jokersounds.comExcelDataReaderの導入
ExcelDataReaderをUnityで使用する方法はいくつかあります。
(nupkgの拡張子をzipに変えて中身を取り出す等)。今回は以下のサイトをチェックアウトし、ビルドしました。
https://github.com/ExcelDataReader/ExcelDataReader/必要そうなものをUnityに追加、追加するフォルダは「Plugins」
制御クラス
Fee.Excel
「ExcelDataReader」あるいは「NPOI」を使って決められた書式のエクセルをJsonとしてロードします。
今回は「ExcelDataReader」を使用しました。
https://github.com/bluebackblue/fee_core/tree/master/Script/ExcelFee.JsonSheet
Json化したエクセルを解析し、データコンバートを行います。
この記事ではここの説明がメインになります。
https://github.com/bluebackblue/fee_core/tree/master/Script/JsonSheetメニューへの追加
コンバート手順等はエクセルに記述します。
/** Convert */ #if(UNITY_EDITOR) public class Convert { /** 全部。 */ [UnityEditor.MenuItem("Takoyaki/Convert/All")] public static void MenuItem_Convert_All() { //ログ。 Fee.Excel.Config.LOG_ENABLE = true; Fee.JsonSheet.Config.LOG_ENABLE = true; //エクセルをJSONシートにコンバート。 Fee.Excel.ExcelToJsonSheet t_exceltojsonsheet = new Fee.Excel.ExcelToJsonSheet(); if(t_exceltojsonsheet.Convert(Fee.File.Path.CreateAssetsPath("/Editor/Excel/convert.xlsx")) == false){ return; } //JSONシート取得。 Fee.JsonItem.JsonItem t_jsonsheet = t_exceltojsonsheet.GetJsonSheet(); if(t_jsonsheet == null){ return; } //コンバート。 Fee.JsonSheet.JsonSheet.ConvertFromJsonSheet(t_jsonsheet); //ログ。 UnityEngine.Debug.Log("Convert_All"); } } #endif各シートの説明
convert
「*」印が付いている項目を上から順番にコンバートしていきます。
今回はアセットバンドルは使用しないので「*」印を外しています。
se_command_list
「*」印が付いている項目を「se.prefab」「Data_SeCommonType.cs」として出力しています。
ENUMシートとオーディオシートの混合シートです。/** Game.Data */ namespace Game.Data { /** 自動生成。 */ public enum SeCommonType { /** マヨヒット */ MayoHit, /** システム、決定 */ Decide, /** キッチン、「カッカッカッ」 */ Right, /** キッチン、「キーン」 */ Left, /** ワサビヒット */ WasabiHit, /** 声「いっただっきまーす」 */ Itadakimasu, /** キッチン、「カタ」 */ Cancel, } }bgm_list
「*」印が付いている項目を「bgm.prefab」「Data_BgmType.cs」として出力しています。
ENUMシートとオーディオシートの混合シートです。/** Game.Data */ namespace Game.Data { /** 自動生成。 */ public enum BgmType { /** */ Title, /** */ Main, } }data_list
「*」印が付いている項目を「datalist.json」として出力しています。
データシートです。
text_id
「*」印が付いている項目を「text_id.json」として出力しています。
JSONシートです。
「datalist.json」からSE,BGM、テクスチャーをロードするコード
たいした量ではなかったのでベタ書きで一括全ロード。
//パス登録。 { UnityEngine.TextAsset t_textasset = UnityEngine.Resources.Load<UnityEngine.TextAsset>(Config.Resources.PATH_DATALIST); if(t_textasset != null){ string t_text = t_textasset.text; if(t_text != null){ System.Collections.Generic.Dictionary<string,Fee.Data.JsonListItem> t_data_list = Fee.JsonItem.Convert.JsonStringToObject<System.Collections.Generic.Dictionary<string,Fee.Data.JsonListItem>>(t_text); if(t_data_list != null){ Fee.Data.Data.GetInstance().RegisterDataList(t_data_list); }else{ Tool.Assert(false); } }else{ Tool.Assert(false); } }else{ Tool.Assert(false); } } //t_id string[] t_id = new string[]{ "TAKOYAKI1", "TAKOYAKI2", "TAKOYAKI3", "WASABI", "MAYO", "GAMEEND_WINDOW", "BUTTON", "SLIDER_BUTTON", "SLIDER_BAR", "SE_COMMON", "BGM", "TEXTID", }; //ロード。 for(int ii=0;ii<t_id.Length;ii++){ Fee.Data.Item t_item = Fee.Data.Data.GetInstance().RequestLoad(t_id[ii]); do{ yield return null; }while(t_item.IsBusy() == true); switch(t_id[ii]){ case "TAKOYAKI1": { if(t_item.GetResultAssetTexture() != null){ this.takoyaki1 = t_item.GetResultAssetTexture(); continue; } }break; ・・・略 case "SE_COMMON": { if(t_item.GetResultAssetPrefab() != null){ UnityEngine.GameObject t_prefab = t_item.GetResultAssetPrefab(); if(t_prefab != null){ Fee.Audio.Pack_AudioClip t_pack_audioclip = t_prefab.GetComponent<Fee.Audio.Pack_AudioClip>(); if(t_pack_audioclip != null){ Fee.Audio.Audio.GetInstance().LoadSe(t_pack_audioclip,SEID_COMMON); continue; }else{ Tool.Assert(false); } }else{ Tool.Assert(false); } }else{ Tool.Assert(false); } }break; case "BGM": { if(t_item.GetResultAssetPrefab() != null){ UnityEngine.GameObject t_prefab = t_item.GetResultAssetPrefab(); if(t_prefab != null){ Fee.Audio.Pack_AudioClip t_pack_audioclip = t_prefab.GetComponent<Fee.Audio.Pack_AudioClip>(); if(t_pack_audioclip != null){ Fee.Audio.Audio.GetInstance().LoadBgm(t_pack_audioclip); continue; }else{ Tool.Assert(false); } }else{ Tool.Assert(false); } }else{ Tool.Assert(false); } }break; case "TEXTID": { if(t_item.GetResultAssetText() != null){ System.Collections.Generic.List<TextId_ListItem> t_list = Fee.JsonItem.Convert.JsonStringToObject<System.Collections.Generic.List<TextId_ListItem>>(t_item.GetResultAssetText()); if(t_list != null){ this.textid_list = new System.Collections.Generic.Dictionary<string,string>(); for(int jj=0;jj<t_list.Count;jj++){ this.textid_list.Add(t_list[jj].id,t_list[jj].text); } continue; }else{ Tool.Assert(false); } }else{ Tool.Assert(false); } }break; } Tool.Assert(false); }ロードしたデータを使用
BGM再生
「Data.BgmType.Title」はコンバート時に生成され、データパスはエクセルで設定できる。
//BGM再生。 Fee.Audio.Audio.GetInstance().PlayBgm(Data.BgmType.Title);SE再生
「Game.Data.SeCommonType.Cancel」はコンバート時に生成され、データパスはエクセルで設定できる。
Fee.Audio.Audio.GetInstance().PlaySe(Data.Data.SEID_COMMON,Game.Data.SeCommonType.Cancel);テクスチャ
「takoyaki1」「takoyaki2」「takoyaki3」はエクセルの"TAKOYAKI1"、"TAKOYAKI2"、"TAKOYAKI3"のパスからロードされている。
//player_texture UnityEngine.Texture2D t_player_texture = null; switch(OnMemory.GetInstance().player_index){ case 1: { t_player_texture = OnMemory.GetInstance().data.takoyaki1; }break; case 2: { t_player_texture = OnMemory.GetInstance().data.takoyaki2; }break; case 3: { t_player_texture = OnMemory.GetInstance().data.takoyaki3; }break; }テキストID
通常
エクセルで指定した文字列を取得。
//たこ焼きパーティー string t_text = OnMemory.GetInstance().data.GetText("TEXTID_TITLE_TITLE");引数指定
実行時に特定のキーワードを数値と差し替える。
//9999 マヨ string t_text = OnMemory.GetInstance().data.GetText("TEXTID_GAMEEND_MAYO",9999);データ更新時間
計算結果を取得。
//2019/07/07 7:00:26 string t_text = OnMemory.GetInstance().data.GetText("TEXTID_VERSION");感想
扱ったデータの総量がそれほど多くなかったため大きな恩恵は得られなかったが、特にトラブルもなく実践導入できた。
- 投稿日:2019-07-08T01:53:32+09:00
Androidビルド in Unity2019.1.2f1
Oculus Quest開発用にAndroidビルド環境を作成しようとしたところ、
UnityEditor.BuildPlayerWindow+BuildMethodException
に詰まりました。今回は、解決しなかったが試した方法をメインに公開します。解決した方法については解決方法までスキップしてください。試行一覧
Unity - 2019.1.2f.1
Android Studio
・SDK Platforms - 8.0, 8.1
・SDK Tools - 26.1.1
・SDK Build Tools - 29
jdk - 1.8.0.211ビルド場所を変更した
Assets配下にビルドするとエラーになるという記事を見たため。
結果:効果なしUnityHubのデフォルトのSDK/NDKを使った
Unityのバージョンが2019以上だと、UnityHub>インストール>モジュールを加える>AndroidBuildSupport>Android SDK&NDK Toolsで追加インポートが可能。Preferenceではそちらを推奨されたため。
結果:効果なしSDK Platformsのバージョンを増やした
4.4 ~ 8.1までのバージョンを追加インストールしました。
結果:効果なしBuild SystemをInternalにしようとした
こちらの記事よりInternalに変更しようとしたが、2019.1.2f1では見当たらなかったです。
結果:不明SDK Toolsのバージョンを変更した
Unity Answersから推奨されていた。SDK Toolsのバージョンを26.1.1から25.2.5に変更しました。
この際、WindowsとMacでアクセスすべきURLが異なるので注意。Windowsはhttp://dl-ssl.google.com/android/repository/tools_r25.2.5-windows.zip
ですが、Macはhttp://dl-ssl.google.com/android/repository/tools_r25.2.5-macosx.zip
です。
SDKのバージョンは公式サイトから確認できます。
結果:おそらく効果あり(要検証)SDK Build Toolsのバージョンを下げた
SDK Build Toolsのバージョンを29から28に変更しました。
結果:効果あり(エラーが7カ所減少した)Scripting Backendを変更した
PlayerSettings>OtherSettings>ConfigrationのScripting BackendをMonoからIL2CPPに変更しました。
結果:効果なしVisual Studio関連のファイルを消去した
こちらの記事より。
.sln
と.vs
のファイルを消去しました。
結果:効果なしPC, Unity再起動
最後の手段、治ればいいなという願望。
結果:効果なし
解決方法
AndroidStudioをアンインストールしたのち、こちらの記事を行い解決しました。SDKのセットアップは記事内の(B)の方法で行ってください。
注意点として、Unity2018.4ですとNDKのバージョンがr13b
でなく、r16b
以上である必要があります。