ぐーるらいふ

底辺。

【Unity】初心者でもCinemachineを使いたい

まずはこれを見てくれ。

複数のカメラが切り替わるとグッとゲームらしいです。
これ作るの面倒なんだよね~、なんて方。
ご安心ください。簡単に作れますよ。そうCinemachineならね。

Cinemachine?

UnityがAssetストアで無料で公開しているAssetで、統合カメラシステムです。

https://www.assetstore.unity3d.com/jp/#!/content/79898

Unity 2017.01.から使用可能になる予定となっていて、
今使いたい場合は、β版のUnityが必要です。

unity3d.com

早速使ってみる

Unity Betaを準備してAsset StoreからImportしたら
Examplesから各Sceneを開いて機能を確かめたら良い!
そして、公式のModule Examplesを見るんだ!

Module Examples – Cinemachine

と言いたい所ですがそれは置いておいて
「自分で使う場合はどうやるんだ?」
って所を書こうと思います。

まずは動かしてみよう

CinemachineをImportしているプロジェクトで画面上部メニューから

Cinemachine > Create Virtual Camera

f:id:ghoul_life:20170706184343p:plain

を押すと、Main Cameraに Cinemachine Brainが追加される。
さらにCinemachine Virtual Cameraが追加されたCM vcam1がSceneに追加される。

f:id:ghoul_life:20170706184129p:plain

もうこれだけでCinemachineが動いています。スゴイ。当たり前かw


見たまんまをあえて書きますと、

Main Cameraの位置に関わらず、CM vcam1の位置からカメラ表示するようになっている

Main Cameraの位置を動かそうとしても追加されたCM vcam1の位置が上書きされて動かせなくなっているはず。
また、sub cameraを作っているのではなく、スクリプト制御しているということもわかります。

ちょっと遊んでみる

もう一度

Cinemachine > Create Virtual Camera

を選択して、2つ目のVirtual Cameraを追加しよう。
ちょっと位置を調整して切り替わりのテストをしてみます。
こんな感じで切り替わりがわかる形にしました。(真上からの視点)

f:id:ghoul_life:20170706190409p:plain

切り替わり

1つ目のカメラをCM vcam1
2つ目のカメラをCM vcam2
とします。

CM vcam1 -> CinemachineVirtualCamera enable ON
CM vcam2 -> CinemachineVirtualCamera enable OFF

の状態で実行して、
CM vcam2のenableをON/OFFしてみよう。

f:id:ghoul_life:20170706190551g:plain

CinemachineVirtualCameraのenableやGameObjectのActiveを切り替えるだけで
自動でカメラが切り替わってくれる事がわかりました。

念のために優先度もテストしておこう

CM vcam1 -> priority 11
CM vcam2 -> priority 10

この状態で CM vcam2 のenableをON/OFF切り替えてみても
何も反応しないということがわかります。
想定通り、優先度の値が大きい方が優先になるということですね。

まとめると

この事から

  • GameObjectのActiveかCinemachineVirtualCameraのenableをON/OFFするだけで動く
  • Priorityが大きい方が優先になる
  • Priorityが同じ場合はONにした方が動く

ということがわかった。

ゲームに応用してみよう

Cinemachineがちょっとは理解できた。結構単純な仕組みで動いていそうだ。
ゲームで使いたい場合は、単純なFollow Modeなどでも十分使えるが、
もうちょっと応用してみて、アクションゲームやADVゲーム風のカメラワークを
作ってみたい。

仕様
  • カメラ自体は動かない
  • カメラはキャラクターを見る(LookAt)
  • カメラごとに範囲を指定
  • 指定範囲にプレイヤーが入ったらそのカメラに切り替わる
  • 切り替わる時のアニメーションは個別に設定
  • カメラ追加も出来るだけ容易に
実装

特に実装は要らないかなと思っていたのですが、
こんなのだけは作る必要がありました。

範囲に入れば優先度を上げ、範囲から出れば優先度を下げるだけ。

このスクリプトを各vcamにセットして、
SphereColliderもAddComponentします。(Is Trigger ONを忘れずに)
SphereColliderのRadiusで範囲指定して、実行すればもうそれだけでOK!

