見出し画像

Unity1Week 「そろえる」に参加しました

Unity1Weekというイベントで、パズルゲームを作りました。
今回は、ふりかえりの記事を書いていきたいと思います。

ゲームは以下のリンク先から遊べます。
3分くらいで遊べます。
ソロエルノ迷宮 | フリーゲーム投稿サイト unityroom


環境

  • Unity2021.3.1f1

  • Universal RP

何を作るか決める

今回のUnity1Weekのテーマは「そろえる」でした。
自分は「そろえる」からパズルゲームを連想したので、パズルゲームを作ることにしました。

制作過程

1日目 : パズルっぽい画面を作る

立方体を並べてパズルっぽい画面を作成しました。
リッチな絵のパズルにしたかったので、PBR(Lit ShaderGraph)を採用しています。

Unity デフォルト Cube を並べる

箱の模様について

CubeのUVから模様を生成しています。

作成したShaderGraph

1日目 : 色味の調整(深海っぽくする)

「パズルを解くことで、世界(深海)の奥深くへもぐりこんでいく」というアイデアを思いつきました。
深海をイメージして色味を調整しました。
・背景に明度の低い青を設定
・Directional Light の色味も青っぽい感じにする

青っぽい感じにする

2日目 : コースティクスの実装

Water Caustics Effectを利用して、キラキラした感じを追加しました。
コースティクスに関しては、アセットを利用しています。
Water Caustics Effect for URP | VFX Shaders | Unity Asset Store

※ このアセットは、Compute Shader を使って実装されているため、WebGLでは動きません。
今回、Compute Shader を使わない形へアセットを改造することで、WebGL上で動作するようにしました。 (7日目に改造しました)


2日目 : ユニティちゃんの実装

「キャラクターを操作するゲームを作ってみたい」
と思ったので、ユニティちゃんを実装してみました。

© Unity Technologies Japan/UCL
この作品はユニティちゃんライセンス条項の元に提供されています


3日目 : 箱を押すシステムの実装

「倉庫番」のシステムを実装してみました。

「箱を1列揃えたら消える」というシステムを実装したらゲームになりそうだとここで思いつきました。

3日目 : 一列揃えると消えるシステム

1列揃えるシステムを実装してみました。


3日目 : 色味の調整

海の写真を観察していると、青緑系の色の写真が多いことに気が付きました。
そこで、背景色を青緑色に調整してみました。


4日目 : カメラ演出の実装

ステージをクリアしたら、次のフロアに進むという演出を実装してみました。

世界の奥へ進んでいくという体験を前面に押し出すと、ゲームとして面白いものになりそうだと感じました。

5日目 : パズルゲームの作問

簡単な背景モデルを作成し、パズルの土台を配置してみました。

ゲームを再生すると、パズルが生成されます。

パズルはScriptableObjectとして定義しおり、このデータを元にしてブロックやゴールを生成します。
プレイヤーの配置の初期位置も、ここで設定します。


5日目 : ブロックの視認性を上げる

黄色のブロックが視認しづらい、という問題がありました。(画像左上)
線の太さを太くして、視認性を高めました。(画像右上)

6日目 : ベジェ曲線でステージを配置

3D空間にステージを配置するのは手間がかかります。
そこで、3D空間にあらかじめベジェ曲線を引いて起き、ベジェ曲線にそってステージを配置する、という仕組みを作ることにしました。

使用したアセット: Bézier Path Creator | Utilities Tools | Unity Asset Store

以下の緑色の線がベジェ曲線になります。

ベジェ曲線に沿って、ステージを配置しています。


ベジェ曲線を書くのはそこそこ手間がかかりますが、
ベジェ曲線を一度引いてしまえば、ステージの配置は容易に行うことができます。


6日目 : サウンドデザイン

サウンド組み込みは以下のように行いました。
1.  イメージに合いそうなBGM、SEを選出し、DAWに取り込む
- BGM / 足音 / ブロックを押す音 / ブロックを消す音 / ステージ遷移の音
2.  実際のゲームをイメージして、効果音とBGMを鳴らしてみる
3. イメージが違えば、別のSEに差し替え
4. イメージがまとまってきたら、リミッターやEQを使って音のバランスを調整


これらのサウンドを、ゲームに組み込んだ結果は以下になります。


Unity上で音量のボリュームを微調整したものの、Unity上でのサウンド調整作業はほとんど行わずに済みました。

7日目 : エフェクト実装

泡のパーティクルエフェクトにはNOVA Shader を使用しました。

カメラ距離に応じてフェードがかかる、デプスフェードという機能を利用しています。

7日目 : WaterCausticsEffectのWebGL対応

今回、コースティクスを表現するために、WaterCausticsEffectというアセットを使わせていただいていました。

このアセットはコースティクス描画にComputeShaderが使用されているため、残念ながらWebGL上では動きません。

