目錄
小遊戲介紹
貪喫蛇小遊戲:貪喫蛇試玩(首次加載比較慢),類似貪喫蛇大作戰的小遊戲。當玩家的蛇頭碰到另一條蛇蛇身時死亡,遊戲結束。
小遊戲cocos creator場景圖
小遊戲的cocos creator節點結構如下圖,Canvas上面的節點是單例(DataManager SoundManager Global WeiXinPlatform)
小遊戲部分JS代碼
小遊戲操控搖桿的JS代碼參考github上的,https://github.com/dreeye/Joystick
一個全局變量節點Global.js,保存了其它單例的指針,遊戲中通過GameGlobal調用其它單例的接口
cc.Class({
extends: cc.Component,
properties: {
//數據管理器
DataManager:
{
default: null,
type: cc.Component,
},
//聲音管理器
SoundManager:
{
default: null,
type: cc.Component,
},
//UI管理器
UIManager:
{
default: null,
type: cc.Component,
},
//Game
Game:
{
default: null,
type: cc.Component,
},
//
Net:
{
default: null,
type: cc.Component,
},
//
WeiXinPlatform:
{
default: null,
type: cc.Component,
},
NameList:[],
//單機測試版
IsSingleMode: true,
//統一爲微信QQ的localStorage
localStorage: null,
//版本號:
GameVersion: '1.0.0'
},
// LIFE-CYCLE CALLBACKS:
onLoad ()
{
this.DataManager = cc.find('DataManager').getComponent('DataManager');
this.SoundManager = cc.find('SoundManager').getComponent('SoundManager');
this.UIManager = cc.find('Canvas/UIManager').getComponent('UIManager');
this.Game = cc.find('Game').getComponent('Game');
this.Net = cc.find('Net').getComponent('Net');
this.WeiXinPlatform = cc.find('WeiXinPlatform').getComponent('WeiXinPlatform');
this.localStorage =
{
getItem(itemKey)
{
if(cc.sys.platform == cc.sys.WECHAT_GAME)
{
}
else if(cc.sys.platform == cc.sys.QQ_PLAY)
{
//return BK.localStorage.getItem(itemKey);
}else
{
return cc.sys.localStorage.getItem(itemKey);
}
} ,
setItem(itemKey, item)
{
if(cc.sys.platform == cc.sys.WECHAT_GAME)
{
}
else if(cc.sys.platform == cc.sys.QQ_PLAY)
{
//return BK.localStorage.setItem(itemKey, item);
}else
{
return cc.sys.localStorage.setItem(itemKey, item);
}
}
};
window.GameGlobal = this;
},
start ()
{
},
// update (dt) {},
});
UIManager.JS UI的管理類
//UI管理類(處理打開與關閉, TODO: 管理UI層級, UI的打開方式)
var UIType = require("UIType");
cc.Class({
extends: cc.Component,
properties: {
//
UIList:
{
default: [],
type: [cc.Node],
},
//通用遮罩
BgMaskSprite:
{
default: null,
type: cc.Sprite,
},
},
// LIFE-CYCLE CALLBACKS:
// onLoad () {},
onEnable()
{
this.BgMaskSprite.node.active = false;
},
start ()
{
},
//獲取UI對應的腳本
getUIScriptName(uiType)
{
var scriptList = ['UIHall', 'UIGame', 'UIGameOver', 'UILoading', 'UIGameEnd', 'UIShare', 'UIMessageTip',
'UIKeFu', 'UIInviteFriend', 'UIQianDao', 'UISkin', 'UISetting', 'UIZSAd'];
return scriptList[uiType];
},
//是否二級彈窗UI
isPopUI(uiType)
{
if(uiType == UIType.UIType_HallInvite
|| uiType == UIType.UIType_KeFu
|| uiType == UIType.UIType_InviteFriend
|| uiType == UIType.UIType_QianDao
|| uiType == UIType.UIType_Setting
|| uiType == UIType.UIType_RankQQ
|| uiType == UIType.UIType_GameOver
|| uiType == UIType.UIType_GameEnd
)
{
return true;
}
return false;
},
//
showMask(isShow)
{
this.BgMaskSprite.node.active = isShow;
},
//打開UI
openUI(uiType)
{
if(uiType >= this.UIList.length)
{
cc.log("openUI invalid uiType, please check UIList");
return;
}
if(this.UIList[uiType] == null || this.UIList[uiType] == undefined)
{
cc.log("openUI invalid uiType, object null");
return;
}
this.UIList[uiType].active = true;
//二級彈窗UI,顯示遮罩
if(this.isPopUI(uiType))
{
this.BgMaskSprite.node.active = true;
var actNode = this.UIList[uiType];
actNode.scale = 0;
actNode.runAction(cc.scaleTo(0.1, 1).easing(cc.easeSineIn()))
}
if(this.isPopUI(uiType) || uiType == UIType.UIType_Skin)
{
//關閉大廳廣告的處理
var uihall = this.getUI(UIType.UIType_Hall);
if(uihall != null && uihall.node.active)
{
uihall.pauseAdShow();
}
}
},
//關閉UI
closeUI(uiType)
{
if(uiType >= this.UIList.length)
{
cc.log("closeUI invalid uiType, please check UIList");
return;
}
//二級彈窗UI,關閉遮罩
if(this.isPopUI(uiType))
{
this.BgMaskSprite.node.active = false;
this.UIList[uiType].active = false;
//var actNode = this.UIList[uiType];
//actNode.scale = 1;
// var callbackClose = cc.callFunc(this.onCloseUI, this, uiType);
// var seq = cc.sequence(cc.scaleTo(0.2, 0).easing(cc.easeBounceOut(3) ), callbackClose)
// actNode.runAction(seq)
}else
{
this.UIList[uiType].active = false;
}
if(this.isPopUI(uiType) || uiType == UIType.UIType_Skin)
{
//QQ廣告恢復展示
var uihall = this.getUI(UIType.UIType_Hall);
if(uihall != null && uihall.node.active)
{
uihall.resumeAdShow();
}
}
},
//關閉UI
onCloseUI(node, uiType)
{
this.UIList[uiType].active = false;
},
//獲取某個UI
getUI(uiType)
{
if(uiType >= this.UIList.length)
{
cc.log("closeUI invalid uiType, please check UIList");
return;
}
return this.UIList[uiType].getComponent(this.getUIScriptName(uiType));
},
//顯示某個消息
showMessage(msg)
{
var uiMessageTip = this.getUI(UIType.UIType_ShowMessage);
if(uiMessageTip)
{
uiMessageTip.showMessage(msg);
}
},
//刷新貨幣
RefreshCoin()
{
var hallUI = this.getUI(UIType.UIType_Hall)
hallUI.updateGoldNum()
hallUI.updateDiamondNum()
},
// update (dt) {},
});
Snake.js 遊戲中的蛇 小部分代碼
var SnakeBody = require('SnakeBody');
var SnakeHead = require('SnakeHead');
cc.Class({
//蛇頭
properties:
{
_SnakeIndex: 0,
_HeadType: -1,
_BodyTypeList: [],
_SnakeHead: cc.Node,
_HeadBodyList: [cc.Node],
_Game: null,
_LastMoveVec: cc.v2(1, 0),
_MoveVec: cc.v2(1, 0),
_MoveSpeed: 0,
_BodyWidth: 0,
//增加的重量
_GrowingWeight: 0,
_Camera: null,
_PlayerSelf: false,
_PlayerName: "",
_MapWidth: 0,
_MapHeight: 0,
_BodyTypeList:[],
//擊殺
_KillCount: 0,
//更新時間
_PosUpdateTime: 0,
_HeadPrePositon: cc.v2(1, 0),
//AI 類型
_CurAIType: 1,
_CurAITimer: 2,
_CurAIMoveCount: 0,
_CurTargetDestDir: cc.v2(1, 0),
_CurTargetChangeDir: cc.v2(0, 0),
_CurAITurnSpeed: 3.14,
_State: 0,
_StateTimer: 3,
_AttachLabel: null,
//大廳展示用
_CurShowMoveDistance: 0,
_CurShowMoveStartPos: cc.v2(0, 0),
_ShowMovePosList:[],
_CurMoveIndex: 0,
//躲避概率
//蛇移動路徑列表
_MovePath: [],
_BodySpace: 30,
//保護罩
_GodSprite: null,
},
init(headType, bodyTypeList, parent, bornPos, camera, isSelf, mapWidth, mapHeight, index)
{
this._SnakeIndex = index;
this._State = 0;
this._KillCount = 0;
this._Game = GameGlobal.Game;
this._Camera = camera;
this._PlayerSelf = isSelf;
this._MapWidth = mapWidth;
this._MapHeight = mapHeight;
this._HeadType = headType;
this._BodyTypeList = bodyTypeList;
//蛇頭
this._SnakeHead = this._Game.GetFreeHead();
this._SnakeHead.parent = parent;
this._SnakeHead.position = bornPos;
this._SnakeHead.setLocalZOrder(1);
var snakeHead = this._SnakeHead.getComponent(SnakeHead);
snakeHead.setSnake(this);
snakeHead.setType(headType);
if(camera)
{
camera.addTarget(this._SnakeHead)
}
this._CurTargetChangeDir = cc.v2(0.996, 0.0871);
//蛇身
for(var i = 0; i < 5; ++i)
{
var body = this._Game.GetFreeBody();
if(body)
{
this._HeadBodyList.push(body);
body.parent = parent;
var snakeBody = body.getComponent(SnakeBody);
snakeBody.setSnake(this);
snakeBody.setBodyIndex(i);
body.setLocalZOrder(-i);
if(camera)
{
camera.addTarget(body);
}
var typeIndex = (Math.floor(i / 3)) % 2;
snakeBody.setType(bodyTypeList[typeIndex]);
this._BodyWidth = body.width;
}
}
if(index < 10)
{
this._GodSprite = GameGlobal.Game.GetFreeGodSprite();
this._GodSprite.parent = parent;
this._GodSprite.active = false;
camera.addTarget(this._GodSprite)
}
},
setName(name, parentNode)
{
this._PlayerName = name;
this._AttachLabel = this._Game.GetFreeNameLabel();
this._AttachLabel.getComponent(cc.Label).string = name;
this._AttachLabel.parent = parentNode;
this._AttachLabel.position = this._SnakeHead.position;
if(this._Camera)
{
this._Camera.addTarget(this._AttachLabel);
}
},
addKillCount()
{
this._KillCount += 1;
},
//0:普通狀態 1:無敵狀態 (死亡剛復活時,給予2秒的無敵,不參與碰撞檢測)
setState(state)
{
this._State = state;
if(state == 1)
{
this._StateTimer = 3;
// this._GodSprite.active = true;
this.updateGodSpritePos();
//console.log("first ", this._GodSprite.width, this._SnakeIndex);
}else
{
this._GodSprite.active = false;
}
},
//死亡重置
deadReset()
{
//將頭與身體返回到對象池中,並重置數據
this._Game.DelUseHead(this._SnakeHead);
this._Game.DelGodSprite(this._GodSprite);
//TODO:待優化
var len = this._HeadBodyList.length;
for(var i = 0; i < len; ++i)
{
this._Game.DelUseBody(this._HeadBodyList[i]);
}
this._HeadBodyList.splice(0, len);
this._SnakeHead = null;
this._Game.DelUseNameLabel(this._AttachLabel);
},
//
resetPos(bornPos)
{
this._SnakeHead.position = bornPos;
},
initMoveDir(moveDir)
{
moveDir.normalizeSelf();
var len = this._HeadBodyList.length;
var body;
var snakeBody;
for(var i = 0; i < len; ++i)
{
body = this._HeadBodyList[i];
var bodyWidth = body.getContentSize().width;
//cc.log('bodyWidth ', bodyWidth);
snakeBody = body.getComponent(SnakeBody);
snakeBody.setInitMoveDir(moveDir);
if(i == 0)
{
var interval = bodyWidth / 3; //this._SnakeHead.width / 2 + body.width / 2;
// body.position = cc.pAdd(this._SnakeHead.position, cc.pMult(moveDir, interval)); //this._SnakeHead.position;
}
else
{
var interval = - bodyWidth;
//body.position = cc.pAdd(this._HeadBodyList[i - 1].position, cc.pMult(moveDir, interval));
}
}
this._LastMoveVec = moveDir;
this._MoveVec = moveDir;
//頭的方向
var curAngle = Math.atan2(moveDir.y, moveDir.x) * (180/Math.PI);
this._SnakeHead.rotation = -curAngle - 90;
var startPos = this._SnakeHead.position;
//初始化path
this._MovePath = [];
for(let i = 0; i < len * this._BodySpace; ++i)
{
this._MovePath.push(cc.pAdd(startPos, cc.pMult(moveDir, -(i + 1) + 15)));
}
},
//主角的 移動方向 angle:角度 由操作驅動
setMoveDir(offX, offY, delta, angle)
{
if(offX == 0 && offY == 0)
{
return;
}
this._MoveVec.x = offX;
this._MoveVec.y = offY;
this._SnakeHead.rotation = -angle - 90;
//cc.log("setMoveDirangle", angle);
},
//其它玩家的
setOtherMoveDir(x, y)
{
if(x == 0 && y == 0)
{
x = 1;
}
this._MoveVec.x = x;
this._MoveVec.y = y;
this._MoveVec.normalizeSelf();
//頭的方向
var curAngle = Math.atan2(y, x) * (180/Math.PI);
this._SnakeHead.rotation = -curAngle - 90;
//cc.log("setOtherMoveDir ", this._MoveVec.x, this._MoveVec.y, this._SnakeHead.rotation);
},
FixDir(dir)
{
if(dir.x == 0 && dir.y == 0)
{
dir.x = 0.001;
}
return dir;
},
//幀更新
update1(delta)
{
if(delta == 0)
{
delta = 0.017;
}
if(this._SnakeHead == null)
{
return false;
}
//狀態重置
this._StateTimer -= delta;
if(this._State == 1)
{
if(this._StateTimer < 0)
{
this._StateTimer = 0;
this._State = 0;
}
}
//邊界位置判定
if(this._PlayerSelf)
{
if(Math.abs(this._SnakeHead.x) > this._MapWidth / 2 || Math.abs(this._SnakeHead.y) > this._MapHeight / 2)
{
var eventObj = new cc.Event.EventCustom('meBound', true);
this._SnakeHead.dispatchEvent(eventObj);
return false;
}
}
else
{
//其它玩家
if(Math.abs(this._SnakeHead.x) > this._MapWidth / 2 - 200)
{
if(Math.abs(this._SnakeHead.y) > this._MapHeight / 2 - 200)
{
this.changeAI(10, cc.v2(-this._MoveVec.x, -this._MoveVec.y));
}else
{
this.changeAI(10, cc.v2(-this._MoveVec.x, this._MoveVec.y));
}
} else if(Math.abs(this._SnakeHead.y) > this._MapHeight / 2 - 200)
{
this.changeAI(10, cc.v2(this._MoveVec.x, -this._MoveVec.y));
}
this.aiUpdate(delta);
}
this._PosUpdateTime -= delta;
var needUpdateBody = false;
if(this._PosUpdateTime <= 0)
{
// this._PosUpdateTime = 0.1;
//記錄上次位置
this._LastMoveVec = this._MoveVec;
this._HeadPrePositon = this._SnakeHead.position;
needUpdateBody = true;
}
//更新頭位置
var moveVec = cc.pMult(this._MoveVec, this._MoveSpeed * delta);
this._SnakeHead.position = this._SnakeHead.position.addSelf(moveVec); // cc.pAdd(this._SnakeHead.position, moveVec);
this._AttachLabel.x = this._SnakeHead.x /* * (moveVec.x >= 0 ? -1 : 1)*/;
this._AttachLabel.y = this._SnakeHead.y + 80 /** (moveVec.y >= 0 ? -1 : 1) */;
//更新身體位置
var bodyLen = this._HeadBodyList.length;
for(var i = 0; i < bodyLen; ++i)
{
var snakeBody = this._HeadBodyList[i].getComponent(SnakeBody);
snakeBody.updateBody(delta, this._SnakeHead, this._HeadBodyList, this._LastMoveVec, this._SnakeHead.position /*this._HeadPrePositon*/, needUpdateBody);
}
return true;
},
//新的更新
update(delta)
{
if(delta == 0)
{
delta = 0.017;
}
if(this._SnakeHead == null)
{
return false;
}
//邊界位置判定
if(this._PlayerSelf)
{
if(Math.abs(this._SnakeHead.x) > this._MapWidth / 2 || Math.abs(this._SnakeHead.y) > this._MapHeight / 2)
{
var eventObj = new cc.Event.EventCustom('meBound', true);
this._SnakeHead.dispatchEvent(eventObj);
return false;
}
}
else
{
if(Math.abs(this._SnakeHead.x) > this._MapWidth / 2 - 200 || Math.abs(this._SnakeHead.y) > this._MapHeight / 2 - 200)
{
if( this._SnakeHead.x > this._MapWidth / 2 - 200 )
{
this._SnakeHead.x = this._MapWidth / 2 - 200 - 10;
}else if( this._SnakeHead.x < -(this._MapWidth / 2 - 200) )
{
this._SnakeHead.x = -(this._MapWidth / 2 - 200) + 10;
}
if(this._SnakeHead.y > this._MapHeight / 2 - 200)
{
this._SnakeHead.y = this._MapHeight / 2 - 200 - 10;
}else if(this._SnakeHead.y < -( this._MapHeight / 2 - 200) )
{
this._SnakeHead.y = -(this._MapHeight / 2 - 200) + 10;
}
this.changeAI(10, cc.v2(-this._MoveVec.x, -this._MoveVec.y));
}
this.aiUpdate(delta);
}
this._PosUpdateTime -= delta;
//更新頭位置
var moveDis = this._MoveSpeed * delta;
var oldPos = this._SnakeHead.position;
var moveVec = cc.pMult(this._MoveVec, moveDis);
this._SnakeHead.position = this._SnakeHead.position.addSelf(moveVec); // cc.pAdd(this._SnakeHead.position, moveVec);
this._AttachLabel.x = this._SnakeHead.x /* * (moveVec.x >= 0 ? -1 : 1)*/;
this._AttachLabel.y = this._SnakeHead.y + 80 /** (moveVec.y >= 0 ? -1 : 1) */;
var newPos ;
//更新身體位置
for(let i = 0; i < moveDis; ++i)
{
newPos = cc.pAdd(oldPos, cc.pMult(this._MoveVec, i));
this._MovePath.unshift(newPos);
}
var bodyLen = this._HeadBodyList.length;
for(var i = 0; i < bodyLen; ++i)
{
var snakeBody = this._HeadBodyList[i].getComponent(SnakeBody);
if((i + 1) * this._BodySpace < this._MovePath.length)
{
snakeBody.node.position = this._MovePath[(i + 1) * this._BodySpace];
}
if(this._MovePath.length > bodyLen * (1 + this._BodySpace))
{
this._MovePath.pop();
}
// snakeBody.updateBody(delta, this._SnakeHead, this._HeadBodyList, this._MoveVec, this._SnakeHead.position /*this._HeadPrePositon*/, needUpdateBody);
}
//狀態重置
this._StateTimer -= delta;
if(this._State == 1)
{
//更新保護罩位置
this.updateGodSpritePos();
if(this._GodSprite.active == false)
{
this._GodSprite.active = true;
//console.log("second ", this._GodSprite.width, this._SnakeIndex);
}
if(this._StateTimer < 0 )
{
this._StateTimer = 0;
this.setState(0);
}
}
return true;
},
})
開發中碰到的問題
遊戲工程加子域工程後,包體超過了4M,導致沒法提交到微信後臺。解決辦法:裁剪引擎代碼與壓縮圖片(tiny png)。裁剪代碼通過 cocos creator 項目--->項目設置中去掉不用的功能模塊,如下圖所示,特別是子域的工程
微信小遊戲子域工程效率問題。如果可以的話,儘量讓排行榜作爲二級彈窗UI。作爲主UI的話,可以降低主工程中的刷新頻率。最徹底的解決辦法就是不使用cocos creator提供的子域方案(主工程從自己服務器取排行榜數據或者子域使用微信原生開發)。
微信子域數據只能在子域獲取,無法傳遞給主工程 。 主工程可以傳遞消息與數據給子域。 子域中有些函數也無法使用,比如本地存儲等
cocos creator中的碰撞檢測(collider)效率問題。當遊戲中的collider數量比較多時,會出現幀率降低明顯,在PC瀏覽器不太明顯,但發佈到微信後,特別是在低端安卓機上特別明顯。後期可以考慮使用四叉樹優化一下碰撞檢測。
工程及說明
cocos creator v1.9.3版本下開發,工程中附帶QQ玩一玩相關代碼(使用玩一玩 vscode下的插件 qqextension 0.5.5及對應的qqplaycore)。完整工程在 "我的資源" 裏下載 貪喫蛇大作戰creator工程源碼