(二回目。これはこの範囲指定vcamを利用して作ってます。範囲に入るとカメラが切り替わっている)

カメラの追加はEditorから作るようにして、一気に必要なComponentの追加、
設定項目の設定なども一気にやっちゃうとさらに良さそう。

    public void AddColliderVirtualCamera()
    {
        var go = new GameObject();
        var sphereCollider = go.AddComponent<SphereCollider>();
        var cinema = go.AddComponent<Cinemachine.CinemachineVirtualCamera>();
        var cinemaCollider = go.AddComponent<CinemaCollider>();

        sphereCollider.isTrigger = true;
    }

Inspectorでボタン押下作れると便利。神、お世話になってます。
baba-s.hatenablog.com

個別にTweenアニメーション指定も出来る…だと?

Cinemachine Brainを改めて見てみる。
Default BlendというEase In Outという表示がセットされている。

f:id:ghoul_life:20170706193635p:plain

これはカメラとカメラを切り替える時のTweenアニメーションを指定している。
ここを個別に設定するには、Custom Blendsを使う。
Custom Blendsはほとんど直感で使えてしまうぐらい簡単だ。

Create Asset -> でMain Camera Blends.assetを作成して、
From , To , Style , Timeを追加していくだけだ。

Fromは「どこから」
Toは「どこまで」
Styleは「どのように」
Timeは「どれくらい」

だ。

f:id:ghoul_life:20170706193650p:plain

FromやToには
「ANY CAMERA」
という項目があり、
これはそのまんま、「いずれかのカメラ」を指す。

StyleのTweenアニメーションもいくつか用意されている

  • Cut -> 即座にパッと切り替わる
  • Linear -> 一定速度で切り替わる
  • Ease In Out -> 開始時と終了時に速度が変化する

など。この辺りは好きに触ってみてほしい。

これをガシガシ追加していけば個別にアニメーションを設定出来る

cinemachine神

ブログに起こすと凄く長くなったが、実際に触ると簡単だし!
でもきっと本気でカットシーン作ろうとしたら凄い苦労するんだろうなぁ。

また、今回作ったサンプルでぐりぐり走ってると結構カメラが激しく動くんでちょっと酔ってしまいました。
大きめなフィールドにしたり、移動速度を調整したり、カメラの切り替わり速度を調整すればその辺は改善できそう。
使うときはいい感じに調整してみて。

よくわからんメモ

CinemachineCollider.cs
といういかにも!なスクリプトがcinemachine側に用意されているのですが、
どうも使い方がよく分からず…自分の思っていた機能ではない?

API Reference無いかなぁ…。

【Unity】UnityのSceneを劇的ビフォーアフターしたい

概要

f:id:ghoul_life:20170703194328p:plain

こうしたい。

いいらいとさんがちょっと教えてくれた。
こういうこと教えてくれる人は少ないです。貴重な御方です。


なるほど、自分もやってみよう!

元のデータを用意

テキトーにSceneを作る。
テキトーにオブジェクトを配置してみる。
ここでは

  • Cube
  • Sphere
  • Cylinder

の3つをFloorに見立てたCubeの上に配置しました。

f:id:ghoul_life:20170703194359p:plain

ダサすぎワロタ。
これをオシャレに変えてみましょう。

まずは空から変えていこう

unityはSkyboxという空間の内側にSceneは描画されます。
そのSkyboxから変えていこう
Shaderで二色のカラーをグラデーションした感じでいかがでしょう。

神がいた。
github.com
もうこれこのままでええやん…?完成形や。

Lerp(A,B,C)はAからBに向かって変化した時に、Cの値で保管する関数

これを利用したMaterialを作って
Windows > Lighting > Setting

f:id:ghoul_life:20170703194442p:plain

f:id:ghoul_life:20170703194450p:plain

SkyboxのMaterialに作ったものをぽいっと設定すると
(眩しすぎるならIntensity Multiplierを減らしてみて)

f:id:ghoul_life:20170703194532p:plain

なんということでしょう、先程まで殺風景だった背面があっという間にオシャレなグラデーションの空間に!

ああ、次はカメラだ

