0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Unityでつくる2DRPG ~⑥プレイヤーの移動処理

Last updated at Posted at 2025-12-24

はじめに

前回は、Tilemapを用いたマップ作成を行いました。

今回は、十字キーによるプレイヤーの移動処理を実装します。

スクリプトの作成

今回は3個のスクリプトを作成します。

Player.cs

まずは1枚目です。
プロジェクトタブで
Create → C# Script
を選択し、新規スクリプトを作成してください。

作成したら、名前を「Player」としてください。

スクリプトを開き、以下のコードをコピー&ペーストしてください。

//Player.cs
using System.Collections;
using UnityEngine;
using UnityEngine.SceneManagement;

public class Player : MonoBehaviour
{
    [Range(0, 2)] public float MoveSecond = 0.1f;
    [SerializeField] public SceneManager SceneManager;

    Coroutine _moveCoroutine;
    [SerializeField] Vector3Int _pos; 

    private void Start()
    {
        if (SceneManager == null) SceneManager = Object.FindObjectOfType<SceneManager>();
        if (SceneManager != null && SceneManager.ActiveMap != null)
            PutPlayerOnPos(_pos);
        _moveCoroutine = StartCoroutine(MoveCoroutine(Position)); 
        if (_moveCoroutine != null) { StopCoroutine(_moveCoroutine); _moveCoroutine = null; }
    }

    public Vector3Int Position
    {
        get => _pos;
        set
        {
            if (_pos == value) return;

            if (SceneManager == null || SceneManager.ActiveMap == null)
            {
                _pos = value;
            }
            else
            {
                if (_moveCoroutine != null)
                {
                    StopCoroutine(_moveCoroutine);
                    _moveCoroutine = null;
                }
                _moveCoroutine = StartCoroutine(MoveCoroutine(value));
            }

        }
    }

    public void PutPlayerOnPos(Vector3Int pos)
    {
        _pos = pos;
        if (SceneManager != null && SceneManager.ActiveMap != null)
        {
            transform.position = SceneManager.ActiveMap.Grid.CellToWorld(pos);
            Camera.main.transform.position = transform.position + Vector3.forward * -10 + Vector3.right * -1;
        }
    }

    public bool IsMoving { get => _moveCoroutine != null; }

    IEnumerator MoveCoroutine(Vector3Int pos)
    {
        var sPos = transform.position;
        var gPos = SceneManager.ActiveMap.Grid.CellToWorld(pos);
        var t = 0f;
        while (t < MoveSecond)
        {
            yield return null;
            t += Time.deltaTime;
            transform.position = Vector3.Lerp(sPos, gPos, t / MoveSecond);
            Camera.main.transform.position = transform.position + Vector3.forward * -10 + Vector3.right * -1;
        }
        _pos = pos;
        _moveCoroutine = null;
    }

    private void ToNormalPos()
    {
        if (SceneManager != null && SceneManager.ActiveMap != null)
        {
            transform.position = SceneManager.ActiveMap.Grid.CellToWorld(Position);
        }
    }
}

SceneManager.cs

次に2枚目です。
同様にスクリプトを作成し、名前を「SceneManager」としてください。

その後、以下のコードをコピペしてください。

