cocos2d-x lua 贪吃蛇游戏 一步一步开发学习
本文地址: http://blog.csdn.net/qq_26437925/article/details/51842647
源代码:
https://github.com/doctording/cocos2dx_lua_snake
===
windows 直接下载windows版本最新的,网址如下(官网可能变化)
http://www.cocos.com/download/cocos2d-lua/
或者下载网盘里的:http://pan.baidu.com/s/1c2fwhKc
解压到某个目录下就行了,setup.py设置环境
切换到如下的目录,既可以创建lua项目
shift +鼠标右键,在该目录下打开cmd,输入
cocos.py new Snake -l lua -d d:\
可以在D:\下建立一个Snake 项目,这些都是可选的,自己随便玩
接着用vs2012 或者vs2013 打开如下的解决方案,并完成编译
最后可以运行出结果,可在如下的目录中运行,点击exe即可
有模拟器 和 日志两个窗口,在实际编程中需要多打日志 和 查看日志 分析问题,并解决。
===
类似cocos2d-x win32 C++ 功能
程序从 E:\workspace\cocos_lua\Snake\runtime\win32\src\下的main.lua开发,包括了各种配置等(如config.lua的一些屏幕显示配置)
接着 src/app 目录下有个 MyApp.lua,加载了 src/app/scenes/下的MainScene
而Mainscene就是继承了Scene的一个场景了,可以看到有HelloWorld
参考 cocos2d-x lua tolua++ 面向对象
http://blog.csdn.net/qq_26437925/article/details/51842400
因为继承了场景类,所以需要重写一些方法
void onExitTransitionDidStart();//2,创建完layer的时候调用,也就是1调用完之后调用
void onEnter();//1,创建时调用
void onExit();//3,退出当前layer的时候调用,在这里都是做一些清除工作
void onEnterTransitionDidFinish();//在3完成之后,调用该成员方法
所以我们需要在 onEnter(); 中写各种东西,这样就可以显示Sprite, Button,Label等等了。
===
显示蛇,首先需要重定义坐标,认识屏幕的坐标系统
cGridSize 大小依据蛇身体图片的大小而定,自定义一个转换函数,方便用来设置Sprite的位置
local cGridSize = 33
local scaleRate = 1 / display.contentScaleFactor
-- 根据自定义的坐标得到实际应该显示的cocos2d-x坐标位置
function Grid2Pos(x,y)
local visibleSize = cc.Director:getInstance():getVisibleSize() -- 获取整个手机可视屏幕尺寸
local origin = cc.Director:getInstance():getVisibleOrigin() -- 获取手机可视屏原点的坐标,屏幕的左上角
local finalX = origin.x + visibleSize.width / 2 + x * cGridSize * scaleRate
local finalY = origin.y + visibleSize.height / 2 + y * cGridSize * scaleRate
return finalX,finalY
end
===
有了上面的基础,可以先显示蛇身了
src/app 目录下定义一个Body类,表示蛇身 , 有坐标,是否头部,其父节点等信息,当然完全看自己如何弄
local Body = class("Body")
-- node为cocos2dx-父节点
function Body:ctor(snake , x, y, node, isHead)
self.snake = snake
self.X = x
self.Y = y
if isHead then -- 根据是否是头部,用不同的图片创建
self.sp = cc.Sprite:create("head.png")
else
self.sp = cc.Sprite:create("body.png")
end
node:addChild(self.sp) -- 添加到父节点
self:Update()
end
-- 更新自己的位置
function Body:Update()
local posx,posy = Grid2Pos(self.X , self.Y)
self.sp:setPosition(posx,posy)
end
return Body
MainScene.lua中
local cGridSize = 33
local scaleRate = 1 / display.contentScaleFactor
-- 根据自定义的坐标得到实际应该显示的cocos2d-x坐标位置
function Grid2Pos(x,y)
local visibleSize = cc.Director:getInstance():getVisibleSize() -- 获取整个手机可视屏幕尺寸
local origin = cc.Director:getInstance():getVisibleOrigin() -- 获取手机可视屏原点的坐标,屏幕的左上角
local finalX = origin.x + visibleSize.width / 2 + x * cGridSize * scaleRate
local finalY = origin.y + visibleSize.height / 2 + y * cGridSize * scaleRate
return finalX,finalY
end
-- require相应的类
local Body = require("app.Body")
local MainScene = class("MainScene", function()
return display.newScene("MainScene")
end)
function MainScene:onEnter() -- MainScene 加载执行
-- 测试body
self.body1 = Body.new(nil,0,0,self,true)
self.body2 = Body.new(nil,2,2,self,false)
end
return MainScene
显示如下,很预想的一样,就证明没有什么错误了
===
有了蛇身体,构造一条蛇就容易了
编写一个蛇Snake类,利用Body类
Snake.lua
local Snake = class("Snake")
local Body = require("app.Body")
local cInitLen = 3 -- 蛇初始长度
-- 构造函数
function Snake:ctor(node)
self.BodyArray = {} -- Body对象数组
self.node = node
self.MoveDir = "left" -- 蛇的初始移动方向
for i = 1,cInitLen do
self:Grow(i == 1)
end
end
--取出蛇尾
function Snake:GetTailGrid()
if #self.BodyArray == 0 then -- 设置蛇头的位置为(0,0)
return 0,0
end
local tail = self.BodyArray[#self.BodyArray]
return tail.X,tail.Y
end
-- 蛇变长
function Snake:Grow(isHead)
local tailX,tailY = self:GetTailGrid()
local body = Body.new(self,tailX,tailY,self.node,isHead)
table.insert(self.BodyArray,body)
end
-- 根据方向改变坐标
local function OffsetGridByDir(x,y,dir)
if dir == "left" then
return x - 1, y
elseif dir == "right" then
return x + 1, y
elseif dir == "up" then
return x, y + 1
elseif dir == "down" then
return x, y - 1
end
print("Unkown dir", dir)
return x, y
end
-- 根据蛇的移动方向 更新蛇,就是BodyArray一个一个往前移动
function Snake:Update()
if #self.BodyArray == 0 then
return
end
for i = #self.BodyArray , 1 , -1 do
local body = self.BodyArray[i]
if i == 1 then -- 蛇头位置 与 方向,得到一个新的位置 存放蛇头
body.X, body.Y = OffsetGridByDir(body.X, body.Y, self.MoveDir)
else
local front = self.BodyArray[i-1]
body.X, body.Y = front.X, front.Y
end
body:Update()
end
end
-- 取出蛇头
function Snake:GetHeadGrid()
if #self.BodyArray == 0 then
return nil
end
local head = self.BodyArray[1]
return head.X, head.Y
end
-- 设置方向
function Snake:setDir(dir)
self.MoveDir = dir
end
return Snake
响应的MainScene.lua中也要调整下
local cGridSize = 33
local scaleRate = 1 / display.contentScaleFactor
-- 根据自定义的坐标得到实际应该显示的cocos2d-x坐标位置
function Grid2Pos(x,y)
local visibleSize = cc.Director:getInstance():getVisibleSize() -- 获取整个手机可视屏幕尺寸
local origin = cc.Director:getInstance():getVisibleOrigin() -- 获取手机可视屏原点的坐标,屏幕的左上角
local finalX = origin.x + visibleSize.width / 2 + x * cGridSize * scaleRate
local finalY = origin.y + visibleSize.height / 2 + y * cGridSize * scaleRate
return finalX,finalY
end
-- require相应的类
-- local Body = require("app.Body")
local Snake = require("app.Snake")
local MainScene = class("MainScene", function()
return display.newScene("MainScene")
end)
function MainScene:onEnter() -- MainScene 加载执行
self.snake = Snake.new(self) -- 创建一条蛇
end
return MainScene
最后显示如下,似乎看不到一条蛇,因为它们都重复了,我们需要是的蛇能够动起来
===
只需要设个定时器,刷新屏幕就行了,所以在上面的基础上,只需要加个定时器,更新小蛇就行了
修改MainScene.lua
local cGridSize = 33
local scaleRate = 1 / display.contentScaleFactor
-- 根据自定义的坐标得到实际应该显示的cocos2d-x坐标位置
function Grid2Pos(x,y)
local visibleSize = cc.Director:getInstance():getVisibleSize() -- 获取整个手机可视屏幕尺寸
local origin = cc.Director:getInstance():getVisibleOrigin() -- 获取手机可视屏原点的坐标,屏幕的左上角
local finalX = origin.x + visibleSize.width / 2 + x * cGridSize * scaleRate
local finalY = origin.y + visibleSize.height / 2 + y * cGridSize * scaleRate
return finalX,finalY
end
-- require相应的类
-- local Body = require("app.Body")
local Snake = require("app.Snake")
local MainScene = class("MainScene", function()
return display.newScene("MainScene")
end)
local cMoveSpeed = 0.3
function MainScene:onEnter() -- MainScene 加载执行
self.snake = Snake.new(self) -- 创建一条蛇
local tick = function()
self.snake:Update() -- 更新蛇
end -- end tick
cc.Director:getInstance():getScheduler():scheduleScriptFunc(
tick,cMoveSpeed,false)
end
return MainScene
定时器,就一句话
schedulerID = cc.Director:getInstance():getScheduler():scheduleScriptFunc(调用的function, 定时时间(秒), 是否暂停(true, false))
我们在更新函数中写了self.snake:Update()
function Snake:Update()
if #self.BodyArray == 0 then
return
end
for i = #self.BodyArray , 1 , -1 do
local body = self.BodyArray[i]
if i == 1 then -- 蛇头位置 与 方向,得到一个新的位置 存放蛇头
body.X, body.Y = OffsetGridByDir(body.X, body.Y, self.MoveDir)
else
local front = self.BodyArray[i-1]
body.X, body.Y = front.X, front.Y
end
body:Update()
end
end
逻辑: 根据一个移动方向,得到新的头部位置,然后从尾部到头部,每一个节点都更新成上一个节点,因为Snake中有个BodyArray ={} ,各个body都用这个table存储着
显示如下:
===
我们希望蛇能够灵活点,我们用鼠标,键盘能够控制它(当然,智能机上没有按键的,我们在桌面开发),我们应该也知道各种事件,事件监听等概念。
MainScene中添加成员方法
local function vector2Dir(x, y)
if math.abs(x) > math.abs(y) then
if x < 0 then
return "left"
else
return "right"
end
else
if y > 0 then
return "up"
else
return "down"
end
end
end
-- 鼠标点击事件处理
function MainScene:ProcessInput()
local function onTouchBegan(touch, event)
local location = touch:getLocation() -- 得到触摸点坐标(cocos2d-x 坐标)
-- 判断移动的方向
local snakex , snakey = self.snake:GetHeadGrid()
local snake_fx,snake_fy = Grid2Pos(snakex,snakey)
local finalX = location.x - snake_fx
local finalY = location.y - snake_fy
local dir = vector2Dir(finalX, finalY)
print("now dir",dir)
self.snake:setDir(dir) -- 设置蛇的移动方向
end
local listener = cc.EventListenerTouchOneByOne:create()
listener:registerScriptHandler(onTouchBegan, cc.Handler.EVENT_TOUCH_BEGAN)
local eventDispatcher = self:getEventDispatcher()
eventDispatcher:addEventListenerWithSceneGraphPriority(listener, self)
end
定义一个事件监听,并注册,分发事件等
onTouchBegan
onTouchMoved
onTouchEnded;
这三个是要重写的,跟cocos2d-x C++, Android中的事件处理都差不多的。
那么我们重写了onTouchBegan() ,里面的逻辑就是 根据 鼠标点击点 和 蛇头 相对位置,判断蛇该往哪个方向,更新蛇的移动方向就行了
别忘了MainScene:onEnter() 调用此方法
function MainScene:onEnter() -- MainScene 加载执行
self:ProcessInput() -- 鼠标touch事件
self.snake = Snake.new(self) -- 创建一条蛇
local tick = function()
self.snake:Update() -- 更新蛇
end -- end tick
cc.Director:getInstance():getScheduler():scheduleScriptFunc(
tick,cMoveSpeed,false)
end
运行截图
有了鼠标控制,自然键盘控制也不应该成为一个问题
同样的MainScene中 添加一个事件处理 成员函数
-- 按键事件处理
function MainScene:ProcessKeyInput()
local function keyboardPressed(keyCode,event)
-- up
if keyCode == 28 then
print("up")
self.snake:setDir("up") -- 设置蛇的移动方向
-- down
elseif keyCode == 29 then
print("down")
self.snake:setDir("down") -- 设置蛇的移动方向
--left
elseif keyCode == 26 then
print("left")
self.snake:setDir("left") -- 设置蛇的移动方向
--right
elseif keyCode == 27 then
print("right")
self.snake:setDir("right") -- 设置蛇的移动方向
end
end
local listener = cc.EventListenerKeyboard:create()
listener:registerScriptHandler(keyboardPressed, cc.Handler.EVENT_KEYBOARD_PRESSED)
local eventDispatcher = self:getEventDispatcher()
eventDispatcher:addEventListenerWithSceneGraphPriority(listener,self)
end
MainScene:onEnter() 调用此方法
function MainScene:onEnter() -- MainScene 加载执行
self:ProcessInput() -- 鼠标touch事件
self:ProcessKeyInput() -- 键盘控制
self.snake = Snake.new(self) -- 创建一条蛇
local tick = function()
self.snake:Update() -- 更新蛇
end -- end tick
cc.Director:getInstance():getScheduler():scheduleScriptFunc(
tick,cMoveSpeed,false)
end
就这样就行了
按下方向键盘,试一下,可以多打打日志,调一调,应该没问题。
===
上例中,我们看到蛇是可以跑出屏幕的,我们需要给蛇弄个围墙,只能在围墙内任意走动,撞到了墙 就死了
编写src/app/Fence.lua
需要一个矩形围墙
能够给出一个撞墙的判断函数
local Snake = require("app.Snake")
local Fence = class("Fence")
local function fenceGenerator(node, bound, callback)
for i = -bound, bound do
local sp = cc.Sprite:create("fence.png")
local posx,posy = callback(i)
sp:setPosition(posx,posy)
node:addChild(sp)
end
end
function Fence:ctor(rowBound, colBound, node)
self.rowBound = rowBound -- 屏幕中心往上或下 有 几个格子
self.colBound = colBound -- 屏幕中心往左或右 有 几个格子
-- up
fenceGenerator(node, colBound,function(i)
return Grid2Pos(i, rowBound)
end)
-- down
fenceGenerator(node, colBound,function(i)
return Grid2Pos(i, -rowBound)
end)
-- left
fenceGenerator(node, rowBound,function(i)
return Grid2Pos(-colBound, i)
end)
-- right
fenceGenerator(node, rowBound,function(i)
return Grid2Pos(colBound, i)
end)
end
-- 判断是否与围墙相撞
function Fence:CheckCollide(x,y)
return x == self.colBound or
x == -self.colBound or
y == self.rowBound or
y == -self.rowBound
end
return Fence
程序需要传递 行,列(其实只是最终围墙的一半),父节点(MainScene控制的,就是MainScene类了)
接着 MainScene.lua 添加显示
在原来的基础上添加,部分代码如下
local Fence = require("app.Fence")
local MainScene = class("MainScene", function()
return display.newScene("MainScene")
end)
local cMoveSpeed = 0.3
local rowBound = 5
local colBound = 8
function MainScene:onEnter() -- MainScene 加载执行
self:ProcessInput() -- 鼠标touch事件
self:ProcessKeyInput() -- 键盘控制
self.snake = Snake.new(self) -- 创建一条蛇
self.fence = Fence.new(rowBound, colBound, self) -- 创建围墙
local tick = function()
self.snake:Update() -- 更新蛇
end -- end tick
cc.Director:getInstance():getScheduler():scheduleScriptFunc(
tick,cMoveSpeed,false)
end
require, 所需变量的定义, onEnter()方法定义出fence, 这几点注意就行了
运行图如下
MainScene.lua的onter方法中加几句话就行了
local Snake = require("app.Snake")
local Fence = require("app.Fence")
local MainScene = class("MainScene", function()
return display.newScene("MainScene")
end)
local cMoveSpeed = 0.3
local rowBound = 5
local colBound = 8
function MainScene:onEnter() -- MainScene 加载执行
self:ProcessInput() -- 鼠标touch事件
self:ProcessKeyInput() -- 键盘控制
self.snake = Snake.new(self) -- 创建一条蛇
self.fence = Fence.new(rowBound, colBound, self) -- 创建围墙
local tick = function()
self.snake:Update() -- 更新蛇
local headX , headY = self.snake:GetHeadGrid()
if self.fence:CheckCollide(headX,headY) then -- 蛇与围墙相撞
print("collide fence")
end
end -- end tick
cc.Director:getInstance():getScheduler():scheduleScriptFunc(
tick,cMoveSpeed,false)
end
加了下面的几句
local headX , headY = self.snake:GetHeadGrid()
if self.fence:CheckCollide(headX,headY) then -- 蛇与围墙相撞
print("collide fence")
end
因为原来我们都写好了很多方法,比如我们是可以得到蛇的头部坐标的,有了坐标我们当然是可以判断是否撞墙的,这里我加了日志
运行如下:
看到日志了,表明成功了,不过蛇并没有死亡,仍然跑出了边界,因为我们什么都没有做。
死亡的处理会比较复杂,我们要处理
* 死亡后,直接重新开始就行了,可以来个死亡动画后再开始游戏
* 死亡后,界面仍在刷新,我们需要设置一个程序运行状态
* 死亡后,我们希望,蛇能够重新生成,开始(因为我们以后的蛇,如果边长了,我们重来,蛇将依然是初始长度)
local Fence = class("Fence")
function Fence:fenceGenerator(node, bound, callback)
for i = -bound, bound do
local sp = cc.Sprite:create("fence.png")
local posx,posy = callback(i)
sp:setPosition(posx,posy)
node:addChild(sp)
table.insert(self.fenceSpArray,sp)
end
end
function Fence:ctor(rowBound, colBound, node)
self.rowBound = rowBound -- 屏幕中心往上或下 有 几个格子
self.colBound = colBound -- 屏幕中心往左或右 有 几个格子
self.fenceSpArray = {}
self.node = node
-- up
self:fenceGenerator(node, colBound,function(i)
return Grid2Pos(i, rowBound)
end)
-- down
self:fenceGenerator(node, colBound,function(i)
return Grid2Pos(i, -rowBound)
end)
-- left
self:fenceGenerator(node, rowBound,function(i)
return Grid2Pos(-colBound, i)
end)
-- right
self:fenceGenerator(node, rowBound,function(i)
return Grid2Pos(colBound, i)
end)
end
-- 判断是否与围墙相撞
function Fence:CheckCollide(x,y)
return x == self.colBound or
x == -self.colBound or
y == self.rowBound or
y == -self.rowBound
end
function Fence:Reset()
for _,sp in ipairs(self.fenceSpArray) do
self.node:removeChild(sp)
end
end
return Fence
改动:
类似 Snake一样 , 用了table 存储了围墙Sprite
添加了一个Rest() 成员方法,用来销毁Fence,注意要保存父节点node
-- 死亡之后的闪烁效果
function Snake:Blink(callback)
for index,body in ipairs (self.BodyArray) do
local blink = cc.Blink:create(3,5)
if index == 1 then -- 蛇头
local a = cc.Sequence:create(blink, cc.CallFunc:create(callback))
body.sp:runAction(a)
else
body.sp:runAction(blink) -- 蛇身
end
end -- for
end
-- 死亡销毁
function Snake:Kill()
for _,body in ipairs(self.BodyArray) do
self.node:removeChild(body.sp)
end
end
其中 Blink 函数中用到了cc.Blink 动画,让蛇闪烁
直接贴上到目前为止MainScene修改后的整个代码
local cGridSize = 33
local scaleRate = 1 / display.contentScaleFactor
-- 根据自定义的坐标得到实际应该显示的cocos2d-x坐标位置
function Grid2Pos(x,y)
local visibleSize = cc.Director:getInstance():getVisibleSize() -- 获取整个手机可视屏幕尺寸
local origin = cc.Director:getInstance():getVisibleOrigin() -- 获取手机可视屏原点的坐标,屏幕的左上角
local finalX = origin.x + visibleSize.width / 2 + x * cGridSize * scaleRate
local finalY = origin.y + visibleSize.height / 2 + y * cGridSize * scaleRate
return finalX,finalY
end
-- require相应的类
-- local Body = require("app.Body")
local Snake = require("app.Snake")
local Fence = require("app.Fence")
local MainScene = class("MainScene", function()
return display.newScene("MainScene")
end)
local cMoveSpeed = 0.3
local rowBound = 5
local colBound = 8
function MainScene:onEnter() -- MainScene 加载执行
self:ProcessInput() -- 鼠标touch事件
self:ProcessKeyInput() -- 键盘控制
self:Reset()
--self.snake = Snake.new(self) -- 创建一条蛇
--self.fence = Fence.new(rowBound, colBound, self) -- 创建围墙
local tick = function()
if self.stage == "running" then
self.snake:Update() -- 更新蛇
local headX , headY = self.snake:GetHeadGrid()
if self.fence:CheckCollide(headX,headY) then -- 蛇与围墙相撞
self.stage = "dead"
self.snake:Blink(function()
self:Reset()
end)
end
end
end -- end tick
cc.Director:getInstance():getScheduler():scheduleScriptFunc(
tick,cMoveSpeed,false)
end
local function vector2Dir(x, y)
if math.abs(x) > math.abs(y) then
if x < 0 then
return "left"
else
return "right"
end
else
if y > 0 then
return "up"
else
return "down"
end
end
end
-- 鼠标点击事件处理
function MainScene:ProcessInput()
local function onTouchBegan(touch, event)
local location = touch:getLocation() -- 得到触摸点坐标(cocos2d-x 坐标)
-- 判断移动的方向
local snakex , snakey = self.snake:GetHeadGrid()
local snake_fx,snake_fy = Grid2Pos(snakex,snakey)
local finalX = location.x - snake_fx
local finalY = location.y - snake_fy
local dir = vector2Dir(finalX, finalY)
print("now dir",dir)
self.snake:setDir(dir) -- 设置蛇的移动方向
end
local listener = cc.EventListenerTouchOneByOne:create()
listener:registerScriptHandler(onTouchBegan, cc.Handler.EVENT_TOUCH_BEGAN)
local eventDispatcher = self:getEventDispatcher()
eventDispatcher:addEventListenerWithSceneGraphPriority(listener, self)
end
-- 按键事件处理
function MainScene:ProcessKeyInput()
local function keyboardPressed(keyCode,event)
-- up
if keyCode == 28 then
print("up")
self.snake:setDir("up") -- 设置蛇的移动方向
-- down
elseif keyCode == 29 then
print("down")
self.snake:setDir("down") -- 设置蛇的移动方向
--left
elseif keyCode == 26 then
print("left")
self.snake:setDir("left") -- 设置蛇的移动方向
--right
elseif keyCode == 27 then
print("right")
self.snake:setDir("right") -- 设置蛇的移动方向
end
end
local listener = cc.EventListenerKeyboard:create()
listener:registerScriptHandler(keyboardPressed, cc.Handler.EVENT_KEYBOARD_PRESSED)
local eventDispatcher = self:getEventDispatcher()
eventDispatcher:addEventListenerWithSceneGraphPriority(listener,self)
end
-- 游戏结束操作
function MainScene:Reset()
if self.snake ~= nil then
self.snake:Kill()
end
if self.fence ~= nil then
self.fence:Reset()
end
self.snake = Snake.new(self) -- 创建一条蛇
self.fence = Fence.new(rowBound, colBound, self) -- 创建围墙
self.stage = "running"
end
return MainScene
改动,
我们定义了Reset()成员方法, 并且给程序运行时加了state状态
在onEnter()中, 直接调用Reset()方法,就可以重新创建 蛇和围墙了
tick() 函数中调用了 snake的死亡闪烁动画,并且设置了state = “dead”
MainScene始终控制着整个程序的逻辑
看下运行截图,小蛇会死,然后重新开始
===
跟Fence, Snake差不多,编写AppleFactory.lua
local AppleFactory = class("AppleFactory")
function AppleFactory:ctor(rowBound, colBound, node)
self.rowBound = rowBound
self.colBound = colBound
self.node = node
math.randomseed(os.time())
self:Generate()
end
local function getRandomPos(rowBound,colBound)
local randomX = math.random(-colBound, colBound)
local randomY = math.random(-rowBound, rowBound)
return randomX, randomY
end
function AppleFactory:Generate()
if self.appleSprite ~= nil then
self.node:removeChild(self.appleSprite) -- 销毁对象
end
local sp = cc.Sprite:create("apple.png")
local x, y = getRandomPos(self.rowBound - 1, self.colBound-1)
local finalX, finalY = Grid2Pos(x,y)
sp:setPosition(finalX, finalY)
self.node:addChild(sp)
self.appleX = x
self.appleY = y
self.appleSprite = sp
end
function AppleFactory:CheckCollide(x,y)
if x == self.appleX and y == self.appleY then
return true
end
return false
end
function AppleFactory:Reset()
self.node:removeChild(self.appleSprite)
end
return AppleFactory
围墙内 产生两个随机点用来显示苹果
在上一步的MainScene.lua 的 Reset()成员方法 加上几句话就行了
-- 游戏结束操作
function MainScene:Reset()
if self.apple ~= nil then
self.apple:Reset()
end
if self.fence ~= nil then
self.fence:Reset()
end
if self.snake ~= nil then
self.snake:Kill()
end
self.snake = Snake.new(self) -- 创建一条蛇
self.fence = Fence.new(rowBound, colBound, self) -- 创建围墙
self.apple = AppleFactory.new(rowBound, colBound, self) -- 创建apple
self.stage = "running"
end
注意需要
local AppleFactory = require("app.AppleFactory")
显示如下
===
在上面的基础上,有了苹果,当蛇头和苹果相撞时,表示蛇吃苹果,那么蛇需要变长,并且需要重新产生苹果
修改MainScene.lua中的onEnter()方法, 添加吃苹果逻辑
function MainScene:onEnter() -- MainScene 加载执行
self:ProcessInput() -- 鼠标touch事件
self:ProcessKeyInput() -- 键盘控制
self:Reset()
--self.snake = Snake.new(self) -- 创建一条蛇
--self.fence = Fence.new(rowBound, colBound, self) -- 创建围墙
local tick = function()
if self.stage == "running" then
self.snake:Update() -- 更新蛇
local headX , headY = self.snake:GetHeadGrid()
if self.fence:CheckCollide(headX,headY) then -- 蛇与围墙相撞
self.stage = "dead"
self.snake:Blink(function()
self:Reset()
end)
elseif self.apple:CheckCollide(headX,headY) then
self.apple:Generate() -- 苹果重新产生
self.snake:Grow() -- 蛇变长
end
end
end -- end tick
cc.Director:getInstance():getScheduler():scheduleScriptFunc(
tick,cMoveSpeed,false)
end
===
MainScene.lua中添加显示分数版的成员方法
-- 创建显示分数
function MainScene:CreateScoreBoard()
display.newSprite("applesign.png")
:pos(display.right - 200 , display.cy + 150)
:addTo(self)
local ttfConfig = {}
ttfConfig.fontFilePath = "arial.ttf"
ttfConfig.fontSize = 30
local score = cc.Label:createWithTTF(ttfConfig, "0")
self:addChild(score)
score:setPosition(display.right - 200 , display.cy + 80)
self.scoreLabel = score
end
-- 设置分数
function MainScene:SetScore(s)
self.scoreLabel:setString(string.format("%d",s))
end
修改MainScene.lua中的 onEnter()和Reset()方法
function MainScene:Reset()
if self.apple ~= nil then
self.apple:Reset()
end
if self.fence ~= nil then
self.fence:Reset()
end
if self.snake ~= nil then
self.snake:Kill()
end
self.snake = Snake.new(self) -- 创建一条蛇
self.fence = Fence.new(rowBound, colBound, self) -- 创建围墙
self.apple = AppleFactory.new(rowBound, colBound, self) -- 创建apple
self.stage = "running"
self.score = 0
self:SetScore(self.score) -- 分数设置
end
function MainScene:onEnter() -- MainScene 加载执行
self:CreateScoreBoard() -- 分数版创建
self:ProcessInput() -- 鼠标touch事件
self:ProcessKeyInput() -- 键盘控制
self:Reset()
--self.snake = Snake.new(self) -- 创建一条蛇
--self.fence = Fence.new(rowBound, colBound, self) -- 创建围墙
local tick = function()
if self.stage == "running" then
self.snake:Update() -- 更新蛇
local headX , headY = self.snake:GetHeadGrid()
if self.fence:CheckCollide(headX,headY) then -- 蛇与围墙相撞
self.stage = "dead"
self.snake:Blink(function()
self:Reset()
end)
elseif self.apple:CheckCollide(headX,headY) then
self.apple:Generate() -- 苹果重新产生
self.snake:Grow() -- 蛇变长
self.score = self.score + 1 -- 分数加1
self:SetScore(self.score) --设置分数
end
end
end -- end tick
cc.Director:getInstance():getScheduler():scheduleScriptFunc(
tick,cMoveSpeed,false)
end
运行如下图
到目前为止,游戏基本能玩了,不过会发现问题有很多,目前版本的源码下载地址如下
http://download.csdn.net/detail/qq_26437925/9569936
参考学习网址:慕课网 Cocos2d-x游戏开发之贪吃蛇
我上面的程序,在老师讲的基础上做了修改。只要自己思考,并且不断的敲出来的,才是自己的。
TODO solve bugs
==
如果设原来往左移动,那么设置方向的时候肯定不能往右移动,同理上下,所以是个互斥的
只要修改下Snake.lua 的设置方向的成员方法,改成如下
-- 设置方向
function Snake:setDir(dir)
local hvTable = {
["left"] = "h",
["right"] = "h",
["up"] = "v",
["down"] = "v",
}
-- 水平 ,垂直的互斥
if hvTable[dir] == hvTable[self.MoveDir] then
return
else
self.MoveDir = dir
end
end
蛇头有个方向,所以移动的设置,需要改变项蛇头方向,可以旋转精灵,当然可以制作出不同的图片,用来设置
也是只需改变 Snake.lua中的setDir()方法
-- 设置方向
function Snake:setDir(dir)
local hvTable = {
["left"] = "h",
["right"] = "h",
["up"] = "v",
["down"] = "v",
}
if hvTable[dir] == hvTable[self.MoveDir] then
return
else
self.MoveDir = dir
-- 取出蛇头
local head = self.BodyArray[1]
-- 顺时针旋转,初始方向为left
local rotTable ={
["left"] = 0,
["up"] = 90,
["right"] = 180,
["down"] = -90,
}
-- 让精灵图片旋转,以改变显示
head.sp:setRotation(rotTable[self.MoveDir])
end
end
往下移动后显示如下,蛇头往下
蛇迟到自己就死亡了,就是判断蛇头是否与蛇身体有碰撞
在Snake.lua中 添加一个成员方法即可
function Snake:CheckCollideSelf()
if #self.BodyArray < 2 then
return false
end
local headX, headY = self.BodyArray[1].X , self.BodyArray[1].Y
for i = 2, #self.BodyArray do
local body = self.BodyArray[i]
if body.X == headX and body.Y == headY then
return true;
end
end
return false
end
然后在 MainScene.lua 逻辑中添加吃掉自己的逻辑,修改onEnter()
先在变成撞墙和自杀 两种死亡模式
function MainScene:onEnter() -- MainScene 加载执行
self:CreateScoreBoard() -- 分数版
self:ProcessInput() -- 鼠标touch事件
self:ProcessKeyInput() -- 键盘控制
self:Reset()
--self.snake = Snake.new(self) -- 创建一条蛇
--self.fence = Fence.new(rowBound, colBound, self) -- 创建围墙
local tick = function()
if self.stage == "running" then
self.snake:Update() -- 更新蛇
local headX , headY = self.snake:GetHeadGrid()
if self.fence:CheckCollide(headX,headY)
or self.snake:CheckCollideSelf()
then -- 蛇与围墙相撞 或 自杀
self.stage = "dead"
self.snake:Blink(function()
self:Reset()
end)
elseif self.apple:CheckCollide(headX,headY) then
self.apple:Generate() -- 苹果重新产生
self.snake:Grow() -- 蛇变长
self.score = self.score + 1 -- 分数加1
self:SetScore(self.score)
end
end
end -- end tick
cc.Director:getInstance():getScheduler():scheduleScriptFunc(
tick,cMoveSpeed,false)
end
如下如,吃到自己
====
参看API文档
http://api.cocos.com/cn/d7/df3/classcocos2d_1_1_director.html
本次暂停,直接调用了Director的pause(),resume()方法
所以在MainScene的键盘事件处理中,用P键 和 R键 来控制正在运行游戏的暂停和恢复
-- 按键事件处理
function MainScene:ProcessKeyInput()
local function keyboardPressed(keyCode,event)
-- up or W
if keyCode == 28 or keyCode == 146 then
print("up")
self.snake:setDir("up") -- 设置蛇的移动方向
-- down or S
elseif keyCode == 29 or keyCode == 142 then
print("down")
self.snake:setDir("down") -- 设置蛇的移动方向
--left or A
elseif keyCode == 26 or keyCode == 124 then
print("left")
self.snake:setDir("left") -- 设置蛇的移动方向
--right or D
elseif keyCode == 27 or keyCode == 127 then
print("right")
self.snake:setDir("right") -- 设置蛇的移动方向
--end
-- P
elseif keyCode == 139 then
print("P -- Pause")
local director = cc.Director:getInstance()
director:pause() -- 暂停
--end
-- R
elseif keyCode == 141 then
local director = cc.Director:getInstance()
director:resume() -- 恢复
end
end
local listener = cc.EventListenerKeyboard:create()
listener:registerScriptHandler(keyboardPressed, cc.Handler.EVENT_KEYBOARD_PRESSED)
local eventDispatcher = self:getEventDispatcher()
eventDispatcher:addEventListenerWithSceneGraphPriority(listener,self)
end
采用跟添加苹果类似的简单程序,因为是可以添加很多障碍物的,所以写好障碍物Block类后,需要写个BlockFactory类,产生多个Block。
在原来的基础上src/app下添加Block.lua,BlockFactory.lua,这里有些和别的文件重复的代码(需要优化,目前先不管)
Block.lua
local cGridSize = 33
local scaleRate = 1 / display.contentScaleFactor
-- 根据自定义的坐标得到实际应该显示的cocos2d-x坐标位置
function Grid2Pos(x,y)
local visibleSize = cc.Director:getInstance():getVisibleSize() -- 获取整个手机可视屏幕尺寸
local origin = cc.Director:getInstance():getVisibleOrigin() -- 获取手机可视屏原点的坐标,屏幕的左上角
local finalX = origin.x + visibleSize.width / 2 + x * cGridSize * scaleRate
local finalY = origin.y + visibleSize.height / 2 + y * cGridSize * scaleRate
return finalX,finalY
end
local cMoveSpeed = 0.3
local rowBound = 5
local colBound = 8
-------------------------------------------
local Block = class("Block")
function Block:ctor(node)
self.node = node
end
-- 设置当前的障碍物 index = 1,2,3 ..依据自己的障碍物图片数目
function Block:Set(index)
if self.sp ~= nil then
self.node:removeChild(self.sp)
end
self.index = index
self.sp = display.newSprite(string.format("block%d.png",index)) --转换图片地址
self.node:addChild(self.sp)
end
-- 设置位置
function Block:SetPos(x, y)
local rbound = rowBound -1
local cbound = colBound -1
local posx , posy = Grid2Pos(x, y)
self.sp:setPosition(posx, posy)
self.x = x
self.y = y
end
-- 清除对象
function Block:Clear()
self.node:removeChild(self.sp)
end
function Block:CheckCollide(x,y)
if x == self.x and y == self.y then
return true
end
return false
end
return Block
BlockFactory.lua
local BlockFactory = class("BlockFactory")
local Block = require("app.Block")
function BlockFactory:ctor(node)
self.BlockArray = {} --障碍物集合
self.node = node
end
-- 在x,y位置添加一个序号为index的障碍物
function BlockFactory:Add(x, y, index)
local b = Block.new(self.node)
b:Set(index)
b:SetPos(x, y)
table.insert(self.BlockArray,b)
end
function BlockFactory:Remove(x,y)
local b,index = self:Hit(x,y)
if b ~= nil then
b:Clear()
table.remove(self.BlockArray, index)
end
end
function BlockFactory:Hit(x,y)
for index, b in ipairs(self.BlockArray) do
if b:CheckCollide(x, y) then
return b, index
end
end
return nil,-1
end
function BlockFactory:Reset()
for _,b in ipairs(self.BlockArray) do
b:Clear()
end
end
return BlockFactory
同样的可以在MainScene.lua中验证测试,require(“…”)不要忘记
MainScene:Reset()中,添加障碍物
-- 游戏结束操作
function MainScene:Reset()
if self.apple ~= nil then
self.apple:Reset()
end
if self.fence ~= nil then
self.fence:Reset()
end
if self.snake ~= nil then
self.snake:Kill()
end
-- 添加障碍物
if self.blocks ~= nil then
self.blocks:Reset()
end
self.blocks = BlockFactory.new(self)
self.blocks:Add(1,1,1)
self.blocks:Add(-3,1,2)
self.snake = Snake.new(self) -- 创建一条蛇
self.fence = Fence.new(rowBound, colBound, self) -- 创建围墙
self.apple = AppleFactory.new(rowBound, colBound, self) -- 创建apple
self.stage = "running"
self.score = 0
self:SetScore(self.score)
end
还要在collide中可以自己要检测碰撞
截个图
场景编辑器的书写,可以利用光标移动,按键等来做设置。仿照MainScene.lua 写
文件可以直接存储为.lua文件, 然后利用require dofile等加载出来
EditorScene.lua
local cGridSize = 33
local scaleRate = 1 / display.contentScaleFactor
-- 根据自定义的坐标得到实际应该显示的cocos2d-x坐标位置
function Grid2Pos(x,y)
local visibleSize = cc.Director:getInstance():getVisibleSize() -- 获取整个手机可视屏幕尺寸
local origin = cc.Director:getInstance():getVisibleOrigin() -- 获取手机可视屏原点的坐标,屏幕的左上角
local finalX = origin.x + visibleSize.width / 2 + x * cGridSize * scaleRate
local finalY = origin.y + visibleSize.height / 2 + y * cGridSize * scaleRate
return finalX,finalY
end
local cMoveSpeed = 0.3
local rowBound = 5
local colBound = 8
-------------------------------------------
local Fence = require("app.Fence")
local Block = require("app.Block")
local BlockFactory = require("app.BlockFactory")
local EditorScene = class("EditorScene", function()
return display.newScene("EditorScene")
end)
local cMaxBlock = 3 -- 障碍物数目
function EditorScene:onEnter()
self.fence = Fence.new(rowBound, colBound,self) --围墙
-- 鼠标光标位置 初始化为左上角
self.curX = 0
self.curY = 0
self.curIndex = 0 -- 选择的物体序号
self:SwitchCursor(1)
self:ProcessInput()
self.blockFactory = BlockFactory.new(self)
end
-- 按键事件处理
function EditorScene:ProcessInput()
local function keyboardPressed(keyCode,event)
-- up
if keyCode == 28 then
self:MoveCursor(0,1)
-- down
elseif keyCode == 29 then
self:MoveCursor(0,-1)
--left
elseif keyCode == 26 then
self:MoveCursor(-1,0)
--right
elseif keyCode == 27 then
self:MoveCursor(1,0)
-- pageUp
elseif keyCode == 38 then
self:SwitchCursor(-1)
-- pageDown
elseif keyCode == 44 then
self:SwitchCursor(1)
-- enter
elseif keyCode == 35 then
self:Place()
-- delete
elseif keyCode == 23 then
self:Delete()
-- F3 加载文件
elseif keyCode == 49 then
self:Load()
-- F4 保存为文件
elseif keyCode == 50 then
self:Save()
end
end
local listener = cc.EventListenerKeyboard:create()
listener:registerScriptHandler(keyboardPressed, cc.Handler.EVENT_KEYBOARD_PRESSED)
local eventDispatcher = self:getEventDispatcher()
eventDispatcher:addEventListenerWithSceneGraphPriority(listener,self)
end
-- 上下左右 移动光标
function EditorScene:MoveCursor(deltaX,deltaY)
self.cur:SetPos(self.curX + deltaX, self.curY+deltaY)
self.curX = self.cur.x
self.curY = self.cur.y
end
-- 可以切换障碍物 delta=1 为下一个物体 ,-1 为上一个障碍物
function EditorScene:SwitchCursor(delta)
if self.cur == nil then
self.cur = Block.new(self)
end
local newIndex = self.curIndex + delta
newIndex = math.max(newIndex, 1)
newIndex = math.min(newIndex, cMaxBlock)
self.curIndex = newIndex
self.cur:Set(newIndex)
self.cur:SetPos(self.curX, self.curY)
end
-- 放置一个物体
function EditorScene:Place()
if self.blockFactory:Hit(self.cur.x,self.cur.y) then
return
end
self.blockFactory:Add(self.curX, self.curY, self.cur.index)
end
-- 删除物体
function EditorScene:Delete()
self.blockFactory:Remove(self.cur.x, self.cur.y)
end
-- lua文件存盘 F4
function EditorScene:Save()
local f = assert(io.open("scene.lua", "w"))
f:write("return {\n")
self.blockFactory:Save(f)
f:write("}\n")
f:close()
print("saved")
end
-- F3
function EditorScene:Load()
local f = assert(dofile("scene.lua"))
self.blockFactory:Reset()
for _,t in ipairs(f) do
self.blockFactory:Add(t.x, t.y, t.index)
end
print("Loaded")
end
return EditorScene
BlockFactory.lua 来设置写入文件的数据格式
-- 写入文件
function BlockFactory:Save(f)
for _,b in ipairs(self.BlockArray) do
f:write(string.format("{x=%d,y=%d,index=%d},\n",b.x,b.y,b.index ))
end
end
保存的scene.lua文件内容
return {
{x=-4,y=1,index=1},
{x=-4,y=-1,index=1},
{x=1,y=1,index=2},
{x=2,y=-2,index=3},
}
上一步中写好场景后,可以在MainScene中加载,这样就能利用自己的场景了
写个加载文件的方法,其实就是让blocks添加障碍物
-- 加载文件
function MainScene:Load()
local f = assert(dofile("scene.lua"))
self.blocks:Reset()
for _,t in ipairs(f) do
self.blocks:Add(t.x, t.y, t.index)
end
print("main Loaded")
end
MainScene.lua中的reset方法对blocks的写法改一下
-- 添加障碍物
if self.blocks ~= nil then
self.blocks:Reset()
end
self.blocks = BlockFactory.new(self)
self:Load() -- 这利用文件加载