269
204

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

NintendoDS風のグラフィックをUnityで作る

Last updated at Posted at 2020-08-29

#はじめに
 2020年8月に開催されたUnity1week(お題:ふえる)で、私はDSのグラフィック(主にポケ〇ン)を再現したアクションゲーム【がんばれ!ソラコくん】を制作しました。本記事では、再現までの過程(?)を備忘録的な意味も少し込めてまとめています。

こんな感じのゲームです

たのしい!!!!!!

#目次
1.素材の準備
┣ 1-1 主人公
┣ 1-2 マップチップ、他
┗ 1-3 木

2.画面仕様の再現
┣ 2-1 解像度
┣ 2-2 BG面(Canvas)
┗ 2-3 色数

3.ゲーム本編の実装
┣ 3-1 ステージ制作
┣ 3-2 2Dキャラの配置
┣ 3-3 2Dキャラの影
┗ 3-4 カメラの再現

4.音楽

#1.素材の準備
 ゲームを作り始める前に、まずは素材を作っていきます。(気分が乗るため)
####1-1 主人公
 DSのポケ〇ンを見るとわかるように、マップ上にあるオブジェクトの多くはドット絵で描画されています。それらのオブジェクトは斜め上から見下ろした俯瞰で表現されており、”DSのポケ〇ンらしさ”の大半はその表現から感じられるものだと思います。(適当に言っています)

例:DP主人公のドット絵 http://hikochans.com/pixelart/trainer/011

 スプライトのサイズは32*32で、停止・歩き(必要なら走り・ジャンプ)のアニメーションを4方向制作します。(キャラクターが左右対称の場合、右向きと左向きは反転で流用できるため3方向)
qiita1.png

 がんばれ!ソラコくんでは、待機5枚+歩き12枚+ジャンプ6枚の計23枚を使用しています。(ダブり有り)今回は32*32ぴったりに描きましたが、Unityで配置してから少し大きいことに気が付いたので、上下それぞれ4pxほど余白を開けて描くのがおすすめです。

####1-2 マップチップ、他
 ひとマス16*16を基準にマップチップを描きます。ここは2Dゲーム用のフリー素材マップチップで代用しても問題ないです。
mapchips.png
...がんばれ!ソラコくんでは時間の都合で16枚しかマップチップを用意していません(この素材は商用利用でなければ使用してもOKです)
bridge.png
kanban.png
橋や看板など、フィールドに置くものも16px単位で作りましょう。

####1-3 木
 ここまでポケ〇ン(ダイパ)風で進めてきましたが、木はポケ〇ン(BW)風で制作しました。
DPPtの木は画像1枚ですが、BWの木は画像5枚で構成されていて立体感があります。今回のゲームはアクションゲームだったので、遊びやすさのためにも立体感がわかりやすいBW風の木を採用しました。
wood.png
※実際に使用しているのは、左から1,4,6,7番目です。

木の画像の組み方は下記ツイートを参考にしてください。

#2.画面仕様の再現
 DSの画面のスペックはこのようになっています。