//SceneManager.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SceneManager : MonoBehaviour
{
    public Player Player;

    public MapChunk InitialMapPrefab;
    public Vector3Int FirstPlayerLocalPos = new Vector3Int(3, 3, 0);


    public MapChunk ActiveMap { get; private set; }
    List<MapChunk> spawnedChunks = new List<MapChunk>();


    Coroutine _currentCoroutine;

    void Start()
    {
        if (Player == null) Player = Object.FindObjectOfType<Player>();

        if (InitialMapPrefab != null)
        {
            InstantiateChunksCenteredOn(InitialMapPrefab, Vector3.zero);
            Player.SceneManager = this;
            Player.PutPlayerOnPos(FirstPlayerLocalPos);
        }

        _currentCoroutine = StartCoroutine(MovePlayer());
    }

    IEnumerator MovePlayer()
    {

        while (true)
        {
            if (GetArrow(out var move))
            {

                var afterPos = Player.Position + move;

                bool insideX = afterPos.x >= 0 && afterPos.x < MapChunk.CHUNK_SIZE;
                bool insideY = afterPos.y >= 0 && afterPos.y < MapChunk.CHUNK_SIZE;

                if (insideX && insideY)
                {
                    var mData = ActiveMap.GetMassData(afterPos);
                    if (mData.isMovable)
                    {
                        Player.Position = afterPos;
                        yield return new WaitWhile(() => Player.IsMoving);
                    }
                }
                else
                {
                    int dx = 0, dy = 0;
                    if (afterPos.x < 0) dx = -1;
                    else if (afterPos.x >= MapChunk.CHUNK_SIZE) dx = 1;
                    if (afterPos.y < 0) dy = -1;
                    else if (afterPos.y >= MapChunk.CHUNK_SIZE) dy = 1;

                    var targetPrefab = ActiveMap.GetNeighbor(dx, dy);
                    if (targetPrefab == null)
                    {

                    }
                    else
                    {
                        Vector3Int newLocal = new Vector3Int(Mod(afterPos.x, MapChunk.CHUNK_SIZE), Mod(afterPos.y, MapChunk.CHUNK_SIZE), 0);
                        var mData = targetPrefab.GetComponent<MapChunk>().GetMassData(newLocal);
                        if (!mData.isMovable)
                        {
                  
                        }
                        else
                        {
                            var savedPlayerWorldPos = Player.transform.position;

                            float chunkWorldSizeX = ActiveMap.Grid.cellSize.x * MapChunk.CHUNK_SIZE;
                            float chunkWorldSizeY = ActiveMap.Grid.cellSize.y * MapChunk.CHUNK_SIZE;
                            Vector3 expectedTargetPos = ActiveMap.transform.position + new Vector3(dx * chunkWorldSizeX, dy * chunkWorldSizeY, 0f);

                            MapChunk existingTargetInstance = FindSpawnedChunkAtPosition(expectedTargetPos);

                            if (existingTargetInstance != null)
                            {
                                RecenterUsingExistingInstance(existingTargetInstance);
                            }
                            else
                            {
                                InstantiateChunksCenteredOn(targetPrefab, ActiveMap != null ? ActiveMap.transform.position : Vector3.zero);
                            }

                            Player.transform.position = savedPlayerWorldPos;
                            Camera.main.transform.position = savedPlayerWorldPos + Vector3.forward * -10 + Vector3.right * -1;

                            Player.Position = newLocal;
                            yield return new WaitWhile(() => Player.IsMoving);

                        }
                    }
                }
            }
            yield return null;
        }
    }


  
    bool GetArrow(out Vector3Int m)
    {
        var isMove = false;
        m = Vector3Int.zero;


        if (Input.GetKeyDown(KeyCode.LeftArrow))
        {
            m.x -= 1; isMove = true;
        }
        else if (Input.GetKeyDown(KeyCode.RightArrow))
        {
            m.x += 1; isMove = true;
        }
        else if (Input.GetKeyDown(KeyCode.UpArrow))
        {
            m.y += 1; isMove = true;
        }
        else if (Input.GetKeyDown(KeyCode.DownArrow))
        {
            m.y -= 1; isMove = true;
        }
        return isMove;
    }

    int Mod(int a, int m)
    {
        int r = a % m;
        if (r < 0) r += m;
        return r;
    }

    void InstantiateChunksCenteredOn(MapChunk centerPrefab, Vector3 centerWorldPos)
    {
        foreach (var c in spawnedChunks) if (c != null) Destroy(c.gameObject);
        spawnedChunks.Clear();
        ActiveMap = null;

        float chunkWorldSizeX = centerPrefab.Grid.cellSize.x * MapChunk.CHUNK_SIZE;
        float chunkWorldSizeY = centerPrefab.Grid.cellSize.y * MapChunk.CHUNK_SIZE;

        MapChunk centerInstance = Instantiate(centerPrefab.gameObject).GetComponent<MapChunk>();
        centerInstance.transform.position = centerWorldPos;
        spawnedChunks.Add(centerInstance);
        ActiveMap = centerInstance;

        for (int dy = 1; dy >= -1; dy--)
        {
            for (int dx = -1; dx <= 1; dx++)
            {
                if (dx == 0 && dy == 0) continue;
                MapChunk neighborPrefab = centerPrefab.GetNeighbor(dx, dy);
                if (neighborPrefab == null) continue;
                var inst = Instantiate(neighborPrefab.gameObject).GetComponent<MapChunk>();
                inst.transform.position = centerWorldPos + new Vector3(dx * chunkWorldSizeX, dy * chunkWorldSizeY, 0f);
                spawnedChunks.Add(inst);
            }
        }

        for (int i = 0; i < spawnedChunks.Count; i++)
        {
            if (spawnedChunks[i] != null)
                spawnedChunks[i].gameObject.name = "Chunk_" + i + "_" + spawnedChunks[i].gameObject.name;
        }
    }

    MapChunk FindSpawnedChunkAtPosition(Vector3 pos)
    {
        const float EPS = 0.01f;
        foreach (var c in spawnedChunks)
        {
            if (c == null) continue;
            if (Vector3.Distance(c.transform.position, pos) < EPS) return c;
        }
        return null;
    }

    void RecenterUsingExistingInstance(MapChunk newCenter)
    {
        if (newCenter == null) return;

        float chunkWorldSizeX = newCenter.Grid.cellSize.x * MapChunk.CHUNK_SIZE;
        float chunkWorldSizeY = newCenter.Grid.cellSize.y * MapChunk.CHUNK_SIZE;

        List<MapChunk> newList = new List<MapChunk>();
        newList.Add(newCenter);

        for (int dy = 1; dy >= -1; dy--)
        {
            for (int dx = -1; dx <= 1; dx++)
            {
                if (dx == 0 && dy == 0) continue;
                Vector3 expectPos = newCenter.transform.position + new Vector3(dx * chunkWorldSizeX, dy * chunkWorldSizeY, 0f);

                MapChunk found = FindSpawnedChunkAtPosition(expectPos);
                if (found != null)
                {
                    newList.Add(found);
                }
                else
                {
                    MapChunk neighborPrefab = newCenter.GetNeighbor(dx, dy);
                    if (neighborPrefab == null) continue;
                    var inst = Instantiate(neighborPrefab.gameObject).GetComponent<MapChunk>();
                    inst.transform.position = expectPos;
                    newList.Add(inst);
                }
            }
        }

        foreach (var old in spawnedChunks)
        {
            if (old == null) continue;
            if (!newList.Contains(old))
            {
                Destroy(old.gameObject);
            }
        }

        spawnedChunks = newList;
        ActiveMap = newCenter;

        for (int i = 0; i < spawnedChunks.Count; i++)
        {
            if (spawnedChunks[i] != null)
                spawnedChunks[i].gameObject.name = "Chunk_" + i + "_" + spawnedChunks[i].gameObject.name;
        }
    }
}

