Unityでファミコンを懐かしもう


みなさんご存知、あの、なかしまです。

ゲームといえばやっぱりファミコンですよね!世代的には小学校一年生のときにスーファミになったので、ど真ん中では無いですが幼稚園の頃はよく遊んでいました。

今回は、ファミコンの構造をUnityを使って理解していこうと思います。

ファミコンの解像度は256×240

ファミコンは横256、縦240の解像度になっていて、それをテレビ画面にペタリと引き延ばして表示していました。昔のテレビは大体4:3のブラウン管なので横に引き伸ばされた画面でプレイしていたことになります。それではそれをUnityで表現してみましょう。まずは256×240の画面サンプルの画像を用意します。
スクリーンショット 2015-02-04 14.34.20
これでファミコンの理想の形で表示出来ました。

ここからテクスチャを画面サイズに合わせて引き延ばします。カメラから右上の座標を取得し、矩形をスケールさせます。


    void Awake () {
        Vector2 max = Camera.main.ViewportToWorldPoint (new Vector2 (11));
        Vector2 scale = max * 2;
        transform.localScale = scale;
    }

スクリーンショット 2015-02-04 14.49.54
ファミコンと同じように表示する端末に合わせて引き伸ばされるようになりました。

あと昔のテレビは画面がフラットではなく湾曲しており、画面が歪んでいました。なのでそれも再現してしまいます。歪ませるのにはImage EffectsのFisheyeを使いました。
スクリーンショット 2015-02-04 14.59.32
これで当時のファミコンのゲーム画面を再現することが出来ました。

 

ファミコンのスプライトは8×8

ファミコンにはスプラト機能があり、8×8のグラフィックを最大で64個まで表示することが出来ます。ここではFamiSpriteコンポーネントを作成して、ファミコンのスプライトを表現してみようと思います。
適当に実装するとこのようになります。
スクリーンショット 2015-02-04 15.22.58
しかし、これでは使い勝手がよくありません。なのでEditor拡張をして、作成したスプライトの完成形をプレビュー出来るようにします。

[CustomEditor(typeof(FamiSprite))]
public class FamiSpriteEditor : Editor {

    public override void OnInspectorGUI() {

        EditorGUILayout.BeginHorizontal();
        GUILayout.FlexibleSpace();
        EditorGUILayout.EndHorizontal();

        var rect = GUILayoutUtility.GetLastRect ();
        float merginY = rect.y + 10;
        var fs = target as FamiSprite;
        for (int i = 0i < 64i++) {
            EditorGUI.DrawRect (new Rect (10(i%8) + (rect.width/2) - 4010(i/8) + merginY1010), fs.sprite[i]);
        }

        GUILayout.Space (100);

        GUILayout.BeginVertical();
        for (int y = 0y < 8y++) {
            GUILayout.BeginHorizontal();
            for (int x = 0x < 8x++) {
                fs.sprite[y8 + x%8] = EditorGUILayout.ColorField(fs.sprite[y8 + x%8]);
            }
            GUILayout.EndHorizontal();
        }
        GUILayout.EndVertical();
    }

}

スクリーンショット 2015-02-04 16.41.15

これでデザイナーさんが手軽にスプライトを作成出来るようになりました。

 

ファミコンにはスプライトとは別にBGがある

BGは背景のグラフィックを担当します。BGもスプライトと同じように8×8のグラフィックを32×30個並べることで背景を作成します。スプライトとBGはそれぞれ256個定義でき、それらを使ってゲーム画面を構築します。
今回の実装ではスプライトとBGをクラスでは区別せずに進めていくため、BGもFamiSpriteで表現します。

 

描画機能を作成する

ここまで出来たら、今度はスプライトの内容を書き出せる仕組みを作ります。始めに作った画面にテクスチャが付けられているので、それを書き換えてゲーム画面を作ります。

public class FamiconScreen : MonoBehaviour {

    Texture2D screen;

    void Awake () {
        Vector2 max = Camera.main.ViewportToWorldPoint (new Vector2 (11));
        Vector2 scale = max * 2;
        transform.localScale = scale;

        screen = renderer.material.mainTexture as Texture2D;
    }

    // Use this for initialization
    void Start () {
    
    }
    
    // Update is called once per frame
    void Update () {
        Clear ();

        DrawBG ();
        DrawSprite ();

        screen.Apply ();
    }

    private void Clear() {
        for (int x = 0x < screen.widthx++) {
            for (int y = 0y < screen.heighty++) {
                screen.SetPixel(x,yColor.white);
            }
        }
    }

    private void DrawBG() {
    }

    private void DrawSprite() {
    }
}
まずはクリア機能を作りました。これで画面をまっさらにします。
スクリーンショット 2015-02-04 17.00.51次に背景とキャラクタを表示します。

    private void DrawBG() {
        for (int x = 0x < 32x++) {
            for (int y = 0y < 30y++) {
                for (int xx = 0xx < 8xx++) {
                    for (int yy = 0yy < 8yy++) {
                        screen.SetPixel(x8+xxy
8+yybg.sprite[xx%8 + yy*8]);
                    }
                }
            }
        }
    }

    private void DrawSprite() {
        int xx = 0;
        for (int x = sprite.xx < 256-8 && x < sprite.x + 8x++) {
            int yy = 0;
            for (int y = sprite.yy < 240-8 && y < sprite.y + 8y++) {
                screen.SetPixel(x,y,sprite.sprite[xx%8 + yy*8]);
                yy++;
            }
            xx++;
        }
    }
スクリーンショット 2015-02-04 17.36.42
キャラクターを十字キーで操作できるようにして表示してみました。
画面を見ても全く意味がわからないですね!でも素敵なデザイナーと組んでちゃんと作ったらいい感じのゲームが出来るはずです!※左下の黒いのがキャラです。

いやー、ファミコンって奥が深いですねー。興味があれば皆さんも作ってみてください。