ぐーるらいふ

底辺。

【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であったりと

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

なんて場合に重宝する。

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

【Unity】UnityのAnimationがわかんないよ

ずっと避けていた。
自作スクリプトでやっちゃったり、iTweenで代用していた。
プログラマーとしてはそれでいいと思っていた。
細かい制御、イベント発行だって自由自在だし、困ったことも無かったし。
何よりよく分からないし。
何度か挑戦はしてみたが、いつも思った動きが作れないので諦めてしまっていた。

が、このままではずっと出来ない。
もう一度挑戦してみよう。

作りたいもの

作りたいものはこれだ。
出来とるやん、って突っ込まない。

f:id:ghoul_life:20170526150208g:plain

一枚の画像がリズムを取るように上下scaleが伸縮するだけ。
もちろんposition , rotation , scaleをいじっても
それに追従して動いてくれるものじゃないと使い物にならない。
相対で動いてくれるものが絶対だ。

自分がやった手順をまとめておく

触ってこなかったので、調べながら見よう見まねでやってみたのをまとめておく。

配置~初期設定
  1. 適当なpngを画面にD&Dでinspectorに配置する
  2. Window > AnimationでAnimationタブを開く
  3. 1で配置したGameObjectを選択している状態でCreateを押下して、適当な.Animファイルを作る
  4. Add PropertyでTransform > Scaleを選択。

ここまではいいだろう。思ったまま操作すればいい。簡単だ。
問題はこっからだ。

パラメータ

まずは全体の長さを確認。何もせずそのまま実行する
ちょっとアニメーションの長さが長いので 0:40に短くしたい。
右側にある最後のキーフレームをグイッと0:40の所まで持っていけばOKだ。

さて、では実際にリズムを取るアニメーションを作る

0:25 , 0:30 , 0:35の箇所3つにAdd keyしてkeyframeを作る
赤い線を持っていって、右クリックしてAdd Keyだ。
カーソルの位置ではなく赤い線の箇所に作られちゃうぞ!
そして、0:30にまた赤い線を合わせて、transformのscale.yを0.5にする。
これで良さそうだ。

実行してみる。

f:id:ghoul_life:20170526150546g:plain

おお、それなりに合ってる。もうちょっと感覚を狭くしたら良さそうだ。
調査段階なのでこれでとりあえずいいだろう。

実験

ちゃんと実用に耐えられるかテストしてみよう。

移動してみる

先程のAnimatorを付けたものを移動させてみる
こんなん。

  if (Input.GetKey(KeyCode.LeftArrow))
  {
      var p = this.transform.position;
      p.x += MOVE_VALUE * Time.deltaTime;
      this.transform.position = p;
  }
  else if(Input.GetKey(KeyCode.RightArrow))
  {
      var p = this.transform.position;
      p.x -= MOVE_VALUE * Time.deltaTime;
      this.transform.position = p;
  }

f:id:ghoul_life:20170526151032g:plain

おお、問題なく動くぞ!(当たり前)

回転させてみる

無茶な傾きではどうか

f:id:ghoul_life:20170526151147g:plain

これも問題なし!
Unity最高だな

拡大させてみる

問題はここだ。
scale値を2,2,2にしたオブジェクトをAnimationさせたらどうなるのか。

f:id:ghoul_life:20170526152146g:plain

はい、死にました。

scale2なら
2 -> 1.5 -> 2
と動いてほしい。

相対的に設定するには…?

なんかあるはずだ。Unityは馬鹿じゃない。
何かしら方法があるはずだ…!

tsubakit1.hateblo.jp

あったあああ!
これだ!神がいた!こうすればいけるはずだ!

早速設定して実行!

f:id:ghoul_life:20170526152944g:plain


……
………

なんだこれは

BlendingをAditiveに設定すると、初期値からの移動量でアニメーションを行う

つまり、初期値からy:-0.5縮んでくれればいいのだ。
が、結果

0 -> -0.5 -> 0

になってしまった。
何故だ。初期値はtranformの2じゃないのか、一体初期値とはどこにあるんだ。

ガクッ。

結果

他で紹介されているのと同じように親オブジェクトの下でAnimation動かせばいいんだ。
そして、親オブジェクトのtransformをいじれば何ら問題ない。

【Unity】フェードイン・アウトを簡単に実現

前回のunity1weekチャレンジで作りたかったけど断念した
この機能を今のうちに用意したいな、なんか無いかなと思ったら
神おったーおったー!
kan-kikuchi.hatenablog.com
参考にさせてもらいます!

参考にして実装

そのまま使いたい所だが、それでは勉強にならない。
参考にしつつ自分なりにカスタマイズして身にしていきたい。

  • 好き勝手なタイミングでFadeInOutさせたい。
  • FadeInした後、待機時間を得てからOutしたい
  • スッキリと1クラスにまとめる

というわけで実装コード。
詳しく解説は書かないけどシンプルなコードなので読めばわかると思う。


呼び出しはこんな感じで

    public Fade fade;

    public void Fire()
    {
        fade.FadeInStarted += FadeInStart;

        fade.FadeInFinished += () => { Debug.Log("Fade in finished"); };
        fade.FadeOutStarted += () => { Debug.Log("Fade out started"); };

        // 2つのメソッドを呼ぶことも可能
        fade.FadeOutFinished += FadeOutFinished1;
        fade.FadeOutFinished += FadeOutFinished2;
        fade.FadeStart();
    }

    private void FadeInStart()
    {
        Debug.Log("Fade in started");
    }

    private void FadeOutFinished1()
    {
        Debug.Log("Fade out finished 1");
    }
    private void FadeOutFinished2()
    {
        Debug.Log("Fade out finished 2");
    }

動作画面は割愛するけど、イベントはしっかり発行されているのを確認。

f:id:ghoul_life:20170530124437p:plain

これは次に使いたいって事でメモ。