Quick-Cocos2d-x,Cocos

Quick-Cocos2d-x初学者游戏教程4

前面我们已经大概的讲解完了Quick的框架和代码结构,接下来,本章开始我们将正式进入到游戏的开发。当然在开发的过程中,如果遇到值得一提的知识点和概念,我们还是会为大家详细讲解的。

哈哈,这章的内容我加它为——开发初探,因为我们将先来实现一些基础的内容。本章将实现的效果如下图所示:

mainscene

菜单场景

从前面章节讲解的的知识点中,我们知道每个新建的 Quick 项目里都已经默认创建好了一个 mainScene 场景,所以下面我们将利用这个现成的场景,把它改造成我们需要的游戏场景。

我们的游戏将是一个简单的游戏,所以不需要太复杂。理所当然的,第一个运行的游戏场景一般都是菜单场景,所以本小节我们的目的就是构建菜单场景,在这个场景中,我们会添加必要的节点和控件。

下面乱入一点基础知识,已经了解 Cocos2d-x 基础概念的童鞋可绕行,不用听我的废话。但如果你对 Cocos2d-x 一窍不通,并且想更加深入地了解这些概念的童鞋可参考《Cocos2d-x 中的基础概念》一文。这里我们也只简单说说:
Cocos2d-x ( Quick 同样)是一款基于节点树渲染的游戏引擎,它把游戏的各个部分抽象成了导演、节点、场景、层、精灵等等一系列的概念,所以你可以把 Cocos2d-x 游戏想象成一部电影。同电影一样,Cocos2d-x 游戏中每个时刻都有一个场景在独立运行,我们通过切换不同的场景可以完成整个游戏流程,场景切换的管理由导演来执行。
一个游戏可有多个不同的游戏场景,每个场景又可包含多个不同的层(或其他节点),每层可拥有任意的游戏节点(常见的是精灵,但也可以是层、菜单、文本等等)。其基本构架如下图所示:
frame
现在还不是很理解也没有关系,因为新手都是在实际运用的过程中慢慢理解,慢慢积累出经验的。

菜单场景对于我们来说就只是一个简单的游戏场景而已,下面开始我们就来在菜单场景中挨个添加游戏对象吧!

添加背景精灵

首先,我们来给游戏添加一张背景图,背景资源大小如第二章分辨率适配时决定的一样,为 1136 x 640 的大小。

添加游戏资源时,我们必须把包括背景在内的所有图片资源都拷贝到 res 目录下(放到别处将无法被引用)。当然,为了和可能有的字体文件、音效文件等等游戏资源区分开来,你也可以像我一样,先在 res 目录下新建一个叫做 image 的文件夹,然后再把资源拷贝到这个 res/image 目录下。

拷贝资源过后,我们就可以修改代码来添加背景图片了。所以,接下来请打开 MainScene.lua 文件,在它的ctor方法中删除创建 “Hello World” 文本的那段多余代码,并添加如下的一段新代码来添加背景图片:

display.newSprite("image/main.jpg")
:pos(display.cx, display.cy)
:addTo(self)

在以上的代码中,我们使用 display.newSprite() 方法创建了一个精灵对象,并把该对象添加到了 MainScene 场景中相应的位置上。

在此之后,保存好修改过的 lua 文件,然后切换到我们项目的 player 模拟器下,按住热键 command + R ( Windows 下按 F5 )就可以刷新 player 模拟器了,此时你就可以立即看到添加了背景图片的游戏场景了。如下图所示:

bg

验证分辨率适配结果

需要注意的是,在 player 模拟器的 View 菜单下,为我们提供了数十种常见的设备分辨率,我们可以任意的切换到各种不同的分辨率上,以便我们测试游戏在不同分辨率上的显示效果。

view

大家可以试着选择一些,不过你会发现,不管我们再怎么选择,我们的屏幕始终是被背景图片铺满的,不会留有任何的黑边。这也就验证了我们之前的分辨率适配是成功的。

fblsp

添加标题并让它上下运动

接下来我们来添加游戏的标题,这个标题会如开篇效果图中显示的一样——会一直不停的上下晃动。
要添加这样的一个效果,我们同样需要在 ctor 方法中添加如下的一段代码:

local title = display.newSprite("img/title.png")
:pos(display.cx / 2 * 3, display.cy)
:addTo(self)

local move1 = cc.MoveBy:create(0.5, cc.p(0, 10))
local move2 = cc.MoveBy:create(0.5, cc.p(0, -10))
local SequenceAction = cc.Sequence:create( move1, move2 )
transition.execute(title, cc.RepeatForever:create( SequenceAction ))

在这里,我们先添加了一个精灵图片到场景上,位置在(display.cx / 2 * 3, display.cy)的坐标处。紧接着,我们让标题精灵执行了一系列的动作效果。
其中 transition.execute(target, action, args) 方法用于执行一个动作效果,它可以为原本单一的动作添加各种附加特性。其参数分别代表:

  • target: 显示对象(cc.Node)
  • action: 动作对象
  • args: table 参数表格对象

在我们的代码中,action 参数是我们自己构建的一个复合动作,它是一个永远都保持上下移动的动作。其中我们使用了3种类型的action来创建它:

  • cc.MoveBy:该动作将使节点从当前坐标点匀速直线运动到相对偏移了一定向量的位置上。其create函数的两个参数分别表示运动到指定位置所需的时间和移动的距离(偏移量),所以 move1 表示在0.5秒内向Y轴的正轴上移动10个像素,move2 表示在0.5秒内向Y轴的负方向上移动10个像素。
  • cc.Sequence: 该动作允许我们把一系列动作组合起来,并按顺序执行它们。在上面的例子中,我们创建了一个顺序执行 move1、move2的动作,这个 SequenceAction 动作会首先执行 move1,等 move1 完成后,再马上执行 move2。 这样一来一回,执行该动作的节点最终会回到原来的位置上。
  • cc.RepeatForever: 该动作是一个无限重复执行的动作。cc.RepeatForever:create( SequenceAction )表示创建了一个无限循环执行 SequenceAction 的动作。

保存文件后刷新 player,你将看到一个一直不停运动的标题栏:

title

添加按钮

下面我们继续添加按钮,其代码如下所示:

cc.ui.UIPushButton.new({ normal = "img/start1.png", pressed = "img/start2.png" })
:onButtonClicked(function()
print("start")
end)
:pos( display.cx / 2, display.cy )
:addTo(self)

在Quick中有三种不同的Button控件,分别是:UIPushButton (按钮控件)、UICheckBoxButton ( CheckButton 控件)和 UICheckBoxButtonGroup ( CheckButton 组控件)。

其中 UIPushButton 是最常用的按钮控件,它继承自UIButton,我们可以通过 cc.ui.UIPushButton.new(images, options) 方法来创建 UIPushButton。参数 images 是 table 类型,它代表各个按钮状态(正常、按下、禁用)下的图片;options 为可选参数,也是 tabl e类型,包含了是否scale9缩放,偏移flipX、flipY值等设置。

onButtonClicked 方法用于监听按钮的点击事件,当点击按钮时,将调用该方法中的代码。如上例中,当我们点击按钮时,会在控制台窗口中打印“start”的字段。同 onButtonClicked 方法类似的还有:

  • onButtonPressed(callback):用于监听按钮的按下事件
  • onButtonRelease(callback):用于监听按钮的释放事件
  • onButtonStateChanged(callback):用于监听按钮的状态改变事件

刷新 player,其效果就会出来。

btn

好了,到现在为止,本章我们的目标就算实现了,下章我们将新建其他游戏场景,敬请期待!!
转载请注明出自:http://shannn.com/archives/392

Quick-Cocos2d-x初学者游戏教程3

本章我们继续解析新建项目的代码文件,衔接不上的童鞋可以先回顾下上章的内容。那么下面我们就言简意赅直接进入正题吧!

2、main.lua

在src目录下,除了 config.lua 文件外,还有一个 main.lua 文件,这个 main.lua 是 Quick 项目的通用入口文件,它类似于 Cocos2d-x 中的 AppDelegate.h/cpp 文件,同时也类似于一般 Windows 工程中的 main 文件。

打开 main.lua 文件,其内容如下所示:

function __G__TRACKBACK__(errorMessage)
print("----------------------------------------")
print("LUA ERROR: " .. tostring(errorMessage) .. "\n")
print(debug.traceback("", 2))
print("----------------------------------------")
end

package.path = package.path .. ";src/"
cc.FileUtils:getInstance():setPopupNotify(false)
require("app.MyApp").new():run()

每个新项目的 main.lua 文件内容都是一样的,一般情况下,我们不需要改动它。
我们只需要知道,main 文件的最后一行代码中通过载入的 app.MyApp 模块创建了一个 MyApp 实例,并且还调用执行了该 MyApp 实例的 run 方法。这行代码将启动并执行 MyApp 脚本。从此处我们也可以看出,main.lua 文件是应用程序 lua 脚本的启动文件。

require 方法表示引入一个文件,使用 require 方法加载文件的过程会检查文件的 lua 语法,同时会完成被加载文件内部变量的初始化。

3、MyApp.lua

main.lua 文件启动执行 MyApp 脚本后,接下来我们就来看看“src/app”目录下的 MyApp.lua 文件,该文件将启动游戏场景,其代码段如下所示:

-- 1
require("config")
require("cocos.init")
require("framework.init")

-- 2
local MyApp = class("MyApp", cc.mvc.AppBase)

-- 3
function MyApp:ctor()
MyApp.super.ctor(self)
end

-- 4
function MyApp:run()
cc.FileUtils:getInstance():addSearchPath("res/")
cc.Director:getInstance():setContentScaleFactor( 640/CONFIG_SCREEN_HEIGHT )
self:enterScene("MainScene")
end

return MyApp

