Unity xLua學習之開發消消樂小遊戲
阿新 • • 發佈:2019-02-15
一、前言
這兩天用xLua開發了一個消消樂小遊戲,在此記錄並總結一下開發過程中遇到的問題和體會。
二、效果圖
三、實現功能
- 消除同行或同列存在連續三個及以上相同的方塊
- 射線檢測玩家點選到了哪個方塊
- 玩家交換方塊後,如果存在1中的條件,則進行消除,否則返回原始位置
四、實現過程
1、匯入xlua外掛
2、新建C#指令碼GlopsByLua,用於載入Lua指令碼Main.lua.txt(參考Xlua的Example中的LuaBehaviour.cs)
public class GlopsByLua : MonoBehaviour
{
public static GlopsByLua Instance { get; private set; }
private Action luaStart;
private Action luaUpdate;
private Action luaOnDestroy;
public LuaEnv luaEnv;
private LuaTable scriptEnv;
private void Awake()
{
Instance = this;
luaEnv = new LuaEnv();
scriptEnv = luaEnv.NewTable();
LuaTable meta = luaEnv.NewTable();
meta.Set("__index" , luaEnv.Global);
scriptEnv.SetMetaTable(meta);
meta.Dispose();
scriptEnv.Set("self", this);
luaEnv.DoString("require('Main')", "GlopsByLua", scriptEnv);
Action luaAwake = scriptEnv.Get<Action>("awake");
scriptEnv.Get("start", out luaStart);
scriptEnv.Get("update" , out luaUpdate);
scriptEnv.Get("ondestroy", out luaOnDestroy);
if (luaAwake != null) luaAwake();
}
private void Start()
{
if (luaStart != null) luaStart();
}
private void Update()
{
if (luaUpdate != null) luaUpdate();
if(luaEnv != null) luaEnv.Tick();
}
private void OnDestory()
{
if (luaOnDestroy != null) luaOnDestroy();
scriptEnv.Dispose();
luaEnv.Dispose();
}
}
3、Main.lua.txt再載入 消消樂的主邏輯程式碼 Example.lua.txt
require('Example')
function start()
print("Main start")
Example.Start()
end
function update()
--print("Main update")
Example.Update()
end
function ondestory()
print("Main ondestory")
end
4、Example.lua.txt檔案中編寫消消樂的邏輯程式碼
Example = {}
RowCount = 9;
--判定時:移動一格的時間
MoveSinglePositionTime = 0.1;
--交換時,移動的時間
SwitchTime = 0.5;
--棋子的預製體
mCubePrefabs ={};
--全部的棋子
mCubes={}
--陣列的索引表示列數, list表示
mMarkChessList = {}
--交換時第一次點選
mGoFirstClickChess = nil;
--交換時的第二次點選
mGoSecondClickChess = nil;
--現在是否有標記過的棋子
mIsMarkedChess = false;
--背景
mGoBackground = nil;
--開始移動時間
mStartMoveTime = 0;
--待移動的被標記的棋子
mMoveMarkChesses = {};
--待移動的非標記的棋子
mMoveNoMarkChesses = {};
--待移動的兩個交換的棋子
mMoveSwitchChesses = {};
function Example:Start()
mGoBackground = CS.UnityEngine.GameObject.Find("Plane")
--載入棋子預製體
Example:LoadResource()
--建立棋子
Example:CreateChesses()
end
function Example:LoadResource()
for index = 1,8 do
--獲取全部的預製體
mCubePrefabs[index] = CS.UnityEngine.Resources.Load("cube"..index)
end
end
function Example:CreateChesses()
for i = 0, 8 do
for j = 0, 8 do
--獲取隨機數,由於會獲取到浮點型的,所以利用上取整轉化為整型
local range =math.ceil(CS.UnityEngine.Random.Range(0, 8));
--獲取cube的預製體
local cubePrefabs = mCubePrefabs[range];
--例項化預製體
local cubeItem = CS.UnityEngine.GameObject.Instantiate(cubePrefabs)
--設定位置
cubeItem.transform.position = CS.UnityEngine.Vector3(i, j, 0);
--設定名字
cubeItem.name = range;
--設定父物體
cubeItem.transform.parent = mGoBackground.transform;
--建立二維陣列
if(mCubes[i] == nil) then
mCubes[i] = {}
end
--將棋子放二維陣列中,集中管理
mCubes[i][j] = cubeItem;
end
end
end
--檢測是否同行或同列有連續三個及以上的相同棋子
function Example:CheckAndRefresh()
Example:CheckAll()
Example:Refresh()
end
function Example:InitMarkChesses()
mIsMarkedChess = false;
for i = 0, 8 do
mMarkChessList[i]={}
end
mMoveMarkChesses = {};
mMoveNoMarkChesses = {};
end
function Example:CheckAll()
--清除之前標記的棋子
Example:InitMarkChesses()
--橫向掃描
for i = 0, 6 do
for j = 0, 8 do
if ((mCubes[i][j].name == mCubes[i + 1][j].name) and (mCubes[i][j].name == mCubes[i + 2][j].name))
then
--橫著相同,標記這三個
Example:MarkChess(i, j);
Example:MarkChess(i + 1, j);
Example:MarkChess(i + 2, j);
end
end
end
--縱向掃描
for i = 0, 8 do
for j = 0, 6 do
if ((mCubes[i][j].name == mCubes[i][j+ 1].name) and (mCubes[i][j].name == mCubes[i][j+ 2].name))
then
--豎著相同,標記這三個
Example:MarkChess(i, j);
Example:MarkChess(i, j + 1);
Example:MarkChess(i, j + 2);
end
end
end
end
function Example:MarkChess(x, y)
local markChessCount = #mMarkChessList[x] + 1;
for index = 1, markChessCount do
if(mMarkChessList[x][index] == mCubes[x][y]) then
--該棋子已被標記,直接退出
return;
end
end
if(mMarkChessList[x][markChessCount] == nil) then
mMarkChessList[x][markChessCount] = mCubes[x][y]
mIsMarkedChess = true;
end
end
function Example:Refresh()
for col = 0, 8 do
--獲取當前列的 被標記的棋子的數量
curColMarkChessesCount = #mMarkChessList[col]
if(curColMarkChessesCount > 0) then
--標記的棋子被清除後,需要將被標記棋子的上方的非標記棋子移動到下方,進行填充
for row = 0, 8 do
--非標記的棋子要下降的高度
local notMarkDescend = 0
for k = 1, curColMarkChessesCount do
--說明當前棋子是 已被標記的棋子, 我們目前是調整非標記棋子的位置,所以直接退出當前迴圈
if(mCubes[col][row] == mMarkChessList[col][k]) then
notMarkDescend = 0;
break;
end
if(mCubes[col][row].transform.position.y > mMarkChessList[col][k].transform.position.y) then
notMarkDescend = notMarkDescend + 1;
end
end
if(notMarkDescend > 0) then
mCubes[col][row - notMarkDescend] = mCubes[col][row];
--移動棋子
local startPos = mCubes[col][row - notMarkDescend].transform.position;
local endPos = CS.UnityEngine.Vector3(col, row-notMarkDescend, 0);
local time = notMarkDescend * MoveSinglePositionTime + 0.3
mMoveNoMarkChesses[#mMoveNoMarkChesses + 1] = {mCubes[col][row - notMarkDescend], startPos, endPos, time}
end
end
--將被標記的棋子,全部移至頂部
for i = 1, curColMarkChessesCount do
markIndex = 0;
for j = 1, curColMarkChessesCount do
--按照移動前的高低放置
if(mMarkChessList[col][i].transform.position.y < mMarkChessList[col][j].transform.position.y) then
markIndex = markIndex + 1;
end
end
mCubes[col][8 - markIndex] = mMarkChessList[col][i];
end
--curColMarkChessesCount大於0,當前位於頂部的棋子,都是被標記的棋子了
for row = (9 - curColMarkChessesCount), 8 do
local moveDistance = row + RowCount;
if( row + moveDistance > 12) then
moveDistance = 12 - row;
end
--重置棋子,更換棋子游戲物體名稱、位置、材質
mCubes[col][row].transform.position = CS.UnityEngine.Vector3(col, row + moveDistance, 0);
local random = math.ceil(CS.UnityEngine.Random.Range(0, 8));
mCubes[col][row]:GetComponent(typeof(CS.UnityEngine.MeshRenderer)).material = mCubePrefabs[random]:GetComponent(typeof(CS.UnityEngine.MeshRenderer)).sharedMaterial;
mCubes[col][row].name = ""..random;
local startPos = mCubes[col][row].transform.position;
local endPos = CS.UnityEngine.Vector3(col, row, 0);
mMoveMarkChesses[#mMoveMarkChesses + 1] ={mCubes[col][row], startPos, endPos, SwitchTime};
end
end
end
if ((#mMoveMarkChesses > 0) or (#mMoveNoMarkChesses > 0)) then
mStartMoveTime = CS.UnityEngine.Time.time;
end
end
--[[
傳入引數:一個待移動的二維陣列,陣列單項引數如下
index 1 : gameObject
index 2 : 開始移動的位置
index 3 : 目標位置
index 4 : 所費時間
]]--
function Example:MoveTo(chesses)
local chessCount = #chesses;
if(chessCount <= 0)then
return false;
end
local finishCount = 0;
for index = 1, chessCount do
local goTarget = chesses[index][1];
local startPos = chesses[index][2];
local endPos = chesses[index][3];
local time = chesses[index][4];
local position = CS.UnityEngine.Vector3.Lerp(startPos, endPos, (CS.UnityEngine.Time.time - mStartMoveTime) / time);
goTarget.transform.position = position;
if(CS.UnityEngine.Vector3.Distance(endPos, position) == 0) then
finishCount = finishCount + 1;
end
end
return finishCount == chessCount;
end
function Example:Update()
if(CS.UnityEngine.Time.frameCount == 60) then
Example:CheckAndRefresh()
end
--Move 先讓非標記棋子移動, 再讓標記棋子移動,最後如果有棋子交換,則讓棋子交換
if(#mMoveNoMarkChesses > 0) then
if(Example:MoveTo(mMoveNoMarkChesses)) then
mMoveNoMarkChesses = {};
mStartMoveTime = CS.UnityEngine.Time.time;
end
elseif(#mMoveMarkChesses > 0) then
if(Example:MoveTo(mMoveMarkChesses)) then
mMoveMarkChesses = {};
--被銷燬的棋子,下降完畢後再重新整理一次
Example:CheckAndRefresh();
mStartMoveTime = CS.UnityEngine.Time.time;
end
elseif(#mMoveSwitchChesses > 0) then --棋子交換
if(Example:MoveTo(mMoveSwitchChesses)) then
mMoveSwitchChesses = {};
--交換過後,檢查是否有棋子被標記
Example.CheckAll();
if(mIsMarkedChess) then
--交換後,有棋子被標記
Example.Refresh();
else
--如果沒有標記棋子,那麼就讓棋子返回交換前的位置
Example:SwtichChess();
end
Example:CancelSeleted();
end
else --沒有棋子移動,判斷是否有使用者輸入
if(CS.UnityEngine.Input.GetMouseButtonDown(0)) then
Example:RaycastChess();
end
end
end
function Example:RaycastChess()
local ray = CS.UnityEngine.Camera.main:ScreenPointToRay(CS.UnityEngine.Input.mousePosition)
if(CS.UnityEngine.Physics.Raycast(ray) == false) then return; end
local hit = CS.UnityEngine.Physics.RaycastAll(ray)[0];
if(hit.transform.parent == mGoBackground.transform) then
--獲取到了被點選的棋子了
if(Example:IsSwitchable(hit.transform.gameObject)) then
Example:SwtichChess()
end
else
--取消選中
Example:CancelSeleted();
end
end
--取消交換
function Example:CancelSeleted()
if(mGoFirstClickChess == nil) then
return;
end
mGoFirstClickChess:GetComponent(typeof(CS.UnityEngine.MeshRenderer)).material.color = CS.UnityEngine.Color.white;
mGoFirstClickChess = nil;
mGoSecondClickChess = nil;
end
--檢測能否交換(基本檢查:連續兩次點選是否為同種型別、距離不為1)
function Example:IsSwitchable(goHit)
if(mGoFirstClickChess == nil) then
--第一次點選,選中棋子
mGoFirstClickChess = goHit;
mGoFirstClickChess:GetComponent(typeof(CS.UnityEngine.MeshRenderer)).material.color = CS.UnityEngine.Color.red;
--直接退出,等待第二次點選
return false;
end
--點選同種型別的棋子
if(mGoFirstClickChess.name == goHit.name) then
Example:CancelSeleted();
return false;
end
--超過距離範圍, 取1.1和0.9 是防止Unity在移動時的bug(可能會具體座標不是整數)
local distance = CS.UnityEngine.Vector3.Distance(goHit.transform.position, mGoFirstClickChess.transform.position);
if((distance > 1.1) or (distance < 0.9)) then
Example:CancelSeleted();
return false
end
mGoSecondClickChess = goHit;
return true;
end
function Example:SwtichChess()
--這裡需要判空,因為在退回交換前的位置時,退回完畢後,還會呼叫此方法,此時,mGoFirstClickChess與mGoSecondClickChess已經被取消選中而置空了
if ((mGoFirstClickChess == nil) and (mGoSecondClickChess == nil)) then
return
end
local posFirst = mGoFirstClickChess.transform.position;
local posSecond = mGoSecondClickChess.transform.position;
mCubes[Round(posFirst.x)][Round(posFirst.y)] = mGoSecondClickChess;
mCubes[Round(posSecond.x)][Round(posSecond.y)] = mGoFirstClickChess;
mStartMoveTime = CS.UnityEngine.Time.time;
mMoveSwitchChesses[1] = {mGoFirstClickChess, posFirst, posSecond, SwitchTime}
mMoveSwitchChesses[2] = {mGoSecondClickChess, posSecond, posFirst, SwitchTime}
end
--四捨五入
function Round(value)
if(math.ceil(value) <= value + 0.5) then
return math.ceil(value)
else
return math.floor(value)
end
end
五、遇到的問題及解決辦法
問題1:射線檢測問題
在C#中 直接用個RaycastHit接收檢測到的物體,但是在lua中沒法用out關鍵字,因為xlua中對於out的解決辦法是通過作為第二返回值(如果函式原本就有返回值的話)的方式,而Physics.Raycast(ray)恰好又是另一個過載函式,所以 Physics.Raycast(ray, out hit)就沒法用了。
解決辦法:通過Physics.RaycastAll(ray)獲取到所有被檢測的物體
六、小技巧(可能也不是)
在使用xlua開發這個消消樂遊戲之後,知道了下面幾個小技巧
- 通過#table + 1模擬一個List的add方法
if( table[#table + 1] == nil ) then
table[#table + 1] = value
end
- 由於xlua只能支援lua本身的所有型別的過載,而C#中的int和float,對lua而言都是Number型別
比如獲取UnityEngine的隨機數
range = math.ceil(CS.UnityEngine.Random.Range(0, 8));
- lua中的math不支援四捨五入,自己寫了個這樣的方法
--四捨五入
function Round(value)
if(math.ceil(value) <= value + 0.5) then
return math.ceil(value)
else
return math.floor(value)
end
end