
遊戲製作進度文跟下載點,未來都會放到BK-MOD官方網站~
https://www.bk-mod.com/
下一篇:
[遊戲製作進度] [大型專案:BK MOD] ~EP.02~ 自定義遊戲地圖編輯器與2D物理系統架構
https://hcyang1227.blogspot.com/2023/04/bk-mod-ep02-2d.html
上一次寫遊戲製作進度的文章,已經是15年前的事情了。今天會執筆繼續寫相關文章,要感謝Twitch台的觀眾“虫馬蟻”大大的熱情推坑,讓小弟想要像小時候一樣,做一款超屌大型改版遊戲,這個想法又重新燃燒起來。其實小弟不知道能夠持續多久,畢竟上一個坑「凱特與繼承的靈」直接胎死腹中….嗯,這就是人生嘛(´・Å・`)
這回要來介紹的,是如何不用Unity內建的地圖編輯器,自己寫一個客製化的地圖編輯器。當然,這個編輯器只花了小弟兩天撰寫,勢必有很多地方不到位,但是也希望能夠拋磚引玉,讓更多的軟工朋友對這個專案有點興趣(●´ω`●)ゞ
正文:

小弟寫Unity的經驗不多,但還是嘗試將以前在遊戲公司淺學、網路Youtube介紹文章、壓榨ChatGPT的結果做個說明。首先是Hierarchy的配置。除了初始的**Main Camera、Directional Light等等以外,小弟多生了StageBG(準備放一堆C#自動生成的GameObject子物件用)、2DCanva(這次沒用到,理論上是用來放位置絕對不變的東東像是血條)、TmpObj > BGcell**(準備用來複製的物件本人,先設定一開始看不到)。

接著是美工的部分,小弟將15多年前畫的美美的草皮platform拿來用,以60*60為一個sprite,用Sprite Editor做Slice(切割),差不多就完成了~因為還是Prototype,圖檔都沒有一一命名,但時間允許的話,還是命名一下比較妥當(。í _ ì。)

接著,您可以觀賞以下影片的Unity撰寫介紹,會詳細告訴您怎麼做Grid System(方格系統),小弟也是以這個C#寫法當作基礎去修改的。
[1] Youtube - Grid System in Unity (Heatmap, Pathfinding, Building Area)
https://www.youtube.com/watch?v=waEsGu–9P8
接下來廢話不多說,上代碼~
MainStage.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MainStage : MonoBehaviour
{
private GridMap gridMap;
private void Start()
{
gridMap = new GridMap(10, 10, 10f, new Vector3(0, 0));
}
private void Update()
{
//Set current camera position
if (Input.GetKey("w") || Input.GetKey("up"))
Camera.main.transform.Translate(new Vector3(0, 3f, 0));
if (Input.GetKey("s") || Input.GetKey("down"))
Camera.main.transform.Translate(new Vector3(0, -3f, 0));
if (Input.GetKey("a") || Input.GetKey("left"))
Camera.main.transform.Translate(new Vector3(-3f, 0, 0));
if (Input.GetKey("d") || Input.GetKey("right"))
Camera.main.transform.Translate(new Vector3(3f, 0, 0));
//Edit WorldMap Background by Mouse & KeyBoard
Vector3 mousePosition = Input.mousePosition;
mousePosition.z = Camera.main.transform.position.z;
Vector3 worldPosition = Camera.main.ScreenToWorldPoint(mousePosition);
if (Input.GetMouseButtonDown(0))
gridMap.AddValue(worldPosition);
if (Input.GetMouseButtonDown(1))
gridMap.SubstractValue(worldPosition);
if (Input.GetKeyDown("space"))
Debug.Log(gridMap.GetValue(worldPosition));
}
}GridMap.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GridMap
{
//input 4 variables of GridMap class
private int width;
private int height;
private float cellSize;
private Vector3 originPosition;
//Content of the grid map
private int[,] gridArray;
private TextMesh[,] gridTextArray;
private SpriteRenderer[,] gridSpriteArray;
//Load full Sprites for game background
private Sprite[] spriteTemplate = Resources.LoadAll<Sprite>("Platform");
//Get parent GameObject "StageBG"
public GameObject StageBG = GameObject.Find("StageBG");
public GridMap(int width, int height, float cellSize, Vector3 originPosition)
{
this.width = width;
this.height = height;
this.cellSize = cellSize;
this.originPosition = originPosition;
gridArray = new int[width, height];
gridTextArray = new TextMesh[width, height];
gridSpriteArray = new SpriteRenderer[width, height];
for (int x = 0; x < gridArray.GetLength(0); x++)
{
for (int y = 0; y < gridArray.GetLength(1); y++)
{
//Create TextMesh
GameObject gameObject = new GameObject("World_Text " + x + "-" + y, typeof(TextMesh));
Transform transform = gameObject.transform;
transform.SetParent(StageBG.transform, false);
transform.localPosition = GetWorldPosition(x, y) + new Vector3(cellSize, cellSize) * .5f;
TextMesh textMesh = gameObject.GetComponent<TextMesh>();
textMesh.anchor = TextAnchor.MiddleCenter;
textMesh.alignment = TextAlignment.Center;
textMesh.text = gridArray[x, y].ToString();
textMesh.fontSize = 20;
textMesh.color = Color.white;
textMesh.GetComponent<MeshRenderer>().sortingOrder = 1;
gridTextArray[x, y] = textMesh;
//Create SpriteRenderer
GameObject gameObject2 = new GameObject("World_Sprite " + x + "-" + y, typeof(SpriteRenderer));
Transform transform2 = gameObject2.transform;
transform2.SetParent(StageBG.transform, false);
transform2.localPosition = GetWorldPosition(x, y) + new Vector3(cellSize, cellSize) * .5f;
transform2.localScale = new Vector3(10, 10, 10);
SpriteRenderer spriteRenderer = gameObject2.GetComponent<SpriteRenderer>();
spriteRenderer.sprite = spriteTemplate[gridArray[x, y]];
gridSpriteArray[x, y] = spriteRenderer;
}
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
private Vector3 GetWorldPosition(int x, int y)
{
return new Vector3(x, y) * cellSize + originPosition;
}
private void GetXY(Vector3 worldPosition, out int x, out int y)
{
x = Mathf.FloorToInt((worldPosition - originPosition).x / cellSize);
y = Mathf.FloorToInt((worldPosition - originPosition).y / cellSize);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
#region [Value +/-/set/get]
public void AddValue(int x, int y)
{
if (x >= 0 && y >= 0 && x < width && y < height && gridArray[x, y] < spriteTemplate.Length - 1)
{
gridArray[x, y] += 1;
gridTextArray[x, y].text = gridArray[x, y].ToString();
gridSpriteArray[x, y].sprite = spriteTemplate[gridArray[x, y]];
}
}
public void AddValue(Vector3 worldPosition)
{
int x, y;
GetXY(worldPosition, out x, out y);
AddValue(x, y);
}
public void SubstractValue(int x, int y)
{
if (x >= 0 && y >= 0 && x < width && y < height && gridArray[x, y] > 0)
{
gridArray[x, y] -= 1;
gridTextArray[x, y].text = gridArray[x, y].ToString();
gridSpriteArray[x, y].sprite = spriteTemplate[gridArray[x, y]];
}
}
public void SubstractValue(Vector3 worldPosition)
{
int x, y;
GetXY(worldPosition, out x, out y);
SubstractValue(x, y);
}
public void SetValue(int x, int y, int value)
{
if (x >= 0 && y >= 0 && x < width && y < height)
{
gridArray[x, y] = value;
gridTextArray[x, y].text = value.ToString();
gridSpriteArray[x, y].sprite = spriteTemplate[gridArray[x, y]];
}
}
public void SetValue(Vector3 worldPosition, int value)
{
int x, y;
GetXY(worldPosition, out x, out y);
SetValue(x, y, value);
}
public int GetValue(int x, int y)
{
if (x >= 0 && y >= 0 && x < width && y < height)
return gridArray[x, y];
else
return 0;
}
public int GetValue(Vector3 worldPosition)
{
int x, y;
GetXY(worldPosition, out x, out y);
return GetValue(x, y);
}
#endregion
}大致上的程序執行順序是:
- StageBG所附加的元件MainStage.cs會首先被執行,透過private GridMap gridMap生成gridMap這個class,並在Start()時初始化方格地圖。
- GridMap.cs會根據MainStage.cs傳入的Grid長、寬、方格大小、起始位置去跑2D巢狀迴圈。
- 巢狀迴圈最裡面會做兩件事:1.新產生TextMesh(for顯示文字) 2.新產生SpriteRenderer(for顯示圖片),生成的物件全部丟到StageBG底下,未來管理遠近(z軸遠近)就靠StageBG底下的分門別類(也可以是z軸深淺)。
- 新生成的TextMesh物件連結到gridTextArray[x, y],新生成的SpriteRenderer物件連結到gridSpriteArray[x, y],未來在變更圖片跟文字,只要動這兩個array即可。至此完成Start()時初始化事件。
- MainStage.cs接著跑到Update()並且每個frame都會執行一次。
- Update()裡面一開始有個"設定目前Camera位置"的code,這段code會偵測是否持續按下wsad或是上下左右鍵,若是,則根據按鍵移動Camera的(x, y),z則不動。
- Update()接著跑到一段"用滑鼠鍵盤設定遊戲世界地圖"的內容。首先,先抓取滑鼠於螢幕上的位置,接著將此位置的z軸變更為Camera的z軸(為ScreenToWorldPoint鋪路),最後透過ScreenToWorldPoint指令,將滑鼠座標轉換成遊戲世界座標。
- 然後,我們再用遊戲世界座標軟換成Grid System座標,gridMap.AddValue、gridMap.SubstractValue、gridMap.GetValue都寫在GridMap.cs裡面,會轉換成Grid用的x, y座標,並且根據此x, y座標改變gridTextArray[x, y]、gridSpriteArray[x, y]當中的特定物件,也就是最後會顯示的文字與圖片。
結語:
Unity真的是個非常強大的遊戲製作引擎,必備的C#程式語法也不會像C\C++這麼艱澀難啃,非常適合對製作遊戲有熱愛且程設剛初入門的朋友。這麼講感覺小弟的作品已經在收尾了,不,才剛開始而已….(〒︿〒)
相關文章:
[2] [遊戲製作進度] [MMF2] 武器、防具以及飾物裝備
https://hcyang1227.blogspot.com/2006/02/blog-post_27.html
[3] [遊戲製作進度] [MMF2] 拯救貪吃老人大作戰 - 地底的洞窟(一)
https://hcyang1227.blogspot.com/2006/09/blog-post.html
[4] [遊戲製作進度] [MMF2]拯救貪吃老人大作戰 - 目前進度列表