我们依次来看以上各代码行的含义:

  1. 使用 require 方法载入配置、cocos 以及 Quick 框架。这些是每个 Quick 程序都必须要加载的。
  2. 使用 class 函数定义一个 MyApp 类。
    class 方法本身是 Quick 框架中定义的一个用于创建自定义 lua 类的函数,该函数可在 Quick 引擎的 “quick/framework/functions.lua” 文件中找到。当然,如果你听了我的话,在 Sublime 下安装了 QuickDev 插件,那么你也可以在 class 字符上单击右键,跳转到它的定义(Goto Definition),这样也能找到它。
    gotodefinition
    在 “functions.lua” 中,function class(classname, super)函数有两个参数,其中 classname 表示的是类名,super 表示的是父类或者创建对象实例的函数。当 super 传入的参数是前者时,将会以传入的对象为父类派生新类,如本处;当传入的参数为后者时,会以传入的函数作为构造函数创建新类,如稍后要讲解的 MainScene 类。
    从本段代码中我们可以看到,MyApp 这个类是从 cc.mvc.AppBase 中继承而来的,而 AppBase 是 Quick 中自带的一个 MVC(面向对象的一种设计模式)的应用程序基础类,这个类为我们的应用程序提供了一些逻辑控制上的功能,比如进入场景,切换场景等等。
  3. MyApp 的ctor()函数相当于 C++ 中的构造(constructor)函数,以及init()函数。所以我们一般会在ctor()函数中初始化我们的游戏数据和信息,每当调用 XXClass.new() 创建对象实例时,也会自动执行ctor()函数。
    另外,在子类的ctor函数中必须手动调用父类的构造函数,这样才等保证子类能“继承”父类的属性和方法,正如上段代码所示。其中 XXClass.super 可以访问指定 XXClass 类的父类。
  4. 在 MyApp 的 run 方法中,这里做了三件事:
    • 首先,通过 addSearchPath 方法设置资源搜索路径。这也是为什么资源一定要放在“res”目录下的原因。
    • 然后,通过 setContentScaleFactor 方法设置资源缩放因子,这行代码是我们在上章进行分辨率适配时自己添加的。
    • 最后,因为 MyApp 类继承于 AppBase 类,因此自然地,MyApp 也同父类一样,具备了些许逻辑控制的功能。所以在 run 方法的最后,程序调用 enterScene 函数进入到名为 “MainScene” 的游戏场景。
      当然我们也可以在 enterScene 函数中设置场景切换的动画,不过为了加深大家对场景切换的印象,我们还是留到后面使用的时候再做详细的讲解。 > 注意:游戏场景默认必须放在 “src/app/scenes” 目录下,放到其他目录下是不能找到的。

4、MainScene.lua

讲的这里,大家应该很清楚 main 文件执行 require("app.MyApp").new():run() 代码的过程了吧。所以,接下来请大家打开“src/app/scenes”目录下的 MainScene.lua 文件,我们接着来看它的结构。其代码如下图所示:

local MainScene = class("MainScene", function()
return display.newScene("MainScene")
end)

function MainScene:ctor()
cc.ui.UILabel.new({
UILabelType = 2, text = "Hello, World", size = 64})
:align(display.CENTER, display.cx, display.cy)
:addTo(self)
end

function MainScene:onEnter()
end

function MainScene:onExit()
end

return MainScene

在 MainScene.lua 这个文件中,第一行代码使用 class 函数创建了一个 MainScene 场景类,同前边创建 MyApp 类不同的是,这里我们的类不是从基础类继承而来,而是通过一个函数创建出来的。

这是因为 Scene 场景对象必须是一个 C++ 的对象,而 C++ 的对象是无法直接派生出 Lua 的类的,所以我们只有用一个函数把它创建出来,然后再为它添加相应的方法。

在创建场景的函数段里,display.newScene() 方法将创建了一个新场景,并返回该 Scene 场景对象。

这里的 display 是一个重要的概念,在 Quick 中,display 模块封装了绝大部分与显示有关的功能,并负责根据 config.lua 中定义的分辨率设定计算屏幕的设计分辨率。display 模块提供了众多的方法和属性,比如创建层(display.newLayer),创建精灵(display.newSprite),以及恢复暂停切换场景等等。

总而言之,display 是个很值得我们细细探索的模块。关于它更多详情内容可参见Quick-Cocos2d-x的API文档

下面我们例举一些 display 常用的属性:

  • display.widthInPixels, display.heightInPixels: 屏幕分辨率的宽、高
  • display.width, display.height: 设计分辨率的宽、高
  • display.cx, display.cy: 设计分辨率中央的 x、y 坐标
  • display.left, display.top, display.right, display.bottom: 设计分辨率四边的坐标
  • display.c_left, display.c_top, display.c_right, display.c_bottom: 当父对象在屏幕中央时,屏幕四边的坐标
  • display.contentScaleFactor: 设计分辨率到屏幕分辨率的缩放因子,不同于内容缩放因子。

结合 display 模块提供的常量,我们可以很方便的获取屏幕的某些与现实相关的属性,如位置和颜色等等。display 模块中关于位置的常量是初始化的时候程序就已经计算好的(不信你可以查看控制台窗口中的打印信息),它们根据分辨率的不同而不同。
displayValue

MainScene 的 ctor 函数中默认添加了一个 UI 标签,这个标签的作用是在屏幕正中间添加一个“Hello World”的文本,并显示到屏幕上。

其中 cc.ui 模块中封装了大量符合脚本风格的 Cocos2d-x 控件,包含 UILabel、UIImage、UISlider 等等。所以正如上述代码一样,我们可以通过调用cc.ui.UILabel.new()来实例化一个新的 UILabel 控件。其先有参数分别表示:

  • UILabelType:创建文本对象所使用的方式。
    1 表示使用位图字体创建文本显示对象,返回对象是 LabelBMFont。
    2 表示使用 TTF 字体创建文字显示对象,返回对象是 Label。
  • text:要显示的文本。
  • size:文字尺寸。

创建好 UILabel 后,通过调用 align 方法设置文本的锚点和显示位置。最后用 addTo 方法把文本添加到场景中。

至于 MainScene 的 onEnter 和 onExit 两个方法,清楚 Cocos2d-x 原理的的童鞋应该很容易理解。其中onEnter 方法是在进入场景的时候调用的,我们可以在这里做一些初始化的工作;而 onExit 方法是在退出场景的时候调用的,在这里我们可以释放资源,重置变量。

小结

到现在为止,我们就已经解析完 src 目录下所有的 lua 文件了,总的来说,这两章所解析的 lua 文件的关系和用途如下图所示:
lc1

好了,上图把这两章讲解的废话都概括完了,我想应该够清楚明了的吧!! O(∩_∩)O~,从下章开始,我们将正式开始类el游戏的开发。

转载请注明出自:http://shannn.com/archives/387

Quick-Cocos2d-x初学者游戏教程2

上一章我们已经了解了Quick的一些基础知识,所以本章我们将开始深入到Quick内部,了解它内部的代码结构,同时在解析的过程中学到相应的原理,并学会如何修改、添加相应的代码文件,比如实现屏幕的分辨率适配。

前面我们创建了一个叫做parkour的游戏项目,其意思就是本人本来打算要做一个跑酷游戏的,但是因为这几天玩了一款叫做《el》的飞行游戏,非常有意境,并且几乎零差评,所以请允许我任性一下,善变的我不想做跑酷游戏了,而是想要挑战下这种类型的游戏。

《el》其实蛮类似跑酷游戏的,同时,它也很像前段时间超火的《Flappy bird》,喜欢这类游戏风格的童鞋不妨可以下载下来玩玩(我木有打广告,不是软文,不是)。

解析代码文件

正如上面所说的一样,本教程改做飞行游戏了。于是乎,我重新创建了一个叫做el的游戏项目。然后打开Sublime编辑器,把整个游戏项目都拖到该编辑器中,这样既方便代码编辑,又方便相关属性的查找。

下面我们就来打开新项目的src文件夹,一个一个的解析里面的lua文件吧。

1、config.lua

首先,打开config.lua文件,config文件中保存了游戏项目一些重要的属性配置,包括调试信息状态、屏幕大小、屏幕适配的方式等等。

下面是它现有属性的具体含义:

  • DEBUG: 配置Quick工程的调试信息状态,0表示关闭,1表示打印少量调试信息,2表示打印标准调试信息。
  • DEBUG_FPS: 是否显示模拟器左下角的FPS信息
  • DEBUG_MEM: 是否要每10秒打印一次内存信息
  • LOAD_DEPRECATED_API: 是否加载已经废弃了的API
  • LOAD_SHORTCODES_API: 是否加载短代码
  • CONFIG_SCREEN_ORIENTATION: 游戏中的屏幕方向,landscape横屏,portrait竖屏。
  • CONFIG_SCREEN_WIDTH: 屏幕宽度,但屏幕方向为“landscape”横屏时,该属性表示屏幕的高度
  • CONFIG_SCREEN_HEIGHT: 屏幕高度,但屏幕方向为“landscape”横屏时,该属性表示屏幕的宽度
  • CONFIG_SCREEN_AUTOSCALE: 屏幕适配策略,如FIXED_WIDTH、FIXED_HEIGHT和FILL_ALL

这些属性都是创建项目时默认配置好的,当然我们可以根据项目需要对这些值进行修改,比如进行分辨率适配时。此外,在config文件中我们也可以加入自定义的一些属性,比如声音、字体、图片、网络配置等等常量和宏的定义。

既然都提起了分辨率适配,那么接下来我们就来看看如何进行分辨率适配吧。

分辨率适配原理

众所周知,现今市场中各种屏幕尺寸和分辨率的移动设备(特别是Android机)层出不穷,所以为了让我们开发的程序项目能够更好地适应这些设备的不同需求,它的分辨率适配就显的尤其的重要。

如果读者之前对Cocos2d-x的分辨率适配原理有一点的了解,那么我想这部分内容理解起来应该是很容易的,但考虑了下初学者,所以下面我们还是简单的说说原理吧。

Cocos2d-x以及Quick中图片显示到屏幕有下面两个逻辑过程,两个过程结合在一起,影响了最终的显示效果。

  1. 资源布局到设计分辨率;
  2. 设计分辨率布局到屏幕。

