- 投稿日:2020-08-08T18:46:40+09:00
UnityでFlutterライクにUIを書けるUIWidgetsを試す
はじめに
「UIWidgets」はUnityでFlutterライクにGUIが書ける公式パッケージです。
UnityTech/UIWidgets: UIWidget is a Unity Package which helps developers to create, debug and deploy efficient, cross-platform Apps.
以前から気になっていたので、試しにサンプルアプリを作ってみました。インストール方法
Unityで新規プロジェクトを作成し、次のようにパッケージをダウンロードします。
cd <プロジェクト>/Packages git clone https://github.com/UnityTech/UIWidgets.git com.unity.uiwidgets
ダウンロード後、
ProjectSettings>Player>OtherSettings>ScriptingDefineSymbols
へ「UIWidgets_DEBUG
」を追加します。
(区切り文字は;
なので、他にもシンボルが定義されている場合は、次のように追加します)
これで、UIWidgetsを使用可能になりました。
使用方法
新しくシーンを作成し、
右クリック>UI>Canvas
でCanvas
をシーンに追加します。
次に、Canvas
を選択し、右クリック>UI>Panel
でPanel
をCanvas
に追加します。
その後、Panel
からImage
を削除しておきます。
あとは、次のようなスクリプトを作成し、Panel
にAddComponentします。UIWidgetsExample.csusing System.Collections.Generic; using Unity.UIWidgets.animation; using Unity.UIWidgets.engine; using Unity.UIWidgets.foundation; using Unity.UIWidgets.material; using Unity.UIWidgets.painting; using Unity.UIWidgets.ui; using Unity.UIWidgets.widgets; using UnityEngine; using FontStyle = Unity.UIWidgets.ui.FontStyle; namespace UIWidgetsSample { // サンプルなカウンターアプリ public class UIWidgetsExample : UIWidgetsPanel { protected override void OnEnable() { // 独自のフォントや、フォントアイコンを使用したい場合 // FontManager.instance.addFont(Resources.Load<Font>(path: "path to your font"), "font family name"); // カスタムフォントを、Weights・Style付きでロード. // FontManager.instance.addFont(Resources.Load<Font>(path: "path to your font"), "Roboto", FontWeight.w500, // FontStyle.italic); // マテリアルアイコンを追加。FamilyNameは必ず"Material Icons" // FontManager.instance.addFont(Resources.Load<Font>(path: "path to material icons"), "Material Icons"); base.OnEnable(); } protected override Widget createWidget() { return new WidgetsApp( home: new ExampleApp(), pageRouteBuilder: (RouteSettings settings, WidgetBuilder builder) => new PageRouteBuilder( settings: settings, pageBuilder: (BuildContext context, Animation<float> animation, Animation<float> secondaryAnimation) => builder(context) ) ); } class ExampleApp : StatelessWidget { public override Widget build(BuildContext context) { return new MaterialApp( home: new Counter() ); } } class Counter : StatefulWidget { public Counter(Key key = null) : base(key) { } public override State createState() { return new CounterState(); } } class CounterState : State<Counter> { private int counter = 0; public override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( title: new Text("Example App")), floatingActionButton: new FloatingActionButton( child: new Icon(Icons.add, color: Colors.white), onPressed: () => { this.setState(() => { this.counter++; }); }), body: new Center( child: new SizedBox( height: 150, child: new FittedBox( child: new Text("Counter: " + this.counter) ) ) ) ); } } } }ほぼほぼFlutterライクに書けました?
実行結果
次のように、カウンターアプリが表示されました。
Unityで神アプリを作ってた➕https://t.co/7BZSsfR611 pic.twitter.com/4ptGKbKNyz
— うぇるち? (@welchi_) August 8, 2020参考
UnityTech/UIWidgets: UIWidget is a Unity Package which helps developers to create, debug and deploy efficient, cross-platform Apps.
Unity - Manual: Platform dependent compilation
- 投稿日:2020-08-08T16:20:29+09:00
Unity 2Dにおける壁と坂道の判定を簡単に制御する
どうもマカロンです。今回は「2Dにおける壁と坂道の判定を簡単に制御する」という記事をnoteの方で書かせていただいたのでその紹介です。
▼Unity 2Dにおける壁と坂道の判定を簡単に制御するための準備
https://note.com/08_14/n/na76e9194f221▼Unity 2Dにおける壁と坂道の判定を簡単に制御する方法
https://note.com/08_14/n/n2aba06e02ae6この記事では2Dゲームの基本、壁と坂道における当たり判定を難しいプログラムを組まずに
作成する方法を紹介しています!
- 投稿日:2020-08-08T15:11:21+09:00
Unity + .NET Core + MagicOnion v3 環境構築ハンズオン
はじめに
MagicOnion は一度環境を構築してしまえば、触り心地が良くてとても使いやすいフレームワークだと思います。
しかし初期構築がやや複雑で、特に Unity と サーバー(.NET Core) の両方の経験が無い方が挑戦した場合はどこかでつまづいてしまうことも多いのではないかと思いました。
そこで、どちらかの経験が無い方でもつまずかずにポチポチと環境構築できる資料を目指して書いたのがこの記事になります。
もし途中でつまづくところがありましたら Twitter で教えていただけると喜びます。
ハンズオンの所要時間は Unity と VisualStudio をインストール済みの状態から開始して、30分~1時間程度です。
目次
1. 環境について
本記事を書くにあたって使用した OS、ツール、ソフトウェアのバージョンです。
- Windows 10
- Unity 2019.4.7f1
- Visual Studio 2019 16.7
- MagicOnion 3.0.12
- MessagePack 2.1.152
- gRPC 2.23.0
2. Unity 側の構築
2.1. プロジェクトの新規作成、PlayerSettings の変更、各種フォルダの作成
今回は 3D プロジェクトを作成します。
プロジェクト名は任意ですが、サーバーサイドのプロジェクトと見分けやすいようにSample.Unity
とします。保存先は任意の保存先を入力してください。
Unity が起動したら
PlayerSettings
を開きます。
Player
を選択して、下記の2箇所を変更します。
- APICompatibilityLevel を
.NET 4.x
に変更する- Allow unsafe Code にチェックをいれる
次に下記のフォルダを作成します。
※Sample.Unity は自分のプロジェクト名に読み替えてください。
- Sample.Unity\Assets\Editor
- Sample.Unity\Assets\Plugins
- Sample.Unity\Assets\Scripts
- Sample.Unity\Assets\Scripts\Generated
- Sample.Unity\Assets\Scripts\ServerShared
- Sample.Unity\Assets\Scripts\ServerShared\Hubs
- Sample.Unity\Assets\Scripts\ServerShared\MessagePackObjects
- Sample.Unity\Assets\Scripts\ServerShared\Services
フォルダ構成が下記のようになっていることを確認します。
2.2. MagicOnion のインストール
GitHub から
MagicOnion.Client.Unity.unitypackage
をダウンロードします。ダウンロードが終わったらダブルクリックしてインポートします。
2.3. MessagePack for C# のインストール
GitHub から
MessagePack.Unity.2.1.152.unitypackage
をダウンロードします。ダウンロードが終わったらダブルクリックしてインポートします。
MagicOnion と重複するファイルがあるため警告が表示されますがこのままインポートします。2.4. gRPC のインストール
gRPC の Daily Builds(2019/08/01) から
grpc_unity_package.2.23.0-dev.zip
をダウンロードします。ダウンロードが終わったら展開し、以下のフォルダを
Sample.Unity\Assets\Plugins
にコピーします。
- Google.Protobuf
- Grpc.Core
- Grpc.Core.Api
2.5. サーバーへ接続するスクリプトの用意
Assets\Scenes
にSampleController
スクリプトを作成します。
SampleController のコードは以下をコピペしてください。
Start() でサーバーへ接続し、OnDestroy() で切断するようになっています。using Grpc.Core; using System.Collections; using System.Collections.Generic; using UnityEngine; public class SampleController : MonoBehaviour { private Channel channel; void Start() { this.channel = new Channel("localhost:12345", ChannelCredentials.Insecure); } async void OnDestroy() { await this.channel.ShutdownAsync(); } }SampleScene へ空の GameObject を追加します。
追加した GameObject に SampleController を AddComponent します。
2.6. Unity ⇔ サーバー間のコード共有の動作確認用のクラスの用意
MagicOnion を利用する際は Unity 側で作成したクラスなどをサーバーサイドと共有して使うことが一般的です。
今回は Assets\Scripts\ServerShared 以下に作成したスクリプトをすべて共有する設定を行います。
コード共有の設定は後でサーバー側で設定を行いますが、先に動作確認用のクラスを用意しておきます。
Assets\ServerShared\MessagePackObjects
にPlayer
スクリプトを作成します。Player のコードは以下をコピペしてください。
using MessagePack; using UnityEngine; namespace Sample.Shared.MessagePackObjects { [MessagePackObject] public class Player { [Key(0)] public string Name { get; set; } [Key(1)] public Vector3 Position { get; set; } [Key(2)] public Quaternion Rotation { get; set; } } }MagicOnion を使用する場合、Client ⇔ サーバー間の通信で使用するクラスはこのような MessagePackObject として定義します。
MessagePackObject として定義するために必要なことは下記の2つだけですので覚えておきましょう。
- class に MessagePackObjectAttribute を付与する
- 各プロパティに KeyAttribute を付与して番号を順番にふる
Unity 側の構築はここまでです。
3. サーバー側の構築
続いてサーバー側の構築作業を進めます。
3.1. ソリューションへサーバー側のプロジェクトを追加
ソリューションを右クリックして、新しいプロジェクトを追加します。
コンソールアプリ(.NET Core)を選択し、次へをクリックします。
任意のプロジェクト名を入力します。(ここでは Sample.Server としました)
場所はプロジェクトのルートを指定してください。
3.2. MagicOnion のインストール
NuGet から MagicOnion をインストールします。
ツール -> NuGet パッケージマネージャー -> ソリューションの NuGet パッケージの管理 を開きます。
参照 をクリックし、 MagicOnion を検索します。
検索結果から MagicOnion.Hosting を選択し、Sample.Server にチェックをいれます。
バージョンは 3.0.12 を選択し、インストールします。
3.3. Program.cs の編集
Program.cs を下記の内容で上書き保存します。
これでサーバー側のプロジェクトを起動すると MagicOnion が起動するようになります。using MagicOnion.Hosting; using Microsoft.Extensions.Hosting; using System; using System.Threading.Tasks; namespace Sample.Server { class Program { static async Task Main(string[] args) { await MagicOnionHost.CreateDefaultBuilder() .UseMagicOnion() .RunConsoleAsync(); } } }3.4. ソリューションへクラスライブラリのプロジェクトを追加
このクラスライブラリを使って Unity 側とサーバー側のコード共有を行います。
ソリューションを右クリックして、新しいプロジェクトを追加します。
クラスライブラリ(C# .NET Standard)を選択します。
任意のプロジェクト名を入力します。(ここでは Sample.Shared としました)
場所はプロジェクトのルートを指定してください。
自動的に作成される Class1.cs は不要なので削除します。
3.5. クラスライブラリへ MagicOnion.Abstractions をインストール
先ほどの MagicOnion のインストールと同じ要領で、NuGet から MagicOnion.Abstractions を検索してインストールします。
対象のプロジェクトは Sample.Shared を選択し、バージョンは 3.0.12 を選択します。
3.6. クラスライブラリへ MessagePack.UnityShims をインストール
同じ要領で Sample.Shared へ MessagePack.UnityShims をインストールします。
3.7. クラスライブラリから Unity 側のコードを参照する
Sample.Shared をダブルクリックして Sample.Shared.csproj を開き、赤枠部分の設定を追加します。
追加する設定はこちらをコピペしてください。
※Sample.Unity の部分は自分の Unity 側のプロジェクト名に読み替えてください。<ItemGroup> <Compile Include="..\Sample.Unity\Assets\Scripts\ServerShared\**\*.cs" /> </ItemGroup>この状態でソリューションエクスプローラーを見てみると、Unity 側で用意した ServerShared フォルダ以下のファイルがクラスライブラリに読み込めたことがわかります。
3.8. サーバー側のプロジェクトからクラスライブラリを参照する
Sample.Server を右クリックし、追加、プロジェクト参照を選択します。
これで Unity とコード共有したサーバーの用意ができました。
4. API の実装と動作確認
ここからは API の実装と動作確認を行います。
MagicOnion は普通の API 通信とリアルタイム通信の2種類の通信が利用できますので、それぞれをテストしてみます。4.1. 普通の API通信
まずは普通の API 通信から試してみます。
4.1.1. Unity 側で API の定義を作る
Assets\Scripts\ServerShared\Services 以下に SampleService スクリプトを作ります。
SampleService の中身は以下をコピペして保存してください。
今回は足し算をしてくれる API と掛け算をしてくれる API を定義してみます。using MagicOnion; namespace Sample.Shared.Services { public interface ISampleService : IService<ISampleService> { UnaryResult<int> SumAsync(int x, int y); UnaryResult<int> ProductAsync(int x, int y); } }4.1.2. サーバー側で API を実装する
Sample.Server を右クリックして、新しいフォルダを追加します。
名前は Services とします。次に Services フォルダ内にクラスを追加します。
SampleService.cs の中身は以下をコピペして保存してください。
using MagicOnion; using MagicOnion.Server; using Sample.Shared.Services; namespace Sample.Server.Services { public class SampleService : ServiceBase<ISampleService>, ISampleService { public UnaryResult<int> SumAsync(int x, int y) { return UnaryResult(x + y); } public UnaryResult<int> ProductAsync(int x, int y) { return UnaryResult(x * y); } } }4.1.3. Unity 側で API を呼ぶコードを実装する
SampleController に SampleService を呼び出すコードを追加します。
下記のコードをコピペして上書きしてください。using Grpc.Core; using MagicOnion.Client; using Sample.Shared.Services; using System.Threading.Tasks; using UnityEngine; public class SampleController : MonoBehaviour { private Channel channel; private ISampleService sampleService; void Start() { this.channel = new Channel("localhost:12345", ChannelCredentials.Insecure); this.sampleService = MagicOnionClient.Create<ISampleService>(channel); this.SampleServiceTest(1, 2); } async void OnDestroy() { await this.channel.ShutdownAsync(); } async void SampleServiceTest(int x, int y) { var sumReuslt = await this.sampleService.SumAsync(x, y); Debug.Log($"{nameof(sumReuslt)}: {sumReuslt}"); var productResult = await this.sampleService.ProductAsync(2, 3); Debug.Log($"{nameof(productResult)}: {productResult}"); } }4.1.4. API 通信の動作確認
まずはサーバーを起動します。
Sample.Server を右クリックして、
スタートアッププロジェクトに設定
をクリックします。
これによって、普段は
Unity にアタッチ
と表示されていたボタンがSample.Server
の表示に変わります。
このボタンを押すとサーバーを起動することができます。
※スタートアッププロジェクトを元に戻す場合は
Assembly-CSharp
を右クリックしてスタートアッププロジェクトに指定します。
※Unity にアタッチしつつサーバーを起動したい場合はマルチスタートアッププロジェクト
を使用します。(後述)続いてサーバーを起動した状態で Unity の Scene を再生します。
4.2. リアルタイム通信
続いてリアルタイム通信を試してみます。
4.2.1. Unity 側で API の定義を作る
普通の API 通信と同じく、まずは API の定義から作ります。
Assets\Scripts\ServerShared\Hubs 以下に SampleHub スクリプトを作ります。SampleHub の中身は以下をコピペして保存してください。
今回はゲームにログイン、チャットで発言、位置情報を更新、ゲームから切断、という4つの API を作ります。using MagicOnion; using Sample.Shared.MessagePackObjects; using System.Threading.Tasks; using UnityEngine; namespace Sample.Shared.Hubs { /// <summary> /// CLient -> ServerのAPI /// </summary> public interface ISampleHub : IStreamingHub<ISampleHub, ISampleHubReceiver> { /// <summary> /// ゲームに接続することをサーバに伝える /// </summary> Task JoinAsync(Player player); /// <summary> /// ゲームから切断することをサーバに伝える /// </summary> Task LeaveAsync(); /// <summary> /// メッセージをサーバに伝える /// </summary> Task SendMessageAsync(string message); /// <summary> /// 移動したことをサーバに伝える /// </summary> Task MovePositionAsync(Vector3 position); } /// <summary> /// Server -> ClientのAPI /// </summary> public interface ISampleHubReceiver { /// <summary> /// 誰かがゲームに接続したことをクライアントに伝える /// </summary> void OnJoin(string name); /// <summary> /// 誰かがゲームから切断したことをクライアントに伝える /// </summary> void OnLeave(string name); /// <summary> /// 誰かが発言した事をクライアントに伝える /// </summary> void OnSendMessage(string name, string message); /// <summary> /// 誰かが移動した事をクライアントに伝える /// </summary> void OnMovePosition(Player player); } }4.2.2 サーバー側で API を実装する
普通の API の実装の時と同じ要領で、Sample.Server 以下に Hubs フォルダを作り、その中に SampleHub.cs を作ります。
SampleHub.cs の中身は以下をコピペして保存してください。
using MagicOnion.Server.Hubs; using Sample.Shared.Hubs; using Sample.Shared.MessagePackObjects; using System; using System.Threading.Tasks; using UnityEngine; public class SampleHub : StreamingHubBase<ISampleHub, ISampleHubReceiver>, ISampleHub { IGroup room; Player me; public async Task JoinAsync(Player player) { //ルームは全員固定 const string roomName = "SampleRoom"; //ルームに参加&ルームを保持 this.room = await this.Group.AddAsync(roomName); //自分の情報も保持 me = player; //参加したことをルームに参加している全メンバーに通知 this.Broadcast(room).OnJoin(me.Name); } public async Task LeaveAsync() { //ルーム内のメンバーから自分を削除 await room.RemoveAsync(this.Context); //退室したことを全メンバーに通知 this.Broadcast(room).OnLeave(me.Name); } public async Task SendMessageAsync(string message) { //発言した内容を全メンバーに通知 this.Broadcast(room).OnSendMessage(me.Name, message); await Task.CompletedTask; } public async Task MovePositionAsync(Vector3 position) { // サーバー上の情報を更新 me.Position = position; //更新したプレイヤーの情報を全メンバーに通知 this.Broadcast(room).OnMovePosition(me); await Task.CompletedTask; } protected override ValueTask OnConnecting() { // handle connection if needed. Console.WriteLine($"client connected {this.Context.ContextId}"); return CompletedTask; } protected override ValueTask OnDisconnected() { // handle disconnection if needed. // on disconnecting, if automatically removed this connection from group. return CompletedTask; } }4.2.3. Unity 側で API を呼ぶコードを実装する
SampleController に SampleHub の各 API を呼び出すコードを追加します。
下記のコードをコピペして上書きしてください。using Grpc.Core; using MagicOnion.Client; using Sample.Shared.Hubs; using Sample.Shared.MessagePackObjects; using Sample.Shared.Services; using UnityEngine; public class SampleController : MonoBehaviour, ISampleHubReceiver { private Channel channel; private ISampleService sampleService; private ISampleHub sampleHub; void Start() { this.channel = new Channel("localhost:12345", ChannelCredentials.Insecure); this.sampleService = MagicOnionClient.Create<ISampleService>(channel); this.sampleHub = StreamingHubClient.Connect<ISampleHub, ISampleHubReceiver>(this.channel, this); // 普通の API の呼び出しはコメントアウトしておきます // 残しておいても問題はないです(リアルタイム通信と両方動きます) //this.SampleServiceTest(1, 2); this.SampleHubTest(); } async void OnDestroy() { await this.sampleHub.DisposeAsync(); await this.channel.ShutdownAsync(); } /// <summary> /// 普通のAPI通信のテスト用のメソッド /// </summary> async void SampleServiceTest(int x, int y) { var sumReuslt = await this.sampleService.SumAsync(x, y); Debug.Log($"{nameof(sumReuslt)}: {sumReuslt}"); var productResult = await this.sampleService.ProductAsync(2, 3); Debug.Log($"{nameof(productResult)}: {productResult}"); } /// <summary> /// リアルタイム通信のテスト用のメソッド /// </summary> async void SampleHubTest() { // 自分のプレイヤー情報を作ってみる var player = new Player { Name = "Minami", Position = new Vector3(0, 0, 0), Rotation = new Quaternion(0, 0, 0, 0) }; // ゲームに接続する await this.sampleHub.JoinAsync(player); // チャットで発言してみる await this.sampleHub.SendMessageAsync("こんにちは!"); // 位置情報を更新してみる player.Position = new Vector3(1, 0, 0); await this.sampleHub.MovePositionAsync(player.Position); // ゲームから切断してみる await this.sampleHub.LeaveAsync(); } #region リアルタイム通信でサーバーから呼ばれるメソッド群 public void OnJoin(string name) { Debug.Log($"{name}さんが入室しました"); } public void OnLeave(string name) { Debug.Log($"{name}さんが退室しました"); } public void OnSendMessage(string name, string message) { Debug.Log($"{name}: {message}"); } public void OnMovePosition(Player player) { Debug.Log($"{player.Name}さんが移動しました: {{ x: {player.Position.x}, y: {player.Position.y}, z: {player.Position.z} }}"); } #endregion }4.2.4. リアルタイム通信の動作確認
普通の API 通信の動作確認と同じ要領でサーバーを起動し、その後で Unity で Scene を再生します。
これで普通の API 通信とリアルタイム通信の両方の動作確認ができました。
5. IL2CPP 対応(コードジェネレーターによるコード生成)
UnityEditor 上で動かすなら今のままでも問題ないのですが、IL2CPP を使う場合(例えば Platform を iOS にしたとき)はこのようなエラーが発生します。
IL2CPP は動的なコード生成に対応していないため、コードジェネレーターを使用して事前に必要なコードを生成する必要があります。
5.1. MagicOnion.MSBuild.Tasks のインストール
NuGet で MagicOnion.MSBuild.Tasks をインストールします。
プロジェクトは Sample.Shared を選択し、バージョンは 3.0.12 を選択します。
5.2. MessagePack.MSBuild.Tasks のインストール
同じ要領で MessagePack.MSBuild.Tasks もインストールします。
5.3. Sample.Shared.csproj の編集
Sample.Shared をダブルクリックし、Sample.Shared.csproj を開きます。
赤枠部分のコードを追加して保存します。
コードは以下をコピペしてください。<Target Name="GenerateMessagePack" AfterTargets="Compile"> <MessagePackGenerator Input=".\Sample.Shared.csproj" Output="..\Sample.Unity\Assets\Scripts\Generated\MessagePack.Generated.cs" /> </Target> <Target Name="GenerateMagicOnion" AfterTargets="Compile"> <MagicOnionGenerator Input=".\Sample.Shared.csproj" Output="..\Sample.Unity\Assets\Scripts\Generated\MagicOnion.Generated.cs" /> </Target>この状態で Sample.Server をビルドすると Sample.Unity\Assets\Scripts\Generated 以下にコードジェネレーターによってコードが生成されます。
5.4. 生成されたコードの使用
次にこのコードを使用する設定を行います。
Scripts フォルダに C# Script を作り、名前を InitialSettings とします。InitialSettings のコードは下記をコピペして保存します。
using MessagePack; using MessagePack.Resolvers; using UnityEngine; namespace Assets.Scripts { class InitialSettings { [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] static void RegisterResolvers() { // NOTE: Currently, CompositeResolver doesn't work on Unity IL2CPP build. Use StaticCompositeResolver instead of it. StaticCompositeResolver.Instance.Register( MagicOnion.Resolvers.MagicOnionResolver.Instance, MessagePack.Resolvers.GeneratedResolver.Instance, BuiltinResolver.Instance, PrimitiveObjectResolver.Instance, MessagePack.Unity.UnityResolver.Instance ); MessagePackSerializer.DefaultOptions = MessagePackSerializer.DefaultOptions .WithResolver(StaticCompositeResolver.Instance); } } }これで IL2CPP の環境でも動作するようになりました。
6. iOS ビルド対応
iOS 用のビルドではさらに以下の追加作業が必要です。
- Disable Bitcode
- Add libz.tbd
Assets\Editor に C# Script を追加し、名前を BuildIos とします。
BuildIos のコードは以下をコピペします。
#if UNITY_IPHONE using System.IO; using UnityEditor; using UnityEditor.Callbacks; using UnityEditor.iOS.Xcode; public class BuildIos { /// <summary> /// Handle libgrpc project settings. /// </summary> /// <param name="target"></param> /// <param name="path"></param> [PostProcessBuild(1)] public static void OnPostProcessBuild(BuildTarget target, string path) { var projectPath = PBXProject.GetPBXProjectPath(path); var project = new PBXProject(); project.ReadFromString(File.ReadAllText(projectPath)); var targetGuid = project.GetUnityFrameworkTargetGuid(); // libz.tbd for grpc ios build project.AddFrameworkToProject(targetGuid, "libz.tbd", false); // libgrpc_csharp_ext missing bitcode. as BITCODE exand binary size to 250MB. project.SetBuildProperty(targetGuid, "ENABLE_BITCODE", "NO"); File.WriteAllText(projectPath, project.WriteToString()); } } #endif環境構築は以上で終了です。お疲れさまでした。
7. マルチスタートアッププロジェクトについて
途中で説明を割愛したマルチスタートアッププロジェクトの利用方法です。
ソリューションを右クリックして、スタートアッププロジェクトの設定
をクリックします。
マルチスタートアッププロジェクト
にチェックをいれ、Assembly-CSharp
とSample.Server
のアクションを開始
にして OK を押します。この状態で
開始
を押すとデバッガーを Unity にアタッチしながらサーバーを起動することができます。
8. 後書きと参考にさせていただいた記事などへのリンク
こんなに長い記事を最後まで読んでいただいてありがとうございます。
少しでも役に立つことがあれば幸いです。環境構築に成功して、より技術的な内容や実践的なコードが必要になった際は下記の記事などがおすすめです。
- 投稿日:2020-08-08T14:15:13+09:00
Relative Joint2Dの解説
今回はRelative Joint2Dの解説をします。
先に言っておくと、Fixed Joint2Dとは全然違う代物でした。Relative Joint2Dとは
Relative Joint2Dとは、オブジェクトどうしの相対位置や相対角度を保とうとするジョイントのようです。相対関係を保とうと頑張りますが、常に保つことはできない感じです。
また、このジョイントの継承元はAnchored Joint2DではなくJoint2D
なので、Distance Joint2DやFixed Joint2Dとは違い、アンカーがありません。Relative Joint2Dの各パラメータ
・Max Force
このオブジェクトに与えることができる最大の力を決めるパラメータです。相対位置を保とうとする力でもあるので、ここの値が高くなるとオブジェクトが動きにくくなります。
ちなみにBreak Forceがこのパラメータより高い値だと、破壊することができなくなるので注意してください。・Max Torque
このオブジェクトに与えることができる最大の回転力を決めるパラメータです。相対角度を保とうとする力でもあるので、ここの値が高くなるとオブジェクトが回りにくくなります。
ちなみにBreak Torqueがこのパラメータより高い値だと、破壊することができなくなるので注意してください。・Correction Scale
この値はよく分かりませんが、挙動がおかしいときにここの値をいじってみると改善されるのかもしれません。
Unityマニュアルには、Max ForceとMax Torqueと何らかの関係性があるような感じで説明されていました。・Auto Configure Offset
ここのチェックボックスにチェックを入れると下の2つのOffsetに自動的に値が入ります。
下の2つのOffsetの値を決めるのが面倒であれば使うことをオススメします。・Linear Offset
アタッチされたオブジェクトとの相対位置を決めるパラメータです。
値はアタッチされたオブジェクトのローカル座標になります。
何もアタッチしていなければワールド座標になります。・Angular Offset
アタッチされたオブジェクトとの相対角度を決めるパラメータです。
ここの角度の表し方は弧度法
という一般ではあまり使われない表し方をするので注意してください。
一般的によく使われる「360°」で表されるアレは六十分法
という表し方です。
ここに六十分法と弧度法の関係をいくつか紹介します。1°=π/180≒0.01745334
2°=π/90≒0.03490667
45°=π/4≒0.7853981
90°=π/2≒1.570796
180°=π≒3.141593
360°=2π=0
よく見ると、弧度法も角度が倍になれば数値も倍になっていることが分かるでしょうか。
弧度法も六十分法と同じく直線的に大きくなっていくので、そこまで難しい表し方ではありません。
一番最後の360°=2π=0
は、360°が0°なのと同じく2πも0と同じ、つまり一周したということです。
ちなみにこのパラメータは時計回りなので、実際には90°=-π/2≒-1.570796
という風になります。使い道
Unityマニュアルにはアイテムやカメラを追従させる等に使えると書いていました。
このジョイントの最大の利点は決められた位置から離れられる
ことであり、それによってより自然な動きでプレイヤーなどの動くものを追いかけられるところです。
なので、そういう追いかけさせたいものに使うといいかと思います。
注意点として、オブジェクトが決められた位置から離れると、両方のオブジェクトが引っぱり合うので、追いかけさせたいオブジェクトの質量は軽くしておく
方がいいかと思います。おわりに
はじめはPhisics2Dについて勉強したくて、記事はついでのつもりだったんですが、最近は記事を書くために勉強してる感じになっています。でも記事を書くのは割と楽しいし、前よりPhisics2Dの魅力をより感じられるようになった気がするので結果オーライって感じですね。
次回はFriction Joint2Dについての記事を書きます。
- 投稿日:2020-08-08T10:23:32+09:00
生存率8%・・・入門動画の再生数から分かるUnityの挫折率
※Unity初心者の自分がとあるチュートリアルに挑戦して最後まで完走したあと、自分のブログに書いたコラムです。
Unityの難しさ
先日チャレンジしたチュートリアル動画の再生数が、回を追うごとに激減していったのが興味深かったのでまとめてみた。自分はゼロからではなく基本操作は問題無し、ただしスクリプトはUnity本を一冊読んだけど全然書けない、よくいる初心者のレベル。でも絶対折れないで最後までやると決めてたので完走できた。一応最後まで完走したけどやっぱりUnityは難しい。原因は主にC#、C#、C#、、、
ゼロからはじめるUnity
今回自分が挑戦した、NCC新潟コンピュータ専門学校さんが無料で配信しているUnityのチュートリアル動画。[入門編 ゲーム作り、プログラミング初心者向け講座]この動画の再生数がどんどん落ちていくのが興味深い。同じ志を持った仲間どんどん減っていきます。。。動画はチャプターごとに分かれていて全9本。
1/9 インストールから基本操作
https://youtu.be/NoJsCcIX3OE58,034回視聴
Unity覚えたい人が約6万人見にきている(^^)/
同志がたくさんいて心強く感じている、自分もがんばろう!
2/9 ステージとキャラクターの作成
https://youtu.be/loz4_lb-JsM30,852回視聴
27,182回減、約半分いなくなった( ゚Д゚)
それでもまだ3万人いる
3/9 スクリプトとステージの調整
https://youtu.be/XZxSi7O-RI415,373回視聴
15,479回減、さらに半分いなくなった(゜ー゜)
それでもまだ1万5千人いる
4/9 カメラの追尾処理と敵の出現
https://youtu.be/WqOei3Nt6KY10,888回視聴
4,485回減、最初の人数の約1/5まで減った( ノД`)シクシク…
それでもまだ1万人いる
5/9 敵オブジェクトのモーション付け
https://youtu.be/PSxQip9cXRM9,010回視聴
1,878回減、離脱率に急ブレーキ(^_-)-☆
残り約9千人
ナカマヘッタ。。。
6/9 ゴールの製作
https://youtu.be/sGgLIFlWTps7,424回視聴
1,586回減、前回と同じくらいの離脱率( `ー´)ノ
残り約7千人弱
7/9 コースレイアウトの調整
https://youtu.be/7KjAw2skglU5,300回視聴
2,124回減、 前回から2千以上減った、難易度上がった?( ;∀;)
残り約5千人弱
8/9 敵をコース上に配置
https://youtu.be/jw4f9EjRZTY4,936回視聴
364回減、 ゴールが見えたのもあるか?ほとんど残った!(*'▽')
残り約5千人
9/9 アセットストアを使ってキャラクターをインポートから完成まで
https://youtu.be/JFQceCuoA_A4,738回視聴
198回減、 微減でフィニッシュ!
報告!生存者4,738名 (`・ω・´)ゞ
挑戦者58,034 - 最終動画到達者4,738 - 生存率8%
最初の再生数約58,000回、最後は約47,00回という大幅減。一人で複数回視聴してるだろうから純粋な人数じゃないだろうけど、学習しようと始めた内の1割ぐらいしか残ってない。。。入門ですらこの数字。スクリプトも写経するだけで最後までいけるのにこの離脱率。Unity学習の難易度がいかに高いが分かる結果だった。スクリプトがからんでくるあたりで一気に減る。。。自分もスクリプトの意味は分かるけど、書けるかといったら基本の文法や仕組みがまだイマイチなので無理。じっくりやっていきます。
最後まで完走したので記録に残しておいた
https://youtu.be/aLUo0An0sJw
- 投稿日:2020-08-08T03:15:17+09:00
[Unity]iosでキーボードから文字入力をする&キャンセル処理
概要
環境
Unity 2019.4.5f1
Xcode 11.6
ios 13.5.1
InputField公式リファレンス
https://docs.unity3d.com/2019.1/Documentation/ScriptReference/UI.InputField.htmlInputField
InputFieldはPCだとキーボードから入力可能だが、スマホへBuildするとスマホのキーボードが出てきて入力が可能になる。
実装は簡単で、Create/UIから選択するだけでいろいろ整えてくれたオブジェクトを出してくれる。
こちらでやるべき部分は下のコード部分。InputFieldManager.csusing UnityEngine.UI; public class InputFieldManager : MonoBehaviour { private InputField inputField; public string resultText; // 入力されたテキストを格納 void Start(){ inputField = this.gameObject.GetComponent<InputField>(); InitInputField (); } // フィールドの初期化 private void InitInputField () { inputField.text = ""; inputText = ""; } // OnValueCangeで呼び出す関数 public void ChangeText(){ // 入力したテキストをstringに格納する resultText = inputField.text; } // OnEndEditで呼び出す関数 public void FinishEditText(){ // 入力が終わった後にどこかに渡すとか // 入力が終わったので初期化 InitInputField (); } }スクリプトを書いたら、適当なオブジェクト(InputFieldやInputPanelなど)にアタッチして、インスペクターからOnValueChangedとOnEndEditのそれぞれにどの関数を呼び出すか指定する。
ちなみに呼び出される順番はOnValueChanged(inputField.textが変わるたびに) → OnEndEdit
キャンセル処理
LINEなどを使ってる時を想像して欲しい。
何かを入力していたが、あれ?と思って一旦中断してキーボードを消し、確認した後で再入力ということがあると思う。CPと挙動が異なる点として、PCはOnEndEditが呼ばれたりinputField.isFocused == falseの場合でも、InputFieldにあるテキストは消えないが、
iosの場合、InputFieldではキーボードを消してしまうとInputFieldにあるテキストも消えてしまう。そこで入力をキャンセルした時にInputFieldにあるテキストを保持したい。
テキストの保持は以下のコードでできる。
注意点として、TouchScreenKeyboard.isSupported=true(Unity Editorは=false)環境のみでTouchScreenKeyboard.Statusは動作する。#if UNITY_IOS
などで囲んでおくといいかも。InputFieldManager.csbool isCancel = false; // OnValueCangeで呼び出す関数 public void ChangeText(){ if (inputField.touchScreenKeyboard.status == TouchScreenKeyboard.Status.Canceled) { // Cancleを押した時 isCancel = true; } else if (inputField.isFocused && !isCancel){ // 他のところをタップした時 inputText = inputField.text; Debug.Log ("inputText: " + inputText); } } // OnEndEditで呼び出す関数 public void FinishEditText(){ if (inputField.touchScreenKeyboard.status == TouchScreenKeyboard.Status.Done) { // 入力完了時何かに渡す // フィールドの初期化 InitInputField (); Debug.Log ("Done"); } else if (inputField.touchScreenKeyboard.status == TouchScreenKeyboard.Status.Canceled || inputField.touchScreenKeyboard.status == TouchScreenKeyboard.Status.LostFocus) { // 入力キャンセル時もしくは inputField.text = inputText; isCancel = false; Debug.Log ("Canseled"); } else { // 他の部分をタップした場合 inputField.text = inputText; isCancel = false; Debug.Log ("Canseled"); } }キーボードとInputField以外の部分をタップすると
inputField.isFocused == false
となり、キーボードが消えてしまう。なぜか、inputField.touchScreenKeyboard.status == TouchScreenKeyboard.Status.LostFocusは呼ばれないようなのでFinishEditTextではelseにもキャンセルされたと認識して同じ処理をしている。
また、inputField.isFocused == false
を利用して他のところがタップされた時はテキストは更新されないようにしている。他にはまった点としては元々はChangeTextでも
resultText = inputField.text
という処理を行っていたが、入力した後にCancelボタンを押すとなぜかinputField.text = ""
という挙動になった。どうやらデフォルトっぽい挙動だが、邪魔だったのでChangeTextでキャンセルが押された場合にはフラグを変更させるだけにさせた。フラグを作った理由としてはCancelを押してキーボードを閉じた場合、
inputField.isFocused == true
となりつつinputField.text = ""
が入ってしまうので、他の部分をタップした時と区別するために行っている。位置調整
下からキーボードが出てくるため何もしなければキーボードで見えなくなる部分が出てくる。
今回はゴリ押しでキーボードの高さを調べて、重なってしまうUIの位置を上にずらすことで対応した。動かすUIは一括りのGameObjectに入れてlocalPositionで変更するのが楽。今回の場合は、InputPanelの上にさらにまとめるGameObjectがあったのでそれを移動させている。
InputFieldのみの移動で大丈夫なら、InputPanelをlocalPosition.y変更すればいい。
Start時に初期位置を保存しておき、
inputField.isFocused
の時一回だけ移動させるようにしてる。
キャンセルか何かでキーボードが消えた時に初期位置を代入してやると元に戻る。private void StartInputText () { if (inputField.isFocused && isOnceInput) { isOnceInput = false; // y軸をいい感じの値にする parentRect.localPosition += new Vector3 (0, 940f, 0); } }最終的なコード
InputFieldManager.csusing UnityEngine; using UnityEngine.UI; public class InputFieldManager : MonoBehaviour { private InputField inputField; public string resultText; // 入力されたテキストを格納 private RectTransform parentRect; private Vector3 defaultParentPos; // 初期位置 private bool isOnceInput = true; // 入力時のfooter・bodyの位置移動フラグ private bool isCancel = false; // cancelボタンが押されたか void Start(){ inputField = this.gameObject.GetComponent<InputField>(); parentRect = this.transform.parent.GetComponent<RectTransform> (); defaultParentPos = parentRect.localPosition; InitInputField (); } void Update(){ StartInputText () } // 入力開始時 private void StartInputText () { if (inputField.isFocused && isOnceInput) { isOnceInput = false; // y軸をいい感じの値にする parentRect.localPosition += new Vector3 (0, 940f, 0); } } // キーボードによって上にずれたUIの位置を戻す public void ResetKeybord () { isOnceInput = true; parentRect.localPosition = defaultParentPos; isCancel = false; } // フィールドの初期化 private void InitInputField () { inputField.text = ""; inputText = ""; ResetKeybord (); } // OnValueCangeで呼び出す関数 public void ChangeText(){ if (inputField.touchScreenKeyboard.status == TouchScreenKeyboard.Status.Canceled) { // Cancleを押した時 isCancel = true; } else if (inputField.isFocused && !isCancel){ // 他のところをタップした時 inputText = inputField.text; Debug.Log ("inputText: " + inputText); } } // OnEndEditで呼び出す関数 public void FinishEditText(){ if (inputField.touchScreenKeyboard.status == TouchScreenKeyboard.Status.Done) { // 入力完了時何かに渡す // フィールドの初期化 InitInputField (); Debug.Log ("Done"); } else if (inputField.touchScreenKeyboard.status == TouchScreenKeyboard.Status.Canceled || inputField.touchScreenKeyboard.status == TouchScreenKeyboard.Status.LostFocus) { // 入力キャンセル時もしくは inputField.text = inputText; ResetKeybord (); Debug.Log ("Canseled"); } else { // 他の部分をタップした場合 inputField.text = inputText; ResetKeybord (); Debug.Log ("Canseled"); } } }
- 投稿日:2020-08-08T01:07:47+09:00
VRMファイルをBlenderとUnityで雰囲気合わせてカスタマイズする
はじめに
VRoidStudioでVRoidを作り始めて慣れてきた頃に、もうちょっと元のキャラの衣装のパーツを再現したいとふと思った。そこで手を付け始めたのがかの有名なBlenderと、ゲーム制作環境であるUnityだ。
本記事ではあくまで自分が作業する上でポイントだと思った点を覚え書き目的で記載していく。
なにせこの記事を書いた時点でまだBlenderも1ヶ月程度の経験レベル・Unityも同等というド素人なのでもっとこうしたほうがいいよ!などアドバイスあればいただきたく。VRoidStudioでベースのVRMを作る
まずはVRoidを作っていく。VRoidStudioは簡単に・かなりの部分をいじれるので正直これだけでも慣れればオリジナルキャラはもちろん、各ゲームやアニメ等のキャラクターを再現することも可能である。
※戦艦少女Rというゲームのキャラクター、巡洋戦艦レナウンしかしながらまだ開発中ということで、衣装の各部位が未実装だったりするのはこれからに期待したいところである。
サンプルとして巡洋戦艦レナウンをVRoidStudioで作成した。このキャラは本来ヘッドセットや足に艤装という戦うための武装をしている。しかし、VRoidStudioでできるのはメイド服やヘッドドレスなどまでだ。
ある程度まで出来たら、「撮影・エクスポート」の「エクスポート」を実行し、VRMファイルを保存しておく。
エクスポート時にはVRoidとしての基本情報を入力する必要がある。
ここですべて入力しなくともあとの工程で入力可能なので、せめてタイトルくらいは入れて出力しておこう。
Blender で必要なパーツを作る
ここからが本番である。
Blenderは2020/08/07時点で最新である2.83を使っている。なお、BlenderでVRMを読み込ませるには公式ヘルプから関連記事に飛べるので、そちらを参考にしてほしい。
VRMファイルの編集方法 / VRMファイルはVRoid Studioで再編集できますか?
目的のパーツの大きさや位置合わせをするのに絶対に必要なので、BlenderでVRMを読み込めるようにしておこう。
パーツを作る
ここからは普通に3Dオブジェクトを作る作業・手順である。書籍等参考にしてガシガシ作っていこう。
レナウンのヘッドセットを作った。(赤く選択されたパーツ)
原作ゲームの絵を見るともっと細かいが、自分の技術的に完全再現は無理なので、パッと見て雰囲気的に合ってる程度に留めた。
テクスチャを準備する
パーツに当てはめるためのテクスチャ画像を用意する。そのためには、「UV Editing」メニューでUVマップ、つまりテクスチャを画像からどのパーツにどのように当てはめたいのかを設定する。
このあたりは、VRoidStudioでテクスチャの編集に慣れていればなんとなくわかるだろう。そして、
あのツールが相当良くできているのも実感するはず。
一から自分で作ると非常に面倒だ。(しかし望むとおりに作れる)
ひねくれたパーツの構成にならないよう、うまくパーツを作り込みたいところである。Shadingをいじる
一通り形ができたら、Shadingメニューをクリックして画面を切りかえる。
すると、「プリンシプルBSDF」というシェーダーが作られてしまう。これでは、元のVRoidの雰囲気と合わない。
そこで、「追加」→「グループ」→「MToon_universioned」を選ぶ。
1, MToon_universionedの右の放射と、「マテリアル出力」の「サーフェス」を接続する。
2, 別途用意したテクスチャファイルを読み込みたいので、「追加」→「テクスチャ」→「画像テクスチャ」を選ぶ。
3, 画像を読み込み、「カラー」を「MainTexture」と「ShadeTexture」の2箇所に接続する。
4, 「アルファ」を「MainTextureAlpha」に接続する。元のVRMの本体のShadingを見ると色々接続されているが、どうやらこれだけでも十分目的を達成できることがわかった。
(実際には全部同じになるようにしないといけないのだろうが)
外部へ出力する
一通りできたら、ファイルメニューの「エクスポート」から「wavefront (.obj)」形式で出力する。(FBXでもよいが、今回はobjにした)
「選択物のみ」にチェックを入れないと、全オブジェクトが出力されて大変なことになる。
ここまでやって、ようやく目的のパーツがほぼできあがる。
Unityで結合作業
UnityでもVRMを読み込むには設定が必要となる。上記リンクから関連記事に飛べるのでそちらを参考にしてほしい。
なおVRoidSDKのため、登録申請が必要となるので忘れずにやっておこう。Unityエディターで読み込む
Unityに次のものを読み込ませる。
・VRMファイル
・objファイル
・mtlファイル
・テクスチャ画像エクスプローラでUnityのプロジェクトフォルダの「Assets」フォルダに放り込んでもいいし、エディタ上で「Assets」フォルダにドラッグアンドドロップしてもどちらでもよい。
読み込みと適切な変換が始まるのでしばらく待つ。UnityにVRoidを配置する
Unityの基本的な使い方に沿ってVRMファイルから配置していく。地面に埋まったり空飛んでいたりする場合があるが、今回はゲームを作るわけではないので気にしない。
マテリアルを作成する
「アセット」→「作成」→「マテリアル」を選びマテリアルを作成する。
Shaderは「VRM/Mtoon」にする。プロジェクトペインからテクスチャ画像を「Texture」の左端の四角い部分にドラッグアンドドロップする。
これでマテリアルができた。
パーツにマテリアルを設定する
パーツのインスペクタを開き、「オンデマンドの再マッピング」のすべてのマテリアルに、先程作ったマテリアルを設定していく。
最後に「適用する」を忘れずに押しておこう。
パーツをVRoidの目的の場所に配置する
Blenderのときと同様に位置を頑張って測って設定していこう。
ライティングやエフェクトの確認
ここまで来たら後はもうVRMファイルを出力し直すだけなのだが、元のVRoidと描画の雰囲気が合っているか、そして光やエフェクトが適切にかかるかを確認しておこう。
ライティングはエディタの上部にある「電球」マークのボタンで切り替えられる。
ここで設定と実際の効果をまとめておきたい。
Blender上のMToon_universioned ・・・ パーツのテクスチャの描画をVRoidのテクスチャと雰囲気を合わせた。
Unity上のマテリアルのVRM/MToon ・・・ ライティングやエフェクトのかかり具合をVRoidに合わせた。
VRMを再出力する
ヒエラルキー上でVRoidをクリックして選択し、「VRM」メニューから「UniVRM-x.xx.x」を選び、「Export humanoid」をクリックする。
この段階でもVRoidの情報を入力できるので、忘れずに入力しよう。
元のVRMと名前を同じにすると上書きするので、それが都合悪いのであれば名前を変えて保存しよう。
終わりに
以上が自分がVRoidStudioの外でVRoidを加工する際に用いた手順である。
おそらく、というか確実にこれよりも更に的確で適切な手順が確立されているに違いない。
が、自分で結果を確認して手順編みだすというのは重要だろう。なお、最初はMToonなんたらを設定しなければならないことを知らなかったため、VRoidのパーツがBlenderの描画丸出しだった。
左:MToon系適用前 右:Mtoon系適用後
VRoid対応アプリで背景にエフェクトがかかっていたりすると、自作のパーツにまでエフェクトがかかったり、逆にVRoidにかかるべきエフェクトがかかってなかったりした。
MToon系の設定は忘れずにやっておこう。これからVRoidを作りたい、もっと工夫したいという方々の参考になれれば幸いである。
- 投稿日:2020-08-08T00:41:43+09:00
Unity で動画をテクスチャとして使用する
VideoPlayer を使う.かつて使用されていた MovieTexture は現在は非推奨 (deprecated) である.
GameObject に貼る
VideoPlayer コンポーネントを GameObject に追加する.
動画ファイルを Unity の管理下に置く
再生する動画ファイルを,Video Clip Asset として import する.ここではテストのため,https://www.pexels.com/ から CC0 ライセンスで利用可能な動画をダウンロードし,フォルダ VideoClips を作成した中に import した.
この動画を,Video Player コンポーネントの Video Clip 欄に drag & drop する.
実行すると動画が再生される.
元の動画(横:縦=16:9)を正方形(横:縦=1:1)に割り当てたため,やや縦長で再生されてしまう.
対象となる GameObject の Scale を変更することで対応する.
動画ファイルを別に管理する or ダウンロード
Video Player コンポーネントの Source をプルダウンメニューで Video Clip から URL に変更し,新しく表示された URL 欄に適切な URL を記述する.ファイルシステム内のファイルであれば file:// で記述する.
カメラの背景に貼る