客户端代码结构
-
scripts文件夹下:
(1) 3rdparty:第三方代码库,如
socket.io。
(2) Components:挂载到游戏节点上的组件。
(3) 全局对象:位于代码的根目录下。

loading场景
1: Loading Scene 挂载了脚本: LoadingLogic.js

加载场景初始化过程:
(1) 初始化全局管理对象,所有对象都记录在cc.vv中。特点是:实例对象以小写开头,例如cc.vv.userMgr = new UserMgr(),而类名以大写开头。全局对象是唯一的。
初始化的管理对象包括:UserMgr、ReplayMgr、HTTP、Global、Net、GameNetMgr、AnysdkMgr、VoiceMgr、AudioMgr、Utils等。
解析URL参数,以确定不同的用户账号。

(2) 显示几秒的启动画面;
(3) 检查更新:通过getServerInfo()获取服务器信息;
(4) startPreloading:预加载resources/textures等资源。
(5) 资源加载完成后,进入“login”场景;
注意:官方开源的最新版本相比泄露的达达麻将早期版本,已进行了升级和更新。如下图所示:1为最新开源版本,2为早期版本。
在1中:打开游戏,第一个UI界面是start:绑定的脚本是AppStart.js

在版本2中:启动游戏后,首先显示的UI界面是loading,其绑定的JS脚本为LoadingLogic.js。

