从本篇博客开始用纯Lua脚本写一个小游戏——别撞车。自从网站写完游戏实例飞机大战以后就再没有更新其他游戏demo了,最近三个月以来博客数目也很少,期间也在断断续续的学习Cocos2d-x,可能是最近三个月比较消极吧!刚刚参加工作还比较迷茫,自己还没有做好准备就工作了,哎,好快啊!好吧,牢骚就到这里吧,从本篇博客开始写一个小游戏,主要的目的是练习Lua,我们都知道Cocos将2dx中的类作为模块导出来给我们使用,那么我们就学习一下如何使用这些东西吧,所以以一个小游戏作为演示,把常用的一些小知识点串联起来,每个知识点单独成一篇博客。该系列博客只说Cocos中的东西,Lua的基本语法不探讨,我想过来学习Lua游戏的至少已经掌握了Lua的基本语法吧,本篇博客的内容较为基础,是写给从来没有用Cocos导出的接口写过代码的童鞋,所以大神请跳过。另外有一点需要说明的是,我是一边完成游戏一边写博客的,本来想把重点放在语法上,但是这个游戏貌似逻辑更难,所以博客上更注重了游戏逻辑,不过现在所有的博客都已经完成了,对Lua不了解的可以看看,权当交流吧!游戏最后的apk扫描二维码下载,是一个百度网盘的链接。

Lua小游戏别撞车——搭建开始场景

首先说明我使用的编辑器是Cocos Code IDE,大家可以去官网下载这个软件(我个人使用的时候总体上来说感觉还是不错的),然后参照官方的教程搭建环境,一切准备OK以后,就开始我们的小游戏吧!

首先用Cocos Code IDE新建一个工程,取名为DontCrash,然后删除原工程下的GameScene.lua文件。再新建一个文件,取名为FlashScene,我们要将该文件作为一个场景类,那么如何组织这个模块呢?我们需要明确的是模块的基本定义是在一个Lua文件中写一个表,然后返回这个表,这样就将FlashScene作为一个模块了。

--创建一个类,继承自scene,class返回一张表
local FlashScene = class("FlashScene",function()
    return cc.Scene:create()
end
)

--最后返回这张表
return FlashScene

我们使用class函数返回了一张表,然后把它赋给了变量FlashScene,class函数的第一个参数是类名,第二个参数是继承的父类,以后需要继承父类的时候参照这种写法就OK了,最后比较重要的是要记得返回FlashScene这张表,当其他模块require该模块的时候,我们返回了FlashScene表,这张表中将包含该类用到的所有方法和变量,我们需要调用函数和使用变量的时候从这张表中获取数据就好了。

接下来再看俩个函数,一个是ctor,一个是create。

--类似c++中的构造函数
function FlashScene:ctor()
    --self代表的就是FlashScene,将size作为它的成员,添加到这张表中
    self.size = cc.Director:getInstance():getWinSize()
end

--create留下给外部调用的接口
function FlashScene:create()
    --new函数的作用是调用了构造函数ctor,然后将表FlashScene的所有元素复制到了一张新的表中,最后返回这张表
    local flashScene = FlashScene:new()
    local layer = flashScene:createLayer()
    flashScene:addChild(layer)

    return flashScene
end

ctor函数类似c++中的构造函数,这个在原生Lua中是没有的,是Cocos采用的做法。当FlashScene调用new函数的时候在函数的内部创建了一张新表,然后至少做了三步操作,1、调用class函数传入的第二个参数——一个函数,在这个函数中完成了初始化父类的工作,然后将父类表里边的内容复制到这张新的表中。2、将表FlashScene的所有元素(变量和方法)复制到了新表中,也就是本表中的成员变量复制到了这张新的表中。3、调用ctor函数,在这个函数中我们可以初始化一些成员变量,然后将这些成员变量放到表中,这样这张新创建的表中就有了父类的所有内容,FlashScene所有的内容,以及初始化的内容,create函数在最后返回了这张新表,所以我们外部调用create的时候就相当于创建了一个对象。因此无论我们是否去创建一个create函数,这个new函数是必须要调用的,否则你自己用Lua实现类和继承。在ctor中,调用cocos提供出来的模块cc,获得了窗口的大小,赋值给了FlashScene中的成员size。在create函数中flashScene还调用了createLayer函数,该函数的实现如下,最后返回一个layer。

--创建一个层,在层上加入元素
function FlashScene:createLayer()
    local layer = cc.Layer:create()

    --动作的回调函数
    local action_callback = function()
        cc.Director:getInstance():replaceScene(require("MainGameScene"):create())
    end

    --使用Cocos提供的模块cc,调用Sprite的方法创建一个logo出来,你在Cocos2d-x看到的内容,都可以从这个模块中获得(前提是有导出)
    local logo = cc.Sprite:create("logo.png")
    logo:setPosition(self.size.width/2,self.size.height/2)
    layer:addChild(logo)

    --logo执行一个淡入淡出的动作
    logo:setOpacity(0)
    local action = cc.Sequence:create(cc.FadeIn:create(2),cc.CallFunc:create(action_callback))
    logo:runAction(action)

    --游戏标题
    local title = cc.Sprite:create("title.png")
    title:setPosition(self.size.width+title:getContentSize().width/2,self.size.height*0.88)
    layer:addChild(title)

    --title执行动作
    --moveto的第二个参数是一个table,里边的成员是命名参数,必须指定参数的名字为x和y,许多的函数调用都需要这么做
    local title_action = cc.Sequence:create(cc.MoveTo:create(0.5,{x=self.size.width/2-100,y=title:getPositionY()}),
         cc.Spawn:create(cc.MoveBy:create(0.1,{x=200,y=0}),cc.SkewTo:create(0.1,0,-45)),
         cc.Spawn:create(cc.MoveBy:create(0.2,{x=-150,y=0}),cc.SkewTo:create(0.2,0,45)),
         cc.Spawn:create(cc.MoveBy:create(0.2,{x=50,y=0}),cc.SkewTo:create(0.2,0,0))
         )
    title:runAction(title_action)     

    return layer
