cocos creator开发微信小游戏(五)贪吃蛇大作战

  • Post author:
  • Post category:其他



目录


小游戏介绍


小游戏cocos creator场景图


小游戏部分JS代码


开发中碰到的问题


工程及说明


小游戏介绍

贪吃蛇小游戏:

贪吃蛇试玩(首次加载比较慢)

,类似贪吃蛇大作战的小游戏。当玩家的蛇头碰到另一条蛇蛇身时死亡,游戏结束。

小游戏cocos creator场景图

小游戏的cocos creator节点结构如下图,Canvas上面的节点是单例(DataManager SoundManager Global  WeiXinPlatform)

贪吃蛇大作战
贪吃蛇大作战
贪吃蛇大作战
贪吃蛇大作战


大厅界面
贪吃蛇大作战大厅

​​
贪吃蛇cocos creator截图
cocos creator 层级

小游戏部分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 项目—>项目设置中去掉不用的功能模块,如下图所示,特别是子域的工程

cocos creator裁剪引擎代码
cocos creator裁剪引擎代码


微信小游戏子域工程效率问题

。如果可以的话,尽量让排行榜作为二级弹窗UI。作为主UI的话,可以降低主工程中的刷新频率。最彻底的解决办法就是不使用cocos creator提供的子域方案(主工程从自己服务器取排行榜数据或者子域使用微信原生开发)。


微信子域数据只能在子域获取,无法传递给主工程 。 主工程可以传递消息与数据给子域。 子域中有些函数也无法使用,比如本地存储等


cocos creator中的碰撞检测(collider)效率问题。

当游戏中的collider数量比较多时,会出现帧率降低明显,在PC浏览器不太明显,但发布到微信后,特别是在低端安卓机上特别明显。后期可以考虑使用四叉树优化一下碰撞检测。

工程及说明

cocos creator v1.9.3版本下开发,工程中附带QQ玩一玩相关代码(使用玩一玩 vscode下的插件 qqextension 0.5.5及对应的qqplaycore)。完整工程在 “我的资源” 里下载

贪吃蛇大作战creator工程源码



版权声明:本文为qq_26902237原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。