cocos2d-x lua 貪吃蛇完整專案
學習視訊見慕課網
開發環境,專案編譯執行
===
解壓到某個目錄下就行了,setup.py設定環境
切換到如下的目錄,既可以建立lua專案
shift +滑鼠右鍵,在該目錄下開啟cmd,輸入
cocos.py new Snake -l lua -d d:\
可以在D:\下建立一個Snake 專案,這些都是可選的,自己隨便玩
接著用vs2012 或者vs2013 開啟如下的解決方案,並完成編譯
最後可以執行出結果,可在如下的目錄中執行,點選exe即可
有模擬器 和 日誌兩個視窗,在實際程式設計中需要多打日誌 和 檢視日誌 分析問題,並解決。
Hello World
===
類似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
因為繼承了場景類,所以需要重寫一些方法
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
因為原來我們都寫好了很多方法,比如我們是可以得到蛇的頭部座標的,有了座標我們當然是可以判斷是否撞牆的,這裡我加了日誌
執行如下:
看到日誌了,表明成功了,不過蛇並沒有死亡,仍然跑出了邊界,因為我們什麼都沒有做。
死亡動畫
死亡的處理會比較複雜,我們要處理
* 死亡後,直接重新開始就行了,可以來個死亡動畫後再開始遊戲
* 死亡後,介面仍在重新整理,我們需要設定一個程式執行狀態
* 死亡後,我們希望,蛇能夠重新生成,開始(因為我們以後的蛇,如果邊長了,我們重來,蛇將依然是初始長度)
Fence.lua
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
Snake.lua 新增兩個成員方法
-- 死亡之後的閃爍效果
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.lua
直接貼上到目前為止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