end

这个函数中涉及的多数知识都是Cocos中的基本内容,比如精灵,动作等等这些东西,相信会Cocos的朋友理解起来不难。我需要说明的是如何使用Cocos提供给我们的接口。我们需要使用模块cc,这个模块通过lua-binding已经放到了Lua的全局表中,在该模块中注册了Cocos给我们导出的所有接口,所以我们要使用这些接口,这些类的时候就需要用cc.的形势,然后其他场景、层、精灵等等的思想就和Cocos中的完全一样了。函数的调用使用冒号的形式,变量的获得(因为是从表中获取)使用.的形式。例如cc.Sprite:create()这个调用,Sprite是模块cc中的变量,所以使用.的形式,create函数是Sprite中的成员,使用:将自己作为一个参数传入到了create函数中,相当于是Sprite.create(Sprite),Cocos提供出来的函数接口我们需要用:的形式访问函数。

这样一个基本的模块就写好了,在Lua中写游戏的时候就是这样一个模块一个模块来写的,每一个模块都是一个场景,而模块的实质就是一张表,把函数和变量放到这个表里边,没有放入到表里边的内容如果是局部变量外部是访问不到的,如果是全局变量外部同样可以访问。以上的内容是一个模块最基本的内容,为了实现其他的功能,我们需要在这个的基础上再添加其他的内容。我将这个基本的结构列举如下,大家在写其他模块的时候参照这个基本的结构来写。

--使用require引入其他模块
require("Cocos2d")
require("Cocos2dConstants")

--创建一个类,继承自scene
local FlashScene = class("FlashScene",function()
    return cc.Scene:create()
end
)

--类似c++中的构造函数
function FlashScene:ctor()
    --self代表的就是FlashScene,将size作为它的成员,添加到这张表中
    self.size = cc.Director:getInstance():getWinSize()
end

--create留下给外部调用的接口
function FlashScene:create()
    --new函数的作用是调用了构造函数ctor,然后将表FlashScene的所有元素复制到了一张新的表中,最后返回这张表
    local flashScene = FlashScene:new()

    return flashScene
end

--其他的一些函数(包括局部函数)以及变量...

--返回FlashScene这个模块
return FlashScene

Lua小游戏别撞车——搭建开始场景

刚刚我们定义了一个模块FlashScene,现在我们在main.lua中使用一下这个模块。我们使用require引入模块FlashScene,require函数会去查找对应的.lua文件或者是库文件,查找的路径放到了变量package.path(查找Lua文件)和package.cpath(查找dll或者是so文件)中。

--使用模块FlashScene
    local scene = require("FlashScene")
    --使用模块中的接口函数create,创建一个场景
    local gameScene = scene.create()

    --判断当前是否存在正在运行的场景,如果存在切换场景,不存在的话就使用runWithScene运行场景
    if cc.Director:getInstance():getRunningScene() then
        cc.Director:getInstance():replaceScene(gameScene)
    else
        cc.Director:getInstance():runWithScene(gameScene)
    end

最后我们在main.lua中添加一下适配的代码,整个main.lua完整的代码如下。

require "Cocos2d"

-- cclog
local cclog = function(...)
    print(string.format(...))
end

-- for CCLuaEngine traceback
function __G__TRACKBACK__(msg)
    cclog("----------------------------------------")
    cclog("LUA ERROR: " .. tostring(msg) .. "\n")
    cclog(debug.traceback())
    cclog("----------------------------------------")
    return msg
end

local function main()
    collectgarbage("collect")
    -- avoid memory leak
    collectgarbage("setpause", 100)
    collectgarbage("setstepmul", 5000)

    cc.FileUtils:getInstance():addSearchPath("src")
    cc.FileUtils:getInstance():addSearchPath("res")
    cc.Director:getInstance():getOpenGLView():setDesignResolutionSize(480, 320, 0)

    --我采用如下的适配方法
    local pEGLView = cc.Director:getInstance():getOpenGLView()
    local frameSize = pEGLView:getFrameSize()
    --这里的尺寸写背景图片的大小
    local winSize = {width=1280,height=720}

    local widthRate = frameSize.width/winSize.width
    local heightRate = frameSize.height/winSize.height

    if widthRate > heightRate then
        pEGLView:setDesignResolutionSize(winSize.width,
            winSize.height*heightRate/widthRate, 1)
    else
        pEGLView:setDesignResolutionSize(winSize.width*widthRate/heightRate, winSize.height,
            1)
    end

    --使用模块FlashScene
    local scene = require("FlashScene")
    --使用模块中的接口函数create,创建一个场景
    local gameScene = scene.create()

    --判断当前是否存在正在运行的场景,如果存在切换场景,不存在的话就使用runWithScene运行场景
    if cc.Director:getInstance():getRunningScene() then
        cc.Director:getInstance():replaceScene(gameScene)
    else
        cc.Director:getInstance():runWithScene(gameScene)
    end

end

local status, msg = xpcall(main, __G__TRACKBACK__)
if not status then
    error(msg)
end