其中设计分辨率是指我们在config文件中设置的CONFIG_SCREEN_WIDTH 和 CONFIG_SCREEN_HEIGHT,它相当于一个参考分辨率。只有确定了我们的参考分辨率,才能得到我们的图片资源的缩放比例。

在第一个过程中,我们需要通过以下函数来完成相应的转换:

cc.Director:getInstance():setContentScaleFactor(value)

setContentScaleFactor方法决定了图片资源显示到屏幕的缩放因子,顾名思义就是决定了整个游戏内容放大或者缩小的比例系数。
它的参数由(背景图片资源宽高/设计分辨率宽高)得到,而不是通过(背景图片资源宽高/屏幕宽高)得来。这样也就避开了游戏开发者去直接关注移动设备的实际屏幕。

setContentScaleFactor通常会用两个方式来设置参数,不同的设置方法会有不同的缩放负作用。

  • 用(资源高/设计分辨率高)的高度比作为参数,也就是内容的缩放因子,这样保证了背景资源的垂直方向在设计分辨率范围内的全部显示,但在水平方向上可能会溢出屏蔽或留有黑边。
  • 用(资源宽/设计分辨率宽)的宽度比作为内容缩放因子,保证了背景资源的水平方向在设计分辨率范围内的全部显示,但在垂直方向上可能会超出屏蔽范围或留有黑边。

第二个过程中,我们需要通过以下函数接口完成转换:

setDesignResolutionSize(width, height, cc.ResolutionPolicy)

setDesignResolutionSize方法会在display.lua中被调用,所以这里我们不用管它,只需要注意它的参数设置就好。
其中参数 width 和 height 指的是设计分辨率的宽、高,cc.ResolutionPolicy 是分辨率适配策略,它们分别由config.lua文件中的 CONFIG_SCREEN_WIDTH、CONFIG_SCREEN_HEIGHT 和 CONFIG_SCREEN_AUTOSCALE来设置。
Quick中CONFIG_SCREEN_AUTOSCALE的值有三种情况:

  • FIXED_WIDTH:保持传入的设计分辨率宽度不变,根据屏幕分辨率修正设计分辨率的高度。
  • FIXED_HEIGHT:保持传入的设计分辨率高度不变,根据屏幕分辨率修正设计分辨率的宽度。
  • FILL_ALL:保证了设计区域总有一个方向铺满屏幕,另一个方向可能超出屏幕或留有黑边。

以上两个过程相辅相成,它们相互影响,所以做好分辨率适配必须确保两步坚固。
说到这里我不得不提的是,在做分辨率适配的时候,常有人忽略以上的第一个过程,因此作为小白的我还是会经常遇到那么一些人问:为什么我的分辨率适配护好了还是有黑边,还是有问题?
对此我要说:请设置内容缩放因子。

下面我们以本教程游戏作为例子,看看具体的实现方法。

分辨率适配的实现

本教程将做一个横屏的飞行游戏,我们需要做到让背景图在高度方向上全部显示,所以显然地,如果高度方向上全部显示,那么在宽度方向上必然会做出一些牺牲(要么被裁减,要么留黑边(留黑边的问题可以通过将图片宽度做得更宽一点来解决))。

要实现这上述目标,需要保证各过程都是在宽度方向上裁减。所以我们给出了以下的实现过程:

1、首先我们选择 1136 x 640 的图片资源,这样宽高比够大,能确保在某些极端的分辨率下也能完整不留黑边的显示整个游戏画面。

2、接着打开src/config.lua,设置配置信息,如下:

CONFIG_SCREEN_ORIENTATION = "landscape"
CONFIG_SCREEN_WIDTH = 480
CONFIG_SCREEN_HEIGHT = 320
CONFIG_SCREEN_AUTOSCALE = "FIXED_HEIGHT"

CONFIG_SCREEN_ORIENTATION这个配置告诉 Quick 引擎,游戏是横屏的。该参数是拿来适配的时候使用的。

注意:如果使用 Cocos Code IDE 调试程序,那么请确保config.json配置文件的isLandscape 字段值为true,两者保持一致。反正,若游戏是竖屏,那么请确保isLandscape值为false。

对于 CONFIG_SCREEN_WIDTH 和 CONFIG_SCREEN_HEIGHT 的配置,其实我觉得只要配置好 CONFIG_SCREEN_AUTOSCALE 和内容缩放因子,那就不会有什么问题。

因为是横屏游戏,所以我们选择了 FIXED_HEIGHT 作为适配模式——让 Y 轴方向能完全显示在屏幕上。

3、最后,打开src/app/MyApp.lua,修改run()方法,加入内容缩放因子。

function MyApp:run()
cc.FileUtils:getInstance():addSearchPath("res/")
cc.Director:getInstance():setContentScaleFactor(640 / CONFIG_SCREEN_HEIGHT)
self:enterScene("MainScene")
end

因为我们选择了FIXED_HEIGHT。故此,计算内容缩放因子时,其参数为:资源高度 / 屏幕分辨率高度。关于MyApp:run()方法,我们后面将做详细的讲解。

好了,本章我们就到这里吧!也不知道是不是我太啰嗦,写了这么多居然才讲了一个.lua文件,看来下章开始不能再啰嗦了。另外,以上分辨率适配的验证将在后面添加资源图片的时候得到验证。

Quick-Cocos2d-x初学者游戏教程1

前言

虽然之前已经写过了好些 Cocos2d-x 相关的教程和文档,但本次却是我第一次接触 Quick,第一次接触 Lua,所以此次的教程本人将站在一个初学者的角度(看到这里是不是想白眼我了,哈哈,别切啊!尽管第一次,但我身边可是有很多 Quick 大神的,廖大大也在旁边办公室,没准撒个娇大神就把他知道的全部要点倾囊相授了啦!),全方位的解析 Quick 的学习过程,并同大家一起学习如何利用 Quick-Cocos2d-x 开发一款属于自己的游戏,包教包会的哦。
好了,那么下面我们就开始进入正题吧。

Quick-Coco2d-x是什么?

首先,想了解 Quick-Coco2d-x 就必须先知道 Cocos2d-x 是什么,不过我想关于 Cocos2d-x 的介绍这里就不用我来废话了吧,毕竟这么牛的游戏引擎还是应该很点名气的吧!

其次,由于 Cocos2d-x 中使用的是 C++ 语言,而 C++ 又对开发人员要求较高,所以逐渐地,开发者们开始将 Cocos2d-x 的 C++ 接口转成了 Lua 接口,从而衍生出了 Cocos2d-lua 的版本。而 Quick(Quick-Coco2d-x)是 Cocos2d-lua 的一个豪华增强和扩展版本,它重写了支持代码、解决了内存泄露和只能使用全局函数做回调等等问题。Quick 能让开发者使用 Lua 这种简单易懂的脚本语言来编写游戏,并大大提高了开发效率。现在廖大大的 Quick 团队也已接手了 Cocos2d-lua 的开发和维护工作,所以未来 Quick 和 Cocos2d-lua 将有望完全合并起来。

本次教程所用 Quick-Coco2d-x 版本为 v3.3 Final,大家可在http://cn.cocos2d-x.org/download/链接中找到对应的下载地址并安装该程序。

注意:不要把 Quick 安装到根目录,或者带有空格或中文的路径中。另外,Quick-Cocos2d-x 需要 Python 的支持,所以请自行检测你的电脑是否装有Python,方法是在终端中输入 python命令行,如未安装请先下载安装2.x版本的 Python。

Quick 安装完成后,在它的根目录下可以找到有两个名为setup_mac.sh、setup_win.bat的批处理脚本,它们分别是搭建Mac和Windows开发环境的脚本,根据自己系统的需要运行相应的脚步,就可以自动为你完成 Quick 环境的配置。在此之后,我们就可以双击安装目录下的 player3 图标(Windows 下桌面上会生成 player 的快捷键),启动 Quick 自带的模拟器了。在该模拟器界面中,我们可以创建、打开、运行项目,同时还能查看很多 Quick 自带的示例项目。

关于 Quick 更多的使用说明可参考安装目录下的 README 文件。

Quick-Coco2d-x开发工具

一般情况下,我们通常都会采用Cocos Code IDE作为开发工具来快速开发游戏,这里Cocos Code IDE是一个基于 Eclipse 的跨平台 IDE,专门为 Cocos2d-x Lua & JavaScript 开发人员准备,通过 IDE 你可以方便的创建游戏工程、编写并且支持在不同平台上调试代码、实时查看代码被改变后的效果,最终直接发布成一个可上架的安装包。

然而,最近学习Quick的时候听同事和很多用户反馈说:Quick-Coco2d-x v3.3 Final这个版本在Cocos Code IDE上还存在一些Bug,并且执行效率也相对较慢。所以在大神的推荐下,本教程我们就不选用Cocos Code IDE作为开发工具了,而是使用另一种高效的方法来完成该跑酷游戏的开发——Sublime + QuickXDev。

当然如果你非要用Cocos Code IDE还是可以的,其安装和使用说明可参考:Cocos Code IDE + Quick-Cocos2d-x 快速开发游戏一文。

** 开发工具的安装配置 **

Sublime Text 是一个具有漂亮的用户界面和强大的功能的代码编辑器,也是HTML和散文先进的文本编辑器,它的很多功能都依赖于其强大的插件系统。Sublime Text支持Lua语言,但它本身不具有像代码提示这样的功能,所以要想用Sublime Text快速的开发Quick-Coco2d-x程序,我们就必须安装强大的QuickXDev插件。

Sublime Text的下载地址为:http://www.sublimetext.com/,下载后直接安装即可。

QuickXDev的下载地址为:http://git.oschina.net/lonewolf/QuickXDev,将它下载解压之后重命名为QuickXDev,然后把该QuickXDev文件夹放入到Sublime Text的Packages目录下(可通过Sublime Text->Preferences->Browse Packages打开)。

