【Unity】C#初心者だけどUniRXを使えるようになりたい
uniRXいいですよね。
バリバリなUnity Developerって感じで。
とはいえ普段C#使わないレベルの初心者だと入りづらいのも事実で、
「そもそも使うメリットとかあるの?」
「余計わかりにくくなるだけじゃないの?」
なんて思ってたりもしました。
ちょっとググってみて、
「なんかチェーンでいい感じにスッキリ書けるみたい。」
「イベント駆動で処理を飛ばしたりと自由自在らしい。」
「MessageBrockerとかBufferとか便利みたいだけどどこで使えばいいのかよくわからん。」
なんて事も分かりました。
ちょっとお硬めなメリットとかはググった先の先人達の記事を参考にして頂くとして、
初心者の自分がどうやって使い始めたかだけメモとして書いてみようと思いました。
UpdateをUniRXで置き換えよう
void Update(){
}
unityでMonoBehaviorやってたら知ってますよね。
毎フレーム処理したい時に使うアレです。
これに処理を入れると
void Update(){ if(_isFire){ // 火を発生させる処理 _isFire = false; // 終わったらフラグを下ろす } // その他処理 }
こんなん書くことがあると思います。
これを言葉で説明してみると
「ファイアフラグが立っている時だけUpdateで火の処理をしたい」
という感じになります。
おお。そうか。これをUniRXに置き換えてみると
using UniRX; using UniRX.Triggers; void Start(){ this.UpdateAsObserbable().Where(_ => _isFire).Subscribe(_ => { // 火を発生させる処理 _isFire = false; // 終わったらフラグを下ろす }); } void Update(){ // もういらない // if(_isFire){ // // 火を発生させる処理 // _isFire = false; // 終わったらフラグを下ろす // } // その他処理 }
このように置き換えることが出来る。
とにかく void Update()は大きくなりやすい。
switchでstate切り分けたり、パラメータチェックしたり。
そのため、細かくメソッドに分けたり、処理を委譲させたりして
管理しやすくする涙ぐましい努力をしているのだけど、
その辺りを助けてくれるってわけだ。
開始アニメーション処理している時は…
this.UpdateAsObserbable().Where(_ => _isStartAnimation).Subscribe(_ => { // アニメーションさせるとか _isStartAnimation = false; // 終わったらフラグを下ろす });
敵が残っている時は…
this.UpdateAsObserbable().Where(_ => _enemies.Count > 0).Subscribe(_ => { // 敵が残っている時 });
HPが0になった時は…
this.UpdateAsObserbable().Where(_ => _player._hp < 1).Subscribe(_ => { // HPが0以下になったら });
とupdate処理をブロックごとに分けて書く事が出来る。
もちろん好き好きでは良いはずだ。
毎フレーム処理をしたいならvoid Update()に処理を書くのが最もシンプルで分かりやすい。
UniRXを使うとこう書けますよというだけだ。
ボタンイベントをUniRX化する
uGUI使ってたらButtonを使うこともあるはず。
普段なら
public void OnClickButton(){ // ボタン押されたら }
こんなん作って、Inspectorで紐付けるはずだ。
これもUniRXで置き換えて紐付けをやめてみよう。
[SerializableField] private Button _joinButton: _joinButton.OnClickAsObservable().Subscribe(_ =>{ // ボタン押されたら });
うーんスッキリ。だけどボタンだけは紐付けないとダメか。(GetComponent?)
Zenjectのidまで連携させればボタンの紐付けすらいらなくなるケド。
[Inject(id="join_button")] private Button _joinButton:
これはちと微妙かも?
(Buttonみたいな汎用なクラスをInjectするのはやめておいたほうが良さそう)
勉強中
まだ使い始めたばかりなので、これから少しずつ使って慣れていきたい所。
次はイベントの送出を勉強したいな。
(GameManagerみたいな親がいて、EnemyControllerのような子クラスのイベントをフックする、的なやつ)
以下コードイメージ。イメージだよ!合ってませんよ!
// 敵が倒されたらその敵に紐付いているスコアを加算して表示する _enemyController.EnemyDestroy(_ => enemy).Subscribe(e => { score += e.score; scoreText.text = "score : " + score; });
こういうイメージ…今まではdelegateとか使ってたけどUniRXならこんな感じで書けるのでは…!?
ととりあえずここまで。出来たらまたメモしよう。
【Unity】第7回 unity1week「当てる」参加作品「SATELLITE ONE」の開発
お疲れ様です。ぐーるです。
体調最悪のコンディションでしたが、なんとか提出することが出来ました。
公開されたので再掲。ワンショットシューティング、「SATELLITE ONE」よろしくお願いします! #unity1week https://t.co/tmStl0FFAC
— ぐーるさん (@uuha_goul) 2018年2月25日
思い出と実装をメモっておきたいと思います。
お題について
「当てる」かー、今回は神題だなと。
前提を考えなければアイデアはいくらでも出せそうだ、と。
デザインをほとんど必要とせずサクッと出来る、シンプルなものがいいなぁとか漠然と考えていて
降ってきたアイデアがSATELLITE ONEです。
内部設計について
いつもそうだけど物理的なunity.sceneは一つだけ。
内部的な画面は
- OpeningScene
- GameScene
- ResultScene
の3つ。
各画面間の切り替えはシンプルなステートマシンで管理。
public interface IState{ void OnStateEnter(); void OnStateExit(); } public class StateMachine : MonoBehaviour { public List<IState> states = new List<IState>(); private IState prevState; private IState currState; public void AddState(IState state) { foreach (var st in states) { if (st == state) { return; } } states.Add(state); } public void RemoveState(IState state) { states.Remove(state); } public void ChangeState<T>() where T : IState { if (currState is T) { return; } if (currState != null) { prevState = currState; prevState.OnStateExit(); // 抜ける前にOnStateExit()を呼ぶ } foreach (var st in states) { if (st is T) { currState = st; currState.OnStateEnter(); // 切り替わると同時にOnStateEnter()を呼ぶ } } } }
/** * // Zenjectで紐付け * [Inject] * private MainScene _mainScene; * [Inject] * private StateMachine _stateMachine; * // ~~ * _stateMachine.AddScene(_mainScene); // 起動時にStateMachineに登録しておく * // ~~ * _stateMachine.ChangeScene<MainScene>(); // _mainScene画面へ切り替え */ public class MainScene : MonoBehavior , IState{ //~~~~~ }
こんな感じでZenjectと組み合わせ。
楽か?って言われるとこの規模だとそこまででも無いけど
Zenject化するのが癖になってしまった。
ターゲットサイトについて
これは円形のプレーンな板をBlenderで用意しておいて、
その上で円を描くShaderを動かしています。
Shader "Custom/Ring" { Properties{ _TargetX("Target X", float) = 0 _TargetY("Target Y", float) = 0 _TargetZ("Target Z", float) = 0 _Alpha("Alpha" , float) = 0 _Color("Color" , Color) = (0,0,0,0) _BG("BG" , Color) = (0,0,0,0) } SubShader{ Tags{ "Queue" = "Transparent" "RenderType" = "Transparent" } // alphaに対応するのに必要 Blend SrcAlpha OneMinusSrcAlpha // alphaに対応するために必要 LOD 200 CGPROGRAM #pragma surface surf Standard alpha:fade #pragma target 3.0 float _TargetX; float _TargetY; float _TargetZ; float _Alpha; float4 _Color; float4 _BG; struct Input { float3 worldPos; }; void surf(Input IN, inout SurfaceOutputStandard o) { float dist = distance(fixed3(_TargetX, _TargetY, _TargetZ), IN.worldPos); float val = abs(sin(dist*3.0 - _Time * 100)); float alpha = _Alpha; if (val > 0.98 && alpha > 0){ o.Albedo = _Color; o.Alpha = alpha; } else { // ↓何でも良い //discard; o.Alpha = 0; //o.Albedo = _BG; //o.Alpha = _BG.a; } } ENDCG } FallBack "Diffuse" }
[SerializeField] private Material _ringMat; // Update is called once per frame void Update () { // 適当なinputのラッパーだと思って下さい。なんでもいいです。 if(_inputManager.isTouchDownOnly(0)){ RaycastHit hit; Ray ray = Camera.main.ScreenPointToRay(_inputManager.GetTouchDownPosition(0)); if (Physics.Raycast(ray, out hit)){ var p = hit.point; p.y += 0.1f; this.transform.position = p; // タッチしたポイントをShaderに渡して中心点を移動した位置にする _ringMat.SetFloat(Shader.PropertyToID("_TargetX") , p.x); _ringMat.SetFloat(Shader.PropertyToID("_TargetY"), p.y); _ringMat.SetFloat(Shader.PropertyToID("_TargetZ"), p.z); } } }
ステージ制作について
SATELLITE ONEのステージは以下の2つの要素から成る。
- フロア
- 敵
そして敵は以下の要素を持っている
- 移動ルートポイント
- ルートポイントごとの移動速度
- 移動イージング
回転とかは賑やかしなので割愛。
ステージ作りがとにかく面倒だなと。
フロアはしょうがないとして、敵の移動ルートを量産出来れば、
ステージがどんどん作れそうだなと考えた。
ステージ制作画面
別でステージ製作専用のエディタを用意した。
- 敵を選択して選択状態に
- 動かしたい所をクリック
- AddPointボタンを押下でルートポイントを追加
という流れでどんどんルートポイントを置いていくことが出来る。
Playで動きの確認、そしてSaveでcsv出力している。
loadももちろん完備。
ここまで必要なのか?と思ったが
結局ゲーム側でも必要な機能がほとんどだったのでついでで。
csvのロードについて
ステージを追加量産するために
Application.dataPathを利用してパスでファイルを読むようにしてたら
当たり前だけどWebGLに出力したら動かない。
(開発中は紐付けなくてもcsvファイルを新規追加読み込み可能なので便利だった)
FileInfo fi = new FileInfo(Application.dataPath + "/Resources/stage/data/" + stageId + ".csv"); var result = new List<string>(); StreamReader reader = new StreamReader(fi.OpenRead()); while (reader.Peek() > -1) { string line = reader.ReadLine(); result.Add(line); } return result;
Editor上だけ使って実ゲームではTextAsset化して泥臭く紐付けて使うことに…。
とほほ。
[SerializeField]
private TextAsset[] _stageDatas;
おまけ
人工衛星なんですが、これはUnity標準のプリミティブオブジェクトの組み合わせで作ってます。
最初は無かったんですが、余りに味気なさすぎたので急遽追加しました。
適当に置いているだけなのですが、ちょっとゲームらしくなってくれたかなと。
感想
後はBGMのON/OFF付けたり、Post Processing Stack付けて微妙な色合いを出したり。頭痛と闘いながら調整。
今回もどうにか参加できました。
何故インフルエンザなんてかかるのか!?
ちゃんと予防接種受けてるのに。
前回は可愛い感じだったので、今回は硬めに。
みなさんお疲れ様でしたー!
次は可愛めで行きたい!
【Unity】Androidビルドを転送すると INSTALL_PARSE_FAILED_MANIFEST_MALFORMED が出る【NCMB】
ゲームが出来た!実機で動かそう!
と思い、ビルドを行い、adbで実機に転送しようとした所
xxxxx.apk: 1 file pushed. 3.6 MB/s (47771508 bytes in 12.536s) pkg: /data/local/tmp/xxxxxxx.apk Failure [INSTALL_PARSE_FAILED_MANIFEST_MALFORMED]
というエラーが出てしまった。
「あー、アプリ名に.(ドット)が入ってるのがマズいのかな?もしくはパッケージ名辺りが悪いとか」
と当てずっぽうで修正に入ったら変にハマってしまったのでメモっておく。
原因と解決策
原因はNCMBのUnitySDKプラグインを入れていまして、その中にある
Plugins/Android/AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" package="YOUR_PACKAGE_NAME" > <!-- Put your package name here. --> <uses-sdk android:minSdkVersion="14"/> <!-- [START gcm_permission] --> <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" /> <uses-permission android:name="android.permission.WAKE_LOCK" /> <!-- Put your package name here. --> <permission android:name="YOUR_PACKAGE_NAME.permission.C2D_MESSAGE" android:protectionLevel="signature" /> <!-- Put your package name here. --> <uses-permission android:name="YOUR_PACKAGE_NAME.permission.C2D_MESSAGE" /> <!-- [END gcm_permission] --> ~~~~~
この
YOUR_PACKAGE_NAME
これだった…。
これをそのままにしてビルドして入れようとしてたので、エラーになっていたのでした。
これを修正したらあっさりインストール出来ました。
まずググろう
ほんとに…分からなかったらまずググろう。反省。
無駄な三十分を過ごしたー!あー!
【Unity】[TIPS] DoTweenで画像のalpha値をアニメーションさせたい
canvasgroupならFadeIn , Outがすぐ出来るみたいだけど、
uGUIのImageならどうやるのかをメモ。
ちょっと調べれば分かることですが…。
DOTween.ToAlphaを使う
var fadeImage = GetComponent<Image>(); fadeImage.enabled = true; var c = fadeImage.color; c.a = 1.0f; // 初期値 fadeImage.color = c; DOTween.ToAlpha( ()=> fadeImage.color, color => fadeImage.color = color, 0f, // 目標値 1f // 所要時間 );
DoTween.ToAlphaを使うと、colorのalpha値に対して処理を行うことが出来る。
そのイベントを値を入れたいImageに渡してあげればOK。
沢山あったらどうなるんや
こんなテストコードで軽く動かしてみる。
public Image[] testImages; public void fadeTest(){ for (var i = 0; i < testImages.Length; i++) { // 初期化 var img = testImages[i]; var c = img.color; c.a = 1.0f; img.color = c; DOTween.ToAlpha( () => img.color, color => img.color = color, Random.Range(0.0f, 0.2f), // 目標値 Random.Range(1f, 3f) // 所要時間 ).SetLoops(-1 , LoopType.Yoyo); } }
問題なし。便利ですね!
【Unity】uGUIで円形のプログレスバーを簡単に作る
お疲れ様です。ぐーるです。
uGUIでの円形プログレスバーの作り方をメモっておきます。
手順
1.一番下になる背景を作る
Imageを作り、円形の画像を当てはめ、名前を「ProgressBar」としておく
ここに実際に使用した200x200の白い円形のpng画像を貼ってあるのですが
見えませんね。見えません。見えないのでイメージ画像も一緒に貼ってあります。
2.中央のマスク部分を作る
1で作ったProgressBarをコピーして子要素にする。その後大きさをちょっと縮小(100x100)して、
名前を「Center」にしておく
3.ゲージ部分を作る
1で作ったProgressBarをコピーして子要素にする。
大きさはそのままにして、色を好きな色に変更、
Image TypeをFilledにします。
4.完成
Image Typeの下にあるImage Amountの値を変えれば
円形プログレスバーの出来上がりです。
uGUIスゲー
あっという間にできてしまった。
画像ではShadow入れて見やすくしていたり、名前を付けてたりしますが、
その辺はもちろん自由です。空中にゲージだけ浮かせたいって人は1の背景も要らないですね。
【Unity】ソロだけど、Zenjectを使ってみたい。超初心者向け?
万年ソロプレイなぐーるです。よろしくお願いします。
また間が空いてしまいました。
なんていうか年末年始は問題がよく出たり、人が抜けたり人が増えたり
と運用をメインにしているとバタバタな期間だったりします。
2-3月は少し落ち着くといいなあ。(4月からはまたバタつく)
zenjectを使ってみたい
C#用のDI container。
オジサン的にはSpringとかseasorとかむかーし使ってたなぁなんて印象です。
個人的にこういうの結構好きでして、多人数開発になると「こうかはばつぐんだ!」っていう感じです。
とはいえ、個人で使う分には手に余るような部分が多く、手を出しづらいのですよね。
重い腰を上げる
まずは「zenject」で検索。
Unity3DのDIフレームワーク、Zenjectの紹介 | Aiming 開発者ブログ
ふむふむ、installerとcontextとbindかぁ。
え、bindするには必ずinstallerをアタッチしないとダメなのかな?
と思ったのだけど、公式のreadmeを見てみると、すごくシンプルでも動きそう?
やってみた手順
1. unityで適当なプロジェクトを作る
2. Asset Storeでzenjectを検索して、プロジェクトにインポートする
3. 上部メニューGameObject > Zenject > SceneContext を押下して、SceneContextを作成
4. uGUIで適当にテストするbuttonを作る
4. 以下の適当スクリプトを作る(計3つ)
DIContainerに使いまわしたいオブジェクトをBindする。
そして使いたい所でResolveするだけ。
Node.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; // DIContainerにbindして色々な所で使いたいクラス public class Node { Node(){ Debug.Log("Node class New : " + this.GetHashCode()); } public void print(string message) { Debug.Log("node print == " + message + " | hashCode == " + this.GetHashCode()); } }
Exec1.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; using Zenject; public class Exec1 : MonoBehaviour { [Inject] DiContainer container; public void Bind(){ // 以下の3つのbind typeがある。 Debug.Log("bind"); //container.Bind<Node>().AsSingle(); //container.Bind<Node>().AsTransient(); container.Bind<Node>().AsCached(); } public void Resolve(){ var node = container.Resolve<Node>(); node.print("exec1"); } public void UnBind(){ Debug.Log("unbind"); container.Unbind<Node>(); } }
Exec2.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; using Zenject; // 使う所その2 public class Exec2 : MonoBehaviour { [Inject] DiContainer container; public void Resolve(){ var node = container.Resolve<Node>(); node.print("exec2"); } }
5. 上で作ったbuttonにイベントを割り当てる
6. 動かしてみる
結果
最小構成で簡単にbindして使う事が出来た。
containerにbindする時以下の3つを指定できる。
これを理解しておこう。
AsSingle()
bind , unbindを繰り返してもnewは一度しか発生せず、resolveの結果は変わらなかった。
つまりunbindしてもzenject内にオブジェクトを保持していて、
再bind時にはそれが利用されているようだ。
ということはGCの対象にならず、アプリを終了するまで開放することが出来ないだろう。
AsTransient()
resolveする度にnewをしている。
そのため、毎回別のオブジェクトとして扱われる。
AsCached()
AsSingle()と挙動が似ている。unbind、再bindを行うとnewされるようだ。(newされる瞬間は最初のResolve時)
つまり、unbind時にはzenjectからも開放されているため、GCの対象になってくれる。
最も使いやすいのでは無いだろうか。迷ったらとりあえずコレで良さそう。
こんな挙動の違いを見ることが出来た。
終わりに
シンプルに使おうと思うとこんなに簡単なんですね。
個人開発でしかunityを使ったことが無いので、こういった開発補助ライブラリ
は余り使わなくてもいいのかもしれないけど、メモとして残しておく!
都内は大雪です。
【Unity】InkPainterをちょっと理解してみたかった
お疲れ様です。
年末商戦とか関係なく休日が潰れてしまうぐーるです。
ゼノブレイド2を買ったのですが、全然出来なくて困ってます。
InkPainter
突然ですが、InkPainterってご存知でしょうか
https://www.assetstore.unity3d.com/jp/#!/content/86210
Unityでスプラトゥーンごっこが出来ちゃう!しかも無料!
製作者さんはこちら
esprog.hatenablog.com
twitter.com
「おおー、すげー」で終わらせたいのだけど、
内部的にどうなってるのかな? 最小限で必要な要素ってなんだろう?
とふと気になったのでちょっと調べてみました。
最小構成の全体の流れ
まずは流れを掴むためにnormalmapもheightmapも無視した状態の最小構成を調査しました。
最小限必要な処理の流れを抜き出してみると
- materialからmainTextureを取り出して、RenderTextureにコピー
- materialのrenderTextureとmainTextureを差し替える(renderTextureがセットされた状態になる)
- bufferを利用してrenderTextureにインクを描画する
というすごくシンプルな実装という事がわかった。
最小構成のシェーダー
shaderもcgincで共通化されていた箇所を取り出してきて、
1ソース内に収めて、整理したらこんな感じ。
Unityプロジェクトに落とし込む
1.Unityでprojectを作る
2.planeを生成してカメラを調整でもしておく
3.Scriptを作る(ソースは上に)
4.Shaderを作る(同じくソースは上に)
5.リソースを用意する(brushとmainTexture)
↑使用したリソース二つなのだけど、見えないかも…?
6.materialを作り、planeにアタッチ
7.Scriptもplaneにアタッチ
8.実行する
マウスでドラッグした箇所にブラシで設定したテクスチャの
形に描画をすることが出来るようになっており、
ゲームを止めて起動し直すと全てが初期化されているのが
確認出来ました。(リソースを破壊してない)
感想
この実装ではマウスでやってるけど、ボールが転がった跡にしたり、
一定時間で消したりとか使用用途はありそう。
平たく言えば
「テクスチャの上にブラシテクスチャで描画出来るようにしただけ」
になってしまったが、
mainTextureをコピーしてRenderTextureを作り、
それを利用してカスタマイズした表現をするという方法を理解して貰えればと。
normalmapも合わせて色の変化やにじみとかもやれるようにすると
もっと表現の幅が広がりそうなのでもしかすると続きを書くかも。
とりあえず一旦ここまで。
ゼノブレイド2やるぞーやるぞー…。
追記
Unity 5.6.0以前で開くと、エラーが出ていた。
Shaderに
#include "UnityCG.cginc"
が必要みたいだったので、追記しておきました。