//------------AppStart.js------------
function urlParse(){
var params = {};
if(window.location == null){
return params;
}
var name,value;
var str=window.location.href; //取得整个地址栏
var num=str.indexOf("?")
str=str.substr(num+1); //取得所有参数 stringvar.substr(start [, length ]
var arr=str.split("&"); //各个参数放到数组里
for(var i=0;i < arr.length;i++){
num=arr[i].indexOf("=");
if(num>0){
name=arr[i].substring(0,num);
value=arr[i].substr(num+1);
params[name]=value;
}
}
return params;
}
function initMgr(){
cc.vv = {};
var UserMgr = require("UserMgr");
cc.vv.userMgr = new UserMgr();
var ReplayMgr = require("ReplayMgr");
cc.vv.replayMgr = new ReplayMgr();
cc.vv.http = require("HTTP");
cc.vv.global = require("Global");
cc.vv.net = require("Net");
var GameNetMgr = require("GameNetMgr");
cc.vv.gameNetMgr = new GameNetMgr();
cc.vv.gameNetMgr.initHandlers();
var AnysdkMgr = require("AnysdkMgr");
cc.vv.anysdkMgr = new AnysdkMgr();
cc.vv.anysdkMgr.init();
var VoiceMgr = require("VoiceMgr");
cc.vv.voiceMgr = new VoiceMgr();
cc.vv.voiceMgr.init();
var AudioMgr = require("AudioMgr");
cc.vv.audioMgr = new AudioMgr();
cc.vv.audioMgr.init();
var Utils = require("Utils");
cc.vv.utils = new Utils();
//var MJUtil = require("MJUtil");
//cc.vv.mjutil = new MJUtil();
cc.args = urlParse();
}
cc.Class({
extends: cc.Component,
properties: {
// foo: {
// default: null, // The default value will be used only when the component attaching
// to a node for the first time
// url: cc.Texture2D, // optional, default is typeof default
// serializable: true, // optional, default is true
// visible: true, // optional, default is true
// displayName: 'Foo', // optional
// readonly: false, // optional, default is false
// },
// ...
label: {
default: null,
type:cc.Label
},
loadingProgess:cc.Label,
},
// use this for initialization
onLoad: function () {
initMgr();
cc.vv.utils.setFitSreenMode();
console.log('haha');
this._mainScene = 'loading';
this.showSplash(function(){
this.getServerInfo();
}.bind(this));
},
onBtnDownloadClicked:function(){
cc.sys.openURL(cc.vv.SI.appweb);
},
showSplash:function(callback){
var self = this;
var SHOW_TIME = 3000;
var FADE_TIME = 500;
this._splash = cc.find("Canvas/splash");
if(true || cc.sys.os != cc.sys.OS_IOS || !cc.sys.isNative){
this._splash.active = true;
if(this._splash.getComponent(cc.Sprite).spriteFrame == null){
callback();
return;
}
var t = Date.now();
var fn = function(){
var dt = Date.now() - t;
if(dt < SHOW_TIME){
setTimeout(fn,33);
}
else {
var op = (1 - ((dt - SHOW_TIME) / FADE_TIME)) * 255;
if(op < 0){
self._splash.opacity = 0;
callback();
}
else{
self._splash.opacity = op;
setTimeout(fn,33);
}
}
};
setTimeout(fn,33);
}
else{
this._splash.active = false;
callback();
}
},
getServerInfo:function(){
var self = this;
var onGetVersion = function(ret){
cc.vv.SI = ret;
if(cc.sys.isNative){
var url = cc.url.raw('resources/ver/cv.txt');
cc.loader.load(url,function(err,data){
cc.VERSION = data;
if(ret.version == null){
console.log("error.");
}
else{
if(cc.vv.SI.version != cc.VERSION){
cc.find("Canvas/alert").active = true;
}
else{
cc.director.loadScene(self._mainScene);
}
}
}.bind(this));
}
else{
cc.director.loadScene(self._mainScene);
}
};
var xhr = null;
var complete = false;
var fnRequest = function(){
self.loadingProgess.string = "正在连接服务器";
xhr = cc.vv.http.sendRequest("/get_serverinfo",null,function(ret){
xhr = null;
complete = true;
onGetVersion(ret);
});
setTimeout(fn,5000);
}
var fn = function(){
if(!complete){
if(xhr){
xhr.abort();
self.loadingProgess.string = "连接失败,即将重试";
setTimeout(function(){
fnRequest();
},5000);
}
else{
fnRequest();
}
}
};
fn();
},
log:function(content){
this.label.string += content + '\n';
},
});
//--------------------------LoadingLogic------------------------
cc.Class({
extends: cc.Component,
properties: {
tipLabel:cc.Label,
_stateStr:'',
_progress:0.0,
_splash:null,
_isLoading:false,
},
// use this for initialization
onLoad: function () {
cc.vv.utils.setFitSreenMode();
this.tipLabel.string = this._stateStr;
this.startPreloading();
},
startPreloading:function(){
this._stateStr = "正在加载资源,请稍候"
this._isLoading = true;
var self = this;
var onProgress = function ( completedCount, totalCount, item ){
//console.log("completedCount:" + completedCount + ",totalCount:" + totalCount );
if(self._isLoading){
self._progress = completedCount/totalCount;
}
};
//cc.loader.loadResDir("textures",cc.Texture2D, onProgress,function (err, assets) {
// self.onLoadComplete();
//});
self.onLoadComplete();
},
onLoadComplete:function(){
this._isLoading = false;
this._stateStr = "准备登陆";
cc.director.loadScene("login");
},
// called every frame, uncomment this function to activate update callback
update: function (dt) {
if(this._stateStr.length == 0){
return;
}
this.tipLabel.string = this._stateStr + ' ';
if(this._isLoading){
this.tipLabel.string += Math.floor(this._progress * 100) + "%";
}
else{
var t = Math.floor(Date.now() / 1000) % 4;
for(var i = 0; i < t; ++ i){
this.tipLabel.string += '.';
}
}
}
});
Login场景
login场景挂载了login.js脚本;

(1) 扩展了String对象的format()函数;
String.prototype.format = function(args) {
if (arguments.length>0) {
var result = this;
if (arguments.length == 1 && typeof (args) == "object") {
for (var key in args) {
var reg=new RegExp ("({"+key+"})","g");
result = result.replace(reg, args[key]);
}
}
else {
for (var i = 0; i < arguments.length; i++) {
if(arguments[i]==undefined) {
return "";
}
else {
var reg=new RegExp ("({["+i+"]})","g");
result = result.replace(reg, arguments[i]);
}
}
}
return result;
}
else {
return this;
}
};
(2) 监听push_need_create_role事件,进入创建角色场景;

(3) 如果不是在网页环境中,隐藏游客登录按钮;

(4) 本地保存微信账号时,自动执行登录;
(5) 处理微信账号的登录响应;
(6) 进行游客账号登录;