これだけでもオシャレさはあるが、まだいける。
次は
Post Processing Stack(無料)
https://www.assetstore.unity3d.com/jp/#!/content/83912
を使う。
Unity5.5以上が対象になってしまうので、それ以前の人は
Import PackageからImage Effectで似たような機能が使えるはずだ。
(間違ってたらゴメンナサイ)

別で作ったProfileの値でPost Effect(色々描画して、最後にかけるエフェクト)を調整出来る

○Profileの作成
Project -> Create -> Post Processing Profileで作成

f:id:ghoul_life:20170703194633p:plain

○Post Efffectの設定
Post Processing BehaviorスクリプトをMain Cameraにアタッチ

○Post Effect profileの反映
作成したPostProcessing Profileを Post Processing Behavior 紐付けて準備完了だ。

PostProcessing Profileの設定

f:id:ghoul_life:20170703194656p:plain

  • Antialiasing
  • Ambient Occlution
  • Depth of Field
  • Bloom
  • Color Grading

の5つをONにしただけでこんな感じ。

f:id:ghoul_life:20170703194833p:plain

まるで豆腐のように美味しそうだったオブジェクトが、
あっという間に食べられなくなりました。

後はお好みでパラメータをいじったり、
point lightを入れてみたりするとそれだけでグッとそれっぽい。

f:id:ghoul_life:20170703194732p:plain

いかがでしょうか

オブジェクトには触らずに

  • Skybox
  • PostProcess

この2つを触るだけでグッと良くなるのがUnity。本当に劇的。
まだパラメータが一杯あるのでまだまだ出来ることはありそう。

【Unity】第三回unity1week「積む」に挑戦 後編 (開発編)

ghoul-life.hatenablog.com

ちゃんとゲームらしく

タイトル -> チュートリアル -> ゲーム -> 結果

と画面を作る。
必要な素材も大体ここで描く。
fireAlpacaで適当に描く。

他のゲームもいきなりゲーム画面のやつばかりやんけ!と
甘えから今まで一回もチュートリアルなんて作ってこなかったが、
今回はちゃんと作ろう。少しでもゲームをプレイして貰いたいし。
説明文に頑張って書く!という最終手段もあるけど。

開発

開発はスイスイ進んだ。流石に三回目ともなれば色々と積み重ねたものがある。
2DベースでuGUIでゲームを作るというあんまりやってはならない事かもしれないが、
それで行く事にした。

f:id:ghoul_life:20170627154507p:plain


各パーツと捉えてこれらは全て別々のCanvasにしている。
Canvasを複数作ってON/OFFをスッと切り替えて、画面遷移を制御するようにして、
順番やsortOrder , positionを細かく自由に変更可能にしていた。

縦書き

unityはデフォルトで縦書きに対応はしていない。
が、先人達がすでに挑戦してくれていたのを拝借。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using System.Linq;

public class VerticalText : UIBehaviour, IMeshModifier
{
    private Text textComponent;
    private List<char> nonRotatableCharacters; // 回転させない文字リスト
    private List<char> adjustCharacters; // 位置を調整する文字リスト

    private void InitLimitCharacters()
    {
        if (nonRotatableCharacters == null)
        {
            nonRotatableCharacters = new List<char>();
            nonRotatableCharacters.Add('ー');
        }

        if (adjustCharacters == null)
        {
            adjustCharacters = new List<char>();
            adjustCharacters.Add('ァ');
        }
        
    }

    public new void OnValidate()
    {
        //base.OnValidate(); -> WebGLビルド時のみエラーになる。iOSやaOSは平気なのに…何故?
        textComponent = this.GetComponent<Text>();

        var graphics = base.GetComponent<Graphic>();
        if (graphics != null)
        {
            graphics.SetVerticesDirty(); // 更新有りと伝える
        }
    }

    public void ModifyMesh(Mesh m) { }
    public void ModifyMesh(VertexHelper verts)
    {
        var stream = ListPool<UIVertex>.Get();
        verts.GetUIVertexStream(stream);

        modify(ref stream);

        verts.Clear();
        verts.AddUIVertexTriangleStream(stream);

        ListPool<UIVertex>.Release(stream);
    }