今回、WebGL上で動かすためにアセットのカスタマイズを行いました。
1. コースティクスを連番テクスチャとして書き出す
2. 連番テクスチャからテクスチャシートを作成する
3. テクスチャシートをシェーダーで再生する


コースティクスを連番テクスチャとして書き出す

WaterCausticsEffectには、コースティクスを単一のpngとして出力する機能が用意されていました。
こちらに手を加え、連番イメージとして書き出すような機能を追加で実装しました。

出力フレーム数や、出力時間の範囲を指定
出力した結果のコースティクス画像


連番テクスチャをテクスチャシートにまとめる

Unity 公式が公開しているVFX Toolbox に同行されている
Image Sequencerに連番テクスチャを登録します。
Unity-Technologies/VFXToolbox: Additional tools for Visual Effect Artists (github.com)

連番アニメーションは最初と最後のフレームがつながっていないため、アニメーションがループしたときに不自然に動きが途切れてしまいます。

Looping

Frame Processors の Looping を使うことで、アニメーションをループさせることができます。

Assemble Flipbook

Image Sequencer の Assemble Flipbook を利用して、
以下のような64フレームのアニメーションを8x8コマで書き出しました。

テクスチャシートの連番アニメーション再生

以下のシェーダーを利用して、コースティクスのアニメーションを再生させました。

Shader "rngtm/FlipbookAnimation"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _NumberX ("Number X", Int) = 8 // フレーム分割数
        _NumberY ("Number X", Int) = 8 // フレーム分割数
        _FrameRate ("Frame Rate", Float) = 24.0 // フレームレート
        _Interval ("Interval", Float) = 0.0 // 最後のフレームを表示してから次のアニメーションを開始するまでの待機時間
    }
    SubShader
    {
        Tags
        {
            "RenderType" = "Transparent"
            "Queue" = "Transparent"
        }
        
        LOD 100
        Blend SrcAlpha OneMinusSrcAlpha

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                float frame : TEXCOORD1;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            int _NumberX;
            int _NumberY;
            float _FrameRate;
            float _Interval;

            float fmod(float x, float a)
            {
                return x - floor(x / a) * a;
            }
            
            v2f vert(appdata v)
            {
                v2f o;

                o.uv = TRANSFORM_TEX(v.uv, _MainTex);

                o.vertex = UnityObjectToClipPos(v.vertex);

                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {
                
                #define NUM (_NumberX * _NumberY)
                int frame = min(1.0, fmod(_Time.y * _FrameRate / NUM, 1.0 + _Interval)) * (NUM - 1);
                
                #define FRAME_X frame % _NumberX
                #define FRAME_Y (_NumberY - frame / _NumberX - 1)
                float2 uv = i.uv;
                uv = frac(uv);
                uv = float2(
                    (uv.x + FRAME_X) / _NumberX,
                    (uv.y + FRAME_Y) / _NumberY);
                
                return tex2D(_MainTex, uv);
            }
            ENDCG
        }
    }
}

結果

WebGL上でコースティクスが動くようになりました。

8日 ~ 14日 : ブラッシュアップ

クオリティに納得がいかなかったので、さらに1週間かけてブラッシュアップを行いました。

ブラッシュアップについて

グラデーションフォグの自作

深度情報とRampテクスチャを利用して、画面上に深度グラデーションをかけています。
これにより、画面が近未来的な色合いになります。

half4 frag(Varyings input) : SV_Target
{
    half4 mainColor = tex2D(_MainTex, input.uv);
    float depth = SampleSceneDepth(input.uv);
    depth = LinearEyeDepth(depth, _ZBufferParams);
    depth = smoothstep(_FogStartDistance, _FogEndDistance, depth);
    half4 ramp = SAMPLE_TEXTURE2D(_RampTexture, sampler_RampTexture, saturate(depth));
    return lerp(mainColor, ramp, pow(ramp.a, _FogExponent));
}

Rampテクスチャの作成には、自前のツールを利用しています


3Dモデルの作成

背景モデルは Houdini を使って自作しました。
立方体をランダムに並べています。


ゲーム非再生時にカメラ演出を確認するツール

カメラの演出を確認する際に、ゲームを毎回再生するのは効率が悪くなります。
そこで、エディタモード時でもカメラの動きを確認できるようにするツールを開発しました。


その他 : ゲームデザインについて

簡単なステージをたくさん出す -> 途中で少し難しい問題を挟む
といった流れを意識してパズルを出すように心がけました。
パズルが難しくて詰まると、世界の奥へ進んでいくという体験が薄れてしまうと考えたためです。

パズルの作問について

まず、パズルを大量に作問しました。 (60個ほど)
そして、パズルをプレイして、どれくらい難しいかをラベリングしていきました。

パズルはScriptableObjectで表現したテーブル上にに登録して、出す順番などを管理しています。
(余談 : もともとは50問くらい出題する予定でしたが、14問くらいしか出せませんでした)


この記事が気に入ったらサポートをしてみませんか?