MapChunk.cs

3枚目です。
スクリプトを新規作成後、名前を「MapChunk」としてください。

その後、以下のスクリプトをコピペしてください。

//MapChunk.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Tilemaps;

public class MapChunk : MonoBehaviour
{
    public Grid Grid { get => GetComponent<Grid>(); }

    Dictionary<string, Tilemap> maps;

    readonly static string BACKGROUND_TILEMAP_NAME = "Background";
    readonly static string NONE_OBJECTS_TILEMAP_NAME = "Path";
    readonly static string OBJECTS_TILEMAP_NAME = "Obstacle";
    readonly static string EVENT_BOX_TILEMAP_NAME = "Event";


    public MapChunk NeighborTopLeft;
    public MapChunk NeighborTop;
    public MapChunk NeighborTopRight;
    public MapChunk NeighborLeft;
    public MapChunk NeighborRight;
    public MapChunk NeighborBottomLeft;
    public MapChunk NeighborBottom;
    public MapChunk NeighborBottomRight;


    public const int CHUNK_SIZE = 8;

    private void Awake()
    {
        maps = new Dictionary<string, Tilemap>();
        foreach (var tilemap in Grid.GetComponentsInChildren<Tilemap>())
        {
            if (!maps.ContainsKey(tilemap.name))
                maps.Add(tilemap.name, tilemap);
        }
    }