    void modify(ref List<UIVertex> stream)
    {
        if (textComponent == null)
        {
            textComponent = this.GetComponent<Text>();
        }

        var characters = textComponent.text.ToCharArray();
        if (characters.Length == 0)
        {
            return;
        }

        InitLimitCharacters();

        float angle = 90.0f;
        // 頂点を云々する。1テキスト6頂点
        // 6文字ずつ進む
        for (int i = 0, streamCount = stream.Count; i < streamCount; i += 6)
        {
            int index = i / 6;
            if (IsNonrotatableCharactor(characters[index]))
            {
                continue;
            }

            // 文字の中央を取得(上なら[i+1])
            var center = Vector2.Lerp(stream[i].position, stream[i + 3].position, 0.5f);

            if (IsAdjustCharactor(characters[index]))
            {
                center = center + new Vector2(-2.0f , 2.0f);
            }

            // 頂点を回す
            for (int r = 0; r < 6; r++)
            {
                var element = stream[i + r];

                var pos = element.position - (Vector3)center; ;
                var newPos = new Vector2(
                    pos.x * Mathf.Cos(angle * Mathf.Deg2Rad) - pos.y * Mathf.Sin(angle * Mathf.Deg2Rad),
                    pos.x * Mathf.Sin(angle * Mathf.Deg2Rad) + pos.y * Mathf.Cos(angle * Mathf.Deg2Rad));
                element.position = (Vector3)(newPos + center);
                


                stream[i + r] = element;
            }
        }
    }

    bool IsNonrotatableCharactor(char character)
    {
        return nonRotatableCharacters.Any(x => x == character);
    }

    bool IsAdjustCharactor(char character)
    {
        return adjustCharacters.Any(x => x == character);
    }
}

OnValidate
https://docs.unity3d.com/ja/540/ScriptReference/MonoBehaviour.OnValidate.html

ModifyMesh
https://docs.unity3d.com/ja/current/ScriptReference/UI.IMeshModifier.ModifyMesh.html

簡単に言えばUIの更新イベントフックして、そこで回転させている。
縦書きにするとァやー(長音符)の位置が少しズレてしまい、違和感を感じるので、
それを調整するようにしました。
また、ListPoolはUnityのソースから拝借。

神様ありがとうございます!
http://madnesslabo.net/utage/?page_id=6042

注意点としては、
WebGLビルドした時に

error CS0117: `UnityEngine.EventSystems.UIBehaviour' does not contain a definition for `OnValidate'

とbase.OnValidate()が怒られる。
これなんでだろー?iOSやaOSビルドは問題が無かった。
とりあえずコメントアウトしていて、特に影響なかったのでそのまま…。

チュートリアル

一番苦労したのはマスタとかコメントに書いたけど、
実装側でちょっと困ったのはこれ。
長くなっちゃったので別記事で

ghoul-life.hatenablog.com

また1つ勉強になった。

リリース

後は体裁を整えたりデバッグしたりで日曜日夜に提出。

締め切りは守る。
これゼッタイ。
みんな決められた時間内で平等に頑張ってる。(と思っている)

感想

変なゲームを好きに作ろうとしたら
被ったー!盛大に被ったー!しかも結構多いw

もうすでに三回目になり、
付いていくのもギリギリですが、
毎回本当に勉強になるなぁ。
少しずつ出来ることが広がっていくのを感じる。

今まではなんかゴチャ混ぜ感があって、
適当に詰め込んだ感じがあったけど今回は割りとまとまった方だと思う。
ゲーム部分をもっと詰めたいな。なんか本とか読んでみよう。

そろそろTweet機能を付けてみてもええか…。

第三回 unity1week 「積む」に挑戦 前編 (企画編)

先週また@naichilabさんのunityroomにて、unity1weekが開催されました。
unityの勉強は継続してやっていて、このブログも出来るだけ続けていきたいなと思っているので
今回もチャレンジしていく事にしました。

積みゲーけしけし | 無料ゲーム投稿サイト unityroom - Unityのゲームをアップロードして公開しよう

企画について

積むって。

もーこれが難しい。だって最初にパッと浮かんだのアレですよ、アレ!
f:id:ghoul_life:20170627020126p:plain
もうずーっと上位にいるネズミとかクマとかファンシーで可愛いアレのイメージが強すぎる。