接着依次打开Preferences->Package Settings->QuickXDev->Settings–Default,如下图所示:
openset
复制该处的内容到Preferences->Package Settings->QuickXDev->Settings–User(此时User是空白的)中,同时在 “quick_cocos2dx_root” 项的后面设置Quick-Coco2d-x的安装路径,如下图所示:

set

设置完成后,User的内容将会覆盖Default下的设置。
注意:以上路径应为你自己的安装路径,如果是Windows系统,则该处的路径形如:

"quick_cocos2dx_root": "D:\\Quick\\quick-3.3"

新建项目

开发工具准备就绪以后,下面我们就可以开始创建我们的项目了.

首先启动 Quick 下的 player3,在这儿的示例标签下你可以看到很多Quick自带的示例,对于初学者来说,看看这些示例的使用方法会对我们学习Quick有很大的帮助,它也是快速学习Quick极好的教材。其界面如下图所示:
quick-example
接着我们来新建一个项目,点击界面上的“新建项目”,player会自动切换到新建项目的界面。
create

设置好新建项目的存放路径,包名,屏幕方向,以及是否复制C++源码等选项后,我们就可以点击右下角的 “Create Project” 按钮创建项目了。这里需要注意的是,如果你的项目不涉及到 C++ 部分,那么可以取消“Copy Source Files”项的勾选,这样可以加快项目的创建速度。

点击 Create Project 按钮后,你会发现右下角的 “Create Project” 按钮变成了“Open..”,同时还会出现一个显示创建项目结果的终端窗口。待终端窗口显示创建完成时,我们就可以点击“Open..”打开项目了。

一个默认创建好的项目其实就是个程序员最熟悉的“Hello World”程序,然后后面我们就可以以这个“Hello World”程序为基础来编写自己的游戏。

每个新建的Quick程序都带了一个控制台窗口,如下图所示。这个控制台窗口是player的输出窗口,它包括了你打印的日志以及崩溃日志,当你的程序报错时,它的输出日志将是很好的检验凭证。

helloworld

项目目录分析

为了更快更好的学习Quick程序开发,了解其项目结构是很有必要的,所以下面我们就来依次看看引擎和新建项目的目录结构。

引擎目录结构分析

打开 Quick 安装目录,其结构如下图所示:
quick

  • build:该目录是 Cocos2d-x 的项目存放目录。
  • cocos: 改文件夹中包含了大部分引擎的库文件,其中包括:2d、3d、声音、基础库、数学库、物理库等等一系列相关的类文件。
  • docs:该文件夹下包含了引擎的API文档、发布文档(最新版本更改介绍,运行环境要求,编译环境要求及如何运行测试用例的相关命令)、Cocos编程规范等等文档。我们可以通过它查看引擎的代码API,以及最新版本更改介绍,quick运行环境要求,编译环境要求及如何运行测试用例的相关命令。里面的文件多是html和md格式的。
  • extensions: 其中主要是GUI扩展库.
  • external中包含物理引擎第三方库,Box2D和chipmunk;数据库第三方库,sqlite3;网络第三方库,webp,websockets;以及一些其他第三方库,像编码转换库、数据格式库等等。
  • licenses里面包含了引擎中用到的各种许可证文件。LICENSE_SpiderMonkey,spider引擎中用到的SpiderMonkey-JS运行环境,需要此许可证,该许可证适用于MPL/GPL/LGPL几种许可证LICENSE_chipmunk,LICENSE_JS,LICENSE_lua等等。引擎在这些许可证下可以对相应的源代码进行任意拷贝和修改。
  • quick:这个是Quick引擎代码。其中包含了创建各个平台新工程的批处理工具,Quick框架的核心目录,2dx和一些其他依赖的c++文件,模版工程,Quick所带的例子等等Quick的核心文件。
  • README.html/README.md:Quick的使用指南,关于Quick的安装、使用、创建等等信息都可以出这里获取,它其实相当于docs内文件的目录。
  • setup_mac.sh: 搭建Mac开发环境的脚本。
  • setup_win.bat: 搭建Windows开发环境的脚本。
  • tools:Quick用做luabinding的工具,可用来导出自定义的C++类。
  • version:版本标示。

新项目目录结构分析

打开新建项目的目录,我们来分析下新建项目的目录结构,其结构如下图所示:

file

  • config.json: 项目信息配置文件。
  • debug.log: 项目日志,即打印控制台窗口输出的所有日志文件。
  • frameworks: 存放Cocos2d-x引擎核心代码及各个平台运行时资源。
  • res:存放项目资源的文件夹,也就是说,我们游戏开发中用到的所有图片、字体、音频等资源都放在这里。
  • runtime:存放预编译的运行时库。
  • src:项目源码所存放文件夹,即游戏中的所有的 .lua 文件都放在这里。

以上目录中 res 和 src 文件夹是比较最要的,开发中我们也只需要对这两个文件夹里的内容进行操作,就可以实现游戏的开发。

在新项目的src文件夹中,现在你是可以看到一些 .lua 文件的,这些就是我们工程的lua代码。接下来我们简单的介绍下src中各项的功能:

  • cocos: cocos引擎代码
  • framework: quick的核心部分,在Cocos2d-x基础上自己搭建的一套framework
  • config.lua: 工程配置文件,包括分辨率适配等信息
  • main.lua: 工程入口
  • app: 工程的界面等文件,存放我们的游戏代码
    • MyApp.lua: 游戏的第一个界面
    • scenes: 存放游戏各个场景代码的文件夹
      • MainScene: 游戏的第一个场景

在游戏开发中,需要修改和添加界面时,我们只需要在相应的文件夹中添加场景就可以了。

好了,这章就算讲完了,下一章我们将先讲解新建项目各个lua文件的代码结构和使用方法,并开始着手新游戏的开发。

Cocos2d-Lua 在发布时加密lua源文件

原文:http://www.tyrantek.com/archives/411

我们知道Cocos2d-Lua(Quick-cocos2d-x) 集成有 Player 模拟器,可以快速测试 Lua 游戏代码。
当游戏开发完成,需要打包发布的时候,我们不希望 Lua 源代码以明文的方式暴露在 apk 或 ipa 包中,否则你的辛苦劳动成果将被轻易剽窃。幸运的是,Quick框架已为我们提供了这方面的支持。
本文记录ipa打包加密Lua全过程。

阅读全文»

【官方教程】使用Quick-Cocos2d-x搭建一个横版过关游戏(六)

原创: 王帅

目录索引:

【官方教程】使用Quick-Cocos2d-x搭建一个横版过关游戏(一)
【官方教程】使用Quick-Cocos2d-x搭建一个横版过关游戏(二)
【官方教程】使用Quick-Cocos2d-x搭建一个横版过关游戏(三)
【官方教程】使用Quick-Cocos2d-x搭建一个横版过关游戏(四)
【官方教程】使用Quick-Cocos2d-x搭建一个横版过关游戏(五)
【官方教程】使用Quick-Cocos2d-x搭建一个横版过关游戏(六)

这一篇是系列文章的最后一篇了,这一章我们会将剩下的UI界面和元素补齐,比如:游戏开始界面、过关界面,画面滚动。

游戏开始界面

在前面我们看到过主界面的结构,在我们的游戏开始界面中,我们只给它添加了一个背景图和一个“开始游戏”的按钮。

我们新建一个名为StartScene.lua的文件,先加入如下内容:

local StartScene = class("StartScene", function()
    return display.newScene("StartScene")
end)

function StartScene:ctor()
    local background = CCSprite:create("start-bg.jpg")
    background:setPosition(display.cx, display.cy)
    addChild(background)
end

return StartScene

然后我们再加入一个开始游戏按钮,在StartScene:ctor()中添加如下代码:

local item = ui.newImageMenuItem({image="#start1.png", imageSelected="#start2.png",
    listener = function()
        display.replaceScene(require("app.scenes.MainScene").new(), "fade", 0.6, display.COLOR_WHITE)
    end})
item:setPosition(display.cx, display.cy)
local menu = ui.newMenu({item})
menu:setPosition(display.left, display.bottom)
self:addChild(menu)

接下来我们要改变游戏的入口为StartScene。打开scripts/app目录下的MyApp.lua文件,这个文件是作为游戏的入口文件。我们打开MyApp.lua来分析下:

local MyApp = class("MyApp", cc.mvc.AppBase)

这一句可以看出MyApp类是从AppBase类继承过来的,AppBase类是是Quick里的MVC架构中里的重要的一环,关于Quick的MVC我们后面再跟大家再讲。

function MyApp:run()
    CCFileUtils:sharedFileUtils():addSearchPath("res/")
    display.addSpriteFramesWithFile("image/role.plist", "image/role.pvr.ccz");
    display.addSpriteFramesWithFile("image/ui.plist", "image/ui.pvr.ccz");
    display.addSpriteFramesWithFile("image/effect.plist", "image/effect.pvr.ccz");
    self:enterScene("StartScene")
end

MyApp:run()是界面的启动函数,在启动时我们首先添加了资源的搜索路径res,这表示我们的资源放到res目录下面。然后接下来将做的大图添加到SpriteFrameCache中。

最后面一句是启动StartScene界面,原来这里是MainScene界面,这里就是我们更改程序启动界面的地方。

好了,改完之后我们刷新Player,界面是不是变了呢?

p1

点击开始游戏,我们进入到原来的战斗界面:

p2

我们再回到原来的暂停界面,里面是不是有一个回到开始界面的图标呢?

p3

我们在原来的暂停界面中把PauseLayer::home()留空了,现在我们来把这个空不上,将下面的代码复制到PauseLayer::home()函数中:

display.resume()
self:removeFromParentAndCleanup(true)
display.replaceScene(require("app.scenes.StartScene").new())

这样,当我们点击回到开始界面的图标时,游戏就会切换到开始界面了。

过关

我们的游戏没有设置关卡选择界面,为了表示过关了,我们在消灭敌人后会提示说可以进下一关了,点了以后,玩家马上可以进入下一个关卡。

