30分鐘簡易復刻《元氣騎士》地圖生成系統

遊資網發表於2020-03-10
對《元氣騎士》這款遊戲想必大家都不陌生。

元氣騎士是由涼屋遊戲工作室研發的一款角色扮演類遊戲,於2017年2月17日釋出。 遊戲講述了外星生物奪走維持世界的魔法石的故事,玩家將扮演騎士、刺客等角色參與遊戲,奪回魔法石。

Unity Connect社群博主超級汽水藉助Unity對遊戲中的地圖生成系統進行了復刻。與各位分享在此。

地圖顯示方式

  • 隨機大小的矩形房間。
  • 隨機的房間數量。
  • 通過走廊連線每個房間。
  • 每個圖都有 倆個特殊房間:“出生房”、“傳送房”。


地圖生成思路梳理

因為在原版遊戲中房間並不是完全的正方形,所以我們在設計時要考慮到邊長的問題。

雖然遊戲中的房間數量是隨機的,但是也是有一個範圍的。所以需要按照生成地圖的最終面積來做一個約束。

走廊連線算是遊戲中的一個難點,因為走廊的長度是不確定的要在生成房間後根據房間的位置和大小來確定走廊的生成位置以及長度。

在遊戲中,每一個大關卡包含多個小關卡,每個小關卡中又包含了多張地圖,每個地圖中又有多個小房間。

這樣說可能有點不太好理解我畫了張圖來幫忙理解:

30分鐘簡易復刻《元氣騎士》地圖生成系統

實現思路以及簡化

在之前博主已經發過兩篇文章介紹tilemap生成隨機地圖的方法,如果對tilemap不太瞭解可以點選【閱讀原文】關注博主,獲取往期教程。

整體的實現流程如下:

1、根據使用者定義建立一張足夠大的地圖 (這裡的地圖指每個小關所有房間所在的大地圖)。

30分鐘簡易復刻《元氣騎士》地圖生成系統

2 、假設大地圖中所有位置佈滿大小相同的房間 (這裡的房間邊長一定要奇數,比較好操作),然後根據所需的房間總數來刪掉多餘的房間。

30分鐘簡易復刻《元氣騎士》地圖生成系統

30分鐘簡易復刻《元氣騎士》地圖生成系統

30分鐘簡易復刻《元氣騎士》地圖生成系統

3 .根據規定的長寬比,來隨機修改每個房間的大小。

30分鐘簡易復刻《元氣騎士》地圖生成系統

4 .生成過道連通所有房間,(這裡黃色的點是原先沒有改變大小時的正方形的中心點)

30分鐘簡易復刻《元氣騎士》地圖生成系統

5 .確定特殊房間的位置。

30分鐘簡易復刻《元氣騎士》地圖生成系統

Tilemap中單獨房間效果圖:

30分鐘簡易復刻《元氣騎士》地圖生成系統
一個簡單的房間以及半個過道

30分鐘簡易復刻《元氣騎士》地圖生成系統
長方形連線正方形房間,黃色方塊為中心點

Unity中詳細操作

1、建立一個普通的Tilemap。

2、建立一個空物體,用來掛載我們的地圖生成指令碼。

30分鐘簡易復刻《元氣騎士》地圖生成系統

3、 建立所需要的變數

  1. <p>public class MapManager : MonoBehaviour</p><p>{</p><p>    [Header("地圖種子")] public int seed = 123456;</p><p>    [Header("每行房間數")] public int mapMaxW;</p><p>    [Header("每列房間數")] public int mapMaxH;</p><p>    [Header("總生成房間數")] public int mapCount;</p><p>    [Header("最大房間寬")] public int roomMaxW;</p><p>    [Header("最大房間高")] public int roomMaxH;</p><p>    [Header("最小房間寬")] public int roomMinW;</p><p>    [Header("最小房間高")] public int roomMinH;</p><p>    [Header("房間的間隔距離")] public int distance;</p><p>    [Header("地板")] public TileBase floor;</p><p>    [Header("牆")] public TileBase wall;</p><p>    [Header("地圖")] public Tilemap tilemap;</p><p>    private int[,] _roomMap;</p><p>    private List<Vector3Int> _centerPoint;</p><p>    private Dictionary<Vector3Int,int> _mapPoint;</p><p>}</p>
複製程式碼

4、列出所需函式

  1. <p>//畫出地圖</p><p>private void DrawMap(){}</p><p>//畫出房間</p><p>private void DrawRoom(int roomX,int roomY){}</p><p>//畫出路</p><p>private void DrawRoad(){}</p><p>//畫出地板和牆壁</p><p>private void DrawFloor(){}</p><p>//生成房間 (用二維 int 陣列表示)</p><p>private int[,] RandomRoom(int maxW, int maxH, int minW, int minH, out int width, out int height){}</p><p>//生成一個地圖 (用二維 int 陣列表示)</p><p>private int[,] GetRoomMap(int mapW, int mapH, int roomCount)</p><p>//獲取一個範圍內的隨機奇數</p><p>private int GetOddNumber(int min, int max){}</p><p>//獲取下一個房間的位置</p><p>private Vector2Int GetNextPoint(Vector2Int nowPoint, int maxW, int maxH)</p><p>
  2. </p>