とりあえずその考えを頭から振り払って、
他のことを考えよう。
荷物を積む、オブジェクトを積み上げて塔を作る、
など上にベクトルを向けるものがイメージとしてすぐに浮かぶが、
これも一杯出るだろうなぁ、と一旦振り払う。

積むんだよな、ゲームだよな、とシンプルに考えだした時
「積む+ゲーム=積みゲーをゲームにしたらどうだろう」
と思い立つ。

詳細をちょっとだけ詰める

積みゲーがただあるだけではダメだ。
それをどうにかしてゲームに落とし込まないと。
積みゲーを消化するって言うよな…。
シンプルに消化していくゲームはどうだろう。

  • 積みゲーから一本ゲームを選ぶ
  • そのゲームにパラメータが割り振ってある
  • ゲームにはクリアまでの所要時間がある
  • プレイヤーは所要時間を消費しそのパラメータ分成長する
  • 称号が手に入る(アクションゲームばかりクリアしていくとアクションマスターなど)
  • 一定期間過ぎた時に結果を振り返ることが出来る(一ヶ月など)

というゲームはどうだろうか。

だめだー

簡単にモックにしてやってみるが、全然駄目。

  • ゲーム拘束時間が長くて、辛い。
  • 長い割に別に大した結果が出るわけでもない。
  • やれる事に比べて周りを固める要素が多く、分かりにくい。

普通の人ならちょっと触ったらもうブラウザ閉じてるわーこれは。
もうこの時点で金曜日の深夜。だけど考え直そう。

無理矢理な方向転換

もっとシンプルにしよう。所要時間も称号も捨てよう。
パラメータももっとシンプルでいい。
パズルゲーム風に要素を調整して…。

・お題が出る
・それを選ぶ
・正解なら+1 不正解なら0

としたらグッと触り心地が良くなった。
(面白いかどうかは一旦置いておいて)
後はこれでなんとか完成まで持っていこう。

開発編につづく
ghoul-life.hatenablog.com

【Unity】uGUIでimageに穴を開けたい!(くり抜く)

ルパン三世的な…?

チュートリアルなどでゲーム画面の説明をしたい。
よくある画面を暗くして見てほしい箇所にライトを当てるような演出をしたい。

f:id:ghoul_life:20170626200151p:plain

こんなんただ塗って穴開けてるだけやん。
それだけなのに、パッと実装が分からない。
Maskじゃない、なんというかMaskの逆?
uGUIだけで出来る?どうやるんだ?

  • すでにゲームは作ってあるので、そこはなるべく触りたくない
  • uGUIベースのゲームなのでuGUIでやりたい。
  • 実装はなるべくシンプルで簡単に

どうも既存機能だけでは厳しそうな感触。
なんらかの工夫が必要か。

くりぬく?

nn-hokuson.hatenablog.com
なるほど、ColorMask 0!
これでも行けそう?だけど
裏のQueueもいじらないと駄目か…。うーむ。
あ、そうか別Layerにして、サブカメラにすればいけそうかな?

ワイプ、トランジション

裏に影響なく画面全体を被せるのはこれが近いのかな?
tsubakit1.hateblo.jp
これを応用すればやりたいことが出来そう?

おお!これか!?これじゃん!
nn-hokuson.hatenablog.com
これを応用してみよう。

実装してみる

もうほとんどそのままだけどパラメータを外から設定出来るように。

Shader "Custom/HoleViewShader"
{
	Properties{
		_BackColor("background color", Color) = (0, 0, 0, 0) // 背景色
		_HoleX("Hole Position X", float) = 0 // 穴の位置X
		_HoleY("Hole Position Y", float) = 0 // 穴の位置Y
		_Radius("hole radius", float) = 0 // 穴の大きさ
		_ScreenW("Screen Width" , float) = 960.0 // 画面の幅
		_ScreenH("Screen Height" , float) = 640.0 // 画面の高さ
		}

	SubShader{
			Tags{ "Queue" = "Transparent" } // alphaに対応するのに必要

			Blend SrcAlpha OneMinusSrcAlpha // alphaに対応するために必要
			Pass{
				CGPROGRAM

				#include "UnityCG.cginc"
				#pragma vertex vert_img
				#pragma fragment frag

				// Propertiesの値をShaderに渡す
				float _HoleX;
				float _HoleY;
				fixed4 _BackColor;
				float _Radius;
				float _ScreenW;
				float _ScreenH;

				fixed4 frag(v2f_img i) : COLOR{

					i.uv.x *= _ScreenW / _ScreenH; // アスペクト比を計算してあげる

					if (distance(i.uv, fixed2(_HoleX, _HoleY)) < _Radius){
						discard; // 指定位置より一定距離以内だったら処理を飛ばすだけ
					}
					return _BackColor;
				}
				ENDCG
			}
		}
}

