【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"
が必要みたいだったので、追記しておきました。
【Unity】第六回 unity1week「space」参加作品「スペースキューブ」の開発
ぐーるです。
ついに始まった、unity1week。
皆勤賞で今回も参加しました。
今回の作品はこちら。
https://unityroom.com/games/spaceproj
unityちゃんが宇宙貨物をスペースシャトルに運ぶゲームです。
三次元的に登ったり降りたり動かしたり重ねたりといった工夫を凝らして
ステージをクリアするパズルゲームです。全10ステージ。
お題について
「space」宇宙モノか隙間モノか…と漠然と考える。
大きな分類で考えると
- 宇宙を舞台にしたシューティングゲーム
- 隙間を利用したカジュアルゲーム
で二分されそう。
うーん、どっちかって言うとシューティングゲームかなぁ。
なんて思っていたのだけど。どうも食指が動かない。
まぁ、グラディウスとかのような名作をイメージしてしまうのは
必然というかそういうのしか頭に浮かばない。
じゃあ、カジュアルゲーム?
これも前にやったしなぁ。と考え直す。
「!」
と来たのは隙間は隙間でも箱をどこに置くか?といったスキマを埋める系のゲーム。
じゃあこれにしてみようかな、と作り始めた。
まず初めに
ざっくりイメージを固める。
ステージがあって、荷物は1つまたは複数で、荷物を指定の位置に置くことが出来たらクリア
というシンプルなものをイメージ。
spaceは「スキマに置け!」みたいな語呂合わせで何とか乗り切ってしまえばいいや。
と軽く考えていたが、全体的な雰囲気を宇宙っぽくしようと考える。
blender
ちょっと怪しげなキューブにすれば、それっぽく見えるだろう。
でもモデルのUV画像を取得して編集するにはどうすればいいんだ?
という事で調査して手順化して下記にまとめておきました。
やっとプログラムに入る
テンプレートやTweenアニメーションエンジンは一切使わないで全部自作。
unityの基本APIのみで実装していきます。
ステージ構成
大きく分けて3つに分類しました。
- Floor
- Gimmick
- Player
Floor
足場になるオブジェクト。プレイヤーは動かすことが出来ないシンプルなもの。コードもシンプル。
起点と終点をセット出来て、配置して命令(InitMove)を出すと終点に移動してから固定される。
また、Stateを持っていて、基点となるGameManagerが状態を監視している。
StateがCreateFinishになると「あ、ステージ作成完了したんだな」と分かるよくあるヤーツです。
今回のゲームでは1つしか無かったが、親クラス(FloorObj)で管理していて、
色々なオブジェクトを使えるようにしている。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class FloorManager : SingletonMonoBehavior<FloorManager> { protected STATE_STAGE _state; protected List<GameObject> gameObjList; public GameObject floorObjPrefab; private const float ADD_INIT_Y_VAL = 20.0f; // 初期位置の高さ。ここから降ってくる // Update is called once per frame void Update () { // ステージ作成中 if (State == STATE_STAGE.CREATE) { bool isCreateFinish = true; foreach (var go in gameObjList) { var fo = go.GetComponent<FloorObj>(); if (fo.IsInitMove) // まだ初期化動作が終わっていない { isCreateFinish = false; break; } } if (isCreateFinish) { State = STATE_STAGE.CREATE_FINISH; } } } public void Init() { Clean(); if (gameObjList == null) { gameObjList = new List<GameObject>(); } } /** * Create Stage Floor */ public void Create() { State = STATE_STAGE.CREATE; var stageDatas = Define.GetFloorList(); var xLen = stageDatas.Length; for(var xIdx = 0; xIdx < xLen; xIdx++) { var zLen = stageDatas[xIdx].Length; for(var zIdx = 0; zIdx < zLen; zIdx++) { var yLen = stageDatas[xIdx][zIdx]; for (var yIdx = 0; yIdx < yLen; yIdx++) { var targetPos = new Vector3(xIdx, yIdx, zIdx); var p = new Vector3(xIdx, yIdx + ADD_INIT_Y_VAL, zIdx); CreateFloorObj(targetPos, p); } } } } public void Clean() { State = STATE_STAGE.CLEAN; CleanGameObjList(); State = STATE_STAGE.CLEAN_FINISH; } protected void CleanGameObjList() { if (gameObjList != null) { for (var i = 0; i < gameObjList.Count; i++) { var go = gameObjList[i]; Destroy(go.gameObject); } gameObjList.Clear(); } } //------------------------------------------------------------------- /** * floor prefab create */ private void CreateFloorObj(Vector3 targetPos, Vector3 initPos) { var go = Instantiate(floorObjPrefab); var fo = go.GetComponent<FloorObj>(); if (fo) { go.transform.SetParent(this.transform); go.transform.position = initPos; fo.targetPos = targetPos; fo.waitTime = Random.Range(0, 2.0f); fo.InitMove(); gameObjList.Add(go); } else { Debug.LogError("ERR CreateFloorObj. please add FloorObj script!!"); Destroy(go); } } public STATE_STAGE State { get { return _state; } set { _state = value; } } public bool isCreate() { return _state == STATE_STAGE.CREATE; } }
using System.Collections; using System.Collections.Generic; using UnityEngine; public class FloorObj : MonoBehaviour { public Vector3 targetPos; // ここに向かって動く public float waitTime; // 動き出すまでに待機時間を設定してランダム性を出す private bool isInitMove = true; private float time = 20.0f; // Use this for initialization void Start () { } // Update is called once per frame void Update () { } public void InitMove() { if (isInitMove) { StartCoroutine(_InitMove()); } } private IEnumerator _InitMove() { yield return new WaitForSeconds(waitTime); while(Vector3.Distance(this.transform.position, targetPos) > 0.01f) { this.transform.position = Vector3.MoveTowards(this.transform.position, targetPos, time * Time.deltaTime); yield return new WaitForEndOfFrame(); } this.transform.position = targetPos; isInitMove = false; yield return null; } public bool IsInitMove { get { return isInitMove; } } }
Gimmick
ゴールも荷物も全てギミックとしました。
Carry , Beam といった子クラスがあり、クラス別に独自の動作が出来る。
Rigidbodyは使わず、Colliderのみ。
その中のCarryを抜粋。当たり判定はRaycastで行っています。
詳しくは後述。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Carry : GimmickObj { private bool isMove; private float moveTime = 1.0f; private float downMoveTime = 5.0f; private HIT_DIR hitDir; Ray leftRay; Ray rightRay; Ray forwardRay; Ray backRay; Ray downRay; // Use this for initialization void Start () { isMove = false; hitDir = HIT_DIR.NONE; } void FixedUpdate() { var p = this.transform.position; RaycastHit hit; leftRay = new Ray(p, Define.vectorLeft); rightRay = new Ray(p, Define.vectorRight); forwardRay = new Ray(p, Define.vectorForward); backRay = new Ray(p, Define.vectorBack); downRay = new Ray(p, Define.vectorDown); var distance = 1.0f; if (!isMove) { // 落下判定 if (!Physics.Raycast(downRay, out hit, distance, LayerMask.GetMask("Floor", "Gimmick"))) { if (!AudioManager.Instance.IsPlaySE()) AudioManager.Instance.PlaySE(AudioManager.SE.FALL_BOX); MoveDown(); } // player接触判定 if (Physics.Raycast(leftRay, out hit, distance, LayerMask.GetMask("Player"))) { hitDir = HIT_DIR.LEFT; } else if (Physics.Raycast(rightRay, out hit, distance, LayerMask.GetMask("Player"))) { hitDir = HIT_DIR.RIGHT; } else if (Physics.Raycast(forwardRay, out hit, distance, LayerMask.GetMask("Player"))) { hitDir = HIT_DIR.FORWARD; } else if (Physics.Raycast(backRay, out hit, distance, LayerMask.GetMask("Player"))) { hitDir = HIT_DIR.BACK; } else { hitDir = HIT_DIR.NONE; } } } public bool MoveLeft() { return Move(Define.vectorLeft , moveTime); } public bool MoveRight() { return Move(Define.vectorRight, moveTime); } public bool MoveForward() { return Move(Define.vectorForward, moveTime); } public bool MoveBack() { return Move(Define.vectorBack, moveTime); } public bool MoveUp() { return Move(Define.vectorUp, moveTime); } public bool MoveDown() { return Move(Define.vectorDown, downMoveTime); } // 移動 private bool Move(Vector3 movePos , float tmpMoveTime) { if (isMove) { return false; } RaycastHit hit; var ray = new Ray(this.transform.position , movePos); var distance = 1.0f; // 衝突判定 if (Physics.Raycast(ray, out hit, distance, LayerMask.GetMask("Floor" , "Gimmick"))) { var fo = hit.collider.gameObject.GetComponent<FloorObj>(); var gio = hit.collider.gameObject.GetComponent<GimmickObj>(); if (fo != null || gio != null) { // 障害物に衝突している return false; } } isMove = true; // 移動中状態フラグを立てる var p = this.transform.position; p += movePos; MoveControl.Instance.Move(this.gameObject, p, tmpMoveTime, FinishMove); return true; } private void FinishMove() { isMove = false; // 移動完了でフラグを下ろす } public HIT_DIR HitDir { get { return hitDir; } } }
Player
プレイヤーです。これも1つしかなかったが、実はFloor同様複数作ることが出来るようになってます。
プレイヤーを切り替えながら橋を架けるようなステージがあっても面白いかも。
と思いましたが、一旦保留に。
オブジェクトの移動
上でお見せしているCarryのソースコードを見れば一目瞭然ですが、
オブジェクトは前後上下左右でRayを飛ばしていて、
「今何とどのように接触しているか?」
を常に取得するようにし、それによって動作を変えるようにしました。
行動を起こす前にも同様にチェックして障害物が無いかを確認。
ここは最後まで悩みました。2Dだと
{0,1,1,0,0,1,0,1} {0,1,1,0,1,1,0,1} {0,1,1,0,1,1,0,1} {0,0,0,0,0,0,0,1}
といったマップを持っておいて、
マップ情報を元に通れるか通れないかを判定することがありますが、今回は不採用。
また、移動処理だけ行うクラスを用意してそこで移動させています。
callbackを設定しておいて、完了通知が飛ばせるようにしてあります。
using System.Collections; using System.Collections.Generic; using UnityEngine; /** * objectの移動を司る */ public class MoveControl : SingletonMonoBehavior<MoveControl> { public void Move(GameObject obj, Vector3 targetPos, float moveTime , System.Action callback) { StartCoroutine(_Move(obj , targetPos , moveTime , callback)); } private IEnumerator _Move(GameObject obj, Vector3 targetPos, float moveTime , System.Action callback) { // ある程度まで近づいたら while (Vector3.Distance(obj.transform.position, targetPos) > 0.01f) { obj.transform.position = Vector3.MoveTowards(obj.transform.position, targetPos, moveTime * Time.deltaTime); yield return new WaitForEndOfFrame(); } obj.transform.position = targetPos; // 強制的に指定位置に callback(); // 完了通知 yield return null; } }
(今思えばSphereでRaycast飛ばして範囲に入ったら、お互いのforwardで内積でも取ればどこで接触しているか取れそう
だなーとは思った…今度やってみよう。)
ジャンプ
最初はありませんでした。ほんとに倉庫番というゲームまんまだったので。
オリジナル要素をもう少し入れたいなーと思った時に3Dらしく上下にも動かしたいなと。
入れてみたら思いの外面白かったのでそのまま採用へ。
MoveControlでMove -> Moveです。
なので二段以上の壁でジャンプすると上には行くが、前には進めないという現象が出るようになっています。
これはこれでカワイイのでそのままにしました。
オブジェクトの再配置
オブジェクト、プレイヤーを画面外に落とすと、初期位置に戻ります。
これ実は「デバッグ機能」でした。
ステージから落ちれない
という仕様にするのが一般的ですが
実装を後回しにしてて、「落ちたら初期位置に戻しゃいいや」と適当スクリプトでガシガシ先に進めてた所
これを利用したステージを思いつき、コレ良いんじゃないかなとそのまま採用へ。
アセットについて
unitychanとSkyboxで使用しました。
周りを見るとみんな一杯使ってるんだなーと。
一週間だからどんどん使った方が良さそうではありますが。
勉強第一。DIY精神で自作頑張ります。(←unityちゃん使ってるヤツが言うセリフではない)
感想
仕事がずっと過渡期でして。
拘束十二時間超えがまれによくある会社とかおかしくないですか。
久しぶりにunity触って、過去の作品の実装とか思い出しながらやってましたが、
思ったよりも忘れてなかった。
自分はこういうゲームが好きだ。
ここ一週間、毎日通勤中にステージ考えて、
evernoteにガシガシアイデアメモってるのも楽しかった。
「家帰ったらUIガガッとやるぞ!」なんて思いながら仕事してました。
もし良かったらちょっとだけやってみて下さいー。
そんなに時間は取らせない…はず。
なんとか提出しました!unityちゃんで宇宙貨物を運ぶゲーム「スペースキューブ」です。よろしくお願いしまーす。 #unity1week https://t.co/8UuZUQrPtM pic.twitter.com/uGkj0hfs4v
— ぐーるさん (@uuha_goul) 2017年11月19日
【Unity】Blenderで簡単にuv展開をする手順まとめ。そして作ったものをUnityで使いたい
お久しぶりです。ぐーるです。
ゾンビみたいな暮らしをしています。
随分と間が空いてしまったのですが、unity1weekがまた始まったので、
久しぶりにブログ更新しようと思いました。
枠付きのcubeを作りたい
枠付きのcubeはこんな感じのもの。
オシャレですよね。ゲームに使うとちょっとしたアクセントになるのでは無いかと。
このぐらいなら自分でも作れるんじゃないか、と思い立ったのが今回になります。
Blenderでcubeを作ってuv展開して色を塗ればそれっぽくなるだろう。
初心者でも簡単簡単…。で、どうやるんだ?
という考えから手順をまとめてみました。
なお、クッソ初心者向けです。
BlenderでcubeをUV展開する手順
step2
いきなりですが、
メインビューの右上にあるツマミを左にグィーとドラッグ
して下さい。
するとビューを2つにすることが出来ます。
この方が分かりやすいかと思いますのでこうしました。
step3
左のビューは「編集モード」に
右のビューは「UV/画像エディタ」に
しておきます。
step4
シームを付けます。
シームとはuv展開をする時にどこに切れ込みを入れ、アジの開きのように展開するか?
という基準線になります。これを入れておかないとuv展開することが出来ません。
左ビューのcubeの頂点を全部選択しておいて(Aキー)
左ペインの「シェーディング/uv」 => 「シームを付ける」を押下します
すると線に赤い色が付きます。これがシームになります。
step5
やっと展開です。
左ペインの「シェーディング/uv」 => 「展開」 => 「展開」
と選択します。
右側に開いておいたuv画像エディタにuv展開された図が展開されるはずです。
上手く表示されない場合はシームが正しく付いていない可能性があるので、
step4からやりなおしてみましょう。
step6
ここで終わりじゃなく、これを編集したいのです。
なので、エクスポートします。
右ビューの下ペイン「uv」 => 「uv配置をエクスポート」
で出力することが出来ます。適当な名前と場所に出力しましょう。
step9
もう反映されています。
左ビューに行き、シェーディングをテクスチャにしてみましょう。
すると編集した画像が適用されたcubeが見えるはずです。
- 上手く表示されない?
なんか部分部分しか反映されないんだけど?
ライティングやカメラの関係が上手く表示されていないのかもしれません。
unityで使う関係上、ライティングやカメラは必要無いので、消しちゃいましょう
「右クリック」 => 「削除」 などでOKです。(xキーでもいける?)
step10
unityで使ってみましょう。
fbxで適当に出力してD&Dで持っていけばOKです。
なお、テクスチャは別で入れる必要がありました。
「fbxとpngを入れる」 => 「pngからMaterialを作る」 =>「fbxをSceneに配置してMaterialを適当」
オブジェクトに適用すればOKのはずです。
fbxに画像を含めるには
nn-hokuson.hatenablog.com
ここに手順が!神さまーありがとうございます!
感想
ステップ10までありますが、実際やってみると15分程度で終わりました。
慣れればもっとスイスイ出来ると思います。
これが理解できればきっと複雑なモデルでもuv展開することが出来る…はず!
あー、それにしてもBlender難しい!
unity1weekの進捗はダメです。
【Unity】アクションゲームの「動く床」を実現するには【Tips】
最近ゲームを作ってまして、そこから何かネタが無いかなーと思うのですが、
これと言って目新しいものが無く…。
その開発過程でちょっとだけ気になった個所をメモ代わりに残しておこう。
動く床
アクションゲームにはよくあるギミックとして、
動く床
というものがある。
地面が動き、タイミングを合わせてジャンプしないと先に進めないというよくあるやつだ。
これをunityで作ってみよう。
床を作る
cubeを作成してスケールを10,1,10。これを動く床とします。
アニメーションを床に付与する
animation > createして適当にtransform.positonのxを動かすようなものを作成
プレイヤーを作る
その上に乗っかるプレイヤーを適当に用意する。
今回はQueryChanを使いました。
Control.cs
(仮実装なんでとりあえずやっつけ…。)
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Control : MonoBehaviour { private const float SPEED = 5.0f; public Animator anim; private float jumpTime = 0; private bool isJump = false; // Update is called once per frame void Update () { var hz = Input.GetAxis("Horizontal"); var vt = Input.GetAxis("Vertical"); if(Mathf.Abs(hz + vt) > 0) { anim.SetBool("Walk", true); } else { anim.SetBool("Walk", false); } if (Input.GetButtonDown("Jump")) { if (!isJump) { anim.SetTrigger("Jump"); isJump = true; jumpTime = 0; } } float jumpVal = 0; if (isJump) { if (jumpTime < 1.0f) { jumpVal += 2 * SPEED * Time.deltaTime; } else if (jumpTime < 2.0f) { //pos.y -= Time.deltaTime; } else { isJump = false; } jumpTime += Time.deltaTime; } var pos = this.transform.position; pos.x += hz * SPEED * Time.deltaTime; pos.y += jumpVal; pos.z += vt * SPEED * Time.deltaTime; this.transform.position = pos; if (Input.GetKeyDown(KeyCode.UpArrow)) { transform.eulerAngles = new Vector3(0,0,0); } if (Input.GetKeyDown(KeyCode.DownArrow)) { transform.eulerAngles = new Vector3(0, 180, 0); } if (Input.GetKeyDown(KeyCode.LeftArrow)) { transform.eulerAngles = new Vector3(0, 270, 0); } if (Input.GetKeyDown(KeyCode.RightArrow)) { transform.eulerAngles = new Vector3(0, 90, 0); } } }
とりあえず実行してみる
早速実行してみるとこうなる。
おかしい。おかしいぞ。
上に乗っかった時は動く床の影響を受けて欲しい。
解決策
では、どうするか?こうすればOKだ。
子要素にする
コードにするとこんな感じ
using System.Collections; using System.Collections.Generic; using UnityEngine; public class MoveFloor : MonoBehaviour { private void OnCollisionEnter(Collision collision) { collision.gameObject.transform.SetParent(this.transform); } private void OnCollisionExit(Collision collision) { collision.gameObject.transform.SetParent(null); } }
これを床に付与して実行してみよう
これでよくある動く床を実装出来た。
別の手段
別の手段としては床をRigidbodyにして、AddForceで動かす
(PhysicsMaterialで滑りを低く)
という手もあるらしいけど、ちょっと試していない。
凄い久しぶりにunityの事書けた…。最近更新も滞ってたし。
もっとメモ代わりにどんどん書きたい。
【Unity】 メッシュをカットした後のcappingを作ろう
以前
以前メッシュカッターについて記事にした事があった。
この時はなるべく簡潔な話にしたく、オブジェクトを切った後の「蓋」については
特に触れなかったのだが、ちょっとやる機会があったので、書いておこうと思う。
メッシュ断面図
メッシュカッターはオブジェクトをポリゴン単位で考え、一つずつ丁寧に処理していく。
カットラインと交点がある場合は、新たに頂点が2つ作られる。
四角形だった場合は以下のような断面になる。
図だと少し分かりにくいが、二つに分かれたオブジェクト両方ともこのようになる。
蓋の考え方
そしてここに蓋を作るのだが、蓋をどう作るか?を考える。
四角形ならどうにでもなりそうだが、円形や人型のオブジェクトだったらどうする?
そういった事なども簡潔に解決する方法として、「中心点を作る」という方法がある。
この2点と中心点を繋いでいく、にすれば簡単な計算で作れそうだ。
実装
まずは交点となった新たに作られた点を別リストとして取っておく
// どちらにもカウントがあるということはplateと交差しているポリゴンということ if (group1PosList.Count > 0 && group2PosList.Count > 0) { CalcCrossPoint(plane, group1PosList, group2PosList); // 2.planeとの交点を求める // 3.両方のグループともに交点を入れる group1PosList.Add(_pos1); group1PosList.Add(_pos2); // capping用の表 group1CapPosList.Add(_pos1); group1CapPosList.Add(_pos2); group2PosList.Add(_pos1); group2PosList.Add(_pos2); // capping用裏側 group2CapPosList.Add(_pos1); group2CapPosList.Add(_pos2); }
中心点を求める方法はそこまで難しくない。
頂点をすべて足して、頂点数で割ればいい。
// 中心点を求める Vector3 center = Vector3.zero; foreach(var v in cappingPosList) { center += v; } center = center / cappingPosList.Count;
そして交点となる2点と中心点を繋いでいく…のだけど、
カッターとなるplateのどちら側にあるかによって蓋の向きが変わる。
この事に注意して、indexを割り振っていく。
var idx0 = centerIdx; var idx1 = i; var idx2 = i + 1; var cross = Vector3.Cross(capPosList[idx2] - capPosList[idx0], capPosList[idx1] - capPosList[idx0]); var inner = Vector3.Dot(cross, plane.normal); // plateに対してどちら側の蓋かによって計算が変わるのに注意 if (isFront) { if(inner < 0) { idx0 = idx2; idx2 = centerIdx; } } else { if (inner > 0) { idx0 = idx2; idx2 = centerIdx; } }
完成
後は全ての頂点とindexをまとめて、
オブジェクトを作れば完成だ。
複雑なオブジェクトでもちゃんと蓋が出来ている。
全体をまとめたプロジェクトごとgithubに公開している。
他にもMaterialを付与したり、Rigidbodyを付与したりという遊びも可能だ。
参考にしてほしい。