【2Dマップ生成】Unityでso_longを作る

#自己紹介
こんにちは、[42tokyo Advent Calendar 2021](https://qiita.com/advent-calendar/2021/42tokyo) の9日目を担当する、在校生のtkanzakiです。
マップが "so long" でも動く2Dゲームを作るという課題があったので、Unityで作ってみました。

#やりたいこと
巨大なマップを読み取って一定範囲内のマップを生成する
いっぺんにオブジェクトを生成すると時間がかかってしまうのでプレイヤーの回りだけ生成されるようにする
![so_long_unity - SampleScene - PC, Mac & Linux Standalone - Unity 2020.3.21f1 Personal_ _DX11_ 2021-12-05 10-55-16_6.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2274692/0583ab0d-2aed-8e63-b8df-a57a1c8b44f5.gif)

#実装手順
1.マップをテキストファイルから読み取る
2.マップのサイズに合わせて背景を出す
3.プレイヤーを出す
4.プレイヤーを動かす
5.プレイヤーの一定範囲にゲームオブジェクトを生成する
![so_long_unity7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2274692/c593950b-e3a2-9955-ea0a-dc3ac72e1be4.png)
![BG3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2274692/d5acb6e1-1d77-c92c-c407-26078c50c296.png)

上の２つの画像を使うのでダウンロードしてください
画像は自作したものなのでフリー素材として使っていただいて大丈夫です
Unityのバージョンは何でもいいです
UnityHubから2Dゲームのテンプレートでプロジェクトを新規作成してください

##1.マップをテキストファイルから読み取る
・MapCreater.cs：マップを生成する
・BlockData.cs：マップの1文字(ブロック)に対応する要素
・BlockCharacter.cs：ブロックの文字の列挙型
・map.txt：マップのテキストファイル
この4つのファイルをAssets下にメニューバーから Assets > Create で作ってください
テキストファイルなどはドラッグアンドドロップすればインポートできます
![スクリーンショット (57).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2274692/806de752-ddbf-49ef-33ee-f53e32a92371.png)

コピペで使えます

```C#:MapCreater.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;

public class MapCreater : MonoBehaviour
{
    [SerializeField] private string mapFilePath; // マップファイルのパス

    private List<List<BlockData>> mapBlocks = new List<List<BlockData>>(); // BlockDataクラスの二次元リスト

    void Start()
    {
        // マップを読み込み
        ReadMap(mapFilePath);
    }

    private void ReadMap(string filePath)
    {
        try
        {
            using (var stream = new StreamReader(filePath))
            {
                InitBlocks(stream);
                // マップの情報をコンソールに出力
                PutDebugLog();
            }
        }
        catch (System.Exception e)
        {
            Debug.Log($"Error!{e}");
        }
    }

    // マップデータを読み込んでブロックリストに格納
    private void InitBlocks(StreamReader stream)
    {
        var count = 0;
        var line = stream.ReadLine();

        while (line != null)
        {
            mapBlocks.Add(new List<BlockData>());
            InitMapLine(line, count);
            line = stream.ReadLine();
            count++;
        }
    }

    // 一行分の読み込んだマップデータをブロックリストに格納
    private void InitMapLine(string line, int index)
    {
        var count = 0;

        foreach (var item in line)
        {
            var character = (BlockCharacter)item;
            var block = new BlockData(character);
            mapBlocks[index].Add(block);
            count++;
        }
    }

    // コンソール出力
    private void PutDebugLog()
    {
        foreach (var line in mapBlocks)
        {
            foreach (var block in line)
            {
                Debug.Log($"block char is {block.GetCharacter()}");
            }
        }
    }
}
```
```C#:BlockData.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BlockData
{
    private BlockCharacter character;

    public BlockData(BlockCharacter character)
    {
        this.character = character;
    }

    public BlockCharacter GetCharacter()
    {
        return (character);
    }
}
```
```C#:BlockCharacter
public enum BlockCharacter
{
    Empty = '0',
    Wall = '1',
    Player = 'P'
}
```
```:map.txt
11111
10P01
11111
```

Hierarchy から Create Empty で MapCreater を作成
![スクリーンショット (58).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2274692/d360a4d6-473a-9ca9-05a0-ff1368bf3965.png)
AddComponent で MapCreater を追加します
Map File Path にマップファイルのパスを入力します
![スクリーンショット (59).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2274692/dcc9a953-1b8d-5f31-e3ad-36d587d51e88.png)
Unity Editor 上部の実行ボタンを押してしてコンソールを確認します
![スクリーンショット (101).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2274692/488ba0d2-1505-552e-df9e-25f25ed9f346.png)
![スクリーンショット (61).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2274692/aeaf3024-fc6c-7caa-c941-b5181d84c429.png)
map.txt と同じ数出力されていれば成功です

##2.マップのサイズに合わせて背景を出す
MapCreater に CreateBG を追加しました
マップを読み込んだ時に縦と横の拡大率が決まります

```C#:MapCreater.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;

public class MapCreater : MonoBehaviour
{
    [SerializeField] private string mapFilePath; // マップファイルのパス

    [SerializeField] private GameObject bG;     // 背景のゲームオブジェクト
    [SerializeField] private int bGImageWidth;  // 背景画像の幅
    [SerializeField] private int bGImageHeight; // 背景画像の高さ
    private float bgScaleX; // 背景画像のXの拡大率
    private float bgScaleY; // 背景画像のYの拡大率

    private List<List<BlockData>> mapBlocks = new List<List<BlockData>>(); // BlockDataクラスの二次元リスト
    private int mapBlocksWidth;  // マップ内の横のブロックの数
    private int mapBlocksHeight; // マップ内の縦のブロックの数

    // Start is called before the first frame update
    void Start()
    {
        // マップを読み込み
        ReadMap(mapFilePath);

        // 背景を作成
        CreateBg();
    }

    // 省略

    // マップデータを読み込んでブロックリストに格納
    private void InitBlocks(StreamReader stream)
    {
        var count = 0;
        var line = stream.ReadLine();

        while (line != null)
        {
            mapBlocks.Add(new List<BlockData>());
            InitMapLine(line, count);
            line = stream.ReadLine();
            count++;
        }

        // マップの幅をセット
        this.mapBlocksHeight = count;

        // 背景の画像に対する大きさをセット
        this.bgScaleY = (float)this.mapBlocksHeight / (float)this.bGImageHeight;
    }

    // 一行分の読み込んだマップデータをブロックリストに格納
    private void InitMapLine(string line, int index)
    {
        var count = 0;

        foreach (var item in line)
        {
            var character = (BlockCharacter)item;
            var block = new BlockData(character);
            mapBlocks[index].Add(block);
            count++;
        }

        // マップの幅をセット
        this.mapBlocksWidth = count;

        // 背景の幅をセット
        this.bgScaleX = (float)this.mapBlocksWidth / (float)this.bGImageWidth;
    }

    private void CreateBg()
    {
        var bGObject = Instantiate(this.bG);
        bGObject.transform.position = new Vector3(0.5f, 1f, 0f);
        bGObject.transform.localScale = new Vector3(this.bgScaleX, this.bgScaleY, 1);
    }
}
```
背景画像をAssets下に置いたら、
Pixels Per Unit を 1
Pivot を Top Left
に変更して、Apply してください
Pivot を左上にするのはマップを読み取るのが左上からだからです
![スクリーンショット (118).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2274692/e69639cc-ef74-cff9-fab9-cde36032d301.png)
![スクリーンショット (120).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2274692/c374c3b5-b6a6-5e60-e074-446d56cab3a7.png)

Assets から Hierarchy にドラッグアンドドロップするとプレハブ化されるので、プレハブ化した後にAssetsに戻しましょう
![スクリーンショット (122).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2274692/4a255e79-da0f-a3c9-bde9-e3e32e4807cc.png)

MapCreaterにプレハブを割り当てて幅と高さを入力します
![スクリーンショット (124).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2274692/cc230640-73de-5e3c-00f0-8077432f35f9.png)

実行してマップと同じサイズの背景画像が出たら成功です
![スクリーンショット (126).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2274692/7a756ae4-e263-e92d-d6ec-4d5afe324a99.png)

##3.プレイヤーを出す
スプライト画像をAssets下に置いたら、
Sprite Mode をMultiple
Pixels Per Unit を 128
に変更して、Apply してください
![スクリーンショット (113).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2274692/1a8c466d-d29b-e4b8-72a9-8892d4085181.png)
そして、Sprite Editor を開き、
Type を Grid By Cell Size
Pixel Size を 128 x 128
Pivot を Bottom
で Slice して Apply してください
![スクリーンショット (115).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2274692/3ab06716-bc75-7765-9de1-0ce488b3561f.png)

Sprite Editor を閉じたら、BG と同じ要領で、
sprite_0 を Hierarchy にドラッグアンドドロップしてプレハブ化してください
名前はわかりやすいように Player にリネームしてください
![スクリーンショット (128).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2274692/ad23b65c-e35c-ebe7-d15e-0590e129d53a.png)

Player の Sprite Renderer の Order in Layer を 1 にして、BG より上に描画するようにします
![スクリーンショット (130).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2274692/9070e94c-8e37-251e-56ff-50dcccc0f7c1.png)

MapCreater に CreatePlayer を追加しました

```C#:MapCreater.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;

public class MapCreater : MonoBehaviour
{
    [SerializeField] private string mapFilePath; // マップファイルのパス

    [SerializeField] private GameObject bG;     // 背景のゲームオブジェクト
    [SerializeField] private int bGImageWidth;  // 背景画像の幅
    [SerializeField] private int bGImageHeight; // 背景画像の高さ
    private float bgScaleX; // 背景画像のXの拡大率
    private float bgScaleY; // 背景画像のYの拡大率

    [SerializeField] private GameObject player; // プレイヤーのゲームオブジェクト
    private int playerStartPosX; // プレイヤーのXの開始位置
    private int playerStartPosY; // プレイヤーのYの開始位置

    private List<List<BlockData>> mapBlocks = new List<List<BlockData>>(); // BlockDataクラスの二次元リスト
    private int mapBlocksWidth;  // マップ内の横のブロックの数
    private int mapBlocksHeight; // マップ内の縦のブロックの数

    // Start is called before the first frame update
    void Start()
    {
        // マップを読み込み
        ReadMap(mapFilePath);

        // 背景を作成
        CreateBg();

        // プレイヤーを作成
        CreatePlayer();
    }

    // 省略

    // 一行分の読み込んだマップデータをブロックリストに格納
    private void InitMapLine(string line, int index)
    {
        var count = 0;

        foreach (var item in line)
        {
            var character = (BlockCharacter)item;
            var block = new BlockData(character);
            mapBlocks[index].Add(block);
            count++;

            // プレイヤーの開始ポジションをセット
            if (character == BlockCharacter.Player)
            {
                playerStartPosX = count;
                playerStartPosY = (-1) * index;
            }
        }

        // マップの幅をセット
        this.mapBlocksWidth = count;

        // 背景の幅をセット
        this.bgScaleX = (float)this.mapBlocksWidth / (float)this.bGImageWidth;
    }

    // 省略

    private void CreatePlayer()
    {
        var player = Instantiate(this.player);
        player.transform.position = new Vector3(this.playerStartPosX, this.playerStartPosY, 1);
    }
}
```
MapCreater に Player を入れる
![スクリーンショット (84).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2274692/50b2f071-082f-ef75-3a76-eeb521069c7c.png)
実行してマップと同じ位置にプレイヤーが出たら成功です
![スクリーンショット (132).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2274692/d31407a5-7fa5-b70e-b2dd-e252c091cd6a.png)

##4.プレイヤーを動かす
まずは、Player を動かすので Player.cs を作り Player に Add Component しましょう

```C#:Player.cs

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

public class Player : MonoBehaviour
{
    [SerializeField] private Animator animator;
    [SerializeField] private float moveSpeed;

    private MapCreater mapCreater;

    void Start()
    {
        mapCreater = GameObject.FindWithTag("MapCreater").GetComponent<MapCreater>();
    }

    void Update()
    {
        // アニメーション切り替え
        AnimationChange();

        // キャラクター移動
        Move();
    }

    private void Move()
    {
        var xPos = this.transform.position.x + Input.GetAxis("Horizontal") * Time.deltaTime * moveSpeed;
        var yPos = this.transform.position.y + Input.GetAxis("Vertical") * Time.deltaTime * moveSpeed;

        transform.position = new Vector3(xPos, yPos, 0f);
    }

    private void AnimationChange()
    {
        // 反転
        SetDirection();
    }

    private void SetDirection()
    {
        var horizon = Input.GetAxis("Horizontal");
        var scale = this.transform.localScale;

        if (horizon > 0.1f)
        {
            scale.x = 1;
        }
        else if (horizon < -0.1f)
        {
            scale.x = -1;
        }
        else
        {
            return;
        }
        transform.localScale = scale;
    }
}
```
Move Speed を 3 くらいにします
![スクリーンショット (89).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2274692/805c5de5-fc38-45d0-80d6-79ff9b1120f4.png)
Add Tag から MapCreater というタグを作って MapCreater に付けます
こうすることで Player から FindWithTag で参照できるようになります
![スクリーンショット (93).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2274692/efb1bb7e-7365-5813-c5eb-8f15a3741547.png)
Player を Open Prefab してカメラを付けることでプレイヤーに追従してくれます
![スクリーンショット (91).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2274692/40eba25e-aca1-ea00-00ac-f1f33c0a9b63.png)

camera の transform.position.z を -4 にして手前から写るようにします
![スクリーンショット (135).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2274692/439a1034-e7e0-5f44-35d0-8e518770bf67.png)

![so_long_unity - SampleScene - PC, Mac & Linux Standalone - Unity 2020.3.21f1 Personal_ _DX11_ 2021-12-05 10-55-16_7.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2274692/a6a78e5b-69bb-be94-d241-6defc958774a.gif)

##5.プレイヤーの一定範囲にオブジェクトを生成する

MapCreater に CreateArround と DeleteArround を追加し、Player から呼び出します

```C#:MapCreater.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;

public class MapCreater : MonoBehaviour
{
    [SerializeField] private string mapFilePath; // マップファイルのパス

    [SerializeField] private GameObject bG;     // 背景のゲームオブジェクト
    [SerializeField] private int bGImageWidth;  // 背景画像の幅
    [SerializeField] private int bGImageHeight; // 背景画像の高さ
    private float bgScaleX; // 背景画像のXの拡大率
    private float bgScaleY; // 背景画像のYの拡大率

    [SerializeField] private GameObject player; // プレイヤーのゲームオブジェクト
    private int playerStartPosX; // プレイヤーのXの開始位置
    private int playerStartPosY; // プレイヤーのYの開始位置

    private List<List<BlockData>> mapBlocks = new List<List<BlockData>>(); // BlockDataクラスの二次元リスト
    private int mapBlocksWidth;  // マップ内の横のブロックの数
    private int mapBlocksHeight; // マップ内の縦のブロックの数
    [SerializeField] private float enableRange;  // ブロックの有効範囲
    [SerializeField] private float disableRange; // ブロックを無効範囲

    [SerializeField] private GameObject wall; // 壁のゲームオブジェクト

    // 省略

    public void CreateAround(float posX, float posY)
    {
        posY = Mathf.Abs(posY);
        var startX = (posX - this.enableRange) > 0f ? (posX - this.enableRange) : 0f;
        var endX = (posX + this.enableRange) < (float)this.mapBlocksWidth ? (posX + this.enableRange) : (float)this.mapBlocksWidth;
        var startY = (posY - this.enableRange) > 0f ? (posY - this.enableRange) : 0f;
        var endY = (posY + this.enableRange) < (float)this.mapBlocksHeight ? (posY + this.enableRange) : (float)this.mapBlocksHeight;

        for (int i = (int)startY; i < (int)endY; i++)
        {
            for (int j = (int)startX; j < (int)endX; j++)
            {
                if (mapBlocks[i][j].IsAlreadyRead == true)
                {
                    continue;
                }

                if (mapBlocks[i][j].GetCharacter() == BlockCharacter.Wall)
                {
                    Debug.Log($"{j}, {i}, {mapBlocks[i][j].GetCharacter()} is created.");
                    mapBlocks[i][j].GameObject = Instantiate(this.wall);
                    mapBlocks[i][j].GameObject.transform.position = new Vector3(j + 1, -i, 0);
                }
                mapBlocks[i][j].IsAlreadyRead = true;
            }
        }
    }

    public void DeleteAround(float posX, float posY)
    {
        posY = Mathf.Abs(posY);
        var startX = (posX - this.disableRange) > 0f ? (posX - this.disableRange) : 0f;
        var endX = (posX + this.disableRange) < (float)this.mapBlocksWidth ? (posX + this.disableRange) : (float)this.mapBlocksWidth;
        var startY = (posY - this.disableRange) > 0f ? (posY - this.disableRange) : 0f;
        var endY = (posY + this.disableRange) < (float)this.mapBlocksHeight ? (posY + this.disableRange) : (float)this.mapBlocksHeight;

        for (int i = 0; i < this.mapBlocksHeight; i++)
        {
            for (int j = 0; j < mapBlocksWidth; j++)
            {
                // 一定範囲内なら削除しない
                if ((i >= startY && j >= startX && i <= endY && j <= endX) || mapBlocks[i][j].IsAlreadyRead == false)
                {
                    continue;
                }
                if (mapBlocks[i][j].GetCharacter() == BlockCharacter.Wall)
                {
                    Debug.Log($"{j}, {i}, {mapBlocks[i][j].GetCharacter()} is deleted.");
                    Destroy(mapBlocks[i][j].GameObject);
                }
                mapBlocks[i][j].IsAlreadyRead = false;
            }
        }
    }
}
```
```C#:BlockData.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BlockData
{
    private BlockCharacter character;

    public bool IsAlreadyRead { get; set; }
    public GameObject GameObject { get; set; }

    public BlockData(BlockCharacter character)
    {
        this.character = character;
        IsAlreadyRead = false;
    }

    public BlockCharacter GetCharacter()
    {
        return (character);
    }
}
```
```C#:Player.cs

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

public class Player : MonoBehaviour
{
    [SerializeField] private float moveSpeed;

    private MapCreater mapCreater;

    // 省略

    void FixedUpdate()
    {
        // プレイヤーの一定範囲のマップを読み込む
        mapCreater.CreateAround(this.transform.position.x, this.transform.position.y);

        // プレイヤーの一定範囲外のマップを消す
        mapCreater.DeleteAround(this.transform.position.x, this.transform.position.y);
    }

    // 省略

}
```
わかりやすいようにマップを大きくします

```:map.txt
111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001
101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101
101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101
1000000P0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001
101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101
101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101
100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001
101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101
101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101
100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001
101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101
101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101
100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001
101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101
101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101
100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001
101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101
101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101
100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001
101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101
101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101
100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001
101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101
101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101
100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001
101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101
101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101
100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001
111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111

```

sprite_9 を Hierarchy にドラッグアンドドロップして Wall のプレハブを作ります
![スクリーンショット (137).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2274692/43e9f4b7-3c1f-e56e-3b10-dc0e295d90e7.png)

Sprite Renderer の Order in Layer も 1 にします
![スクリーンショット (139).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2274692/8f36721b-6eff-b822-b27a-c80809092ae2.png)

MapCreater の 
Enable Range に 10 を入力
Disable Range に 15 を入力
Wall にプレハブを割り当て
![スクリーンショット (99).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2274692/67d71172-28ff-4db1-f7c2-f2099c292954.png)
プレイヤーにあたり判定を付与します
Rigidbody 2D を Add Component
Gravity Scale を 0
Constrains の Freeze Rotation の Z を有効化
Box Collider 2D を Add Component
![スクリーンショット (105).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2274692/a914a822-0723-f6c9-6aa0-91710cc5c4d0.png)
壁も同様にあたり判定を付与します
壁は動かないので Freeze Position の X, Y を 有効にします
![スクリーンショット (104).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2274692/5e6410d1-5b38-080b-3bcb-6323c674bc78.png)
見やすいように Unity Editor のレイアウトを 2 by 3 に変更して実行すると 
![so_long_unity - SampleScene - PC, Mac & Linux Standalone - Unity 2020.3.21f1 Personal_ _DX11_ 2021-12-05 10-55-16_8.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2274692/429c839c-9467-c952-3728-a591a384f063.gif)

うまくいきました！

長すぎると見づらいのでアニメーションは省略しました
プレイヤーにアニメーションを付けるスクリプトはGithubで公開しときます
Animator や Animation は自分で作らないと動かないので調べてみてください

https://github.com/tkanzaki42/so_long_unity

敵や、ゴールを実装するとゲームらしくなるのでよかったら自分なりに拡張してみてください

#終わりに
42ではminilibXというライブラリを使ってC言語で作るのですが、めちゃくちゃ大変でした。
画像ファイルから色データをpixel単位で読み取ったり、キーの状態もシグナルハンドラで制御したりと、低レイヤーなところから自分で実装していくので、普段ゲームエンジンを使ってると意識しないようなことを調べ実装しなくてはなりませんでした。
特に、あたり判定などの物理挙動はUnityだと10秒くらいで実装できるので、「Unityってほんとに偉大だなー。」と改めて認識することができました。

明日は、機械学習エンジニアの[Kotablog](https://qiita.com/Kotabrog)さんが経路探索超入門について書いてくれる予定ですので、そちらの記事もお楽しみに！