複製程式碼

5 、實現各個函式

程式碼已經放在文末的附件,這裡我講一下我的大體思路以及各個函式的作用。

首先是使用,我在 Update 函式中檢測按下 R 鍵重新生成地圖,生成很簡單隻要呼叫 DrawMap 函式就可以。

  1. <p>private void Update()</p><p>{</p><p>    if (Input.GetKeyDown(KeyCode.R))</p><p>    {</p><p>        DrawMap();</p><p>    }</p><p>}</p>
複製程式碼

在 DrawMap 中 會呼叫 DrawRoad 和 DrawFloor 倆個函式來分別畫出道路和地板、牆壁。

部分函式解析:

  1. <p>//取一定範圍內的一個奇數</p><p>private int GetOddNumber(int min, int max)</p><p>{</p><p>    while (true)</p><p>    {</p><p>        var temp = Random.Range(min, max);</p><p>        if ((temp & 1) != 1) continue;</p><p>        return temp;</p><p>    }</p><p>}</p>
複製程式碼

這個函式主要用來在房間生成的時候取一個隨機的奇數出來,因為我們的房間連線需要一箇中心點所以奇數更方便使用。

  1. <p>//生成一個房間,用二維 int 陣列表示</p><p>private int[,] RandomRoom(int maxW, int maxH, int minW, int minH, out int width, out int height)</p><p>{</p><p>    width = GetOddNumber(minW, maxW);</p><p>    height = GetOddNumber(minH, maxH);   </p><p>    var room = new int[width, height];</p><p>    //方便以後擴充套件使用了二維陣列,這裡為了演示方便對房間內生成其他物體</p><p>    for (var i = 0; i < width; i++)</p><p>    {</p><p>        for (var j = 0; j < height; j++)</p><p>        {</p><p>            room[i, j] = 1;</p><p>        }</p><p>    }</p><p>    return room;</p><p>}</p>
複製程式碼

這個函式主要用來生成一個房間,選用二維int陣列主要是為了擴充套件、儲存時方便。本案例中 普通地板為 1,牆壁為 0。同時為了後續步驟計算方便這裡把生成的房間的長寬也返回了。

  1. <p>private int[,] GetRoomMap(int mapW, int mapH, int roomCount)</p><p>{</p><p>    //第一個房間的座標點</p><p>    var nowPoint = Vector2Int.zero;</p><p>    //當前生成的房間數</p><p>    var mCount = 1;</p><p>    //當前地圖</p><p>    var map = new int[mapW, mapH];</p><p>    //第一個格子總有房間,作為出生房間</p><p>    map[nowPoint.x, nowPoint.y] = 1;</p><p>
  2. </p><p>    while (mCount < roomCount)</p><p>    {</p><p>        nowPoint = GetNextPoint(nowPoint, mapW, mapH);</p><p>        if (map[nowPoint.x, nowPoint.y] == 1) continue;</p><p>        map[nowPoint.x, nowPoint.y] = 1;</p><p>        mCount ++;</p><p>    }</p><p>    return map;</p><p>}</p>
複製程式碼

這個函式用來生成總體的地圖,也就是用來檢視哪裡需要生成房間。需要生成房間的地方為 1 ,空白的地方為 0。與GetNextPoint 連用獲取下一個房間的座標點。因為房間總是相連所以不存在間隔。

  1. <p>private Vector2Int GetNextPoint(Vector2Int nowPoint, int maxW, int maxH)</p><p>{</p><p>    while (true)</p><p>    {</p><p>        var mNowPoint = nowPoint;</p><p>
  2. </p><p>        switch (Random.Range(0, 4))</p><p>        {</p><p>            case 0:</p><p>                mNowPoint.x += 1;</p><p>                break;</p><p>            case 1:</p><p>                mNowPoint.y += 1;</p><p>                break;</p><p>            case 2:</p><p>                mNowPoint.x -= 1;</p><p>                break;</p><p>            default:</p><p>                mNowPoint.y -= 1;</p><p>                break;</p><p>        }</p><p>
  3. </p><p>        if (mNowPoint.x >= 0 && mNowPoint.y >= 0 && mNowPoint.x < maxW && mNowPoint.y < maxH)</p><p>        {</p><p>            return mNowPoint;</p><p>        }</p><p>    }</p><p>}</p>
複製程式碼

這個函式大體的思想還是參照我前倆篇文章中講到的隨機遊走法的思路。

最終效果

1、生成房間

30分鐘簡易復刻《元氣騎士》地圖生成系統

2 、新增橫向過道

30分鐘簡易復刻《元氣騎士》地圖生成系統

3 、新增縱向過道

30分鐘簡易復刻《元氣騎士》地圖生成系統

4 、新增牆壁

30分鐘簡易復刻《元氣騎士》地圖生成系統

作者:超級汽水  
來源:Unity官方平臺
原地址:https://mp.weixin.qq.com/s/OZggLLodTF9cownbt2FzGg


相關文章