文档结构
一 游戏入口
二 游戏统计
三 视频播放、音频播放
四 支付
五 http通讯
六 游戏配置文件
七 关键字屏蔽
八 新手指引
文档结构
cocos2d-js-min.js cocos引擎
egreth5sdk.js 白鹭SDK
exif.js ???
gameApi.js 游戏API(充值、分享统计等)
gameCenterSA.js ???
main.js 充值、统计、发送桌面
project.js 游戏主文件
settings.js ???
zm_engine_v2.js ???
代码:
Global.js 全局支付、请求指引
data.js 对话配置、关键词屏蔽
gconst.js 全局常量 (字体名、商品类型等游戏变量)
gevent.js 事件名
glang.js 全局语言(所有动态文本的固定文字)
gplayer.js 游戏主类
grpc.js http通讯
gsdk.js 原生交互、cordova视频、支付
gutils.js 数组、字符串等数据处理
guideUI.js 新手指引
createRole.js 创建角色(昵称)
gwnd.js 升级会话框
homeUI.js 主页
loginUI.js 登录
main.js 将游戏内函数存入window,供外部调用
paymentUI.js 支付UI
videoPlayerUI.js 视频播放
一 游戏入口
1 从白鹭平台获取egretID、用户信息等,然后进入游戏正式链接
<div id="gameIframeBox" style="display:block;margin:0 auto;100%;height:0px;background-color:#000">
<iframe id="gameIframe" name="gameIframe" src="http://api.egret-labs.org/v2/game/20409/91822?chanId=20409&channelId=20409&showLoginPanel=no&time=1509974135&egretstartfrom=gamece
nter&egretSdkDomain=https://api.egret-labs.org/v3&egretServerDomain=https://api.egret-labs.org/v3&egretOauthUser=1&egretId=3d66bcad0342d9c2b838810445fc166a&userId=D02F30FBF9C4E6C3F2F7B9738D11772F__qq
&userName=危险的画本&userImg=http://q.qlogo.cn/qqapp/101237728/D02F30FBF9C4E6C3F2F7B9738D11772F/100&userSex=1&sign=118620d8e41c0ccfc78406b0c24c84e4&egretwt=mobile&isEgretLogin=1"
frameborder="no" border="0" marginwidth="0" marginheight="0" scrolling="no" width="100%" height="100%"></iframe>
</div>
2 正式游戏网页,加载cocos-js代码,运行游戏
<script src="src/settings.js" charset="utf-8"></script>
<script src="main.js" charset="utf-8"></script>
二 游戏统计
百度统计
TalkingData
三 视频播放、音频播放
新用户进入游戏的视频单独申请播放,和游戏中升级后播放的视频申请步骤不一样。
游戏中levelup后的视频,在升级后根据等级level请求服务器获取相应视频链接。
PC端播放视频
请求加载视频
login_preload: function() {
gaudio.loadBgSound(),
gevent.emit(gevent.EVENT_LOAD_VIDEO, 1101)
}
gevent.on(gevent.EVENT_LOAD_VIDEO,
function(e) {
var t = e.detail;
1 != gsdk.videoType() && this.loadVideo(this.getVideoURL(t))
}
cocos自带的video播放html视频
loadVideo: function(e) {
if (1 != gsdk.videoType()) {
var t = new cc.Node("VideoPlayer");
t.setAnchorPoint(cc.p(0, 0));
var n = t.addComponent(cc.Widget);
n.isAlignTop = !0,
n.isAlignBottom = !0,
n.isAlignLeft = !0,
n.isAlignRight = !0,
n.top = 0,
n.bottom = 0,
n.left = 0,
n.right = 0,
this.videoPlayer = t.addComponent(cc.VideoPlayer),
this.videoPlayer.keepAspectRatio = !0,
this.node.addChild(t),
this.videoPlayer.remoteURL = e;
var i = document.getElementsByClassName("cocosVideo")[0];
i.setAttribute("x5-video-player-type", "h5"),
i.setAttribute("webkit-playsinline", "true");
var o = function() {
this._onVideoEnded()
};
this.videoPlayer.node.on("completed", o, this),
this.videoPlayer.node.on("stopped", o, this),
this.videoPlayer.play(),
this.videoPlayer.pause(),
this.videoPlayer.node.active = !1
}
}
App播放视频
一种是进入游戏时播放固定视频
enterGmae: function() {
var e = this.editBox.string;
if (e.length <= 0) gwnd.showPrompt(glang.NameEmpty);
else if (this._checkName(e)) gwnd.showPrompt(glang.ContainsIllegalCharacters);
else {
gevent.emit(gevent.EVENT_LOAD_VIDEO, 1102);
var t = this;
grpc.CreateRole(function(n) {
0 === n && (gsdk.analytics(gconst.TD_EVENT.CREATE_ROLE, {
roleName: e
}), gsdk.reportUserProgress(1), t.playVideo())
},
e)
}
}
第二种是角色升级时,播放视频
_tryShowLevelUpAction: function() {
this._tryShowComingTelphone() || this.tryShowStoryVideo()
},
tryShowStoryVideo: function() {
var e = gplayer.level;
if (gplayer.max_story_video_level >= e) return ! 1;
var t = gsheet.Level.find({
Level: e
});
if (t.StoryVideoId <= 0) return ! 1;
grpc.WatchStoryVideo(function(t) {
0 == t && (gplayer.max_story_video_level = e)
}),
gaudio.pauseBgSound();
var n = cc.instantiate(this.levelupAnim);
this.node.addChild(n);
return n.once("anim-complete",
function(e) {
n.removeFromParent(),
gwnd.openVideo(t.StoryVideoId, gconst.VIDEO_TYPE.STORY)
},
this),
!0
}
需要播放视频时,根据视频ID和视频类型,请求播放视频。
gwnd.openVideo(1101, gconst.VIDEO_TYPE.STORY);
VIDEO_TYPE: {
STORY: 1,
FACE_TIME: 2,
LIBRARY: 3,
SEVENLOGIN: 4
},
VIDEO_PLAY_STATE: {
NONE: 0,
PLAYING: 1,
PAUSED: 2,
COMPLETED: 3
}
发送请求视频事件
openVideo: function(e, t, n) {
gevent.emit(gevent.EVENT_OPEN_VIDEO, {
videoId: e,
callBack: n,
videoType: t
})
}
处理播放视频事件,关闭背景音乐,并尝试播放视频
gevent.on(gevent.EVENT_OPEN_VIDEO,
function(e) {
this._isOpenVideo = !0,
this.bgSoundState("video", !0),
this.windowLayer.getChildren().length <= 0 && this.anim.play("Exit")
}
gevent.on(gevent.EVENT_OPEN_VIDEO,
function(e) {
var t = e.detail;
1103 == t.videoId && (this.food = !0),
this._quondamVideoId = t.videoId,
this._videoId = gutils.getmapVideoID(t.videoId),
this._videoType = t.videoType,
this._callBack = t.callBack,
Global.home.active = !1,
1 == gsdk.videoType() && (document.body.style.backgroundColor = "transparent", cc.director.setClearColor(cc.color(0, 0, 0, 0)), this.touchLayer.active = !0),
this._tryPlay()
},
this)
判断是否需要加载小标题再播放视频,或是直接播放视频
_tryPlay: function() {
this._playState = gconst.VIDEO_PLAY_STATE.NONE,
this._lastSubtitle = null,
this._subtitles = new Array;
var e = !1;
gconst.VIDEO_TYPE.STORY != this._videoType && gconst.VIDEO_TYPE.INTERIM != this._videoType || (e = !0),
e && 1 == gsdk.videoType() ? this._loadSubtitle() : this._play()
}
获取并拼接视频正式Url,并使用cordova播放视频,监听播放进度和播放完成事件
_play: function() {
var e = this;
if (1 == gsdk.videoType()) {
this.maskLayer.active = !0,
gsdk.videoStop();
var t = 0,
n = 0;
gplayer.hasOwnProperty("account_id") && (t = gplayer.account_id),
gplayer.hasOwnProperty("level") && (n = gplayer.level),
this.setloadingView(!0),
gsdk.getVideoURL(this._videoId, t, n,
function(t) {
gsdk.videoPlay(t),
e._onVideoPlaying(),
gsdk.watchProgress(function(t) {
e._onVideoProgress(t)
},
function() {
e._onVideoEnded()
})
},
function(e, t) {})
} else this.onClicked();
gaudio.pauseBgSound()
}
使用插件cordova播放视频
videoPlay: function(e) {
cordova.plugins.videobackground.play(e, !1)
}
播放视频结束,可能是多段视频组成的场景。第一段播放,中间有两个选项,根据用户选择,再播放第二段视频。
_onVideoEnded: function() {
var e = !0;
if (this.removeVideo(), this.food) this.food = !1,
e = !1,
this.nodeSelectFood.getChildByName("txtSelect").getComponent(cc.Label).string = glang.SelectFood,
this.nodeSelectFood.active = !0;
else {
var t = gsheet.Video.find({
ID: this._quondamVideoId
});
if (t) {
this.btnPlay.active = !1,
e = !1,
this.options.active = !0;
var n = this.options.getChildByName("op1"),
i = this.options.getChildByName("op2");
n.getComponent(cc.Label).string = t.NextOption1,
i.getComponent(cc.Label).string = t.NextOption2,
n.once(cc.Node.EventType.TOUCH_END,
function() {
this.options.active = !1,
this._quondamVideoId = t.NextVideoId1,
this._videoId = gutils.getmapVideoID(t.NextVideoId1),
this.loadVideo(this.getVideoURL(this._videoId)),
this._tryPlay()
},
this),
i.once(cc.Node.EventType.TOUCH_END,
function() {
this.options.active = !1,
this._quondamVideoId = t.NextVideoId2,
this._videoId = gutils.getmapVideoID(t.NextVideoId2),
this.loadVideo(this.getVideoURL(this._videoId)),
this._tryPlay()
},
this)
}
}
e && this.closeVideoPlayer()
}
四 支付接入
点击商品,提示购买
clickEvent: function() {
var e = gsheet.ShopGoods.find({
ID: this._sheet.GoodsId
});
if (gplayer.isHaveShopGoods(e)) gwnd.showPrompt(e.HaveTip);
else if (e.OpenFuncId > 0 && !gplayer.isFuncOpend(e.OpenFuncId)) {
var t = gsheet.FuncOpening.find({
ID: e.OpenFuncId
}),
n = gsheet.Level.find({
Level: t.OpenLevel
}),
i = gutils.substitute(glang.BuyUnopenedGoodsTip, e.Name, n.Day);
gwnd.showPrompt(i)
} else Global.sdk_payment_pay(e.ID)
}
弹出购买商品的提示框
payment_pay: function(e) {
var t = this;
lobby.purchaseConfirm ? gwnd.confirmPrompt(function() {
t.payment_resume()
},
"商品购买", "请为您选购的商品进行支付", gconst.PROMPT_WND_TYPE.ONE, "关闭") : gwnd.loading(!0);
var n = {
ID: e.ID,
Price: 100 * e.Price,
Label: e.Name,
level: gplayer.level,
Name: gplayer.name,
accountId: gplayer.account_id
};
lobby.purchase(n, e.ID,
function(e) {
e ? t.payment_OnRecipt(e) : (gwnd.closeConfirmPrompt(), t.payment_resume()),
gwnd.loading(!1)
},
function(e, t) {
if (gwnd.closeConfirmPrompt(), gwnd.loading(!1), e || t) {
var n = "";
t && t.length > 0 && (n += t),
e && e.length > 0 && (n += "错误码:" + e),
gwnd.showPrompt(n)
} else gwnd.showPrompt("充值失败!")
},
function(e, t, n, i, o) {
gwnd.loading(!1),
e >= n.Price ? gwnd.open("common_wnd", [n, i, o]) : gwnd.open("common_nomoney_wnd", [e, t, n, i, o])
})
}
确认购买。获取商品ID、价格、名字等信息,发起购买请求。
project.js
payment_resume: function() {
window.lobby && lobby.processPendingPurchases(this.payment_OnRecipt)
}
"商品购买", "请为您选购的商品进行支付", gconst.PROMPT_WND_TYPE.ONE, "关闭") : gwnd.loading(!0);
var n = {
ID: e.ID,
Price: 100 * e.Price,
Label: e.Name,
level: gplayer.level,
Name: gplayer.name,
accountId: gplayer.account_id
};
lobby.purchase(n, e.ID,
function(e) {
e ? t.payment_OnRecipt(e) : (gwnd.closeConfirmPrompt(), t.payment_resume()),
gwnd.loading(!1)
},
生成商品订单
gameApi.js
//充值补单
this.processPendingPurchases = function(payment_OnRecipt_callback){
self.rpc.request(function(code, errmsg, rcptArr){
if(200 === code && rcptArr){
for(var i=0;i<rcptArr.length;i++){
payment_OnRecipt_callback(rcptArr[i]);
}
}
},self.sdkHash.loveportalurl + "pendingpurchase.php",{gameid:self.appId,account:self.lovechaccount,channel:self.CHANNEL_PLATFORM});
}
//充值 cData : {ID: 道具ID,Price: 价格 (分) ,Label: (商品名or描述) }
this.purchase = function(cData, dialysis, succeedCallback, failedCallback){
if(!self.sdkJs){
failedCallback(0, "登录失败请重试");
return;
}
var _attachs = {
itemname:cData.Label,
playerlevel:cData.level,
playername:cData.Name,
accountid:cData.accountId,
channelattachs:self.sdkHash.attachs
}
var createOrderData = {
gameid:self.appId,
account:self.lovechaccount,
sdkname:self.CHANNEL_SDKNAME,
channel:self.CHANNEL_PLATFORM,
itemid:cData.ID,
attachs:_attachs
}
self.sdkJs.purchase(cData, createOrderData, succeedCallback, failedCallback);
}
发起http请求购买。ZmSDK是啥...
main.js
//充值
this.purchase = function (cData, createOrderData, succeedCallback, failedCallback) {
//创建订单
self.rpc.request(function(code, errmsg, sData){
if(200 === code){
self.purchaseStart(cData, sData, succeedCallback, failedCallback);
}else{
failedCallback(code, errmsg);
}
},self.sdkHash.loveportalurl + "requestpay.php",createOrderData);
}
this.purchaseStart = function (cData, sData, succeedCallback, failedCallback) {
var payinfojson = {
check: sData.attachs.check,
feeid: cData.ID+"",
fee: sData.price+"",
feename: cData.Label,
extradata: sData.gameorderid,
serverid: 1+"",
rolename: cData.Name,
roleid: cData.accountId+"",
servername: ""
};
ZmSdk.getInstance().pay(payinfojson, function(data){
//跳转到了支付页面
if(data.retcode == 3)
return;
if(data.retcode == 1){
failedCallback(0,data.msg);
return;
}
if(data.retcode == 2){
failedCallback(0,"您取消了支付");
return;
}
if(data.retcode == 0)
succeedCallback();
});
}
支付成功回调
project.js
payment_OnRecipt: function(e) {
grpc.SdkPaymentPay(function(t, n, i, o, s, a, c, l, r) {
1 !== t ? 0 === t && (lobby.finishPurchase(e), gplayer.buyShopGoodsSuccess(n, i, o, s, a, c, l, r), gwnd.showPrompt(glang.PaySuccess), gevent.emit(gevent.PAY_SUCCESS)) : lobby.finishPurchase(e)
},
e)
}
发起http请求,告知服务端支付完成
gameApi.js
//完成一笔订单
this.finishPurchase = function(rcpt){
self.rpc.request(function(){},self.sdkHash.loveportalurl + "finishpurchase.php",{gameid:self.appId,receipt:rcpt});
}
游戏内更新界面
gevent.on(gevent.PAY_SUCCESS, this._paySuccessHandler, this)
_paySuccessHandler: function() {
this.updateView()
}
五 Http通讯协议
post模式。
http包含token,且所有请求都写在Http类里面,留出回调接口
grpc: [function(e, t, n) {
"use strict";
cc._RF.push(t, "e1dd3ZUONZBW4caM364h+q8", "grpc");
var i = {
token: "",
request: function(e, t, n) {
gwnd.loading(!0);
var i = new XMLHttpRequest;
i.onreadystatechange = function() {
if (cc.log("xhr.readyState:" + i.readyState + "--xhr.status:" + i.status + "--xhr.responseText:" + i.responseText), i.readyState == XMLHttpRequest.DONE) if (200 == i.status) {
var e = i.responseText;
cc.log("rpc:|" + e);
var n = JSON.parse(e);
if ("err10001" == n[0]) return gwnd.confirmPrompt(function() {
gsdk.isExitLobby() ? gsdk.exitToLobby() : cc.director.loadScene("logoCanvas")
},
" 重复登录提醒", "检测到您的账号在其他设备登录游戏,请重新登录验证。", gconst.PROMPT_WND_TYPE.ONE),
void gwnd.loading(!1);
t.apply(this, n.rpc),
"mail" in n && gplayer.addNewMails(n.mail),
"activity" in n && gplayer.addNewActivitys(n.activity),
"activity_target" in n && gplayer.updateActivityTargets(n.activity_target),
gwnd.loading(!1)
} else gwnd.loading(!1)
},
i.open("POST", gsdk.serverURL(), !0);
var o = "A=" + encodeURIComponent(e);
if ("" !== this.token && (o = o + "&T=" + encodeURIComponent(this.token)), void 0 !== n) {
o += "&G=";
var s = JSON.stringify(n);
o += encodeURIComponent(s)
}
cc.log("rpcName:" + e + "--send:" + o),
i.setRequestHeader("Content-type", "application/x-www-form-urlencoded"),
i.send(o)
},
getSDKGiftReward: function(e, t) {
this.request("getSDKGiftReward", e, [t])
},
getSendToDesktopReward: function(e) {
this.request("getSendToDesktopReward", e, [])
},
BindAccount: function(e, t, n, i, o) {
this.request("BindAccount", e, [t, n, i, o])
},
六 游戏配置文件
所有标题、随机姓名、对话全部都写在代码里
七 关键字屏蔽
取名时,从服务器下载name_forbit.txt,里面有被屏蔽的关键字。
cc.loader.loadRes("forbid/name_forbid.txt",
function(t, n) {
t || (e._forbidNames = n.split("
"))
})
八 新手指引做法
每次进入某界面(有新手指引的界面)时,检查该界面是否需要显示新手指引,如果有,则显示新手指引。
这个检查会每次进入某界面都会检查一次,新手指引完成后,会向服务器发送保存当前指引步骤。
点击新手指引按钮时,会执行bind的处理函数handler,来继续指引操作。
下一步新的指引
newbieGuide: function(e, t, n, i) {
gevent.emit(gevent.EVENT_GUIDE_START_STEP, {
step: e,
btnView: t,
callback: n,
isEnable: i
})
}
处理下一步新手指引
onLoad: function() {
gevent.on(gevent.EVENT_GUIDE_START_STEP, this.guideStartHander, this)
},
guideStartHander: function(e) {
this.step = e.detail.step,
this.btnView = e.detail.btnView,
this.callback = e.detail.callback,
this.isEnable = e.detail.isEnable,
this._data = gsheet.GuideStep.find({
ID: this.step
}),
gsdk.analytics(gconst.TD_EVENT.BEGIN_MISSION, {
guideStep: this.step
}),
this.newbieGuide()
}
显示Dog新手指引
newbieGuide: function() {
if (this.node.on(cc.Node.EventType.TOUCH_END, this.touchNodeEnd, this), this.createDog(), void 0 != this.btnView) {
var e = this.btnView.getComponent(cc.Button);
void 0 != e && void 0 == this.isEnable && (e.interactable = !1),
this.perch = cc.instantiate(this.perchPf),
this.node.addChild(this.perch),
this.schedule(this.updateDisplay, .2),
this.perch.on(cc.Node.EventType.TOUCH_END, this.touchBtnEnd, this),
this.arrow = cc.instantiate(this.arrows),
this.arrow.rotation = this._data.Rotation,
this.node.addChild(this.arrow),
this.perch.active = !1,
this.arrow.active = !1,
this.updateDisplay()
}
},
updateDisplay: function() {
var e = this.btnView.getNodeToWorldTransform();
if (void 0 != this.worldTransform) if (this.worldTransform.tx == e.tx && this.worldTransform.ty == e.ty) {
this.perch.active = !0,
this.arrow.active = !0,
this.perch.x = e.tx,
this.perch.y = e.ty;
var t = this.btnView.getContentSize();
this.perch.setContentSize(t),
this.arrow.x = e.tx + t.width / 2,
this.arrow.y = e.ty + t.height / 2
} else this.worldTransform = e;
else this.worldTransform = e
}