- 投稿日:2021-07-20T23:54:09+09:00
MoveIt!についてざっくりと説明
Unity公式のRoboticsHUBではロボットのモーションプランニングにMoveItが使用されていたので、ざっくりとできることを調べてみました。 概要 ROS通信用オープンソース産業用ロボティックスプランニングフレームワーク グリッパーを指定した姿勢への経路を計算し、移動させるマニュピレーターの制御に向いている。 ライセンスはBSD モーションプランニング(動作計画) 衝突を回避した軌道予測を行うことができる。 稼働域の制限を考慮した動作になる。 計画に利用できるアルゴリズムは複数から選択することができる。 ロボットの操作 ロボットでワークを掴んだり、離したりする制御が用意に実現できる。 3D環境知覚 センサ等を使用し、周囲の環境を取り込むことができる。 逆運動学(IK) 特定の座標にアームを動かすための姿勢を推定することができる。 順運動学 アームの位置から座標を取得することができる。 衝突チェック 周囲の環境衝突が発生しないパスを計画することができる。 自己の衝突も検知できる。 URDFに定義されたメッシュを使用して判定を行う。 GUIシミュレーター RVizを用いたGUIシミュレーションができる。 物理シミュレーター Gazeboを用いたシミュレーションができる。 簡単なロボットのセットアップ URDF,SRDFファイルからRobotModelを構築できる。 URDF(Unified Robot Description Format) ROSおよびROS2でロボットを記述するためのフォーマット 各ロボットメーカーから入手できる。 サンプルは以下の通り。 MoveIt Commander PythonまたはC++用のユーザ向けライブラリで、ロボットの動作計画の基本APIをROSの知識なく利用できる。RoboticsHUBではRVizを使用していないので、こちらを利用しているはず。
- 投稿日:2021-07-20T18:40:37+09:00
【Unity2019.4.9f1】Appファイルを出力する方法
[:contents] PlatformをAndroidにする SDKとNDKにチェックを入れる (インストールされていない場合は、Unityhubの「モジュールを加える」からインストール) CompanyName、ProductNameを記入する keyを作成する Target API Levelを新しいものにする ScriptingBackEndをIL2CPPにし、ARM64にチェックを入れる Run Deviceを設定してDevelopmentBuildにチェックを入れてBuild これで、実機でテストする際のファイルが出力されます。 Build App Bundleにチェックを入れてBuild こちらでは、実際にGoogleplayストアに出力する際のファイルが出力されます 注意 実機で動作を確認するには、お持ちのAndroidの設定を「開発者モード」にする必要があります。 デバイスを開発者モードにする方法はここでは記事にしないので、書いてくださった方のリンクを載せておきます。 [https://qiita.com/knakamigawa/items/5b8bd2793920b517bf25:embed:cite] 書籍紹介 [asin:4862464130:detail]
- 投稿日:2021-07-20T15:35:27+09:00
UnityでNavMeshを使わず、特定のGameObjectを巡回させるスクリプト
Patrol.cs using UnityEngine; using System.Collections; public class Patrol : MonoBehaviour { [SerializeField, TooltipAttribute("経由地点の数を入力し,シーン上に配置した空のオブジェクトをアサインします")] public Transform[] wayPoints; public Transform target; public float speed; public int currentRoot; void Update() { //配列に入れたTransformを順に巡る.AIを使っていればスムーズに曲がるがこれは鋭角に曲がる float step = speed * Time.deltaTime; transform.position = Vector3.MoveTowards(transform.position, wayPoints[currentRoot].position, step); Vector3 pos = wayPoints[currentRoot].position; float test = Vector3.Distance(target.position, pos); transform.LookAt(pos); if(Vector3.Distance(transform.position, pos) < 0.5f) { currentRoot = (currentRoot < wayPoints.Length - 1) ? currentRoot + 1 : 0; } } }
- 投稿日:2021-07-20T14:02:30+09:00
Unityで音ゲーを作った!!(アルゴリズムとソース付き)
はじめに 音ゲーを作ろうとした時に調べても具体的なソースコードやアルゴリズムに関する記事が無かったので、自己解決した方法を載せます。 分からないことがあったり、間違いがあればコメントお願いします。 手順 1.譜面を作る 2.ノーツを作る 3.ノーツをタイミングに合わせて生成する 4.ノーツのタップ判定を作る 5.音楽を再生する 1.譜面を作る 私はNoteEditorという外部Assetを使用して作りました。 NoteEditorの使い方はコガネブログさんが説明されてますので、そちらを参考にしてください。 ちなみにBPMが分からないものはBPMカウンターという便利なサイトがあるので使用してみてください。 2.ノーツを作る 生成したいノーツを作りましょう。 後でノーツ生成から判定場所までかかる時間を取得するので、それを考慮したものの方が良いと思います。 今回は簡単に横移動のみのノーツでやっていきます。 とりあえずこんな感じです。 これはPrefabにしておきます。 3.ノーツをタイミングに合わせて生成する ①譜面の解説 ②譜面を読み込む ③読み取った情報からノーツを生成する の順で説明していきます。 ①譜面の解説 先ほど作った譜面はJson形式で保存されています。 まず、そのファイルを見てみましょう。 Score.json { "name": "MusicName", "maxBlock": 5, "BPM": 130, "offset": 0, "notes": [ { "LPB": 8, "num": 2, "block": 4, "type": 1, "notes": [] }, { "LPB": 8, "num": 137, "block": 1, "type": 1, "notes": [] }, こういった形式になっています。 こちらに書かれているもので使用しないものもあるので、使用したものを上から順に説明していきます。 BPM 一分間に何回拍が打たれるかを数値にしたものです。 例えば、BPM120なら60秒間に120拍、つまり60/120で1拍0.5秒ということになりますね。 この1拍の時間というのは後々使うことになるので覚えておいてください。 notes 1つのノーツの情報をまとめた配列になってます。 ここに書いてあるものも使うので説明していきます。 LPB 私の知る限り音楽用語ではありません。 NoteEditor独自の用語ではないでしょうか? これは1拍を何分割してノーツを置くかというものです。 NoteEditorでは下記のようになっています。 太い縦線が拍を意味していて、細線がここでいうLPBを意味しています。 例えば、BPM120でLPB8で、細線に二連続で置くと、その間隔は 60/120/8[秒] となりますね。 これも後々使います。 途中でLPBを変えると色々おかしくなってしまうので注意してください。 num これは開始の縦線を0、次の縦線を1としてどこの縦線に置かれているかの情報です。 ということは、縦線の間隔は均等なので開始からどのくらいの時間が必要なのかは『60/BPM/LPB*num』で分かりますね。 block これは横線の位置を意味しています。 上から0,1,2,3,4です。 私はノーツの種類を分ける時に使いました。 ②譜面を読み込む UnityでJSONファイルを読み込むを参考に読み込みます。 NotesGenerator.cs using UnityEngine; using System; public class NotesGenerator : MonoBehaviour { [Serializable] public class InputJson { public Notes[] notes; public int BPM; } [Serializable] public class Notes { public int num; public int block; public int LPB; } private int[] scoreNum;//ノーツの番号を順に入れる private int[] scoreBlock;//ノーツの種類を順に入れる private int BPM; private int LPB; void Awake() { MusicReading(); } /// <summary> /// 譜面の読み込み /// </summary> void MusicReading() { string inputString = scoreData.ToString(); InputJson inputJson = JsonUtility.FromJson<InputJson>(inputString); scoreNum = new int[inputJson.notes.Length]; scoreBlock = new int[inputJson.notes.Length]; BPM = inputJson.BPM; LPB = inputJson.notes[0].LPB; for (int i = 0; i < inputJson.notes.Length; i++) { //ノーツがある場所を入れる scoreNum[i] = inputJson.notes[i].num; //ノーツの種類を入れる(scoreBlock[i]はscoreNum[i]の種類) scoreBlock[i] = inputJson.notes[i].block; } } } これでscoreNum[]にすべての音符の場所データが入りました。 さてここから音楽のタイミングに合わせて生成を行います。 ③読み取った情報からノーツを生成する 一番の難所であろうところです。 いやぁ・・・苦戦したな・・・。 先に生成部分のソースコードを載せます。 NotesGenerator.cs using UnityEngine; using System; public class NotesGenerator : MonoBehaviour { [SerializeField] private GameObject notesPre; private float moveSpan = 0.01f; private float nowTime;// 音楽の再生されている時間 private int beatNum;// 今の拍数 private int beatCount;// json配列用(拍数)のカウント private bool isBeat;// ビートを打っているか(生成のタイミング) void Awake() { InvokeRepeating("NotesIns", 0f, moveSpan); } /// <summary> /// 譜面上の時間とゲームの時間のカウントと制御 /// </summary> void GetScoreTime() { //今の音楽の時間の取得 nowTime += moveSpan; //(1) //ノーツが無くなったら処理終了 if (beatCount > scoreNum.Length) return; //楽譜上でどこかの取得 beatNum = (int)(nowTime * BPM / 60 * LPB); //(2) } /// <summary> /// ノーツを生成する /// </summary> void NotesIns() { GetScoreTime(); //json上でのカウントと楽譜上でのカウントの一致 if (beatCount < scoreNum.Length) { isBeat = (scoreNum[beatCount] == beatNum); //(3) } //生成のタイミングなら if (isBeat) { //ノーツ0の生成 if (scoreBlock[beatCount] == 0) { } //ノーツ1の生成 if (scoreBlock[beatCount] == 1) { Instantiate(notesPre); } beatCount++; //(5) isBeat = false; } } } (1)InvokeRepeatingを使いmoveSpan(0.01秒)ごとに処理を行っているためです。 (2)NoteEditorでいう、今どこの縦線にいるかの取得をします。 (3)scoreNum[]はノーツの場所の情報を順に入れているものなので、いまどこの縦線にいるかを見ているbeatNumと等しかったらノーツ生成のタイミングになります。 (4)生成が終わったら次のscoreNum[]の要素を見たいので足します。 さあこれでノーツの生成が終わりました。 4.ノーツのタップ判定を作る タップした時の判定を作っていきます。 生成からタップさせたいタイミングのところまでの移動でかかる時間からカウントダウンしていき、その差で判定します。 NotesMove.cs using UnityEngine; using System; public class NotesMove: MonoBehaviour { [SerializeField] private float notesSpeed; [SerializeField] private Vector2 startPos;//ノーツの開始位置 [SerializeField] private Vector2 judgePos;//判定したい場所 public static float moveSpan = 0.01f;//回すスパン private float notesTime; void Start() { notesTime = (startPos.x - judgePos.x) / notesSpeed; InvokeRepeating("NotesMove", 0, moveSpan); } void NotesMove() { transform.position += new Vector3( -notesSpeed, 0f, 0f); notesTime -= moveSpan; NotesJudge(); } void NotesJudge() { if(Math.Abs(notesTime) < 0.5f) { //判定した時の処理を書く } } } こんな感じです。 これを使い次の工程に移ります。 5.音楽を再生する 音ゲーに欠かせないのが音楽です。 先ほどの生成のタイミングと音楽が噛み合わなかったら元も子もないですよね。 今のコードでは本来ノーツが判定されるタイミングで生成を行っています。 つまり、生成から判定したい場所までにかかる時間分ずれていることになります。 このずれを直していきます。 まず、譜面のnotes[0]に Score.json "notes": [ { "LPB": 8, "num": 0, "block": 0, "type": 1, "notes": [] }, を追加します。LPBは各自合わせてください。 次にNotesGeneratorに NotesGenerator.cs using UnityEngine; using System; public class NotesGenerator : MonoBehaviour { [SerializeField] private AudioSource gameAudio; public static isAudioPlay=false; //ノーツ0(音再生用ノーツ)の生成 if (scoreBlock[beatCount] == 0) { } //音再生開始 void AudioPlay() { gameAudio.enabled = isAudioPlay; } } を追加します。 そして、音再生用ノーツの設定です。 先ほど作ったノーツのprefabを複製し、タグをAudioPlayとしておきます。 コードは NotesMove.cs using UnityEngine; using System; public class NotesMove: MonoBehaviour { void NotesJudge() { if(this.gameObject.tag=="AudioPlay") { if(notesTime =< 0)//判定位置に来たら { NotesGenerator.isAudioPlay= true; } } } } を追加します。 これはどういうことかというと、NoteEditorでは音楽が開始したときにnum0になっています。 これはプレイヤーがノーツをタップしたいタイミングですよね。 ということはすべてnum0からの差分で生成するタイミングを取っているので、num0が判定場所に来てそのタイミングで音を開始したらずれません。 完成 これで完成です。 誰かの力になれたら嬉しいです。
- 投稿日:2021-07-20T01:09:21+09:00
【Unity】ボタンを押したらシーン遷移する実装を簡単に作れるSceneControllerの紹介
概要 ボタンを押したらシーン遷移する実装で毎度使っている SceneController コンポーネントを紹介します。 説明 ボタンの UnityEvent などで LoadScene() 等の関数を追加して使用します。 項目 説明 ProcessOnAwake このコンポーネントがはじめて有効になった時に実行する処理 SceneName 対象となるシーン名 Delay 実行までにかかる時間 Public関数 説明 LoadScene() [Delay]秒後、[SceneName]に遷移 LoadScene(float) [float]秒後、[SceneName]に遷移 LoadScene(string) [Delay]秒後、[string]に遷移 LoadAddScene() [Delay]秒後、[SceneName]を追加 LoadAddScene(float) [float]秒後、[SceneName]を追加 LoadAddScene(string) [Delay]秒後、[string]を追加 UnloadScene() [Delay]秒後、[SceneName]を破棄 UnloadScene(float) [float]秒後、[SceneName]を破棄 UnloadScene(string) [Delay]秒後、[string]を破棄 コード SceneController.cs using System; using System.Collections; using UnityEngine; using UnityEngine.SceneManagement; public class SceneController : MonoBehaviour { enum ProcessOnAwake { None, LoadScene, AddScene, UnloadScene } [SerializeField] ProcessOnAwake processOnAwake; [SerializeField] string sceneName; [SerializeField] float delay; private void Start() { switch (processOnAwake) { case ProcessOnAwake.LoadScene: LoadScene(); break; case ProcessOnAwake.AddScene: LoadAddScene(); break; case ProcessOnAwake.UnloadScene: UnloadScene(); break; } } /// <summary> /// シーン移動 /// </summary> /// <param name="sceneName">シーン名</param> /// <param name="delay">遅延時間</param> public void LoadScene(string sceneName, float delay) { AsyncOperation operation = SceneManager.LoadSceneAsync(sceneName); operation.allowSceneActivation = false; StartCoroutine(DelayedCall(delay, () => operation.allowSceneActivation = true)); } public void LoadScene(string sceneName) => LoadScene(sceneName, delay); public void LoadScene(float delay) => LoadScene(sceneName, delay); public void LoadScene() => LoadScene(sceneName, delay); /// <summary> /// シーン追加 /// </summary> /// <param name="sceneName">シーン名</param> /// <param name="delay">遅延時間</param> public void LoadAddScene(string sceneName, float delay) { AsyncOperation operation = SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Additive); operation.allowSceneActivation = false; StartCoroutine(DelayedCall(delay, () => operation.allowSceneActivation = true)); } public void LoadAddScene(string sceneName) => LoadAddScene(sceneName, delay); public void LoadAddScene(float delay) => LoadAddScene(sceneName, delay); public void LoadAddScene() => LoadAddScene(sceneName, delay); /// <summary> /// シーン破棄 /// </summary> /// <param name="sceneName">シーン名</param> /// <param name="delay">遅延時間</param> public void UnloadScene(string sceneName, float delay) { AsyncOperation operation = SceneManager.UnloadSceneAsync(sceneName); operation.allowSceneActivation = false; StartCoroutine(DelayedCall(delay, () => operation.allowSceneActivation = true)); } public void UnloadScene(string sceneName) => UnloadScene(sceneName, delay); public void UnloadScene(float delay) => UnloadScene(sceneName, delay); public void UnloadScene() => UnloadScene(sceneName, delay); /// <summary> /// 遅延処理 /// </summary> /// <param name="second">遅延時間</param> /// <param name="action">処理内容</param> /// <returns></returns> private IEnumerator DelayedCall(float second, Action action) { yield return new WaitForSeconds(second); action.Invoke(); } }
- 投稿日:2021-07-20T00:11:31+09:00
ATT対応とUnityAdsを併用したらリジェクトを食らった話と、その解決法
はじめに iOSアプリを作成する上では避けては通り難い、ご存知iOS14からのATT(App Tracking Transparency)対応。 自分も今回拙作「二枚残す道場」に組み込んでみました。 アプリはこちら↓(宣伝) 最初の実装の際に参考にさせて頂いたのはこちら↓ 不意のリジェクト さて、何度かバージョン更新した所、不意にリジェクトが…。 Guideline 5.1.2 - Legal - Privacy - Data Use and Sharing We noticed you collect data to track after the user selects "Ask App Not to Track" on the App Tracking Transparency permission request. Specifically, we noticed your app accesses web content you own and collects cookies for tracking after the user asked you not to track them. Next Steps To resolve this issue, please revise your app so that you do not collect data for tracking purposes if the user does not give permission for tracking. Resources - Tracking is linking data collected from your app with third-party data for advertising purposes, or sharing the collected data with a data broker. Learn more about tracking. - See Frequently Asked Questions about the new requirements for apps that track users. Please see attached screenshot for details. 「ATTダイアログで収集を【許可しない】を選択したにも関わらずCookieを収集しているぞ」との通知。 慌ててスクリーンショットを確認した所、どうやらUnityAdsがCookie収集しているようでした。 なるほど、ということは2つの対応をしなければなりません。 ATTダイアログでユーザが選択した結果を取得する ユーザが選択した結果に合わせてUnityAdsの表示方法を変える さてどうしたものか。 iOS 14 Advertising Supportを発見→実装 ユーザのATT選択状況を取得する上で良い方法はないかなーと色々調査した結果、どうやら2021年5月辺りにUnityのパッケージに「iOS 14 Advertising Support」というパッケージが追加されたようでした。 パッケージマネージャからインストールし、サンプルを読んで実装します。サンプルにはご丁寧にATTダイアログ直前に表示するダイアログもついてきました。 とりあえず仕上がったコードとしてはこちら↓ Main.csのStart()関連処理抜き出し private IEnumerator Start() { : yield return SetupAttAndAdAsync(); : //タイトル開始 SetState<MainStateTitle>(); } /// <summary> /// ATTとAdの準備 /// </summary> /// <returns></returns> private IEnumerator SetupAttAndAdAsync() { //IOSはUnityAds初期化前にATT対応を終わらせておく #if UNITY_IPHONE || UNITY_IOS // check with iOS to see if the user has accepted or declined tracking var status = ATTrackingStatusBinding.GetAuthorizationTrackingStatus(); //まだATT方針を決めていないなら if (status == ATTrackingStatusBinding.AuthorizationTrackingStatus.NOT_DETERMINED) { ここに、ダイアログを表示して、完了したら OnClickCenterButtonAttDialog() を呼ぶ処理を書く iOS 14 Advertising Supportのサンプルが参考になるかと。 } //実機の時だけ完了待ちをする #if !UNITY_EDITOR while (status == ATTrackingStatusBinding.AuthorizationTrackingStatus.NOT_DETERMINED) { status = ATTrackingStatusBinding.GetAuthorizationTrackingStatus(); yield return new WaitForEndOfFrame(); } #endif #else Debug.Log("Unity iOS Support: App Tracking Transparency status not checked, because the platform is not iOS."); #endif //広告の初期化 #if UNITY_ANDROID || UNITY_IPHONE || UNITY_IOS Advertisement.AddListener(this); //gameId,testModeはご自身のアプリに合わせて Advertisement.Initialize(gameId, testMode); //バナーの初期化はご自身で実装してください↓ yield return RequestBannerAsync(); #else yield break; #endif } private void OnClickCenterButtonAttDialog() { #if UNITY_IPHONE || UNITY_IOS Debug.Log("Unity iOS Support: Requesting iOS App Tracking Transparency native dialog."); ATTrackingStatusBinding.RequestAuthorizationTracking(); #else Debug.LogWarning("Unity iOS Support: Tried to request iOS App Tracking Transparency native dialog, but the current platform is not iOS."); #endif } ポイントは ATTrackingStatusBinding.RequestAuthorizationTracking();でATTダイアログが呼べる! ATTrackingStatusBinding.GetAuthorizationTrackingStatus()でATTの状態を取得できる! yield return でstate変更の完了待ち! でしょうか。 RequestAuthorizationTracking()を使用すれば、最初に貼ったリンクのDLLImport対応などが不要になるので随分楽になりました。 またyield return new WaitForEndOfFrame();は、yield return null;でも良いかも。 自分が実装した時はnew WaitWhile()やnew WaitUntil()ではどうにも上手くいかなかったので、もし情報あれば頂けるとありがたいです。 UnityAdsのCookie収集表記を切り替える さて、上記のiOS 14 Advertising SupportパッケージでATTの状態取得はできるようになりました。 次は「その情報を元にUnityAdsの表示切り替えをする」 ということで調査した所、下記のスレッドを発見しました。 記事曰く「GDPRの表示をさせないようにすれば良い」とのこと。 また、そのためには「Advertisingの初期化前にGDPRに値(false)を設定すれば良い」とのこと。 コードとしてはこんな感じで↓ var gdprMetaData = new MetaData("gdpr"); gdprMetaData.Set("consent", ”false”);//falseは文字列で渡す Advertisement.SetMetaData(gdprMetaData); ということで、先程のコードを修正します。 Main.csのStart()関連処理抜き出し(GDPR対応版) private IEnumerator Start() { : yield return SetupAttAndAdAsync(); : //タイトル開始 SetState<MainStateTitle>(); } /// <summary> /// ATTとAdの準備 /// </summary> /// <returns></returns> private IEnumerator SetupAttAndAdAsync() { //IOSはUnityAds初期化前にATT対応を終わらせておく #if UNITY_IPHONE || UNITY_IOS // check with iOS to see if the user has accepted or declined tracking var status = ATTrackingStatusBinding.GetAuthorizationTrackingStatus(); //まだATT方針を決めていないなら if (status == ATTrackingStatusBinding.AuthorizationTrackingStatus.NOT_DETERMINED) { ここに、ダイアログを表示して、完了したら OnClickCenterButtonAttDialog() を呼ぶ処理を書く iOS 14 Advertising Supportのサンプルが参考になるかと。 } //実機の時だけ完了待ちをする #if !UNITY_EDITOR while (status == ATTrackingStatusBinding.AuthorizationTrackingStatus.NOT_DETERMINED) { status = ATTrackingStatusBinding.GetAuthorizationTrackingStatus(); yield return new WaitForEndOfFrame(); } #endif //追加部分ここから //ATT対応の結果を持って、Adを初期化しておかないとCookieの収集を行ってしまいリジェクトを食らってしまう var gdprMetaData = new MetaData("gdpr"); //許可の時だけ true var isGdpr = ATTrackingStatusBinding.GetAuthorizationTrackingStatus() == ATTrackingStatusBinding.AuthorizationTrackingStatus.AUTHORIZED; gdprMetaData.Set("consent", isGdpr.ToString()); Advertisement.SetMetaData(gdprMetaData); //追加部分ここまで #else Debug.Log("Unity iOS Support: App Tracking Transparency status not checked, because the platform is not iOS."); #endif //広告の初期化 #if UNITY_ANDROID || UNITY_IPHONE || UNITY_IOS Advertisement.AddListener(this); //gameId,testModeはご自身のアプリに合わせて Advertisement.Initialize(gameId, testMode); //バナーの初期化はご自身で実装してください↓ yield return RequestBannerAsync(); #else yield break; #endif } private void OnClickCenterButtonAttDialog() { #if UNITY_IPHONE || UNITY_IOS Debug.Log("Unity iOS Support: Requesting iOS App Tracking Transparency native dialog."); ATTrackingStatusBinding.RequestAuthorizationTracking(); #else Debug.LogWarning("Unity iOS Support: Tried to request iOS App Tracking Transparency native dialog, but the current platform is not iOS."); #endif } これで、許可の時だけGDPRにtrueが渡され、それ以外ではfalseが渡されるようになりました。 そもそも許可の時は値を設定しなくても良いかもですが、その辺りは良い感じに修正してもらう方向で…。 おわりに という事で、上記のコードを加えて審査に提出する事でレビューを通すことができました。 もしかするといずれはUnityAds内で勝手に対応してもらえる時が来るかもしれませんが、 なかなか調査に時間がかかったので念の為記事化しておきます。