自分に合ったクラス図作成ツールを探した【UML】
はじめに
自分に合ったクラス図作成ツールを探すネットサーフィンの旅に出ました。
調査対象は有料。
できれば買い切りで欲しい。
一応たどり着いた結論
- astah* Professional
調査
①Lucidchart
概要
お値段:月900円、年12000円
支払いタイプ:サブスク
お試しクラス図
よかったこと
- UIが見やすい、使いやすい
- おしゃれ
- ナビゲーションやチュートリアルが親切
- 困ったらCtrl + Spaceしてやりたいことを検索すればすぐ使えそう
- 例:「エクスポート」で検索かけると、実際のメニューの場所を矢印で示してくれる。
気になること
- 特に不快な要素はなかった。
②Wondershare EdrawMax
概要
お値段:19600円
支払いタイプ:買い切り
お試しクラス図
よかったこと
下のカラーパレットがおしゃれ
図の折り畳み機能がある
- 図形にアクションボタンが付いていて、これを押すと、参照元もしくは参照先をまとめて表示/非表示にできる。
- コンパクトにまとめたいときに便利
- 買い切りにしては安いと思う。
気になったこと
- 矢印の配置が少し面倒くさい?
- 2つ以上の矢印を同じ場所に繋げようとしたとき、そのまま図形から矢印線を引いた場合だと線がまとまらなかった。矢印が微妙にズレて重なって、見栄えが良くない。
- ただし、サイドバーの図形から矢印オブジェクトを置いて繋げたらきれいに矢印がまとまった。こっちが正攻法かな?
- (自分が操作を知らないだけの可能性はあるので、簡単にきれいに引く方法はあるかもしれない。)
- 2つ以上の矢印を同じ場所に繋げようとしたとき、そのまま図形から矢印線を引いた場合だと線がまとまらなかった。矢印が微妙にズレて重なって、見栄えが良くない。
- サイドバーの図形の矢印オブジェクトの矢じりが小さくて見にくい。
- 矢印オブジェクトも図形として登録されているが、何の矢印か見にくい。(自分の視力のせいもあるかもしれないけど・・・。)
- 設定でサイドバーのアイコン大きくできたりするのか・・・?
- 矢印オブジェクトも図形として登録されているが、何の矢印か見にくい。(自分の視力のせいもあるかもしれないけど・・・。)
③astah* Professional
概要
お値段:30800円
支払いタイプ:買い切り
お試しクラス図
よかったこと
- 操作が直観的で作るのが簡単だった。
- 画面内でダブルクリックするだけで、クラス図がポンポン作れる。
- 変数、メソッドの初期構文のないシンプルなクラス図が作れる。
- 配置した図形から矢印の種類などが直接選択できたり、同じ場所を参照する矢印線もあとで簡単にまとめられた。
- 各変数、メソッドは1行ずつ挿入、削除できる
- 画面内でダブルクリックするだけで、クラス図がポンポン作れる。
気になったこと
- 特に不快な要素はなかった。
おわりに
3年使えば元が取れる!
Oculus Quest 2でPCに無線接続する
はじめに
Oculus Quest 2でPCに無線接続する方法を記す。
最終目的はSteamVRを触ること。
私の知る限り下記2パターンのやり方がある。
方法1.Virtual Desktop を使う方法
有料アプリのアレを使うパターン。
(2000円くらい)
手順(SteamVRを遊ぶまで)
↓これの手順にそってやればOK(*一部スキップ箇所あり)
参考:oculus quest 2 - SteamVRを無線で利用する - コトバノウタカタ
*「Virtual Desktop VR Patchを入れる」のところはスキップした。
(現在はもう入れなくていいようになっている。)
方法2.Oculus Air Link を使う方法
公式の説明: https://support.oculus.com/363042741730476/?locale=ja_JP
手順
①OculusのPCアプリをインストール
https://www.oculus.com/rift/setup/
②PC側の設定
[設定]→[ベータ]→Air Link を オン
③Quest2側の設定
ユニバーサルメニューから
[設定]→[テスト機能]→Air Link をオン
クイックアクションからOculusAirLinkの項目を押して、
ペアリング & 接続すればOK
バーチャルデスクトップはメニューから押せば開ける。
④PCのSteamVRを遊ぶ場合
PCのOculusアプリの
[設定]→[一般]から
提供元不明 にチェックを入れる。
おわりに
私の場合、PCの接続目的がSteamVRをやることだけなので、
普段使いは「Virtual Desktop」だと思う。
理由は、PC側のアプリが自動起動しているのですぐ繋げるから。
(AirLinkは、24時間使用されていないと自動でオフになってしまうみたい。)
Air Linkは、無料 & ホーム画面でいろいろ遊べるから触ってみる価値はあると思う。
AWSのまだ動いていたサービスを調べて削除する
はじめに
AWSにて、 使用していたサービスを覚えているものは片っ端から削除したが、 1か月経って、まだ課金されているサービスがあったので、調べた。
内容
使用をやめるときにAWSのアクティブなサービスを削除していくのだが、 いざ削除しようと思ったときに、 何のサービスを使ったか、何に紐づけられているか把握しておくのは、きつい。
なのでその使用サービス一覧を見て対処していくのがよい?
AWS Systems Managerで使用しているサービス一覧を調べる
公式ドキュメント docs.aws.amazon.com
ナビゲーションメニューより、 ▼変更管理 → 自動化 に遷移
オートメーション実行で AWSSupport-ListEC2Resources を検索して → それを選択して次へ
シンプルな実行
タグでフィルタリングできるみたいだが、 個人でそんなにサービス立ち上げていたわけではないので、
そのまま「シンプルな実行」で実行する。
実行結果を見る
「出力」を確認。
私の中ではすべてサービス終了させていたはずなので、 ほぼ、No Resources Foundが出力されたが、 一か所Elastic IPで動いているサービスが残っていた。
このアドレスを開放するには、このURLからできますよ。という案内も出力してくれるので 助かる。
そこのサービスを見に行って、削除した。めでたし
おわりに
これで来月は請求がなくなるはず。
追記 (2022/04/09)
何かRDSのスナップショットデータが残っていたらしく、15円ほど請求された。 気づかねーよこんなのw
RDSのページ → スナップショット → 残っていたやつを削除
UnityでVRアプリをビルドして試してみる
はじめに
UnityでOculus Quest 2向けの環境設定を行って、VRアプリをビルドして実機で確認するまでの流れ
- Unity:2019.4.15.f1
- VR機器:Oculus Quest 2
手順
1.アセットストアからOculus Integrationを導入
これを入れる。
2.Build SettingsでPlatformをAndroidにする。
[Texture Compression (テクスチャー圧縮)]を[ASTC]に設定します。
3.Project Settingsより、XRSettings → Virtual Reality Supportedにチェックを入れる
(入れ子になった下の方のチェックは勝手に付いていた。よくわからん。)
4.Graphics APIsの「Vulkan」を消してね。とのエラーが出たので。これも消す。
Error building Player: BuildFailedException: XR is currently not supported when using the Vulkan Graphics API. Please go to PlayerSettings and remove 'Vulkan' from the list of Graphics APIs.
5.Oculus Quest 2を接続してRun Deviceでデバイスを指定する。
6.ビルドシーンについて
アセット付属のDemoシーンをビルドしてもいいし、 自作のシーンをビルドしてもいい。(←自作の場合は、とりあえずアセット付属のVR用のカメラをシーンに配置しておけばOK)
7.Build and Runを押す
ビルド後、実機確認ができる。
8.もう一度そのアプリを確認するには
Oculus Quest 2起動後のホーム画面で、 右手のコントローラーのOculusのマーク?を押して、 メニュー画面 → アプリを押す。 アプリ画面の右上のところの「すべて」を 「提供者不明」に変えると、先ほど実機で実行したアプリが表示される。
9.実機確認したやつ
はじめてのVRアプリ(謎の儀式感) pic.twitter.com/0AOqWIQlZu
— tasky (@fromtasky) March 21, 2021
10.おわりに
はい終わり
アセットストアのアセットのダウンロード先フォルダを変更する
割と記事は出ているけど自分用にメモ
目的
Cドライブが圧迫されていたので、保存先をDドライブの方に変更したい。
Cドライブの容量を少しでも軽くしてあげたい。
やること
1.CドライブにあるアセットのフォルダをDドライブに移動
2.Cドライブから、Dドライブの方へジャンクションを作成する。
コマンドプロンプト開いて 、ジャンクションを作成する。
mklink /j "C:\Users\<user_name>\AppData\Roaming\Unity\Asset Store-5.x" "D:\Unity\Asset Store-5.x"
はいできた。
MagicOnionを使ってリアルタイム通信を試す。【Unity】
はじめに
1年ほど前に使ったことがあったので、 久しぶりに導入してみたら躓いたので、 記録として残しておきます。
とりあえず覚えながら作ってみたやつ
マッチングシステムができた。はず pic.twitter.com/eL2IfDtwad
— tasky (@fromtasky) 2020年4月17日
別々に動いた。とりあえずローカルではOK pic.twitter.com/TtDQyUkb0N
— tasky (@fromtasky) 2020年4月21日
MagicOnionについて
Web API ライクな通信や、リアルタイム通信ができるフレームワーク
特徴
サーバー側もクライアント側もC#で書ける。
gRPCを使用。(HTTP/2や、Protocol Buffersを使っていて、高速)
MessagePack for C#を使用。(LZ4で圧縮されてサイズが小さく、高速)
フィルタ機能を持ち、多段に重ねて処理を行うことができる。 ←この様子がMagicOnionの由来だとか
(↑通信の前後に追加の処理を行うことができる。)
色々調べていたら、チャットとか、マルチプレイを実現できそうなものとして、下記のようなものもありました。
SignalR
Photon(PUN2)
MagicOnionを選んだ理由は、1度勉強会で扱ったことがあるからです。
使うイメージ
クライアント側とサーバー側が共有するインターフェースを用意して、 それぞれ実装します。 互いのメソッドを呼び出してあげれば通信できます。
準備
- Unity
2018.4.2f1
各種ダウンロード
ダウンロードファイルの***.zipファイルですが、MagicOnionをIL2CPP環境で使う場合に使用します。 この対応については、最後の方に後述します。
MagicOnion
https://github.com/cysharp/MagicOnion/releases
バージョン
3.0.11
取得するファイル
MagicOnion.Client.Unity.unitypackage
moc.zip
gRPC
バージョン
2.26.0
取得するファイル
- grpc_unity_package.2.26.0-dev.zip
その時最新だった2.29.0で試したが、VisualStudioでプロジェクト作成して作業しようとしたら、「予期せぬエラーです。」と表示されてしまった。
分からなかったので、このバージョンに落とした。
もしかしたら、System.Memory.dllバージョンが噛み合ってないからだろうか・・・。
https://github.com/grpc/grpc/issues/21908
↑2.26.0で動いたというコメントがあったので、このバージョンを使用した。
MessagePack for C#
https://github.com/neuecc/MessagePack-CSharp/releases
バージョン
2.1.90
取得するファイル
MessagePack.Unity.2.1.90.unitypackage
mpc.zip
導入
Unityの設定変更
「File」→「Build Settings」から、 「Player Settings...」 を押す。
ビルド対象OSの 「Other Settings」項目 → 「Configuration」項目の
「Api Compatibility Level」 → .NET 4.x 「Allow 'unsafe' Code」 → チェック
に設定する。
用意したファイルのインポート
Unityプロジェクトを立ち上げ、**.unitypackageのファイルをインポートします。
MagicOnion.Client.Unity.unitypackage
MessagePack.Unity.2.1.90.unitypackage
ファイルの競合
導入時に、Assets/Plugins/ 直下の
System.Buffers
System.Memory
System.Runtime.CompilerServices.Unsafe
System.Threading.Tasks.Extensions
上記のファイルが競合するので、
名前を変えて、競合を回避する。
なんか消すのが怖かったので、実際どうなんだろう・・・。
これらを行えば、とりあえずUnity Editor上のエラーは消えた。
ひとまずテキトーにスクリプトを開いてVisual Studioを立ち上げる。
VisualStudioの設定
VisualStudioに.Net Core SDKとランタイムをインストールしていない場合は、インストールしておきます。 私の環境だと入っていたので、多分この辺りを確認すればよいと思います。
クライアント側の用意
特になし
サーバー側の用意
Unityのソリューションから右クリックで、
新規のプロジェクトを作成する。
サーバー側にパッケージをインストールする。
MagicOnion.Hosting
MessagePack.UnityShims
図:パッケージインストール例
(今思えば、これらのパッケージを参考にクライアント側のバージョンを合わせてダウンロードすればよかったのでは?と思う自分であった。)
クライアント - サーバー間の共有ファイルの設定
サーバー側のプロジェクトファイルを編集して、共有するフォルダの設定を行います。
- クライアント側
- サーバー側
サーバー用プロジェクトの設定に、
Unityプロジェクトの共有用フォルダのパスを指定します。
クイックスタートをやってみる
GitHubに載ってたクイックスタートを参考にやってみる。
https://github.com/cysharp/MagicOnion
サーバーのスタート処理
- Program.cs(サーバー側)
using System; using Grpc.Core; using MagicOnion.Server; namespace MagicOnionDemoServer { class Program { static void Main(string[] args) { GrpcEnvironment.SetLogger(new Grpc.Core.Logging.ConsoleLogger()); // setup MagicOnion and option. var service = MagicOnionEngine.BuildServerServiceDefinition(isReturnExceptionStackTraceInErrorDetail: true); var server = new global::Grpc.Core.Server { Services = { service }, Ports = { new ServerPort("localhost", 12345, ServerCredentials.Insecure) } }; // launch gRPC Server. server.Start(); // and wait. Console.ReadLine(); } } }
API通信
コーディング
共有するインターフェースを作成する。
- IMyFirstService.cs
using Grpc.Core; using MagicOnion; using MagicOnion.Server; using System; namespace ServerShared.Services { public interface IMyFirstService : IService<IMyFirstService> { /// <summary> /// 足し算を行う /// </summary> /// <param name="x"></param> /// <param name="y"></param> /// <returns></returns> UnaryResult<int> SumAsync(int x, int y); } }
サーバー側実装
- MyFirstService.cs
using System.Collections.Generic; using System.Text; using Grpc.Core; using MagicOnion.Hosting; using MagicOnion; using MagicOnion.Server; using ServerShared.Services; class MyFirstService : ServiceBase<IMyFirstService>, IMyFirstService { public async UnaryResult<int> SumAsync(int x, int y) { Logger.Debug($"Received:{x}, {y}"); return x + y; } }
クライアント側実装
- ApiConnectionTest.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; using Grpc.Core; using MagicOnion.Client; using ServerShared.Services; public class ApiConnectionTest : MonoBehaviour { async void Start() { // standard gRPC channel var channel = new Channel("localhost", 12345, ChannelCredentials.Insecure); // get MagicOnion dynamic client proxy var client = MagicOnionClient.Create<IMyFirstService>(channel); // call method. var result = await client.SumAsync(100, 200); Debug.Log("Client Received:" + result); } }
動作確認
サーバー側のプロジェクトをスタートアッププロジェクトに設定し、
プログラムを実行します。
クライアント側は、 空のGameObjectを作成して、ApiConnectionTest.csをアタッチして実行します。
通信できました。
リアルタイム通信
コーディング
インターフェースを作成
- IGamingHub.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using MagicOnion; using ServerShared.MessagePackObjects; using UnityEngine; namespace ServerShared.GamingHub { /// <summary> /// クライアント → サーバー 処理 /// </summary> public interface IGamingHub : IStreamingHub<IGamingHub, IGamingHubReceiver> { /// <summary> /// 入室処理 /// </summary> /// <param name="roomName"></param> /// <param name="userName"></param> /// <param name="position"></param> /// <param name="rotation"></param> /// <returns></returns> Task<Player[]> JoinAsync(string roomName, string userName, Vector3 position, Quaternion rotation); /// <summary> /// 退出処理 /// </summary> /// <returns></returns> Task LeaveAsync(); /// <summary> /// 移動処理 /// </summary> /// <param name="position"></param> /// <param name="rotation"></param> /// <returns></returns> Task MoveAsync(Vector3 position, Quaternion rotation); } /// <summary> /// サーバー → クライアント 処理 /// </summary> public interface IGamingHubReceiver { /// <summary> /// 入室を接続クライアント全員に通知 /// </summary> /// <param name="player"></param> void OnJoin(Player player); /// <summary> /// 退出を接続クライアント全員に通知 /// </summary> /// <param name="player"></param> void OnLeave(Player player); /// <summary> /// 移動したことを接続クライアント全員に通知 /// </summary> /// <param name="player"></param> void OnMove(Player player); } }
サーバー側実装
- GamingHub.cs
using System; using System.Collections.Generic; using System.Text; using MagicOnion; using MagicOnion.Server.Hubs; using Grpc.Core; using ServerShared.MessagePackObjects; using ServerShared.GamingHub; using System.Threading.Tasks; using UnityEngine; using System.Linq; namespace MagicOnionDemoServer { public class GamingHub : StreamingHubBase<IGamingHub, IGamingHubReceiver>, IGamingHub { IGroup room; Player self; IInMemoryStorage<Player> storage; public async Task<Player[]> JoinAsync(string roomName, string userName, Vector3 position, Quaternion rotation) { self = new Player() { Name = userName, Position = position, Rotation = rotation }; // Group can bundle many connections and it has inmemory-storage so add any type per group. (room, storage) = await Group.AddAsync(roomName, self); // Typed Server->Client broadcast. Broadcast(room).OnJoin(self); return storage.AllValues.ToArray(); } public async Task LeaveAsync() { await room.RemoveAsync(this.Context); Broadcast(room).OnLeave(self); } public async Task MoveAsync(Vector3 position, Quaternion rotation) { self.Position = position; self.Rotation = rotation; Broadcast(room).OnMove(self); } /// <summary> /// 切断処理 /// </summary> /// <returns></returns> protected override ValueTask OnDisconnected() { // on disconnecting, if automatically removed this connection from group. return CompletedTask; } } }
クライアント側実装
- GamingHubClient.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Grpc.Core; using MagicOnion.Client; using ServerShared.GamingHub; using ServerShared.MessagePackObjects; using UnityEngine; public class GamingHubClient : IGamingHubReceiver { Dictionary<string, GameObject> players = new Dictionary<string, GameObject>(); IGamingHub client; public async Task<GameObject> ConnectAsync(Channel grpcChannel, string roomName, string playerName) { client = StreamingHubClient.Connect<IGamingHub, IGamingHubReceiver>(grpcChannel, this); var roomPlayers = await client.JoinAsync(roomName, playerName, Vector3.zero, Quaternion.identity); /* 今いるプレイヤーの生成(自分も含めて生成される) foreach (var player in roomPlayers) { (this as IGamingHubReceiver).OnJoin(player); } */ return players[playerName]; } // methods send to server. public Task LeaveAsync() { return client.LeaveAsync(); } public Task MoveAsync(Vector3 position, Quaternion rotation) { return client.MoveAsync(position, rotation); } // dispose client-connection before channel.ShutDownAsync is important! public Task DisposeAsync() { return client.DisposeAsync(); } // You can watch connection state, use this for retry etc. public Task WaitForDisconnect() { return client.WaitForDisconnect(); } // Receivers of message from server. void IGamingHubReceiver.OnJoin(Player player) { Debug.Log("Join Player:" + player.Name); var cube = GameObject.CreatePrimitive(PrimitiveType.Cube); cube.name = player.Name; cube.transform.SetPositionAndRotation(player.Position, player.Rotation); players[player.Name] = cube; } void IGamingHubReceiver.OnLeave(Player player) { Debug.Log("Leave Player:" + player.Name); if (players.TryGetValue(player.Name, out var cube)) { GameObject.Destroy(cube); } } void IGamingHubReceiver.OnMove(Player player) { Debug.Log("Move Player:" + player.Name); if (players.TryGetValue(player.Name, out var cube)) { cube.transform.SetPositionAndRotation(player.Position, player.Rotation); } } }
- RealTimeConnectionTest.cs
テキトーなGameObjectを作成して、アタッチします。
using System.Collections; using System.Collections.Generic; using UnityEngine; using Grpc.Core; using ServerShared.GamingHub; public class RealTimeConnectionTest : MonoBehaviour { public static GamingHubClient gamingHubClient; Channel channel; async void Start() { this.channel = new Channel("localhost:12345", ChannelCredentials.Insecure); RealTimeConnectionTest.gamingHubClient = new GamingHubClient(); await gamingHubClient.ConnectAsync(channel, "SampleRoom", "tarou"); } }
動作確認
API通信と同様、サーバー側を実行しておいて、 クライアント側を実行します。
うまくいけば、四角いプレイヤーオブジェクトが生成されます。 これに、プレイヤーを動かすスクリプトを実行中にアタッチして、 確認してみます。
- PlayerController.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerController : MonoBehaviour { float speed = 3f; Vector3 pos, currentPos; void Update() { Move(); } void Move() { pos = transform.position; currentPos = pos; if (Input.GetKey("left")) { pos.x -= speed * Time.deltaTime; } if (Input.GetKey("right")) { pos.x += speed * Time.deltaTime; } if (Input.GetKey("up")) { pos.y += speed * Time.deltaTime; } if (Input.GetKey("down")) { pos.y -= speed * Time.deltaTime; } if (currentPos == pos) //値が変わらなかったら何もしない { return; } //サーバーを通して移動できるか確認したいので、コメントアウト //transform.position = pos; //移動情報送信 RealTimeConnectionTest.gamingHubClient.MoveAsync(pos, transform.rotation); } }
↑*静止画です
移動はガタガタですが、情報のやりとりができました。
IL2CPP対応
MagicOnionをIL2CPP環境で使う場合は、事前のコードジェネレートが必要になります。
mpc.zipとmoc.zipファイルの中にコードジェネレートのための実行ファイルがあるので、
それを設定します。
minamiさんの記事が参考になりました。
- Unity+.NET Core+MagicOnionの環境構築ハンズオン
https://qiita.com/_y_minami/items/c7899fdf1db505c06ba2
その中のMenuItems.csを、こちらが使うファイルパスに変更して、InitialSettings.csは下記のように設定してみました。
(このあたりのインスタンス登録処理は手探り状態で、どれを登録したらよいか、よくわかっていない。)
- InitialSettings.cs
using MagicOnion.Resolvers; using MessagePack.Resolvers; using MessagePack.Unity; using MessagePack; using UnityEngine; class InitialSettings { [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] static void RegisterResolvers() { StaticCompositeResolver.Instance.Register( UnityResolver.Instance, MagicOnionResolver.Instance, GeneratedResolver.Instance, BuiltinResolver.Instance, PrimitiveObjectResolver.Instance ); var options = MessagePackSerializerOptions.Standard.WithResolver(StaticCompositeResolver.Instance); MessagePackSerializer.DefaultOptions = options; } }
MagicOnionResolver.Instance、GeneratedResolver.Instanceあたりは、
はじめは参照エラーだったが、
コードジェネレートした後に確認したら、参照エラーが解決していた。
さいごに
Taskや、await/asyncなどの理解をもう少し深めようと思った。
設計って難しい。
ここの処理はクライアント側orサーバー側に持たせる。とか
レスポンス速度これくらいで大丈夫かな。とか
通信が途中で切断したときどうしよう。とか
そういうところ意識しないとな。
参考
- MagicOnion入門
https://www.slideshare.net/torisoup/magiconion-174973732
https://tech.cygames.co.jp/archives/3181/
- Unity+.NET Core+MagicOnionの環境構築ハンズオン