画面:3インチ(対角)半透過反射型バックライトつきTFTカラー液晶ディスプレイ×2枚
解像度:256×192、26万色表示
下画面に抵抗膜方式透明アナログタッチパネルつき
(https://ja.wikipedia.org/wiki/%E3%83%8B%E3%83%B3%E3%83%86%E3%83%B3%E3%83%89%E3%83%BCDS より引用)

 今回はどちらか片方の画面のみの再現とします。(全く同じものを複製すれば二画面も可能)

####2-1 解像度
 DSの解像度である256*192をそのままゲームの解像度にしてしまうと、現代のPC画面ではめっっっっちゃ小さくなって見れたものではありません。そこで、2D Pixel Perfect Camera を使って画面を大きくしつつシャギシャギ感を出します。「3Dゲームなのに”2D”って書いてあるヤツ使えるのかよ!!」と思うかもしれませんが、普通に使えます。

導入についてこの記事で書くと長くなってしまうので、参考になる記事のリンクを貼ります。
http://tsubakit1.hateblo.jp/entry/2018/05/29/003530

コメント 2020-08-25 191831.jpg
Pixel Perfect Camera の設定はこのようにします。

####2-2 BG面(Canvas)
 2-1を実行すると、UnityのCanvasが初期設定のままではうまく使えない状態になります。これはCanvasをワールド空間に配置することによって解決することが可能です。

Canvas設定の過程
1.ワールド空間にUIを配置するため、2-1で作ったカメラを複製しキャラが写らない場所に移動します。
image.png
2.複製したカメラは、クリアフラグを「クリアしない」カリングマスクを「UI」に設定します。
また、Pixel Perfect Camera の Run In Edit Modeを有効にしておきます。
image.png
3.ヒエラルキーで右クリック→UI→Canvas を選択し、新しいキャンバスを作成します。(位置ずれ対策のため、UI用カメラとCanvasは同一のオブジェクトの子に設定することをおすすめします。)
4.canvasを「スクリーンスペース - カメラ」に設定し、レンダーカメラに1で複製したカメラを登録、「平面の距離」を10に設定します。
コメント 2020-08-28 233906.jpg
5.ゲームビューの画面を小さくしていき、canvasのrect transformが256:192に近くなったら止めます。
コメント 2020-08-29 185348.jpg
6.canvasのレンダーモードをワールド空間に変更します。
7.お好みで「ユニット毎の動的ピクセル数」を2にします。

(おまけ)解像度合わせに便利なテスト画像を配布します。
canvasの調整時、この画像をPanelにしておくと確認が楽です。
DSDisplayTest.png

####2-3 色数
ネット上にはDSの発色数は26万色という記載が多くありますが、その数字はRGB各64色(18ビットカラー?)の262144色から来ていると考えられます。Unityの標準はRGB各256色の24ビットカラーなので、画面全体に減色処理(ポスタライズ)をすることが必要です。
※どう考えても自己満足の世界です。

左:18ビットカラー風 右:デフォルト
コメント 2020-08-29 185602.jpg
DSっぽい!!!!!!!!

エフェクトをかけるうえで参考になるページ
http://cyario.hateblo.jp/entry/2016/04/08/104225

18ビット風に減色するシェーダー(ほぼコピペです...ゆるして...)

Posterize.shader

Shader "Custom/Posterize" {

	Properties
	{
		_MainTex ("Texture", 2D) = "white" {}
        _Posterize("階調",int) = 256
	}

	SubShader
	{
		Cull Off ZWrite Off ZTest Always

		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;
			};

			v2f vert (appdata v)
			{
				v2f o;
				o.vertex = UnityObjectToClipPos(v.vertex);
				o.uv = v.uv;
				return o;
			}
            
            inline float3 transferColor(in float3 srcCol) {
                // test srcCol if the two of r,g,b are 0.
                float r = (int(srcCol.r*64))/64.0;
                float g = (int(srcCol.g*64))/64.0;
                float b = (int(srcCol.b*64))/64.0;
                

                fixed3 rgb = fixed3(r,g,b);

                return float3(rgb);
            }

			sampler2D _MainTex;

			float3 frag (v2f i) : SV_Target
			{
				float3 color = tex2D(_MainTex, i.uv).rgb;
                color = transferColor(color);
				return color;
			}
			ENDCG
		}
	}
}

#3.ゲーム本編の実装
####3-1 ステージ制作
 UnityTile3Dを使うことで、2Dゲームのマップを作る感覚で立体的なマップを作ることができます。
コメント 2020-08-29 202918.jpg
べんり!!!!!!!!!!!!!!!!

URL:https://github.com/NoelFB/UnityTile3D

※使い方については配布ページをご覧ください。(投げやり)

####3-2 2Dキャラの配置
下の画像のように、実はプレイヤーとオーブは斜めに(常にカメラの方向を向いて)配置されています。
コメント 2020-08-29 193411.jpg
これは「ビルボード」と呼ばれる板ポリなどの平面オブジェクトを常にカメラの方に向ける技術で、今よりも性能の制約がきびしかった時代に多用されていました。(現在でもパーティクルなどに用いられています。)

アタッチするだけでいいかんじにビルボードできるスクリプト

