- 投稿日:2019-09-29T22:51:13+09:00
Unite Tokyo 2019行って個人開発で役立てたいな思ったこと
前置き
- Unite Tokyo 2019に会社のお金で行かせていただきました
- 業務の話は社内にシェアするとして
- 本記事は個人開発(超小規模)ゲームに関しての話
- 業務ではUnity3年くらい離れていたので、今更かよ……という内容が一部含まれてるかも……
個人的に役に立てたいと思ったお話(上から優先度高)
UnityTestRunner
役に立ったと思ったポイント
- 個人開発だからこそ効率良くテストしないと……と単純に思った
- 導入不要。menuからすぐ始められそう
- Editorモードだと実行早そう
- テストコードは保守が大変になるので、ある程度粒度は落とす
- テストコードはガラスのように壊れやすいものではなく、竹のようにしなやかで柔軟性の高いものを目指すべき
講演資料
Unity Test Runnerを活用して内部品質を向上しよう
参考資料
Unity使いは全員Unity Test Runnerを使え!爆速のトライ&エラー環境だぞ!
サーバサイドサービス(BaaS)
大きく二つ紹介されていたので、お話聞きました。
役に立ったと思ったポイント
- どちらもサーバサイド側のゲームに必要な処理群(ログイン処理/PlayerData管理 等々)を用意してくださってるので、UnityからはAPI叩くだけ楽々導入できそう
- PlayFab側は機能制限ありの完全無料プランもあるとのことで、使うとしたらまずはそちらを使ってみたい
- GS2は1APIリクエストごとに0.02円(でもHPみるとFreeプランありそう……?)
講演資料
Visual Effect Graph
役に立ったと思ったポイント
- GPUで実行されるので早いとのこと
- ノードベースで定義できるようになり、作りやすそう
講演資料
パーティクルエフェクトが超進化する! Visual Effect Graph の基礎と応用
ハイパーカジュアルゲームのお話
役に立ったと思ったポイント
- パッと見てそれが何をするものかを容易に想像できるように、アフォーダンスを意識
- 子ども心をくすぐるようなものを意識(むずかしいぃ……)
- とりあえずリリースしたものでも、後から化けるケース(思いつきによる機能改修)もあるから諦めないこと
- SAY GAMESさんのゲームはめっちゃ参考にされたとのこと
講演資料
データよりも面白さ? 芸者東京に学ぶ!グローバルでヒットするハイパーカジュアルゲームの作り方
Unity Servicesの話
役に立ったと思ったポイント
- Cloud Diagnostics(クラッシュレポート&ユーザの任意のレポート管理)が講演の中で唯一フリーで手軽に使えそうだった。(ただしフリーの範囲だと、25件のレポートしか保存できない)
講演資料
開発から運用まで、デベロッパーをサポートするUnity Services
その他
- Game Developers Meeting
- 最新の技術や情報をシェアするDeNAさん主催のゲームクリエイター向け勉強会というものを定期的に開催されているとのこと(渋谷じゃなかったら行きたかったという話)
- IRIAMさんのデモアプリ
- 画像一枚から、機械学習を用いて、瞬き/口パクの差分を生成して、カメラで自分の表情をsyncする技術がめっさ感動しました。アプリ的にはVtuberとのコミュニケーションツールとのことでした。自分でも1枚絵から自動でLive2Dのごとく動くものが生成できるものを作りたいという願望だけが生まれた
感想
- 色々進化していてびっくりした
- 個人開発におけるテストについても考えさせられた
(まだ未公開のゲーム開発において、依頼主にほぼテスト丸投げしてしまった経緯もあり、すみませんでしかなかった)- サーバサイドはもはや趣味で開発しない世界がありそうで、個人開発的にはすごく嬉しいお話
- 投稿日:2019-09-29T22:32:51+09:00
[Unity] Project Tinyでのミニゲーム作成 - UI
はじめに
Project Tinyでミニゲームを作成時に使用した機能やトラブルを説明していきます。
今回はUIについてです。
リポジトリはこちら。UIの作成方法
uGUIと基本的には同じです。
RootとなるEntityにUICanvasとRectTransformを設定し、
その子としてスプライトやテキストを配置していきます。
ボタン
ボタンを作成するために下記のコンポーネントが必要になります。
- Sprite2DRenderer
- RectTransform
- Button
- PointerInteraction
- Sprite2DRendererOption
- ColorTintTransition
ボタンの実装
HierarchyでEntityに上記の必要なコンポーネントを設定し、
BUttonのsprite2DRendererにボタン画像のSprite2DRnedererのEntity、
transitionにColorTintTransitionのEntityを設定すればいいだけです。
必要に応じてColorTintTransitionの各色を変更してください。
あとはシステム内でPointerInteractionのclickedからクリックしたかどうか判定します。今回の場合だと、ゲームオーバー時のリトライボタンで使用しています。
GameOverSystem.cs// リトライボタンのクリック Entities.WithAll<RetryButton>().ForEach((Entity entity, ref PointerInteraction pointerInteraction) => { // クリックされたらリトライを行う retry = pointerInteraction.clicked; pointerInteraction.clicked = false; });テキスト
テキストを作成するために必要なコンポーネントです。
- RectTransform
- Text2DStyleBitmapFont
- Text2DRenderer
- Text2DStyle
- TextString
- LayerSorting
テキストの実装
Entityに上記の必要なコンポーネントを設定します。
各コンポーネントのフィールドを下記のように設定してください。Text2DStyleBitmapFont
font : Text Mesh ProのフォントアセットText2DRenderer
style : Text2DStyleのEntityText2DStyle
color : フォントカラー
size : フォントサイズTextString
Value : 描画する文字列LayerSorting
layer : レイヤー
order : レイヤー内での描画順(大きいほど手前になる)
- 投稿日:2019-09-29T19:50:35+09:00
AnimationControllerのStateが何故か読み込めなくなるエラー
急にAnimationControllerのStateが何故か読み込めなくなるエラーがありました.
Consoleを見るとこんなエラーが出てました.
NullReferenceException: Object reference not set to an instance of an object
UnityEditor.Graphs.AnimationStateMachine.Graph.GenerateConnectionKey (UnityEditor.Graphs.Node srcNode, UnityEditor.Graphs.Node dstNode) (at C:/buildslave/unity/build/Editor/Graphs/UnityEditor.Graphs/AnimationStateMachine/Graph.cs:264)
UnityEditor.Graphs.AnimationStateMachine.Graph.GetEdgeInfo (UnityEditor.Graphs.Edge edge) (at C:/buildslave/unity/build/Editor/Graphs/UnityEditor.Graphs/AnimationStateMachine/Graph.cs:283)
UnityEditor.Graphs.AnimationStateMachine.EdgeGUI.DoEdges () (at C:/buildslave/unity/build/Editor/Graphs/UnityEditor.Graphs/AnimationStateMachine/EdgeGUI.cs:71)
UnityEditor.Graphs.AnimationStateMachine.GraphGUI.OnGraphGUI () (at C:/buildslave/unity/build/Editor/Graphs/UnityEditor.Graphs/AnimationStateMachine/GraphGUI.cs:390)
UnityEditor.Graphs.AnimatorControllerTool.StateMachineView (UnityEngine.Rect position, System.Single zoomLevel) (at C:/buildslave/unity/build/Editor/Graphs/UnityEditor.Graphs/Animation/AnimatorControllerTool.cs:1888)
UnityEditor.Graphs.AnimatorControllerTool.DoGraph (UnityEngine.Rect graphRect, System.Single zoomLevel) (at C:/buildslave/unity/build/Editor/Graphs/UnityEditor.Graphs/Animation/AnimatorControllerTool.cs:1251)
UnityEditor.Graphs.AnimatorControllerTool.<SetupGUI>m__D () (at C:/buildslave/unity/build/Editor/Graphs/UnityEditor.Graphs/Animation/AnimatorControllerTool.cs:1663)
UnityEditor.Graphs.AnimatorControllerTool.ScopedOnGUI (System.Action onGUIHandler) (at C:/buildslave/unity/build/Editor/Graphs/UnityEditor.Graphs/Animation/AnimatorControllerTool.cs:1423)
UnityEditor.Graphs.AnimatorControllerTool.<SetupGUI>m__5 () (at C:/buildslave/unity/build/Editor/Graphs/UnityEditor.Graphs/Animation/AnimatorControllerTool.cs:1660)
UnityEngine.Experimental.UIElements.IMGUIContainer.DoOnGUI (UnityEngine.Event evt, UnityEngine.Matrix4x4 worldTransform, UnityEngine.Rect clippingRect, System.Boolean isComputingLayout) (at C:/buildslave/unity/build/Modules/UIElements/IMGUIContainer.cs:266)
UnityEngine.Experimental.UIElements.IMGUIContainer.HandleIMGUIEvent (UnityEngine.Event e, UnityEngine.Matrix4x4 worldTransform, UnityEngine.Rect clippingRect) (at C:/buildslave/unity/build/Modules/UIElements/IMGUIContainer.cs:438)
UnityEngine.Experimental.UIElements.IMGUIContainer.HandleIMGUIEvent () (at C:/buildslave/unity/build/Modules/UIElements/IMGUIContainer.cs:412)
UnityEngine.Experimental.UIElements.ImmediateStylePainter.DrawImmediate (System.Action callback) (at C:/buildslave/unity/build/Modules/UIElements/ImmediateStylePainter.cs:113)
UnityEngine.Experimental.UIElements.IMGUIContainer.DoRepaint (UnityEngine.Experimental.UIElements.IStylePainter painter) (at C:/buildslave/unity/build/Modules/UIElements/IMGUIContainer.cs:98)
UnityEngine.Experimental.UIElements.VisualElement.Repaint (UnityEngine.Experimental.UIElements.IStylePainter painter) (at C:/buildslave/unity/build/Modules/UIElements/VisualElement.cs:862)
……
AnimationStateMachine NullReferenceExceptionで調べてみるとこんな記事が
https://answers.unity.com/questions/1020334/unexplanable-null-refrence-exception.html
結構前から時々起きるエラーみたいですね….再起動したら直ってStateが全部表示されました.
- 投稿日:2019-09-29T18:53:47+09:00
[Unity] Project Tinyでのミニゲーム作成 - プレハブ・インスタンス作成
はじめに
Project Tinyでミニゲームを作成時に使用した機能やトラブルを説明していきます。
今回はゲーム中のプレハブ・インスタンス作成の実装についてです。
リポジトリはこちら。実装方法
このゲームではランダムで障害物を生成する際に使用しています。
簡単に説明するとプレハブとして扱う障害物用のEntityをいくつか用意し
EntityManager.Instantiateでインスタンス作成しています。
あとは障害物が画面外にでたら削除するという、特に難しいことは行っていません。
ただ、この実装になるまでに紆余曲折あったのでそこらへんを説明していきます。サンプルの"Spawn and Destroy"
PackageManagerから取得できるサンプルに"Spaen and Destroy"というものがあります。
[+]ボタンをクリックすると戦闘機が倍に増えていくシンプルな内容です。最初はこれを参考に実装しようとしていたため、なかなか混乱することになりました…。
"Spawn and Destroy"の実装
Hierarchyに戦闘機のEntityが一つだけ配置してあるシーンを用意し、
そのシーンをSceneService.LoadSceneAsyncでロードすることでインスタンスの作成を行っています。
削除の場合はSceneService.UnloadAllSceneInstancesで同一のシーンのインスタンスを全削除しています。
この方法の場合、インスタンス作成はいいんですが削除する時に同一のシーンのものが削除されてしまうのが
都合が悪かったため他の方法を探すことにしました。EntityManagerでのインスタンス作成
検索したらすぐ出てきました…。
感覚的にもGameObejctのInstatiateと同じように使えるし、やりたいことを実現できるのでこちらで実装することにしました。
ただ、ここでもプレハブの扱いでちょっとつまづきました。Prefabコンポーネント
ECSではPrefabコンポーネントがあり、これを使って実装を試しました。
Prefabコンポーネントについての詳細はF_さんのこちらの記事がわかりやすいです。クエリ作成のオプションでEntityQueryOptions.IncludePrefabを指定し
Prefabを含むようにしてみたが、何故かうまく取得出来ませんでした…。
Entityのコンポーネント設定や色々試してみましたが、結局うまくいかず最終的に次で紹介する方法で落ち着きました。最終的な実装
障害物用のObstacleコンポーネントを用意し、その中にプレハブかどうか判定するboolを用意しました。
Obstacle.cspublic struct Obstacle : IComponentData { public bool IsPrefab; }HierarchyにObstacleコンポーネントを設定しIsPrefabをtrueにしたEntityをいくつか用意。
インスタンス管理を行うシステムでIsPrefabがtrueのEntityをプレハブとしてNativeArrayに入れておき、
インスタンス作成時にIsPrefabをfalseにすることで同じObstacleコンポーネントでも
プレハブかインスタンスかを判別できるようにしています。SpawnAndDestroyObstacleSystem.csvar prefabs = new NativeArray<Entity>(4, Allocator.Temp); var count = 0; // プレハブの取得 Entities.ForEach((Entity obstacleEntity, ref Obstacle obstacleComponent) => { if (obstacleComponent.IsPrefab) { prefabs[count] = obstacleEntity; ++count; } }); // インスタンス作成 var spawnObstacle = EntityManager.Instantiate(prefabs[_random.NextInt(prefabs.Length)]); var translation = EntityManager.GetComponentData<Translation>(spawnObstacle); if (existObstacleCount == 0) { translation.Value = lastObstaclePos; } else { var speedScale = 1.0f; Entities.ForEach((ref GameState game) => { speedScale = game.SpeedScale; }); var offset = 0.0f; if(speedScale < 1.75f) { offset = _random.NextFloat(7.5f, 10.0f); } else { offset = _random.NextFloat(10.0f, 12.5f); } translation.Value = new float3(lastObstaclePos.x + offset, 0.0f, 0.0f); } EntityManager.SetComponentData(spawnObstacle, translation); // 座標の設定等 var scroll = EntityManager.GetComponentData<Scroll>(spawnObstacle); var startPos = translation.Value; scroll.StartPosition = new float2(startPos.x, startPos.y); scroll.Distance = 100.0f + startPos.x; scroll.Enable = true; lastObstaclePos = startPos; EntityManager.SetComponentData(spawnObstacle, scroll); var obstacle = EntityManager.GetComponentData<Obstacle>(spawnObstacle); // IsPrefabをfalseにしてインスタンスしたEntityとして扱う obstacle.IsPrefab = false; EntityManager.SetComponentData(spawnObstacle, obstacle); prefabs.Dispose();
- 投稿日:2019-09-29T18:44:19+09:00
【Unity(C#)】OculusIntegrationを使ってVR空間でSliderを操作
目的
VR空間内でスライダーのUIを操作して、ゲームの難易度を設定する
というのが今回の目的です。この記事ではVR空間内でスライダーのUIを操作の部分を記述します。
デモ
実際に作成したものがこちらです。
見た目は置いといて、機能としては完成です。OculusIntegrationの設定
①OVRInputModuleの設定
Create Empty
でGameObjectを生成して、OVRInputModule
をAdd Componentします。設定項目としては3か所だけです。
今回は人差し指のトリガーで操作する設定にしてます。
Cursorの箇所は後述します。
②Cursorの設定
続いて、Cursorの設定です。
Create Empty
でGameObjectを生成して、LaserPointer
をAdd Componentします。
同じオブジェクトにLineRenderer
もAdd Componentします。あと、LineRendererが太すぎるのでWidthを0.03くらいにしました。
お好みでmaterialを設定してレーザーの色を変えたりできます。
今回のデモではカーソルは表示していませんが、
必要な場合はCursorVisual
お好きなカーソル(ゲームオブジェクト)をアタッチすればOKです。
③OVRRaycasterを追加
Canvasに
OVRRaycaster
をAdd Componentします。
これで、そのCanvasの子のUIに対して操作が可能になります。
もともとあるGraphyicRaycaster
は消してしまって構いません。設定完了!
これでもうUIを操作できます。
OculusIntegration神ですね。コード
スライダーの値を取得するメソッド、
GetLevel
を用意します。Sliderにアタッチusing UnityEngine; using UnityEngine.UI; public class LevelGetFromSlider : MonoBehaviour { Slider levelSlider; void Start() { levelSlider = this.gameObject.GetComponent<Slider>(); } public int GetLevel() { return (int)levelSlider.value; } }Textにアタッチusing UnityEngine.UI; using UnityEngine; public class LevelText : MonoBehaviour { [SerializeField] LevelGetFromSlider _levelGetFromSlider; Text levelText; void Start() { levelText = this.gameObject.GetComponent<Text>(); } public void LevelTextChange() { levelText.text = _levelGetFromSlider.GetLevel().ToString(); } }後はテキスト側で
On Value Changed
に登録するメソッド内で先程のGetLevel
を使います。
On Value Changed
というのは、Slider Componentに用意されているイベントハンドラーです。
画像のように登録して使います。これでSliderの値が変更された際に、テキストも変わります。
Editor上でSliderのValueを直接編集しても反映されないので注意です。
- 投稿日:2019-09-29T18:14:38+09:00
UnityでPlayFabを使い始める方法(インストール〜匿名ログイン)
Unite2019でPlayFabの講演を聞き、興味を持ったのでMicrosoftブースで色々伺ってきました。
講演資料はこちら -> https://www.slideshare.net/UnityTechnologiesJapan002/unite-tokyo-2019unity-playfab-liveops
自分が作ろうとしているスマホゲームで活用できそうだったので、とりあえず触ってみようと思います。
環境
- 書いた日 2019/09/29
- Unity 2019.1.5f1
- macOS Mojave 10.14.6
- PlayFab SDK 2.74.190916
PlayFabに登録
まずはアカウント登録が必要なのでサインアップします。
https://azure.microsoft.com/ja-jp/services/playfab/
こちらから登録してログイン。
PlayFabはAzureのいちサービスっぽいけれど、Azureポータル経由せずに利用できるので楽ですね。
PlayFabの価格
- Essentials(無料プラン)
- Indie Studio($99/月)
- Professional($299/月〜)
- Enterprise(お問い合わせ)
こんな分類みたいです。
私は個人開発なので無料〜インディープランでうまくやりくりする感じになりそうです。
無料でも結構な機能が使えるっぽいですし、インディープランも1000MAUまでは無料らしいので活用していきましょう。料金について詳しくはこちら https://playfab.com/pricing/
スタジオとタイトルを作る
PlayFabではいきなりゲームを登録する訳ではなく、スタジオとタイトル(ゲーム)を順番に登録していきます。
私は個人開発なので、
naichilab
というスタジオ(赤枠)とplayfab-sample-game
というタイトル(緑枠)を登録しました。スタジオ単位でメンバーや管理者を設定したり、前述のプラン契約ができるようです。
当然ですが、スタジオ内に複数タイトルを登録することもできます。
タイトルのダッシュボードへ
タイトルをクリックするとダッシュボードに移動します。
まだ空っぽですね。
SDKのダウンロード
ダッシュボード右上のヘルプからクイックスタートへ
SDKのダウンロードリンクがあるのでUnity用をダウンロードします。
Unity3Dっていつの呼び方・・・
エディター拡張機能をダウンロードしておきます。
PlayFabEditorExtensions.unitypackage
が手に入りました。Unityへインポート
空のUnityプロジェクトを作成し、先ほどのunitypackageをインポート
目立つウィンドウが出てくる。
めっちゃ分かりづらいけどこれは登録画面で、すでにブラウザで登録済みなのでログイン画面へ切り替える。
ID/PASSを入力してログイン
UnityにPlayFabSDKをインポート
Install PlayFab SDK
押しても反応なし・・・
何度か押してたらPlayFabSdkというフォルダが出来ていました。
Refresh
押しても無反応。分かりづらい。Unityを開き直すことでやっと認識されました。
スタジオとタイトルを選択
Settingsタブからブラウザで作成したスタジオとタイトルを選択しておきます。
匿名ログイン
PlayFabには標準でLoginAPIがたくさん用意されてるようです。
今回はただの練習なので
LoginWithCustomID
でログインしてみます。(IDも固定値)PlayFabLogin.csusing PlayFab; using PlayFab.ClientModels; using UnityEngine; public class PlayFabLogin : MonoBehaviour { public void Start() { var request = new LoginWithCustomIDRequest {CustomId = "MyCustomId", CreateAccount = true}; PlayFabClientAPI.LoginWithCustomID(request, OnLoginSuccess, OnLoginFailure); } private void OnLoginSuccess(LoginResult result) { Debug.Log("Congratulations, you made your first successful API call!"); } private void OnLoginFailure(PlayFabError error) { Debug.LogWarning("Something went wrong with your first API call. :("); Debug.LogError("Here's some debug information:"); Debug.LogError(error.GenerateErrorReport()); } }これをMainCameraとか適当なGameObjectにはっつけて実行。
Congratulations, you made your first successful API call!
とログが出ました。
管理画面でプレイヤーを見ると1名増えています。
さらに詳細を見るとスクリプトで設定した
MyCustomId
というカスタムIDとリンクしていることも確認できました。TwitterやFacebooko連携とかを行うとさらにここに追加されるのでしょう。きっと。
まとめ
SDKインストールしてUnityから匿名ログインするところまで〜だととても簡単でした。
少しずつ他の機能も試していきます。
その他のドキュメント
この辺りをみていけば分かりそうな感じですね。
https://docs.microsoft.com/ja-jp/gaming/playfab/#pivot=documentation&panel=playfab
- 投稿日:2019-09-29T16:57:58+09:00
[Unity] Project Tinyでのミニゲーム作成 - 当たり判定・アニメーション
はじめに
Project Tinyでミニゲームを作成時に使用した機能やトラブルを説明していきます。
今回はゲーム中の当たり判定・アニメーションの実装についてです。
リポジトリはこちら。当たり判定
使用するコンポーネントはRectHitBox2Dです。
マニュアル
https://docs.unity3d.com/Packages/com.unity.tiny@0.16/manual/module-hitbox2d.html判定方法
Sprite2DRnederer,RectHitBox2Dが設定されているEntity同士が接触すると
HitBoxOverlapコンポーネントが追加され、非接触の状態になると削除されます。
このHitBoxOverlapコンポーネントの存在の有無で当たり判定を行います。
今回のゲームではキャラクターが障害物に接触したら即ゲームオーバーなので、
HitBoxOverlapが存在したらゲームオーバーフラグを立てる処理を行っています。GameMain.csEntities.ForEach((Entity entity, ref Character character, ref RectHitBox2D rectHitBox2d) => { if (EntityManager.HasComponent<HitBoxOverlap>(entity)) { isGameOver = true; } });不便な点
Colliderであれば設定したサイズをSceneビューで見ることが出来ますが、
RectHitBox2Dではそういうプレビュー的なものが存在しないためサイズ調整がなかなか難しいです。
なので、このランゲームではけっこういい加減な判定になっています。アニメーション
Sprite2DSequenceとSprite2DSequencePlayerの2つのコンポーネントを使用します。
リファレンス
Sprite2DSequence
Sprite2DSequencePlayer設定手順
- 適当なEntityにSprite2DSequenceをAdd Componentする
Sprite2DRendererが設定されているEntityにSprite2DSequencePlayerをAdd Componentする
Sprite2DSequencePlayerのsequenceにSprite2DSequenceのEnitityを設定する
Sprite2DSequencePlayerのフィールドの説明
sequence
Sprite2DSequenceが設定されているEntityspeed
再生スピード
1が通常スピード
1未満でスロー再生、1以上で倍速再生time
現在の時間
値を設定することで途中からの再生を行うことができるpaused
一時停止ループタイプ
Name Description ClampFoever 再生後最終フレームを維持する Loop 通常ループ Once 一度だけ再生 PingPong いったり来たりをする再生を繰り返す PingPongOnce PingPongを一度だけ行う アニメーションの切り替え
Animationsコンポーネントを作成しその中に各アニメーションのSprite2DSequenceのEntityを設定できるようにしてあります。
Animations.cspublic struct Animations : IComponentData { public Entity SequenceIdleEntity; public Entity SequenceRunEntity; public Entity SequenceJumpEntity; public Entity SequenceDeathEntity; }あとはCharacterAnimationSystemで入力や状態を見て、Sprite2DSequencePlayerのsequenceのEntityを変更することでアニメーション切り替えを行っています。
CharacterAnimationSystem.cspublic class CharacterAnimationSystem : ComponentSystem { protected override void OnUpdate() { Entities.ForEach((ref Animations anims) => { var sequenceIdleEntity = anims.SequenceIdleEntity; var sequenceRunEntity = anims.SequenceRunEntity; var sequenceJumpEntity = anims.SequenceJumpEntity; var sequenceDeathEntity = anims.SequenceDeathEntity; var isGameOver = false; var isPlaying = false; var begun = false; var retry = false; Entities.ForEach((ref GameState game) => { isGameOver = (!game.GameOverPrev && game.GameOver); isPlaying = game.Playing; begun = game.Begun; retry = game.Retry; }); Entities.ForEach((ref Character character, ref Sprite2DSequencePlayer sequencePlayer) => { if (true) { if (isGameOver) { // ゲームオーバー sequencePlayer.time = 0.0f; sequencePlayer.loop = LoopMode.ClampForever; sequencePlayer.sequence = sequenceDeathEntity; } else if (begun) { // ジャンプ sequencePlayer.time = 0.0f; sequencePlayer.loop = LoopMode.ClampForever; sequencePlayer.sequence = sequenceJumpEntity; } else if (retry) { // 走り sequencePlayer.time = 0.0f; sequencePlayer.loop = LoopMode.Loop; sequencePlayer.sequence = sequenceRunEntity; } else { if (!character.jumpingPrev && character.jumping) { // ジャンプ sequencePlayer.time = 0.0f; sequencePlayer.loop = LoopMode.ClampForever; sequencePlayer.sequence = sequenceJumpEntity; } else if (character.jumpingPrev && !character.jumping) { // 着地 sequencePlayer.time = 0.0f; sequencePlayer.loop = LoopMode.Loop; sequencePlayer.sequence = sequenceRunEntity; } } } }); }); } }
- 投稿日:2019-09-29T14:55:00+09:00
UnityでProjectSettingsと共にunitypackageをExportする
雑記
レイヤー設定と共にパッケージを作成したい機会があったので。
問題ないかは不明。http://myoujing.wpblog.jp/2014/03/274/
こちらを参考にしましたが、余計なdll等も含まれてしまったため
以下のようにしてみました。#if UNITY_EDITOR using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEditor; public class ExportSetting : MonoBehaviour { [MenuItem("Assets/ExportWithSettings")] static void Export() { string[] assetPaths = { "Assets/含めたいフォルダー", "ProjectSettings/AudioManager.asset", "ProjectSettings/ClusterInputManager.asset", "ProjectSettings/DynamicsManager.asset", "ProjectSettings/EditorBuildSettings.asset", "ProjectSettings/GraphicsSettings.asset", "ProjectSettings/InputManager.asset", "ProjectSettings/NavMeshAreas.asset", "ProjectSettings/NetworkManager.asset", "ProjectSettings/Physics2DSettings.asset", "ProjectSettings/ProjectSettings.asset", "ProjectSettings/QualitySettings.asset", "ProjectSettings/TagManager.asset", "ProjectSettings/TimeManager.asset", "ProjectSettings/UnityConnectSettings.asset" }; string exportPath = "test.unitypackage"; AssetDatabase.ExportPackage(assetPaths, exportPath, ExportPackageOptions.Interactive | ExportPackageOptions.Recurse); } } #endifProjectSettingsは再帰で読まれなかった。
環境
Unity 2017 4.2.8f1
- 投稿日:2019-09-29T13:30:42+09:00
[Unity] Project Tinyでのミニゲーム作成 - プロジェクト作成とDOTSモード
作成したもの
Project Tinyでシンプルなランゲームを作成しました。
Chromeのイースターエッグの"T-Rex dinosaur game"(chrome://dino/)を真似て作りました。プロジェクトデータはGitHubにて公開しています。
ライセンスはMITです。
https://github.com/chocolate-ice-cream/TinyRun開発環境
- Windows 10 Home
- Unity2019.2.0b10
- Project Tiny 0.16.1 preview
あそびかた
リポジトリのREADME.mdを参照してください。
https://github.com/chocolate-ice-cream/TinyRun/blob/master/README.mdこの記事について
Project Tinyでミニゲームを作成時に使用した機能やトラブルをいくつかに分けて説明していきます。
今回はプロジェクト作成とDOTSモードについてです。Project Tinyのプロジェクト作成手順
Project Tinyのインストール
Project TinyはUnityのPackage Managerからインストールします。
Project Tinyをインストール後、Burstのパッケージでエラーが出ます。
その場合はBurstのパッケージを1.1.2にアップデートします。
Prject Tiny用プロジェクトデータ作成
メニューから[DOTS] -> [New Projet...]を選択します。
ダイアログが表示されるので、任意のフォルダに任意のファイル名をつけて保存してください。
プロジェクト作成が終了すると、エディタが"DOTSモード"という今までとは少し違った表示に変わります。
これでPrject Tiny用プロジェクトデータ作成は完了です。
あとはこのプロジェクトにアセット、コンポーネント、システムを作成しゲームを作っていきます。DOTSモード
DOTSモードとは?
Project TinyはGameObjct,MonoBehaviourを使用した開発環境とは違い
DOTS環境で構築されており、エディタも"DOTSモード"という専用の表示に変わっています。
詳細についてはこちらの0.15.3リリース時のフォーラムのアナウンスを確認してください。操作方法
Entityを作成しそこにコンポーネントを追加、プロパティの値や参照を設定するという、
シーンを作成する基本的な部分はそんなに変わらなかったりします。
なので、"DOTSモード"といっても実際は今までと同じような感覚で作業ができます。DOTSモードで戸惑った・不便だった点
Entityのアクティブ、非アクティブの扱い
Entityは非アクティブにするとDisabledコンポーネントが追加され、
アクティブにするとDisabledコンポーネントが削除されます。
EntityはGameObejctのように自身のアクティブの状態を保持するプロパティを持っていないため、
Disabledコンポーネントの有無でアクティブ状態を判別しています。
最初これを知らなかったため、自分は少し戸惑ってしまいました。一部のコンポーネント以外のプリセットのEntityが作成出来ない
プレビュー版なので仕方ない部分だとは思いますが、メニューから作成できるのが
"Audio Source","Camera","Sprite","Canvas"のみなのはやはり不便でした。
特にUI用のテキストについては"RectTransform""Text2DStyleBitmapFont","Text2DRenderer",
"Text2Dstyle","TextString"といくつものコンポーネントが必要なためなかなか手間でした。実行時にSceneビュー、Gameビューでの確認が出来ない
通常のエディタでPlayボタンを押すとGameビューで確認できますが、
DOTSモードではビルドを行い実際に作成されたものを実行します。
なので、HierarchyでEntityを選択しコンポーネントの値を確認しながらといったようなことが出来ず
調整やデバッグに苦労しました。感想
Project Tinyが0.15.3からC#の使用が可能になり、ECSの勉強も兼ねてミニゲームを作ってみました。
わかってはいたことですが、プレビュー版のため使用できる機能が少ないので
Project Tinyでゲーム開発を行うのはまだ時期尚早かなというのが率直な意見です。
開発に関するネット上の情報が全くないため、開発中に詰まった場合の情報源は
ドキュメントとUnity Forumしかないのもなかなかつらいところでした。反省点としてECSでの開発が初めてだっため、コンポーネントや処理がとっちらかってる感じが否めません。
特にマウス・キーボードの入力周りで場当たり的な実装をしてしまっている箇所が多く、
今後の開発では改善していきたい部分です。前述したように情報が全く無いので、今後Project Tinyを使ってゲーム開発をする人の参考になるよう
使用した機能の説明やハマったポイントを記事にして公開したいと思います。
- 投稿日:2019-09-29T05:53:05+09:00
【Unity】私流 はじめてのゲーム開発の流れ
動機
「Unity 入門」等で調べるとブロック崩しやシューティングゲームの作り方やサンプルプロジェクトがあります。ただプログラミング初心者がサンプルプロジェクトを見ても、実際にどうやってここまで作るのか想像がつかないと思います(私はそうでした)。
そこでプログラミング初心者の方に向けて、Unityでのゲーム開発の流れがなんとなくわかる記事を書こうと思いました (本記事では作り方・使い方について書きませんので必要に応じて調べてください)。また記事の下部に「よく使われる言葉の説明」と「ウィンドウの説明」を記載しました。
はじめてのゲーム開発の流れ
- 各種フォルダ作成
- シーン作成
- プレイヤーを作る
- ステージを少しだけ作る
- 敵を数体作る
- ステージと敵をプレハブにして、ステージを長くしていく
- 新しいステージを作りたいなら新しいシーンを作って 2 に戻る
- 十分にステージができたら、メニュー画面を作ってシーン遷移を行う
- タイトル画面を作る
- オプション画面を作る
1. 各種フォルダ作成
下図は私が軽く作ったものですが「unity フォルダ構成」で検索して調べた方が理解が深まります。
3. プレイヤーを作る
- プレイヤーとなる Playerオブジェクト をシーンに生成・配置
- Playerオブジェクト をプレハブ化
- Player.cs(Playerコンポーネント)をScriptsフォルダ内に作成
- Playerオブジェクトのプレハブ にPlayerコンポーネントを追加
- 「Unity プレイヤー」等で検索して出てきたプログラムをPlayer.csの中に書いて動してみる
4~7. ステージ制作の流れ
- シーン作成
- Playerオブジェクトのプレハブ を配置
- 壁や敵などのプレハブ作成
- コンポーネントを (作って) プレハブに追加
- プレハブ配置
その後
プレハブにもっとコンポーネントを追加するなら4に戻る
そのプレハブの配置が済んだら3に戻る
シーンが完成したら1に戻るよく使われる言葉の説明
Scene (シーン)
沢山のオブジェクトの配置情報を保存するファイルです。
UIやプレイヤーをマネージャーシーンに配置して、ステージシーンを加算する形を取れば、ステージクリア型のゲームを楽に作れます。
Object (オブジェクト)
シーン上に配置するモノです。機能を追加できます。Component (コンポーネント)
オブジェクトに追加する機能を指します。
カメラやライト、物体の描画や音声、物理演算や当たり判定など、標準で用意されているコンポーネントは沢山あります。
実際のゲーム開発では、プレイヤーや敵の動きなどの用意されていないコンポーネントはプログラムを書いて作ることになります。
(Transform コンポーネントはオブジェクトに最低限必要なコンポーネントで、オブジェクトの位置・回転・拡縮情報を記録しています)
Prefab (プレハブ)
シーン上のオブジェクトを設計図化したものです。Instance(インスタンス)
プレハブを元に生成するオブジェクトを指します。
プレハブをヒエラルキー上に配置すると「オブジェクト名 (Clone)」という名前のインスタンスが生成されます。
実際のゲーム開発では、床や壁, 敵や弾, 落ちているアイテムやエフェクトなど、あらゆるオブジェクトをプレハブにして、シーン上にインスタンスとして配置することでステージを作っていきます。
プレハブ本体に変更を加えると全てのインスタンスに反映されるので、仕様変更が楽になります。
ウィンドウの説明
ウィンドウは自分の使いやすいように配置しましょう。主に使うウィンドウを左から説明します。
シーン
開いているシーンファイルを見たり編集したりできます。画像左上の各アイコンを押すと操作モードが変わります。
ヒエラルキー
シーン上に配置されているオブジェクトが一覧になっています。
プロジェクト
プロジェクトのフォルダ構成を見たり編集したりできます。
インスペクタ
ヒエラルキー上でオブジェクトをクリックすると、オブジェクトのコンポーネントが見られます。コンポーネントは編集できます。
- 投稿日:2019-09-29T03:02:16+09:00
【Unity】RPGのレベルシステムの書き方
レベルシステム、RPG作っていれば必ず書くと言っていい処理ですが、自分流の書き方を残しておきます。
累計経験値と累計経験値に応じたレベルのテーブルがあれば、そこから現在Lvと次のLvまでの残り経験値を算出できます。
しかし、Lvを取得する度に都度計算していては流石に重たいので、累計経験値が加算される度に現Lvと残り経験値を更新するようにします。累計経験値に応じたレベルのテーブルを関数の引数にとって外部から参照してますが、これはレベルテーブルは固有な値として
static readonly
で修飾して使うことが多いためです。あるいは、UnityならScriptableObjectから参照する場合もあるでしょう。
いずれにせよ外部またはサブクラス上のstaticな値を汎用的に参照させたいので関数の引数に含めました。
ExpLevelClass.csusing System.Linq; using UnityEngine; [System.Serializable] public class ExpLevelClass { [SerializeField] int _exp; [SerializeField] int _remainExp; [SerializeField] int _minLevel; [SerializeField] int _level = 1; public int Exp => _exp; public int RemainExp => _remainExp; public int MinLevel => _minLevel; public int Level => _level; // Expを加算してLvを初期化する public void AddExp(int exp, int[] expArray) { //カンストを考慮して加算 _exp = Mathf.Clamp(_exp + exp, 0, expArray[expArray.Length - 1]); // 値の更新 UpdateLevel(expArray); UpdateRemainExp(expArray); } void UpdateLevel(int[] expArray) { // 現Exp以下の値の中で最大の値のインデックスを取得 var maxIdx = expArray.Where(x => x <= _exp).Select((val, idx) => new {V = val, I = idx}) .Aggregate((max, working) => (max.V > working.V) ? max : working).I; _level = maxIdx + 1; } void UpdateRemainExp(int[] expArray) { // 現Expより大きい値の中で最小の値のインデックスを取得 var minIdx = expArray.Where(x => x > _exp).Select((val, idx) => new {V = val, I = idx}) .Aggregate((min, working) => (min.V < working.V) ? min : working).I; _remainExp = expArray[minIdx] - _exp; } }Player.cs[System.Serializable] public class Player { public string Name = "主人公"; public ExpLevelClass ExpLevel = new ExpLevelClass(); static readonly int[] TOTAL_EXP_ARRAY = {0,100,300,600,1000}; // 経験値獲得処理 public void AddExp(int exp) { ExpLevel.AddExp(exp, TOTAL_EXP_ARRAY); } }おまけ
関数でLevelとRemainExpの計算結果を返す
ExpLevelClass.csusing System.Linq; using UnityEngine; [System.Serializable] public class ExpLevelClass { [SerializeField] int _exp; [SerializeField] int _remainExp; [SerializeField] int _minLevel; [SerializeField] int _level = 1; public int Exp => _exp; public int RemainExp => _remainExp; public int MinLevel => _minLevel; public int Level => _level; // Expを加算してLvを初期化する public (int afterLevel, int remainExp) AddExp(int exp, int[] expArray) { //カンストを考慮して加算 _exp = Mathf.Clamp(_exp + exp, 0, expArray[expArray.Length - 1]); // 値の更新 UpdateLevel(expArray); UpdateRemainExp(expArray); return (Level, RemainExp); } void UpdateLevel(int[] expArray) { // 現Exp以下の値の中で最大の値のインデックスを取得 var maxIdx = expArray.Where(x => x <= _exp).Select((val, idx) => new {V = val, I = idx}) .Aggregate((max, working) => (max.V > working.V) ? max : working).I; _level = maxIdx + 1; } void UpdateRemainExp(int[] expArray) { // 現Expより大きい値の中で最小の値のインデックスを取得 var minIdx = expArray.Where(x => x > _exp).Select((val, idx) => new {V = val, I = idx}) .Aggregate((min, working) => (min.V < working.V) ? min : working).I; _remainExp = expArray[minIdx] - _exp; } }Player.cs[System.Serializable] public class Player { public string Name = "主人公"; public ExpLevelClass ExpLevel = new ExpLevelClass(); static readonly int[] TOTAL_EXP_ARRAY = {0,100,300,600,1000}; // 経験値獲得処理 public (int afterLevel, int remainExp) AddExp(int exp) { return ExpLevel.AddExp(exp, TOTAL_EXP_ARRAY); } }参考
Linqで快適な生活を
[LINQ][C#] Aggregateの使い方(畳み込み, fold)
C# 7 の Tuple は戻り値が複数の時に便利
- 投稿日:2019-09-29T00:48:12+09:00
レベルに応じてゲームの難易度を変える実装
UNITYでゲーム作るときに難易度調整するためのデータ作りについて
ScriptableObjectを使います。
このような数値調整をUNITY上でやりたい場合のやり方です。
上記の画像にあるデータは、いくつのモノを壊したら、次のレベルに進めるか、そのレベルの難易度はどのぐらいかといったデータを表しています。
どんなゲームを作った際に使ったものか
まず、Garbagersというゲームを作ったのですが、その際の難易度などに関するデータ作りに関して作ったものです。
https://mosq.xyz/Garbagers/まず基本となるデータ
スリーマッチパズルゲームで、多くのゴミを壊すと次のレベルに進み少しづつ難しくなるゲームでした。テトリスのように。
なので、いくつ壊したら次のレベルに進めるか(int)、ゴミが出現するインターバル時間(float)を設定できるようにしました。(GVLevelInfo)
さらに、ゴミがつっかえて溜まってくるとゴミが速く出るようにしました。
横、縦への速度、回転速度、インターバルを指定できるようにしました。(GVPressureInfo)LevelClasses.csusing System; using System.Collections; using System.Collections.Generic; using UnityEngine; [Serializable] public class GVLevelInfo { public int NextBlockCount; public float SpawnInterval = 0f; } [Serializable] public class GVPressureInfo { public float HorizontalSpeed; public float VerticalSpeed; public float AngularSpeed; public float Interval; }[Serializable]アトリビュートをつけておく必要があります。
これらをレベルごとに用意するためリストにします。
それとScriptableObjectにして、UNITY上に置けるデータにします。
(全部publicで書いちゃってますが、UNITYでは[SerializeField]アトリビュートをつけても、シリアライズされます)GVLevelInfoのリスト化したScriptableObjectを作る
class宣言以降、三行だけが重要であとはおまけの関数です。
GVLevelInfoList.csusing System; using System.Collections; using System.Collections.Generic; using UnityEngine; public class GVLevelInfoList : ScriptableObject { public List<GVLevelInfo> List = new List<GVLevelInfo>(); public int GetLevel(int breakCount) { int level = 0; foreach(var v in List) { if (v.NextBlockCount <= breakCount) { ++level; continue; } break; } return level; } public float ToNextRatio(int level,int breakCount) { int prevCount = 0; if (0 <= level-1) { prevCount = List[level-1].NextBlockCount; } if (level < List.Count) { var v = List[level]; return (breakCount - prevCount) / (float)(v.NextBlockCount-prevCount); } return 1f; } }GVPressureInfoのリストも同様に作ります。
GVPressureInfoList.csusing System; using System.Collections; using System.Collections.Generic; using UnityEngine; public class GVPressureInfoList : ScriptableObject { public List<GVPressureInfo> List = new List<GVPressureInfo>(); public GVPressureInfo GetPressure(int pressure) { return List[Mathf.Clamp(pressure,0,List.Count-1)]; } }ということで、データ構造としては用意できました。
あとは、このデータをUNITYプロジェクトの中に追加する方法です。Editorフォルダを作ります
UNITYにおいて、Editorフォルダは特別な名前です。
ここに置かれたスクリプトファイルは、UNITYエディタ上でのみ利用され、ビルドして出力したアプリなどには含まれません。
エディタ上で、データを作ったりする際に使います。LevelInfoMenu.csusing System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEditor; public static class LevelInfoMenu { [MenuItem ("Create/CreateGVLevelInfoList")] static void CreateGVLevelInfoList () { var asset = ScriptableObject.CreateInstance<GVLevelInfoList> (); AssetDatabase.CreateAsset (asset, "Assets/LevelInfoList.asset"); AssetDatabase.Refresh (); } [MenuItem ("Create/CreateGVPressureInfoList")] static void CreatePressureInfoList () { var asset = ScriptableObject.CreateInstance<GVPressureInfoList> (); AssetDatabase.CreateAsset (asset, "Assets/PressureInfoList.asset"); AssetDatabase.Refresh (); } }このスクリプトを書くと、UNITYのトップメニューにCreateが追加されています。
実行すると、指定されたパスの場所にデータが追加されます。
インスペクター上で、データを編集することが可能です。保存場所を選べるようにしたい場合は、下記のような関数を使います。
https://docs.unity3d.com/ja/current/ScriptReference/EditorUtility.SaveFilePanel.html
→プロジェクト内に保存する場合は、こっちでした。
https://docs.unity3d.com/ScriptReference/EditorUtility.SaveFilePanelInProject.htmlこれをゲームで使っていきます。
実際に使う
普通に、MonoBehaviourにインスペクタから設定することができます。
GarbagersSystem.csusing System.Collections; using System.Collections.Generic; using UnityEngine; public class GarbagersSystem : MonoBehaviour { [SerializeField] LevelInfoUI _levelInfo; [SerializeField] GVPressureInfoList _pressureInfo; ...インスペクタに先ほどのメニューから作ったデータをドラッグ&ドロップすればデータが入るので、使いましょう。