【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を付与したりという遊びも可能だ。
参考にしてほしい。
【Unity】第5回 unity1week 「フロー」の開発
unity1week襲来
もう大分時間が経ってしまったけど、書く。
naichiさんのunityroomで定期的に開催されているunityの一週間でゲームを作るイベント
https://unityroom.com/unity1weeks
もう5回目にもなる。
unityを始めてまだまだ日が浅いけど、なんか色々パッと出来るようになってきた。
(と思いたい)
見知らぬ、お題
毎回あるお題が出て基本的にそれに沿った形で開発を行う。
今回のお題は「フロー」。
フローとか言われると仕事感が凄いというか、
エスカレーションフローを思い出すというか…。
とにかくイメージが悪い。
(勝手に浮かんでくる自分が悪い。仕事柄しょうがないのだけど…。)
鳴りすぎる、Slack
お題見てからどうしよっかなーと頭抱えたかったのだが、
そんな事とは関係なくSlackが鳴り止まなかった。
どうも8月はなんか忙しい。
毎年4月〜5月は桜の季節だからか全体的に緩いムードなのだが、
梅雨のどんよりした季節から加速していき、夏場にピークを迎える傾向にある。
というわけで結局技術的な追求なども特に出来ず、
やれる事をやる事に。
アイデア、風呂の向こうに
フロー…フロー…とりあえず風呂入って考えよう。
といった具合で特にアイデアが出ないまま時間が過ぎていく…。
流れるように水の上を滑る!
みたいなものを作ってみたが、なんというか今一つ面白くならず…。
マリオ64の滑り台パチモンみたいなもんになってしまった。
あああああああああああ! #unity1week pic.twitter.com/35DcIpeWlz
— ぐーるさん (@uuha_goul) 2017年8月23日
これにコインでも付けて一番下まで滑ろう!なんて適当案が最初に思いついたのだが、
どうしよっかなぁ、このまま詰めるしか無いのか…と
アイデアが降りてくるのを待ったが、やっぱりSlackが鳴り止まない。
ベッドダイバー
もう飛んだ。
朝Slackで起こされる -> 家からリモートで暫定対応して、会社へ -> 午前中MTG -> 午後客先など -> 日付変わるぐらいに帰宅 -> 就寝
というループが続き、気がつけば金曜日。
冷や汗しか出ない。
静止した頭の中で
- >豆腐
- >お箸
テキトーにFractureして壊してみる!
- >予めオブジェクト分けといて、死んだらRigidbody.AddForceでぶっ飛ばす
苦手なAnimationに挑戦!
→何度か使ってるんだけど、毎回戸惑う!
後は割と過去の遺産を掘り起こしてUIやらなんやらそのまま使いました。
FractureとModel
予め壊れたものにしておいて、AddForceでぶっ飛ばす…だけ
メッシュカッターからcapping、そしてvolonoi法(もどき)について
まとめきれないいいい。
Anchor
unityにはanchorという考えを使いたい場合、ちょっとだけ工夫が必要だ。
色々手はあるが、親子構造にして、親オブジェクトをanchorの代わりにするのが単純だ。
お箸ではこれを使ってる
せめて、ゲームらしく
(これが書きたかっただけ)
出来上がったのがこれ。
豆腐が皿に乗るまでのフロー #unity1week pic.twitter.com/MZw5pddJrj
— ぐーるさん (@uuha_goul) 2017年8月27日
最初はすげー難しくて自分で作ってクリア出来ないというひどい出来w
動く床は失敗だった…。
うまく追従してくれなくて、どうしようかなと一旦そのままにしている。
こういうアクションゲーム作ったことある人はunity使いに多いと思うが、
基本的にコード記述量が少ない。
基本誤魔化し誤魔化しでなんとか形にしておいた…程度。
命の選択を
ラクガキしてる方が多くないかw
キーボードよりペンタブ触ってる方が長かったんじゃないかw
違うよこれはただの現実逃避だよ!
とりあえず夏休みを取りたい。
この炎上案件さえ終わればきっと取れる。
そう信じたい。
【Unity】第四回 unity1week 「夏」の開発について【レールの作り方】
第四回 Unity一週間ゲームジャムに参加
Unity 1週間ゲームジャム | 無料ゲーム投稿サイト unityroom - Unityのゲームをアップロードして公開しよう
一回目から参加し続けてもう四回目にもなってしまった。
参加者もちょっと少なかったのかもしれない。
イカとドラクエのせいだ。
みんな早い!自分も遅れずに投稿しました。お疲れ様でした😀 サマーサーフィン | ゲーム投稿サイト unityroom #unity1week https://t.co/xZYTPpFWXa pic.twitter.com/JhQwPujqr0
— ぐーるさん (@uuha_goul) 2017年7月30日
結論から言えば、今回も結果は振るわなかったのだけど、
まだまだUnity学びたいし頑張ろうと思っている。
アイデアについて
とりあえず
- スイカ
- 花火
- 海
- お化け
この辺りが夏の記号になりそうだなと漠然と考えた。
で、とりあえずスイカ割らせてみたり。
今回のテーマは「夏」。
— ぐーるさん (@uuha_goul) 2017年7月24日
アイデアが出ずに遊んでいる。
何やってんだ…俺は。
うああああああああああああ!
どうしよおおおおおお!#unity1week pic.twitter.com/0TVkCgNvOK
切り方はMeshCutterで数回ランダムに切っているだけ。
毎回結果が変わるのが面白い所。
だが、スイカ割りは特に人気で被りまくりそう。
花火も上に同じ。
最近気になっていたレール式ランゲームをちょっとやってみたかったので、
サーフィンを題材に今回はそれに。
レール式ランゲーム
決められたレールの上を走るランゲーム。
ジェットコースターやトロッコとかイメージすればOK。
例えば右方向へ向かって走るランゲームと比べて、
3Dを活かしたダイナミックな動きを出すことが容易に出来る。
また制御しやすい。(一回転を作ったり、この曲がり角で敵を出す!とか)
unityに落とし込む
- ランダムにルートを生成する
- 無限に走ることが出来ないとならない
- 敵の配置
- ゲームとしての体裁を整える
敵の配置やゲームとしての体裁を整える辺りは長くなりすぎるので割愛。
今回はルート生成についてコードレベルで紹介。
ルート生成のアルゴリズム
一言で言えばルートはpointの集合体です。
「次に向かうポイント」
をどんどん点で打っていけばOKです。
流れとしては
- ポイントをざっくり置く
- 滑らかに整える
- デコレーションする
- 描画する
という流れになります。
ポイントをざっくり置く
まずはじめにポイントをざっくりと置いていきます。
for (var i = 1; i < LINE_CNT; i++) { var distance = Random.Range(1, 5); // 距離の力 range = Random.Range(-3, 3) * 30; // ランダムな方向を向ける lastAngle = Mathf.Clamp(lastAngle + range, 0, 180); // 前回の角度から0度~180度の間でランダムな方向を出す float angle = lastAngle * Mathf.PI / 180; // 極座標の公式で計算する path.LinePoints[i] = path.LinePoints[i - 1] + new Vector3(Mathf.Cos(angle), 0, Mathf.Sin(angle)) * distance; }
pointはランダムな方向と、角度から位置を計算します。
極座標の公式
線の長さ(r)と角度(θ)で到達点座標(x,y)が求まる (x,y) = (r cosθ , r sinθ)
イメージとしてはこんな感じでまだガタガタな点が作られます。
滑らかに整える
点を細かくして補完していきます。
この計算をどのようにするかによって補完の仕方が変わります。
http://d.hatena.ne.jp/nakamura001/20111117/1321539246
イージングのアルゴリズムを応用して、緩急を作り出したりしても面白い!
/// <summary> /// 線を滑らかに補完する /// </summary> /// <param name="linePoints"></param> /// <param name="smoothness"></param> /// <returns></returns> private Vector3[] SmoothCurve(Vector3[] linePoints , float smoothness) { if (smoothness < 1.0f) smoothness = 1.0f; var pointsLength = linePoints.Length; var curvedLength = (pointsLength * Mathf.RoundToInt(smoothness)) - 1; var curvedPoints = new List<Vector3>(curvedLength); float t = 0.0f; List<Vector3> points; for (int i = 0; i < curvedLength + 1; i++) { t = Mathf.InverseLerp(0, curvedLength, i); points = new List<Vector3>(linePoints); for (int j = pointsLength - 1; j > 0; j--) { for (int k = 0; k < j; k++) { points[k] = (1 - t) * points[k] + t * points[k + 1]; } } curvedPoints.Add(points[0]); } return (curvedPoints.ToArray()); }
青い線が補完後の線になります。
ちょっと滑らかさが強いかもしれませんね…。
デコレーションする
一旦パスを作った後、ゲーム的な要素を追加します。
敵を置いたり、ループ作ったり。
基本的に完成したパスを再度舐めつつ、
ランダムに処理をしていけばOK
var EFFECT_IDX = 20; // 20個目までの点には効果を出さないように~とか。 for (var i = EFFECT_IDX ; i < path.LinePoints.Length ; i++) { var pos = path.LinePoints[i]; // 効果がランダムで発動 switch (Random.Range(1, 5)) { // 障害物を出す、とか //case XXX: // ~~~ // break; } }
描画する
無くてもいいのですが、
見せてもいいのです。
LineRendererにまとめて入れるだけ
_lineRenderer.positionCount = _linePoints.Length; _lineRenderer.SetPositions(_linePoints);
あれ、SetVertexCount だったような…とちょっと気になるヤーツ。
https://docs.unity3d.com/ja/540/ScriptReference/LineRenderer.SetVertexCount.html
変わったようです。
処理速度について
もうここまででもいいんじゃね?って話ですが
ゲームにした場合、気になるのは処理速度です。
どうしても計算はちょっと長いのでストレートに処理していたらプチフリーズしてしまうでしょう。
そして、パスの終点まで到達した時に処理をしていたのでは、遅れてしまうでしょう。
- 並列に処理する
- 予め次を計算しておく
この2つを満たせばOKです。
並列に処理するのは何でもいいです。
Coroutineでも、Taskでも、Threadでも…。
慣れ親しんだCoroutine辺りでとりあえずいいのではないでしょうか。
次を用意しておく処理はこんな感じで
// indexが半分を過ぎたら。ここはもっと早くてもいいかも。ゲームスピードとの兼ね合い var len = currentPath.LinePoints.Length; if (index > 0.5f * len && isCreateNext) { var lastPoint = currentPath.LastPoint; // 最後のposition var lastAngle = currentPath.LastAngle; // 最後の角度 // 続きのサブを作成しておく StartCoroutine(CreateRailPath(lastPoint, lastAngle , subPath)); isCreateNext = false; // 何度も作らせないために } // 最後まで来たらswapする if (index > len - 1) { // 予め作っておいたサブとカレントを切り替える var tmpPath = currentPath; currentPath = subPath; subPath = tmpPath; isCreateNext = true; // 次を作らせるフラグをON index = 0; }
currentとsubを用意しておいて、
終わり際にswapする!というだけ。
後は進捗をチェックしつつ、次を用意させるだけです。
進んでいくと
交互に次が生成されるのがわかると思います。
この辺りだけgithubにまとめておきました。
よく分からない所はこちらを参考に。
今回の感想
というわけで第四回も終わりました。
今回も大変だった…。手が遅くて後手後手に…。
アイデアは相変わらずヒドイもんで、
センスが腐ってんなと思います。
次こそはもうちょっと良いものを作りたい。