Unity xLua学习之开发消消乐小游戏

1 篇文章 0 订阅

一、前言

这两天用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

七、源码下载

xlua消消乐源码下载

  • 5
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在五花八门的游戏分类中,益智游戏是不可或缺的一大板块;而在益智游戏这个分类中,消除合成类游戏又是当之无愧的No.1。从考古级别的《俄罗斯方块》到现在风靡全国的《开心消消乐》,消除合成类游戏经久不衰,并且不断地涌现出新的经典作品,带给我们新的启发和乐趣。作为Unity3D的初级开发者,从头到尾独立完成一款简单的2D棋盘消除合成小游戏是你初级实战的最好选择。既能全面而详细的检验你的基础框架能力、C#编程能力、UGUI适配技巧,又可以在此基础上,较为深入的学习协程方法、DoTween插件和消除合成类的核心算法,一举数得。一言以蔽之,消除合成类游戏不难,但要写好却并不容易。它琐碎的功能逻辑、层出不穷的延时操作以及复杂的状态控制很容易让人顾此失彼,手忙脚乱,最后的结果往往是,费劲九牛二虎之力把功能实现好了,但代码已经乱成一团糟,定位Bug难、扩展新功能难,甚至自己读起来也难。本课程的最大目的,就是帮初级开发者梳理逻辑,写出规范而有条理的代码,避免陷入以上尴尬境地。条理捋清楚了,你就会觉得一切似乎变得简单了,很多地方都会顺理成章。本课程没有什么高深的编程技巧,更没有炫酷的视觉效果,只是一步一步教你如何踏踏实实、稳稳当当的完成一款《简单消消乐》。因此,资深大牛你就别进了,想学高深知识的你就别点了,自认为消除小case、分分钟搞定的高手你也趁早走人。在这门课程里,你能够学到的,只有以下几点:1、如何使用UGUI搭建棋盘游戏的基础阵列;2、如何快速的实现通用的三消逻辑;3、如何使用Dotween插件实现物体的位移、缩放、旋转动画;4、如何熟练的使用协程方法完成延时操作逻辑;5、数组、集合、引用类型、Prefab等基础知识的巩固;6、如何写出规范而有条理的代码;7、益智小游戏的通用模板。
Unity XLua 商业级游戏源码是指使用Unity引擎和XLua语言进行开发的高质量、专业水准的游戏源代码。 首先,Unity是世界上最流行和强大的游戏引擎之一,广泛用于创建各种类型的游戏,包括PC、手机、虚拟现实和增强现实游戏。它提供了丰富的工具和功能,使开发者可以轻松创建出错综复杂和令人印象深刻的游戏。Unity的跨平台特性也使得游戏可以在不同的设备上运行。 而XLua是一种基于Lua语言的脚本编程框架,它可以与Unity引擎完美集成,为开发者提供了更灵活、高效的游戏开发方式。XLua具有良好的性能和扩展性,可以轻松地将Lua脚本与C#代码结合起来,发挥出各自的优势。 商业级游戏源码意味着这些源代码是由具有丰富游戏开发经验的专业开发团队编写的。他们经过精心设计和优化,代码结构清晰,易于理解和维护。商业级游戏源码通常包含了各种常见的游戏开发功能,例如角色控制、物体碰撞、动画系统、UI界面、游戏关卡设计等。这些源码可以作为学习游戏开发的参考,也可以用于商业游戏项目快速搭建和开发。 总结来说,Unity XLua 商业级游戏源码是一种高质量和专业水准的游戏源代码,它利用Unity引擎和XLua语言的强大功能,为游戏开发者提供了高效、灵活的开发方式,可以用于学习游戏开发和商业游戏项目的开发

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值