如果account为空,则从本地获取;若本地未找到,则随机生成一个基于时间的账号。
随后,通过HTTP请求将account发送到服务器,消息回调处理函数为onAuth。
cc.vv.http.sendRequest("/guest", {account: account}, this.onAuth);

var URL = "http://127.0.0.1:9000";
exports.master_url = null;
exports.url = null;
exports.token = null;
init();
function init() {
exports.master_url = URL;
exports.url = URL;
}
function setURL(url) {
URL = url;
init();
};
function sendRequest(path, data, handler, extraUrl) {
var xhr = cc.loader.getXMLHttpRequest();
xhr.timeout = 5000;
if (data == null) {
data = {};
}
if (exports.token) {
data.token = exports.token;
}
if (extraUrl == null) {
extraUrl = exports.url;
}
//解析请求路由以及格式化请求参数
var sendpath = path;
var sendtext = '?';
for (var k in data) {
if (sendtext != "?") {
sendtext += "&";
}
sendtext += (k + "=" + data[k]);
}
//组装完整的URL
var requestURL = extraUrl + sendpath + encodeURI(sendtext);
//发送请求
console.log("RequestURL:" + requestURL);
xhr.open("GET", requestURL, true);
if (cc.sys.isNative) {
xhr.setRequestHeader("Accept-Encoding", "gzip,deflate", "text/html;charset=UTF-8");
}
var timer = setTimeout(function() {
xhr.hasRetried = true;
xhr.abort();
console.log('http timeout');
retryFunc();
}, 5000);
var retryFunc = function() {
sendRequest(path, data, handler, extraUrl);
};
xhr.onreadystatechange = function () {
console.log("onreadystatechange");
clearTimeout(timer);
if (xhr.readyState === 4 && (xhr.status >= 200 && xhr.status < 300)) {
// console.log("http res(" + xhr.responseText.length + "):" + xhr.responseText);
cc.log("request from [" + xhr.responseURL + "] data [", ret, "]");
var respText = xhr.responseText;
var ret = null;
try {
ret = JSON.parse(respText);
} catch (e) {
console.log("err:" + e);
ret = {
errcode: -10001,
errmsg: e
};
}
if (handler) {
handler(ret);
}
handler = null;
}
else if (xhr.readyState === 4) {
if(xhr.hasRetried){
return;
}
console.log('other readystate == 4' + ', status:' + xhr.status);
setTimeout(function() {
retryFunc();
}, 5000);
}
else {
console.log('other readystate:' + xhr.readyState + ', status:' + xhr.status);
}
};
try {
xhr.send();
}
catch (e) {
//setTimeout(retryFunc, 200);
retryFunc();
}
return xhr;
}
exports.sendRequest = sendRequest;
exports.setURL = setURL;
服务器中9000是账号服务器:

游客登录逻辑
-
从URL中获取用户参数,如果存在,则使用传递的参数。
//游客登录
onBtnQuickStartClicked:function(){
cc.vv.userMgr.guestAuth();
}, -
获取本地存储的用户账号,如果未找到,则基于当前时间随机生成一个账号。
//游客验证登录
guestAuth:function(){
var account = cc.args["account"];
if(account == null){
account = cc.sys.localStorage.getItem("account");
}
if(account == null){
account = Date.now();
cc.sys.localStorage.setItem("account",account);
}
cc.vv.http.sendRequest("/guest",{account:account},this.onAuth);
},
3.将游客登录请求发送至服务器;
cc.vv.http.sendRequest("/guest",{account:account},this.onAuth);
userMgr将请求发送到账号服务器,响应地址为/guest;
4.账号服务器响应,返回成功状态及登录用户信息和大厅服务器的IP地址;
//游客登录服务器返回消息处理
onAuth:function(ret){
var self = cc.vv.userMgr;
if(ret.errcode !== 0){
console.log(ret.errmsg);
}
else{
self.account = ret.account;
self.sign = ret.sign;
cc.vv.http.url = "http://" + cc.vv.SI.hall;//大厅服务器的地址
self.login();
}
},
5: userMgr: login函数,
login:function(){
var self = this;
var onLogin = function(ret){
if(ret.errcode !== 0){
console.log(ret.errmsg);
}
else{
if(!ret.userid){
//jump to register user info.
cc.director.loadScene("createrole");
}
else{
console.log(ret);
self.account = ret.account;
self.userId = ret.userid;
self.userName = ret.name;
self.lv = ret.lv;
self.exp = ret.exp;
self.coins = ret.coins;
self.gems = ret.gems;
self.roomData = ret.roomid;
self.sex = ret.sex;
self.ip = ret.ip;
cc.director.loadScene("hall");
}
}
};
cc.vv.wc.show("正在登录游戏");
cc.vv.http.sendRequest("/login",{account:this.account,sign:this.sign},onLogin);
},
发送请求至大厅服务器,由client_server.js提供的/login接口处理;