値を渡すスクリプト側はこんな感じ。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class HoleView : MonoBehaviour {

    void Start()
    {
        // 外部から設定する
        SetHole(new Vector2(480, 320), 0.2f);
    }

    public void SetHole(Vector2 pos, float radius)
    {

        // 画面との比率を出す
        var pinPos = new Vector2(pos.x / Screen.width, pos.y / Screen.height);

        // アスペクト比の問題なのか、位置的にはこのあたりで調整すると思った位置になるので計算してあげる
        // x range 0.45 - 1.05 この値が画面端から画面端でいい感じの所
        // y range 0.3 - 0.7

        // 比率と範囲値を計算して値を出す
        pinPos.x *= (1.05f - 0.45f);
        pinPos.y *= (0.7f - 0.3f);

        // 最下値を足して範囲に入れる
        pinPos.x += 0.45f;
        pinPos.y += 0.3f;

        // shaderに値を渡す
        var img = GetComponent<Image>();
        img.material.SetFloat("_HoleX", pinPos.x);
        img.material.SetFloat("_HoleY", pinPos.y);
        img.material.SetFloat("_Radius", radius);
        img.material.SetFloat("_ScreenW", Screen.width);
        img.material.SetFloat("_ScreenH", Screen.height);
    }
}

githubにもさくっと。
github.com



Inspectorはこれだけ!
Image一個あればOK!

f:id:ghoul_life:20170626190258p:plain

これで超簡単!後からちょい載せで使えるヤーツの完成です。
uGUI同士ならCanvasのSortOrderとかで順番をいい感じにしてあげてください。

注意事項として

  • ホールの形

え、星型にくり抜きたい?shaderで書くんだ!ってのは嘘で、
複雑な形にくり抜きたいならLayer&サブカメラ&ColorMask0でやったほうがいいかも。

こればっかりはどうしようも無い?
汎用的に外部パラメータ化してスクリプトから渡すように。
後は実際にゲーム画面を見ながらスクリプト側で調整してあげて下さい。

【Unity】Inputをいい感じにしたい

UnityでInputを取りたいなぁ。なんて思ったら
void Update()
などで
Input.GetMouseButton(0)
とか書くだろう。

そしてif分で押したらこのイベントをして…
なんていう処理を書くだろう

そしてそれが複数必要になることだってあるだろう。
そして仕様上それら全てを一時的に無効にしたいタイミングが来たりしたら
とっても大変だろう。(pause中とかね)

なので、Input周りはWrapperを使うようにしている。

自分はこんな感じで。

SingletonMonoBehaviorはこちら。
ghoul-life.hatenablog.com



シンプルだけど重宝している…。
simple is best.

どっからでもアクセス出来て、on/offも一括だし。
一括on/offは

Input.simulateMouseWithTouches = false

これでももしかしていける?使ったこと無いけど。

とりあえずこれはmouseだけど、touchとかも入れていけば
mobileにも対応できそう?
なんかそのままでもmobileでとりあえずは動いちゃうとか
聞いたこと有る。

一部だけ有効にしたい、とか
マルチタッチに対応したい、とか
DownとUpの差を見てフリック判定する、とか(時間も要るか)

要望は尽きない。メンテナンスしていきたい所。
なんかパッと書けちゃいそうだけどねーこのレベルだと。
でも自分用にメモっとこう。

【Unity】SingletonMonoBehaviourでUnityObjectにアクセス

ゲーム全体を管理するGameManagerであったり、Inputであったりと

  • 一意で管理したい
  • 好きな所からサクッとアクセスしたい

なんて場合に重宝する。

これだけだけど、メモっておこう