首先,我们要知道敌人挂了。我们添加一个table到MainScene中,在MainScene的ctor中我们添加一句

self.enemys = {}

在我们添加完enemy到MainScene中之后,我们也要把enemy添加到self.enemys中:

self.enemys[#self.enemys+1] = enemy1
self.enemys[#self.enemys+1] = enemy2

另外我们还需要给MainScene添加一个敌人死亡的消息:

CCNotificationCenter:sharedNotificationCenter():registerScriptObserver(nil, function(_, enemy) self:enemyDead(enemy) end, "ENEMY_DEAD")

当然,要记得在退出的时候把消息取消掉。

我们打开Enemy1.lua和Enemy2.lua文件,在里面的Enemyx:dead()函数的remove()中添加这样一句:

CCNotificationCenter:sharedNotificationCenter():postNotification("ENEMY_DEAD", self)

接下来,我们在MainScene里添加一个MainScene:enemyDead(enemy)的方法来响应敌人死亡的事件。函数内容如下:

function MainScene:enemyDead(enemy)
    print("EnemyDead")
    -- 检测敌人是否已经没血了
    self:removeEnemy(enemy)

    -- 如果敌人全部挂了
    if #self.enemys == 0 then
        self:showNextLevelItem()
    end
end

removeEnemy的作用是将enemy从self.enemys里移除掉,具体内容可以参考github上的代码。showNextLevelItem的作用是显示继续下一关的按钮,像这样:

p4

首先,我们在创建菜单时创建一个图片菜单项,然后隐藏,在showNextLevwelItem中我们,让他显示出来,并且执行闪烁动画:

-- 显示进入下一关的按钮
function MainScene:showNextLevelItem()
    local goItem = self.menu:getChildByTag(2)
    goItem:setVisible(true)
    goItem:runAction(CCRepeatForever:create(CCBlink:create(1, 1)))
end

在点击下一关按钮时,我们移动背景:

-- 进入下一关
function MainScene:gotoNextLevel()
    local goItem = self.menu:getChildByTag(2)
    transition.stopTarget(goItem)
    goItem:setVisible(false)

    self.background:move("left", self.player)
end

在移动完成后,会发送一个背景移动结束的消息给MainScene,这时再添加敌人到场景中:

function MainScene:backgroundMoveEnd()
    self:addEnemys()
end

过关界面

在我们的游戏中,因为没有等级和金币系统,所以我们的过关界面没有内容,在这里就不列出来了,在代码中有个GameoverLayer.lua实现了一个简单的过关界面,有兴趣的同学可以下来自己完成,为了T恤,加油哦!

文章配套代码和资源下载

【官方教程】使用Quick-Cocos2d-x搭建一个横版过关游戏(五)

原创: 王帅

目录索引:

【官方教程】使用Quick-Cocos2d-x搭建一个横版过关游戏(一)
【官方教程】使用Quick-Cocos2d-x搭建一个横版过关游戏(二)
【官方教程】使用Quick-Cocos2d-x搭建一个横版过关游戏(三)
【官方教程】使用Quick-Cocos2d-x搭建一个横版过关游戏(四)
【官方教程】使用Quick-Cocos2d-x搭建一个横版过关游戏(五)
【官方教程】使用Quick-Cocos2d-x搭建一个横版过关游戏(六)

上一章中我们讲到了怎么在游戏中添加血量条等元素,接下来我们来学下怎么在Quick中使用物理引擎。

Quick中使用的物理引擎

在Quick中通过CCPhysicsWorld类来使用物理引擎Box2D和Chipmunk(目前仅支持Chipmunk)。至于Box2D和Chipmunk的优劣问题,在这里我就不比较了,敢兴趣的同学可以自己度娘。因为Quick目前只支持Chipmunk,所以大家也不用想太多了。

CCPhysicsWorld接口

关于CCPhysicsWorld类使用,大家可以打开Quick里面的Smaples下的physics,里面有一个简单的物理引擎的使用例子。打开文件lib/cocos2d-x/external/chipmunk/cocos2dx_support下的CCPhysicsWorld.h文件,里面包含了CCPhysicsWorld类的所有定义。我们在Lua里使用时只需要直接调用里面的接口就可以了。在Quick的physics例子中创建一个PhysicsWorld是这样写的:

-- create physics world
self.world = CCPhysicsWorld:create(0, GRAVITY)

-- add world to scene
self:addChild(self.world)

通过调用CCPhysicsWorld:create可以创建一个物理世界,create的两个参数分别为x轴上和y轴上的重力因子。当为负数时表示往左方或者是下方运动,当为正数时表示往右方或者上方运动,绝对值的数值越大,重力加速度越大。用过Chipmunk的同学知道,Chipmunk里有几种Body的类型。

  • 圆形 通过调用createCircleBody来创建一个圆形的Body
  • 矩形 通过调用createBoxBody来创建一个矩形的Body
  • 多边形 通过调用createPolygonBody来创建一个多边形的Body

用法如下:

-- 创建一个矩形碰撞区域
local leftWallBody = self.world:createBoxBody(0, WALL_THICKNESS, display.height)

-- 创建一个圆形碰撞区域
local coinBody = self.world:createCircleBody(COIN_MASS, COIN_RADIUS)

通过createXXXBody创建出来的Body要通过bind方法绑定到精灵上。为了更好的模拟物理环境,也可以通过以下接口来设置一些系数:

  • body:setFriction(friction) 摩擦系数 0-1.0
  • body:setElasticity(elasticity) 反弹系数 0-1.0
  • body:setIsSensor(isSensor) 是否可以感应
  • body:setVelocity(velocityX, velocityY) 速度
  • body:setAngleVelocity(velocity) 角速度

比如你的物体从一个斜坡上下滑时,你可以设置一个摩擦系数;如果你的物体是从高处掉落,那么你可以给他设置一个反弹系数。这些参数设置能够更真实的反应物理世界里的一些物理特性。

创建Body时需要注意的是函数的第一个参数,当mass的值<=0时表示是静态刚体,当mass的值大于0表示是普通刚体。

CCPhysicsWorld碰撞检测

在Quick中增加碰撞检测,有这么几个步骤:

  • body:setIsSensor(true): 设置能感应Body
  • body:setCollisionType(碰撞类型): 设置Body的碰撞类型,这个参数当有不同的刚体时,必须要设置不同的整型值。
  • world:addCollisionScriptListener(事件响应函数,碰撞类型1,碰撞类型2): 碰撞类型1和碰撞类型2,是你想要检测的碰撞发生的两个物体。

比如我们现在有两个物体,分别为类型1和类型2,我们可以这样写:

world:addCollisionScriptListener(handler(self, self.onCollision), 1, 2)

function ClassA:onCollision(eventType, event)
    if eventType == 'begin' then
    elseif eventType == 'preSolve' then
    elseif eventType == 'postSolve' then
    elseif eventType == 'separate' then
    end
end

碰撞响应函数会传入两个参数,一个为eventType,有下面几种可能的值:

  • begin: 碰撞开始
  • preSolve: 持续碰撞(相交),可以设置相交时独自的摩擦,弹力等
  • postSolve: 调用完preSolve后,做后期处理,比如计算破坏等等
  • separate: 分开,当删除一个shape时也会调用

通过这几个事件,能够对物体之间的碰撞做出及时的反应。

在游戏中添加碰撞

给游戏添加元素

为了更好的添加效果,我们还要给玩家角色和敌人对象添加被击中的效果,并且在被击中后用户要掉一部分血。怎么添加在前面的章节中已经讲解过,我们就不细说了,不明白的可以参看代码

添加碰撞代码

首先,我们先创建一个CCPhysicsWorld对象,为了方便管理,我们新建了一个类PhysicsManager, 代码如下:

local world = nil

local PhysicsManager = class("PhysicsManager", function()
    -- 创建
    return CCPhysicsWorld:create(0, 0)
end)

CollisionType = {}
CollisionType.kCollisionTypePlayer = 1
CollisionType.kCollisionTypeEnemy = 2

function PhysicsManager:getInstance()
    if world == nil then
        world = PhysicsManager.new()
    end

    return world
end

return PhysicsManager

这样就创建了一个PhysicsManager的单例对象。

接下来,我们再在每个玩家和敌人对象中添加碰撞的刚体,比如,我们在玩家对象中添加如下语句:

local world = PhysicsManager:getInstance()
self.body = world:createBoxBody(1, self:getContentSize().width/2,     self:getContentSize().height)
--    self.body:bind(self)
self.body:setCollisionType(CollisionType.kCollisionTypePlayer)
self.body:setIsSensor(true)

self:scheduleUpdate();
self:addNodeEventListener(cc.NODE_ENTER_FRAME_EVENT, function()     self.body:setPosition(self:getPosition()) end)

这样就给Player添加了一个大小为Player的1/2宽度,高度为Player的高度的矩形刚体。这里要注意的是被注释掉的

self.body:bind(self)

Quick是更新body和精灵坐标的方式有两种,一种是通过将精灵绑定到body上,然后移动body;还有一种是通过移动精灵,在精灵内通过update来更新body的坐标。我们这游戏里采用了第二种方式。

在添加完碰撞检测后,我们再回到MainScene中,在MainScene:ctor中添加如下代码:

-- 获取world并且添加到Scene中
local world = PhysicsManager:getInstance()
self:addChild(world)

-- 启动世界更新
world:start()

-- 显示物理引擎调试层
self.worldDebug = world:createDebugNode()
self:addChild(self.worldDebug)

-- 添加碰撞检测函数
world:addCollisionScriptListener(handler(self, self.onCollision) ,
    CollisionType.kCollisionTypePlayer, CollisionType.kCollisionTypeEnemy)

为了在碰撞时检测事件,我们添加MainScene:onCollision函数:

function MainScene:onCollision(eventType, event)

    if eventType == 'begin' then
        self.canAttack = true
        local body1 = event:getBody1()
        local body2 = event:getBody2()

        if body1:getCollisionType() == CollisionType.kCollisionTypePlayer and body2 then
            body2.isCanAttack = true
        end
    elseif eventType == 'separate' then
        self.canAttack = false
        local body1 = event:getBody1()
        local body2 = event:getBody2()

        if body1:getCollisionType() == CollisionType.kCollisionTypePlayer and body2 then
            body2.isCanAttack = false
        end
    end
end

这里添加个isCanAttack,这个是为了在接触时表示玩家可以砍敌人了,离开以后玩家是不能砍敌人的。我们稍微更改下MainScene:clickEnemy函数:

function MainScene:clickEnemy(enemy)
    if self.canAttack then
        if self.player:getState() ~= "attack" then
            self.player:doEvent("clickEnemy")
            print("enemy:canAttack " .. tostring(enemy:getCanAttack()))
            if enemy:getCanAttack() and enemy:getState() ~= 'hit' then
                enemy:doEvent("beHit", {attack = self.player.attack})
            end
        end
    else
        local x,y = self.player:getPosition()
        self.player:walkTo({x=x, y=y})
        if self.player:getState() ~= 'walk' then
            self.player:doEvent("clickScreen")
        end
    end
end

这样一来,只有在碰撞时,玩家才能砍敌人。当然现在的敌人是不会动的,怎么解决敌人动的问题,就看大家的智慧了。嘿嘿!

【官方教程】使用Quick-Cocos2d-x搭建一个横版过关游戏(四)

原创: 王帅

目录索引:

【官方教程】使用Quick-Cocos2d-x搭建一个横版过关游戏(一)
【官方教程】使用Quick-Cocos2d-x搭建一个横版过关游戏(二)
【官方教程】使用Quick-Cocos2d-x搭建一个横版过关游戏(三)
【官方教程】使用Quick-Cocos2d-x搭建一个横版过关游戏(四)
【官方教程】使用Quick-Cocos2d-x搭建一个横版过关游戏(五)
【官方教程】使用Quick-Cocos2d-x搭建一个横版过关游戏(六)

上一篇文章中我们讲到了Quick中的状态机,接下来我们来看下添加血量条之类的UI元素。

添加血量条

添加玩家血量条

用过Cocos2d-x的同学们应该都知道里面有一个CCProgressTime类,在Quick中也同样的封装了这个类,在Quick里我们是通过display.newProgressTimer()方法来创建这样一个控件的。

我们先新建一个Progress.lua文件,保存到app/ui文件夹下。粘贴以下代码进去:

local Progress = class("Progress", function(background, fill)
    local progress = display.newSprite(background)
    local fill = display.newProgressTimer(fill, display.PROGRESS_TIMER_BAR)
 
    fill:setMidpoint(CCPoint(0, 0.5))
    fill:setBarChangeRate(CCPoint(1.0, 0))
    fill:setPosition(progress:getContentSize().width/2, progress:getContentSize().height/2)
    progress:addChild(fill)
    fill:setPercentage(100)
 
    return progress
end)
 
function Progress:ctor()
end
 
function Progress:setProgress(progress)
self.fill:setPercentage(progress)
end
 
return Progress

我们来分析下代码,第一句我们在第一篇文章里面已经讲过了,在这里的作用就是创建了一个新的名为Progress的类。分别有两个参数,一个是background,一个是fill。Background参数代表的是进度条的底图,比如这个图片: p1

fill参数表示的是血量条的填充条,比如这个图片: p2

如果我们是使用这两个图片作为血量条的参数的话是这样调用的:

local progress = Progress.new("#player-progress-bg.png", "#player-progress-fill.png")

这样我们就生成了一个血量条,是不是so easy呢!接下来我们看下血量条的实现:

local progress = display.newSprite(background)
local fill = display.newProgressTimer(fill, display.PROGRESS_TIMER_BAR)

这里先创建了一个progress的背景精灵,然后我们创建了一个fill的CCProgressTimer控件。

fill:setMidpoint(CCPoint(0, 0.5))
fill:setBarChangeRate(CCPoint(1.0, 0))
fill:setPosition(progress:getContentSize().width/2, progress:getContentSize().height/2)
progress:addChild(fill)
fill:setPercentage(100)

这几句设置了CCProgressTimer的一些数据

  • setMidPoint: 设置进度条的起点,CCPoint(0, 0.5)表示从最左边的中间为起点;
  • setBarChangeRate: 设置进度条变化速度,CCPoint(1.0, 0)表示只在x轴上变化;
  • setPercentage: 设置进度条的进度,100表示100%,50表示50%。

为了方便在外部使用,我们给Progress再添加一个setProgress方法:

function Progress:setProgress(progress)
    self.fill:setPercentage(progress)
end

这样我们把血量条就建立起来了。在外部只需要把底图和血量变化的图传入就可以使用了。打开MainScene.lua文件,在MainScene::ctor()中添加

self.progress = Progress.new("#player-progress-bg.png", "#player-progress-fill.png")
self.progress:setPosition(display.left + self.progress:getContentSize().width/2, display.top - self.progress:getContentSize().height/2)
self:addChild(self.progress)

给敌人添加血量条

我们攻击的敌人也是有血量条的,打开Enemy1.lua文件,在Enemy1:ctor()中添加如下代码

self.progress = Progress.new("#small-enemy-progress-bg.png", "#small-enemy-progress-fill.png")
local size = self:getContentSize()
self.progress:setPosition(size.width*2/3, size.height + self.progress:getContentSize().height/2)
self:addChild(self.progress)

这样,就给Enemy1添加了一个血量条。

在Enemy2.lua中执行相同的操作,给Enemy2也添加一个血量条。

添加暂停界面

玩家在游戏中有可能被其他事情打断,这个时候我们需要给他们提供一个暂停按钮和暂停界面。使用过Cocos2d-x的同学都知道要建立一些按钮,使用的是CCMenu,那么在Quick中怎样使用CCMenu呢?我们打开Quick下的framework/ui.lua文件,和CCMenu有关的方法有:

function ui.newMenu(items)
function ui.newImageMenuItem(params)
function ui.newTTFLabelMenuItem(params)

使用的过程大概是这样的,通过ui.newImageMenuItemui.newTTFLabelMenuItem创建MenuItem,之后用创建的MenuItem来创建一个Menu对象。比如我们要在MainScene中添加一个暂停按钮:

local itemPause = ui.newImageMenuItem({image="#pause1.png", imageSelected="#pause2.png",
    tag=1, listener = function(tag) self:pause() end})
local menu = ui.newMenu({itemPause})
itemPause:setPosition(display.right-itemPause:getContentSize().width/2, display.top-itemPause:getContentSize().height/2)
itemSkill:setPosition(display.left + itemSkill:getContentSize().width/2, display.bottom + itemSkill:getContentSize().height/2)
menu:setPosition(0,0)
self:addChild(menu)

将以上代码放入MainScene:ctor()中,刷新Player,可以看到下面的界面: p3

在点击暂停按钮后,我们需要弹出一个暂停界面,在上面的实现中,我们在itemPause的参数中有一个listener = function(tag) self:pause() end。我们需要给MainScene实现一个pause方法。在pause方法中做的事情有:

  • 暂停游戏运行
  • 显示暂停界面

我们先给MainScene添加一个pause方法,加入以下代码:

function MainScene:pause()
    display.pause()
    local layer = PauseLayer.new()
    self:addChild(layer)
end

display.pause()将游戏暂停下来,然后显示了一个暂停的层。这里暂停的层我们新建了一个类,名字叫做PauseLayer。我们新建一个PauseLayer.lua文件,添加一下代码:

local PauseLayer = class("PauseLayer", function ()
    return display.newColorLayer(ccc4(162,162,162,128))
end)
 
function PauseLayer:ctor()
    self:addUI()
    self:addTouch()
end
 
function PauseLayer:addUI()
    local background = display.newSprite("#pause-bg.png")
    background:setPosition(display.cx, display.cy)
    self:addChild(background)
 
    local home = ui.newImageMenuItem({
        image = "#home-1.png",
        imageSelected = "#home-2.png",
        listener = function()
            self:home()
        end
    })
 
    local resume = ui.newImageMenuItem({
        image = "#continue-1.png",
        imageSelected = "#continue-2.png",
        listener = function()
            self:resume()
        end
    })
 
    local backgroundSize = background:getContentSize()
 
    home:setPosition(backgroundSize.width/3, backgroundSize.height/2)
    resume:setPosition(backgroundSize.width*2/3, backgroundSize.height/2)
 
    local menu = ui.newMenu({home, resume})
    menu:setPosition(display.left, display.bottom)
 
    background:addChild(menu)
end
 
function PauseLayer:addTouch()
    local function onTouch(name, x, y)
 
    end
 
    self:addNodeEventListener(cc.NODE_TOUCH_EVENT, function(event)
        return onTouch(event.name, event.x, event.y)
    end)
 
    self:setTouchEnabled(true)
end
 
function PauseLayer:resume()
    self:removeFromParentAndCleanup(true)
    display.resume()
end
 
function PauseLayer:home()
 
end
 
return PauseLayer

首先,我们来看下addUI(),里面新建了一个背景精灵background和一个菜单变量menu,其中菜单中有两个菜单项,分别为home和resume,一个是返回到主界面,另外一个是恢复游戏。因为我们现在是没有主界面,所以home响应函数没有任何操作,resume函数中我们先把暂停界面移除掉,之后调用display.resume()来恢复游戏。 接下来我们看下addTouch()。因为暂停层的图片是覆盖在游戏层上面的,如果我们不把游戏层的触摸事件拦截的话,会触发游戏层的触摸事件。addTouch就是为了在暂停层拦截掉所有的触摸事件。

【官方教程】使用Quick-Cocos2d-x搭建一个横版过关游戏(三)

原创: 王帅

目录索引:

【官方教程】使用Quick-Cocos2d-x搭建一个横版过关游戏(一)
【官方教程】使用Quick-Cocos2d-x搭建一个横版过关游戏(二)
【官方教程】使用Quick-Cocos2d-x搭建一个横版过关游戏(三)
【官方教程】使用Quick-Cocos2d-x搭建一个横版过关游戏(四)
【官方教程】使用Quick-Cocos2d-x搭建一个横版过关游戏(五)
【官方教程】使用Quick-Cocos2d-x搭建一个横版过关游戏(六)

在这一章,我们来说下Quick中的状态机的设计和使用方式。 在游戏设计中,经常会用到状态机的概念。那么什么是状态机呢?其实状态机就是状态转移图。举个最简单的例子。在游戏中人有四个状态,分别是:静止,行走,攻击和死亡。触发的条件有用户点击屏幕(t1),用户选中敌人(t2),用户被敌人砍没血(t3),用户放弃选中(t4)。所以状态机就是 静止-( t3) ->死亡;静止-(t1)->行走;静止-(t2)->攻击;行走-(t3)->死亡;行走-(t2)->攻击;攻击-(t3)->死亡;行走- (t4) ->静止;攻击- (t4) -> 静止等等。就是这样状态在不同的条件下跳转到自己或不同状态。如下图所示:

p1

在上面的状态转换图中(图比较丑,请忽略……),我们可以很容易的看到人物的四个状态之间的转换关系。每个状态都有一个触发条件,当你触发T1的时候,如果用户是静止的,那么状态机会转换到行走状态,当行走执行结束时,状态机又会转换到静止状态。

Quick中的状态机机制

Quick下的状态机是从Javascript的状态机改过来的,但是充分的结合了Lua语言的优点,使用table大大简化了状态机的编写,同时也方便了开发人员的工作量。 为了更好的学习状态机,我们先来分析下Quick中的状态机的例子。 首先,我们打开quick-x/sample/statemachine/scripts/scenes/MainScene.lua。先看MainScene:ctor()方法

self.fsm_ = {}
cc.GameObject.extend(self.fsm_)
    :addComponent("components.behavior.StateMachine")
    :exportMethods()

这样就创建了一个状态机对象。函数接下来执行

self.fsm_:setupState({
    events = {
        {name = "start", from = "none",   to = "green" },
        {name = "warn",  from = "green",  to = "yellow"},
        {name = "panic", from = "green",  to = "red"   },
        {name = "panic", from = "yellow", to = "red"   },
        {name = "calm",  from = "red",    to = "yellow"},
        {name = "clear", from = "red",    to = "green" },
        {name = "clear", from = "yellow", to = "green" },
    },
 
    callbacks = {
        onbeforestart = function(event) self:log("[FSM] STARTING UP") end,
        onstart       = function(event) self:log("[FSM] READY") end,
        onbeforewarn  = function(event) self:log("[FSM] START   EVENT: warn!", true) end,
        onbeforepanic = function(event) self:log("[FSM] START   EVENT: panic!", true) end,
        onbeforecalm  = function(event) self:log("[FSM] START   EVENT: calm!",  true) end,
        onbeforeclear = function(event) self:log("[FSM] START   EVENT: clear!", true) end,
        onwarn        = function(event) self:log("[FSM] FINISH  EVENT: warn!") end,
        onpanic       = function(event) self:log("[FSM] FINISH  EVENT: panic!") end,
        oncalm        = function(event) self:log("[FSM] FINISH  EVENT: calm!") end,
        onclear       = function(event) self:log("[FSM] FINISH  EVENT: clear!") end,
        onleavegreen  = function(event) self:log("[FSM] LEAVE   STATE: green") end,
        onleaveyellow = function(event) self:log("[FSM] LEAVE   STATE: yellow") end,
        onleavered    = function(event)
            self:log("[FSM] LEAVE   STATE: red")
            self:pending(event, 3)
            self:performWithDelay(function()
                self:pending(event, 2)
                self:performWithDelay(function()
                    self:pending(event, 1)
                    self:performWithDelay(function()
                        self.pendingLabel_:setString("")
                        event.transition()
                    end, 1)
                end, 1)
            end, 1)
            return "async"
        end,
        ongreen       = function(event) self:log("[FSM] ENTER   STATE: green") end,
        onyellow      = function(event) self:log("[FSM] ENTER   STATE: yellow") end,
        onred         = function(event) self:log("[FSM] ENTER   STATE: red") end,
        onchangestate = function(event) self:log("[FSM] CHANGED STATE: " .. event.from .. " to " .. event.to) end,
    },
})

这一段代码执行了什么操作呢?我们可以来看看StateMachine类中的setupState方法

function StateMachine:setupState(cfg)
    assert(type(cfg) == "table", "StateMachine:ctor() - invalid config")
 
    -- cfg.initial allow for a simple string,
    -- or an table with { state = "foo", event = "setup", defer = true|false }
    if type(cfg.initial) == "string" then
        self.initial_ = {state = cfg.initial}
    else
        self.initial_ = clone(cfg.initial)
    end
 
    self.terminal_   = cfg.terminal or cfg.final
    self.events_     = cfg.events or {}
    self.callbacks_  = cfg.callbacks or {}
    self.map_        = {}
    self.current_    = "none"
    self.inTransition_ = false
 
    if self.initial_ then
        self.initial_.event = self.initial_.event or "startup"
        self:addEvent_({name = self.initial_.event, from = "none", to = self.initial_.state})
    end
 
    for _, event in ipairs(self.events_) do
        self:addEvent_(event)
    end
 
    if self.initial_ and not self.initial_.defer then
        self:doEvent(self.initial_.event)
    end
 
    return self.target_
end

从实现来看,传入的参数包含这样几个字段:initial、terminal或者final、events、callbacks

  • initial字段表示的是状态机的初始状态,可以是简单的字符串;
  • terminal或者final表示的是结束状态;
  • events表示状态发生转变时对应的事件;
  • callbacks表示发生转变时的回调函数。

我们再回来看statemachine.lua中的代码

events = {
        {name = "start", from = "none",   to = "green" },
        {name = "warn",  from = "green",  to = "yellow"},
        {name = "panic", from = "green",  to = "red"   },
        {name = "panic", from = "yellow", to = "red"   },
        {name = "calm",  from = "red",    to = "yellow"},
        {name = "clear", from = "red",    to = "green" },
        {name = "clear", from = "yellow", to = "green" },
},

分析这段代码我们可以知道,状态机包含了4种状态,分别为none,green,yellow,red,事件有start,warn,panic,calm,clear发生这些事件时会产生不同的状态转换。比如,当产生start事件时,如果前一个状态是none,它将会转变为green;当发生panic时,如果前一个状态为green,那么将会转变为red状态。现在我们前面提到过的状态转换可以转成下面的event表:

events = {
        {name = "t1", from = "none",   to = "green" },
        {name = "t2", from = "green",  to = "yellow"},
        {name = "t3", from = "green",  to = "red"   },
        {name = "t4", from = "yellow", to = "red"   },
},

接下来我们看下callbacks,callbacks存放的是所有的状态转换的事件处理方法。我们先看statemachine里面的例子:

onbeforestart = function(event) self:log("[FSM] STARTING UP") end,
onstart       = function(event) self:log("[FSM] READY") end,
onbeforewarn  = function(event) self:log("[FSM] START   EVENT: warn!", true) end,
onbeforepanic = function(event) self:log("[FSM] START   EVENT: panic!", true) end,
onbeforecalm  = function(event) self:log("[FSM] START   EVENT: calm!",  true) end,
onbeforeclear = function(event) self:log("[FSM] START   EVENT: clear!", true) end,
onwarn        = function(event) self:log("[FSM] FINISH  EVENT: warn!") end,
onpanic       = function(event) self:log("[FSM] FINISH  EVENT: panic!") end,
oncalm        = function(event) self:log("[FSM] FINISH  EVENT: calm!") end,
onclear       = function(event) self:log("[FSM] FINISH  EVENT: clear!") end,
onleavegreen  = function(event) self:log("[FSM] LEAVE   STATE: green") end,
onleaveyellow = function(event) self:log("[FSM] LEAVE   STATE: yellow") end,
onleavered    = function(event)
    self:log("[FSM] LEAVE   STATE: red")
    self:pending(event, 3)
    self:performWithDelay(function()
        self:pending(event, 2)
        self:performWithDelay(function()
            self:pending(event, 1)
            self:performWithDelay(function()
                self.pendingLabel_:setString("")
                event.transition()
            end, 1)
        end, 1)
    end, 1)
    return "async"
end,
ongreen       = function(event) self:log("[FSM] ENTER   STATE: green") end,
onyellow      = function(event) self:log("[FSM] ENTER   STATE: yellow") end,
onred         = function(event) self:log("[FSM] ENTER   STATE: red") end,
onchangestate = function(event) self:log("[FSM] CHANGED STATE: " .. event.from .. " to " .. event.to) end,

我们从上面的代码可以看出来,所有的事件触发函数的命名都是有规则的,比如:onbeforestart表示的是start事件前执行的操作,onstart是start事件执行已经完成,ongreen表示进入green状态,onleavegreen表示离开green状态。在Quick里这种命名规则是如下所示:

  • onbeforexxx: 执行xxx事件前的响应函数;
  • onxxx或者onafterxxx: 执行xxx事件完成的响应函数;
  • onenterxxx或者onxxx: 进入xxx状态时的响应函数;
  • onleavexxx: 离开xxx状态时的响应函数;
  • onbeforeevent: 执行所有事件之前会执行该响应函数,事件信息以参数形式下发;
  • onafterevent或者onevent: 执行所有事件完成之后执行该响应函数,事件信息以参数形式下发;
  • onchangestate: 改变状态时的响应函数,事件信息会以参数的形式下发;
  • onenterstate: 进入状态时的响应函数,事件信息会以参数形式下发:
  • onleavestate: 离开状态时的响应函数,事件信息会以参数形式下发。

到这里我们基本上了解了在Quick中如何建立一个状态机,我们现在创建一个自己的状态机。在文章最开始我们描述了一个状态机的转换过程,我们就以这个为例子:

events = {
        -- t1:clickScreen; t2:clickEnemy; t3:beKilled; t4:stop
        {name = "clickScreen", from = {"idle", "attack"},   to = "walk" },
        {name = "clickEnemy",  from = {"idle", "walk"},  to = "attack"},
        {name = "beKilled", from = {"idle", "walk", "attack"},  to = "dead"},
        {name = "stop", from = {"walk", "attack"}, to = "idle"},
 },

我们这里的4个状态转换和事件就一一对应起来了,另外在from中可以使用通配符"*"来匹配所有状态,如果有多个状态也可以使用table来赋值。为了方便进行演示,我们状态转换的响应先打印几个语句。状态响应函数如下:

-- 状态转变后的回调
callbacks = {
    onidle = function () print("onidle") end,
    onwalk = function () print("move") end,
    onattack = function () print("attack") end,
    ondead = function () print("ondead") end
},

加完之后的代码如下:

self.fsm_ = {}
cc.GameObject.extend(self.fsm_)
    :addComponent("components.behavior.StateMachine")
    :exportMethods()
 
self.fsm_:setupState({
    -- 初始状态
    initial = "idle",
 
    -- 事件和状态转换
    events = {
        -- t1:clickScreen; t2:clickEnemy; t3:beKilled; t4:stop
        {name = "clickScreen", from = {"idle", ""attack"},   to = "walk" },
        {name = "clickEnemy",  from = {"idle", "walk"},  to = "attack"},
        {name = "beKilled", from = {"idle", "walk", "attack"},  to = "dead"},
        {name = "stop", from = {"walk", "attack"}, to = "idle"},
    },
 
    -- 状态转变后的回调
    callbacks = {
        onidle = function () print("idle") end,
        onwalk = function () print("move") end,
        onattack = function () print("attack") end,
        ondead = function () print("dead") end
    },
})

把上面的代码放到Player类的ctor()方法中,接下来我们添加事件响应函数,为了简化过程,我们在触摸状态响应函数里加上事件的处理,每个事件都执行一次,直到人物dead。在Player类中添加方法doEvent(),把下面的代码放进去:

function Player:doEvent(event)
    self.fsm_:doEvent(event)
end

打开MainScene.lua文件,在onTouch函数中加入:

index = index or 1  -- 取事件字符串的索引
local fsmEvents = {"clickScreen", "clickEnemy", "beKilled", "stop"}
self.player:doEvent(fsmEvents[index])
index = index + 1

打开Quick的Player或者刷新Player,点击屏幕,在Quick Player的输出信息窗口可以看到下面的信息:

p2

如果你在玩家dead之后继续点击鼠标会报错:

p3

至于游戏内部的更改因为涉及各个方面的动作代码两会比较多,我就不在这里贴出来了,感兴趣的可以直接看代码,其实就是讲原来的动作和状态用现在的状态机来控制。大家看代码的过程可以熟悉下状态机的写法。同时代码中的不足之处也欢迎各位批评指正。

【官方教程】Quick-Coco2d-x开发环境搭建

原创: 王帅

1.Quick-Coco2d-x介绍

Quick-Coco2d-x是Cocos2d-x在Lua上的增强和扩展版本,廖宇雷廖大觉得官方Cocos2d-x的Lua版本不是太好用,于是便在官方Lua版本的基础上进行了一次自己的封装,并且添加了一些游戏开发中常用的第三方库,便于开发人员使用。至于Lua与Javascript的比较或者是Quick-Coco2d-x与Cocos2d-x的开发效率之类的比较,就不赘述了,相信看这篇文章的童鞋都已经在其他地方看过了。

2.工具准备

Quick-Coco2d-x的底层基本是沿袭Cocos2d-x的那套,所以可移植性也差不多,常用的Mac,iOS,Windows,Android也是支持的,如果原来开发过Android或者iOS游戏的开发者可以无视2.22.3两节。而如果第一次来做Android或者iOS游戏开发的话,咱们还是一步一步来,毕竟东西也不是太多,如果我写的不是太清楚的地方,也欢迎各位批评和指正。

2.1 获取Quick-Coco2d-x的代码

首先,自然是要获取Quick-Coco2d-x(以下简称quick)的代码,quick的代码被廖大分别托管在github和osc上,osc的代码貌似现在是已经停止更新了,github在quick被触控收购后已经转移到触控的仓库中,地址是:

https://github.com/chukong/Quick-Coco2d-x

git作为程序员的社交网站,具体功能相信不需要我多说。打开quick的代码仓库的页面如下:

咱们先确定是使用哪个分支来做开发,quick一直维护的只有两个分支,一个是master分支,一个是develop分支。master分支是正式版本,缺点是Cocos2d-x的版本可能会稍微落后于官方,一些quick新添加的功能可能没有,优点在于稳定版本是经过测试使用后没问题,可以放心大胆的使用;develop版本是目前正在开发的版本,缺点是问题可能会比较多,优点是Cocos2d-x的版本比较新,有一些新添加的功能在里面。如果是商用游戏的话推荐使用正式版本,如果是自己学习的话既可以使用正式版本也可以使用develop版本。

选好版本之后我们把版本clone下来,至于clone的工具,既可以使用git命令行,也可以使用github客户端或者SourceTree之类的工具,个人推荐使用SourceTree,GUI界面还是比较简单直白的^_^。

2.3 获取Android工具链

Android下开发需要的工具有:

  • 1. Eclipse+ADT/AndroidStudio/IDEA
  • 2. NDK-r9+
  • 3. Java JDK
  • 4. Lua 5.1.4
  • 5. Android SDK

因为在Android下开发游戏还是要使用到C++代码,所以咱们还是需要使用NDK-r9的版本来做开发。至于编辑器最好是用Eclipse,目前Android的开发支持的最完整的还是Eclipse+ADT这一套装备。如果是嫌装ADT麻烦的话可以到android的开发者官网下载android-bundle(点这里下载)。
先把上面列出的软件安装好,然后打开Eclipse或者android-bundle,为了方便开发Lua,我们需要给Eclipse安装LDT,LDT的地址是:http://download.eclipse.org/koneki/releases/stable。如果是有Eclipse Market的Eclipse版本,直接打开Eclipse Market,搜索LDT安装即可。如果没有Eclipse Market,选中Help->Install New Software,输入上面的LDT的地址,并安装。

安装成功后重启Eclipse,LDT就成功的安装了。

3.环境配置

所有的软件安装完成后,我们还不能马上使用quick,还需要配置一些环境变量和参数。

3.1 Quick-Coco2d-x环境配置

相对来说,quick的环境变量配置是很简单的,较老的版本需要在windows中配置环境变量QUICK_COCOS2DX_ROOT

右键我的电脑或者计算机->属性->高级->环境变量,打开环境变量设置窗口

点击新建,添加一个名为QUICK_COCOS2D_ROOT的变量,值为你clone下来的quick的根路径。

新版本的quick包含一个自动设置环境变量的脚本。在quick的根目录下可以找到一个名为setup.bat的批处理脚本,运行它,就会自动为你完成刚才说的步骤了。当你改变了quick的文件夹时,也需要改变环境变量中QUICK_COCOS2D_ROOT的值。

配置好这些参数基本上quick的player就可以运行起来了。打开quick根目录下的player/win下的Quick-x-player.exe文件,为了方便也可以在桌面上创建一个快捷方式。打开后的界面如下:

图中的窗口1是调试信息窗口,窗口2是运行的界面窗口。

我们来运行quick的一个demo来试试。点击查看示例,player中的窗口会变成下面这样:

作为从quick发布到现在一直陪伴左右的coinflip当然是我们展示的首选。点击coinflip,进入游戏。

漂亮的界面就出现了,大家开心的玩耍吧。

3.2 Android环境配置

配置好quick的参数后我们运行quick自带的demo是毫无问题了,接下来,我们要让quick运行到自己的设备上。

首先,用quick的player的新建项目建立一个项目

我在这里新建了一个名为fristtest的项目,路径放到quick目录下的smaples里,包名随便填吧^_^,点击create project创建好项目。在项目创建完成后,关闭创建项目窗口,回到player,打开刚才创建的项目。这里要注意,要选中项目的根目录,不要选proj.android之类的指定平台目录。如果一切正常的话看到应该是如下界面:

如果屏幕方向不对或者是想看看其他屏幕方向的显示效果可以点菜单栏的Screen,然后选择Landscape或者Portrait来做更改。

既然在Player上运行没问题,接下来我们就可以让它在android上运行起来。打开Eclipse,在Eclipse的Package Explorer中点击右键,选择Import,选择Android->Existing Android Code Into Workspace,然后找到刚才创建项目的proj.android目录,确定后就可以导入到Eclipse的workspace中了。

不出意外的话你看到的应该是一片xx,这是因为咱们还没导入Cocos2d-x的java代码导致的。

导入Cocos2d-x的Java代码有两种方式,一种是直接把代码拷贝到src目录下,还有一种方式是直接将Cocos2d-x的代码作为第三方库的形式导入。第一种方式适合要经常修改引擎Java代码的同学,第二种方式的话不是经常更改引擎Java代码的同学。
第一种方式,不多说,直接把quick根目录\lib\Cocos2d-x\cocos2dx\platform\android\java\src下的文件拷贝到项目的src目录下然后刷新项目就可以了。
第二种方式和导入quick的android工程一样导入quick根目录\lib\Cocos2d-x\cocos2dx\platform\android\java下的代码到workspace中,之后选择我们在quick中建立的工程,点击右键->Properties->Android,在Library中点击Add,

在弹出的窗口中libcocos2dx项目

点击OK退出。好了,现在所有的xx都消失了。

接下来我们还需要配置NDK的编译环境,至于怎么配置NDK的编译环境,大家可以度娘下,这里就不细说了。在配置好NDK编译环境后,在proj.android下执行命令ndk-build或者build_native,生成C++库。
再到Eclipse中按照原来的方式点击Run就可以在手机上运行了。

?>