服务器返回登录信息;

//配置好响应请求:登录到大厅服务器;
app.get('/login',function(req,res){
if(!check_account(req,res)){
return;
}
var ip = req.ip;
if(ip.indexOf("::ffff:") != -1){
ip = ip.substr(7);
}
var account = req.query.account;
db.get_user_data(account,function(data){
if(data == null){
http.send(res,0,"ok");
return;
}
var ret = {
account:data.account,
userid:data.userid,
name:data.name,
lv:data.lv,
exp:data.exp,
coins:data.coins,
gems:data.gems,
ip:ip,
sex:data.sex,
};
//判断是否在游戏,还是在房间;则直接进当前房间
//一个账户,不能同时在两个房间里面游戏;
db.get_room_id_of_user(data.userid,function(roomId){
//如果用户处于房间中,则需要对其房间进行检查。 如果房间还在,则通知用户进入
if(roomId != null){
//检查房间是否存在于数据库中
db.is_room_exist(roomId,function (retval){
if(retval){
ret.roomid = roomId;
}
else{
//如果房间不在了,表示信息不同步,清除掉用户记录
db.set_room_id_of_user(data.userid,null);
}
http.send(res,0,"ok",ret);
});
}
else {
http.send(res,0,"ok",ret);
}
});
});
});
将用户信息保存到userMgr;
进入大厅场景;
如果没有用户,进入到创建角色场景,创建完角色以后,又再重新登陆一次;
创建角色场景:

//-------------CreateRole.js--------------------
cc.Class({
extends: cc.Component,
properties: {
inputName:cc.EditBox,
// foo: {
// default: null,
// url: cc.Texture2D, // optional, default is typeof default
// serializable: true, // optional, default is true
// visible: true, // optional, default is true
// displayName: 'Foo', // optional
// readonly: false, // optional, default is false
// },
// ...
},
onRandomBtnClicked:function(){
var names = [
"上官",
"欧阳",
"东方",
"端木",
"独孤",
"司马",
"南宫",
"夏侯",
"诸葛",
"皇甫",
"长孙",
"宇文",
"轩辕",
"东郭",
"子车",
"东阳",
"子言",
];
var names2 = [
"雀圣",
"赌侠",
"赌圣",
"稳赢",
"不输",
"好运",
"自摸",
"有钱",
"土豪",
];
var idx = Math.floor(Math.random() * (names.length - 1));
var idx2 = Math.floor(Math.random() * (names2.length - 1));
this.inputName.string = names[idx] + names2[idx2];
},
// use this for initialization
onLoad: function () {
cc.vv.utils.setFitSreenMode();
this.onRandomBtnClicked();
},
onBtnConfirmClicked:function(){
var name = this.inputName.string;
if(name == ""){
console.log("invalid name.");
return;
}
console.log(name);
cc.vv.userMgr.create(name);
}
// called every frame, uncomment this function to activate update callback
// update: function (dt) {
// },
});
1.场景挂载了CreateRole脚本;
(1) 随机生成一个名字;
(2) 随机生成名字的函数绑定到随机按钮;
(3) 确定按钮:调用userMgr的create函数创建用户;

2.创建角色:
发送请求至hallserver的client_service.js,处理/create_user接口;
调用cc.vv.userMgr.create(name);创建用户。
返回信息后继续按照原有的登录流程进行处理;

3273

被折叠的 条评论
为什么被折叠?