BillBoard.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BillBoard : MonoBehaviour
{
    [SerializeField] bool billDebug;
    public bool spin180 = true;
    public bool DirectionFix = false;
    [SerializeField] bool x = true;
    [SerializeField] bool y = true;
    [SerializeField] bool z = true;
    void Update()
    {
        Vector3 p = Camera.main.transform.position;
        //p.y = transform.position.y;
        transform.LookAt(p);
        if (billDebug)
        {
            Debug.Log("local:" + transform.localEulerAngles.y);
        }

        if (spin180)
        {
            if (transform.localEulerAngles.y > 90 && transform.localEulerAngles.y < 270 || DirectionFix == false)
            {
                transform.Rotate(new Vector3(0, 180, 0));
            }
        }
        else {
            if (transform.localEulerAngles.y > 270 && transform.localEulerAngles.y < 90 || DirectionFix == false)
            {
                transform.Rotate(new Vector3(0, 180, 0));
            }
        }
        if (!x) transform.localRotation = Quaternion.Euler(0, transform.localEulerAngles.y, transform.localEulerAngles.z);
        if (!y) transform.rotation = Quaternion.Euler(transform.localEulerAngles.x, 0, transform.localEulerAngles.z);
        if (!z) transform.rotation = Quaternion.Euler(transform.localEulerAngles.x, transform.localEulerAngles.z, 0);
    }
}

####3-3 2Dキャラの影
 ポケ〇ンDPPtをはじめとした少し古いゲームでは、キャラクターの影を円で表現することが多いです。
コメント 2020-09-02 162041.jpg
「がんばれ!ソラコくん」では、オブジェクトから下方向にレイを飛ばし、地面の位置に半透明の影スプライトを移動させるというような実装で再現しました。

影を出したいオブジェクトにアタッチするスクリプト
※あらかじめ影用の画像を子オブジェクトにしておき、shadowSpriteTransformに紐づけすることが必要です。また、前後左右に移動するオブジェクトの場合はisMovingをtrueにしてください。

ShadowScript.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ShadowScript : MonoBehaviour
{
    [SerializeField] Transform shadowSpriteTransform;
    [SerializeField] bool isMoving = false;
    Vector3 staticPosition;

    void Start()
    {
        drawShadow();
        staticPosition = shadowSpriteTransform.position;
    }
    void Update()
    {
        if (isMoving)
        {
            drawShadow();
        }
        else
        {
            shadowSpriteTransform.position = staticPosition;
        }
    }
    void drawShadow()
    {
        RaycastHit hit;

        //レイが当たったか判定
        if (Physics.Raycast(transform.position, Vector3.down, out hit, 10))
        {
            shadowSpriteTransform.position = hit.point + new Vector3(0, 0.05f, 0);
        }

    }
}

####3-4 カメラの再現
 これは完全に主観ですが、デフォルトのカメラから有効視野を狭めた方がポケ〇ンらしさが増すように感じます。(デフォルトは60、がんばれ!ソラコくんは24です)

左:有効視野24 右:デフォルト 角度は同一
有効視野比較2.jpg

#4.音楽
 NintendoDSの同時発音数は16音です...が、今回は完全に無視しました。
多分音源の方が”らしさ”を出すために重要です。「DPPt Soundfont」などで検索するとそれっぽい音源が出てくるのでうまく使いましょう。

※今回、Domino+VirtualMIDIsynth+Pokemon_DPPt_Soundfont.sf2の組み合わせでBGMを作りましたが、エラーとかを無理やりねじ伏せたので解説はなしです。
 
#5.あとがき?
 ...ここまでつらつら書いてみましたが、いつまでたっても書き切れる気がしないので一旦〆ます。それでも、以上のことを実践すればきっと輝かしいDS風Unityライフが待っているはず!!!!!!!!!!!
 なお、筆者は技術記事を書くのが初めてのひよっこぴよぴよなのでいじめないで...

他に知りたいことがあれば追記するので、Twitter(@FizDv)までリプライorDMをください!

(※2020年9月1日の午前、一瞬Qiitaのトレンド(日)3位をいただきました!ありがとうございます…!)

269
204
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
269
204

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?