    public MapChunk GetNeighbor(int dx, int dy)
    {
        if (dx == -1 && dy == 1) return NeighborTopLeft;
        if (dx == 0 && dy == 1) return NeighborTop;
        if (dx == 1 && dy == 1) return NeighborTopRight;
        if (dx == -1 && dy == 0) return NeighborLeft;
        if (dx == 1 && dy == 0) return NeighborRight;
        if (dx == -1 && dy == -1) return NeighborBottomLeft;
        if (dx == 0 && dy == -1) return NeighborBottom;
        if (dx == 1 && dy == -1) return NeighborBottomRight;
        return null;
    }

    public Vector3 GetWorldPos(Vector3Int pos)
    {
        return Grid.CellToWorld(pos);
    }

    public class MassData
    {
        public bool isMovable;
        public TileBase specialTile;
    }


    public MassData GetMassData(Vector3Int position)
    {
        var mass = new MassData();
        mass.specialTile = null;
        mass.isMovable = true;

        if (maps == null) Awake(); 

        if (maps.ContainsKey(EVENT_BOX_TILEMAP_NAME))
            mass.specialTile = maps[EVENT_BOX_TILEMAP_NAME].GetTile(position);

        if (maps.ContainsKey(OBJECTS_TILEMAP_NAME) && maps[OBJECTS_TILEMAP_NAME].GetTile(position) != null)
        {
            mass.isMovable = false;
        }
        else if (!maps.ContainsKey(BACKGROUND_TILEMAP_NAME) || maps[BACKGROUND_TILEMAP_NAME].GetTile(position) == null)
        {
            mass.isMovable = false;
        }
        return mass;
    }
}

スクリプトのアタッチ

作成したスクリプトをオブジェクトに取り付けていきます。

Player.csはPlayerに
SceneManager.csはSceneManagerに
MapChunk.csは各チャンクプレハブすべてに1つずつ

ドラック&ドロップでアタッチしてください。

インスペクターの設定

Player

「Scene Manager」という欄があるので、シーンにあるSceneManagerをドラック&ドロップしてください。

SceneManager

「Player」という欄に、シーンのPlayerをドラック&ドロップしてください。

「Initial Map Prefab」には、初期チャンクを設定します。ゲーム実行時最初に表示するチャンクプレハブを選び、ここに割り当ててください。

「First Player Local Pos」には、プレイヤーの初期座標を入力します。マップ上の障害物と重ならないような座標を設定してください。

今回は、Chunk1、(x, y)=(3, 3)をそれぞれ設定しています。

MapChunk

各チャンクに対し、隣接しているチャンクを設定します。

前回、以下のような並びになるようチャンクを作成しました。

[1]  [2]  [3]  [4]
[5]  [6]  [7]  [8]
[9]  [10] [11] [12]

(数字はチャンク番号)

そのため、例えばChunk1では、
「Neighbor Right」にChunk2を
「Neighbor Bottom」にChunk5を
「Neighbor Bottom Right」にChunk6を割り当てます。

隣接しているチャンクが無い場合は、そのまま空欄にしてください。

この作業をすべてのチャンクプレハブに対し行います。

おわり

今回は、プレイヤーの移動処理を作成しました。

次回は、この処理をざっくり解説していきます。

0
0
0

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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?