zoukankan      html  css  js  c++  java
  • Cocos2d-html5入门之2048游戏

    一、介绍

    Cocos2d-JS是Cocos2d-x的Javascript版本,它的前身是Cocos2d-html5。在3.0版本以前叫做Cocos2d-html5,从3.0版本开始叫做Cocos2d-JS。我们知道Cocos2d-x支持使用C++、Lua、Javascript来进行程序开发,其所内置的是一个Javascript引擎,通过用C++解析Javascript去执行;而Cocos2d-html5是使用Javascript进行开发,最终运行在浏览器里的。那么在v3.0的时候,Cocos2d-html5和Cocos2d-x JSBinding被合到了一起,称作Cocos2d-JS

    和Cocos2d-html5不同的是,Cocos2d-JS开发的程序不仅可以运行在浏览器里,还可以编译运行在Mac OSX, Windows, iOS, Android的原生平台上,真正做到“一次开发,全平台运行”。Cocos2d-JS支持Cocos2d-x的所有特性并提供更简单易用的Javascript风格API,它还自带了Cocos Console,一个用于简化项目创建和不同目标平台编译发布流程的终端工具。


    二、Cocos2d-html5目录结构

    为了快速入门,所以决定开发一个最近比较热门的2048小游戏。当然,首先需要去Cocos2d-x官网下载Cocos2d-html5-v2.2.3引擎,下载后解压就行了。Cocos2d-html5的目录结构如下:

    • cocos2d:游戏引擎的主要文件
    • extensions:包含一些扩展的功能,且支持自定义扩展
    • external:包含box2d和chipmunk这两个物理引擎
    • HelloHTML5World:一个最简单的HelloWorld项目
    • samples:官网例子和几款小游戏
    • template:项目模板

    我们可以通过拷贝 HelloHTML5World 或 template中的项目模板,然后基于它编写自己的代码程序。

    这里使用WebStorm作为IDE进行游戏开发,WebStorm有非常强大的智能补全功能,提高了开发效率。还能与 Google Chrome 浏览器配合,完成实时编辑和调试。只需要下载WebStorm然后载入引擎目录稍微配置一下就可以了,不知道怎么做的可以看看快速搭建 Cocos2d-HTML5 开发调试环境这篇博文。


    三、2048游戏开发


    1、创建项目

    Cocos2d-html5中创建项目很简单,只需要拷贝HelloHTML5World项目(当然你也可以复制template下的文件夹),然后重命名就行了。

    • res:资源文件夹(图片、音频等)
    • src:js脚本存放目录(我们自己的项目源码目录)
    • index.html:主页面
    • build.xml:文件的引用,包括引擎库文件和自己写的文件
    • cocos2d.js:定义了程序运行需要的一些参数,如 是否显示FPS,是否加载扩展库等
    • main.js:定义程序的入口,初始化导演类等等。

    我们需要在src文件夹里编写我们自己的代码,首先我们删除myApp.js中的所有代码,开始写我们自己的2048游戏。


    2、创建游戏场景

    由于2048游戏很简单,它只需要一个场景,下面我们来创建一个场景:

    // 层
    var Helloworld = cc.Layer.extend({
        init:function () {
            this._super();
            return true;
        }
    });
    
    // 场景
    var HelloWorldScene = cc.Scene.extend({
        onEnter:function () {
            this._super();
            var layer = new Helloworld();
            layer.init();
            this.addChild(layer);
        }
    });

    很简单吧!我们只需要创建一个Layer类,然后将它的一个实例加入Scene中,程序运行时main.js会创建一个Scene的实例作为程序入口。


    3、创建卡片类

    我们把2048游戏中的每一个方格看作一个卡片,上面的数字是它的属性。也就是说我们需要4x4=16个卡片类的对象。新建一个CardSprite.js文件:

    var CardSprite = cc.Layer.extend({
        // 全局变量
        number:0,
        labelCardNumber:null,
        cardColorBG:null,
        // 构造函数
        ctor:function()
        {
            this._super();
        },
        // 初始化卡牌
        initCard:function(num, width, height, positionX, positionY)
        {
            this.number = num;
            // 背景层,也就是一个小正方形
            this.cardColorBG = cc.LayerColor.create(new cc.Color4B(200, 190, 180, 255), width-15, height-15);
            this.cardColorBG.setPosition(positionX, positionY);
            // 添加数字,大于0显示,否则不显示
            if(this.number > 0)
            {
                this.labelCardNumber = cc.LabelTTF.create(this.number,"Arial", 60);
                this.labelCardNumber.setPosition(this.cardColorBG.getContentSize().width/2, this.cardColorBG.getContentSize().height/2);
                this.labelCardNumber.setTag(8);
                this.cardColorBG.addChild(this.labelCardNumber);
            }
            else
            {
                this.labelCardNumber = cc.LabelTTF.create("","Arial", 60);
                this.labelCardNumber.setPosition(this.cardColorBG.getContentSize().width/2, this.cardColorBG.getContentSize().height/2);
                this.labelCardNumber.setTag(8);
                this.cardColorBG.addChild(this.labelCardNumber);
            }
            this.addChild(this.cardColorBG);
        },
        // 获取数字
        getNumber:function()
        {
            return this.number;
        },
        // 重置卡片数字
        setNumber:function(num)
        {
            this.number = num;
            if(this.number > 0)
            {
                this.labelCardNumber.setString(this.number);
            }
            else
            {
                this.labelCardNumber.setString("");
            }
        }
    });
    
    // 静态函数
    CardSprite.createCardSprite = function(num, width, height, positionX, positionY)
    {
        var card = new CardSprite();
        if(card)
        {
            card.initCard(num, width, height, positionX, positionY);
            return card;
        }
        return null;
    }
    这里将CardSprite类继承自Layer,然后初始化卡片背景和上面的数字,还定义了number的set/get方法。这里需要注意几点:
    • 新建.js文件以后需要在build.xml和cocos2d.js相关位置添加文件路径。
    • 继承都需要有 this._super();,一般写在构造函数ctor:function()中。
    • 注意静态函数的写法,类名.函数名 = function(){}

    4、初始化界面

    下面我们来初始化游戏界面,主要是在Layer的init()函数中初始化,我们使用一个4x4的二维组来放置在主界面创建16个卡片:

    var Helloworld = cc.Layer.extend({
        cardArr:null,  // 存放4x4=16个卡片
        // 初始化
        init:function () {
            this._super();
            var size = cc.Director.getInstance().getWinSize();
    
            // 主界面背景层->设置颜色
            var lazyLayer = cc.LayerColor.create(new cc.Color4B(180, 170, 160, 255) , null, null);
            this.addChild(lazyLayer);
    
            // 创建卡片数组
            this.cardArr = new Array(4);
            for(var i=0; i<4; i++) {
                this.cardArr[i] = new Array(4);
            }
    
            // 初始化所有卡片,数字为0,不显示
            this.createCards(size);
    
            // 开始的时候,随机生成两个数字
            this.autoCreateCardNumber();
            this.autoCreateCardNumber();
    
            return true;
        },
    
        // 初始化卡片数组*******************************
        createCards:function(size)
        {
            var unitSize = (size.height - 80)/4;
            for(var i=0; i<4; i++) {
                for(var j=0; j<4; j++) {
                    var card = CardSprite.createCardSprite(0, unitSize, unitSize, unitSize*i + 210, unitSize*j + 30);
                    this.cardArr[i][j] = card;
                    this.addChild(card);
                }
            }
        },
    
        // 随机生成新的2或4的卡片****************
        autoCreateCardNumber:function()
        {
            while(1) {
                var i = Math.floor(Math.random()*4);  // 随机生成0~3
                var j = Math.floor(Math.random()*4);
    
                if (this.cardArr[i][j].getNumber() == 0) {   
                    // 4与2生成的概率比为1:9
                    this.cardArr[i][j].setNumber(Math.floor(Math.random()*10) < 1 ? 4 : 2);
                    break;
                }
    
                if (!this.shouldCreateCardNumber()) {
                    break;
                }
            }
        },
    
        // 判断是否还有空位******************
        shouldCreateCardNumber:function()
        {
            var should = false;
            for(var i=0; i<4; ++i) {
                for(var j=0; j<4; ++j) {
                    if (this.cardArr[i][j].getNumber() == 0) {
                        should = true;
                        break;
                    }
                }
            }
            return should;
        }
    });
    通过调用this.createCards(size);初始化所有16个卡片,由于0不显示,所以卡片上都没有数字。然后调用this.autoCreateCardNumber();在随机的两个卡片上生成数字,在随机生成的时候我们应该先判断还有没有空位,否则有可能会陷入死循环。
    初始化的界面如下图:


    5、定义手势动作

    我们需要通过触摸滑动来操作游戏,所以就需要定义上下左右的手势动作。这就要用到引擎的触摸响应机制,Cocos2d-html5与Cocos2d-x一样,有多点触控单点触控。默认情况下是多点触控,要使用单点触控,我们要使用addTargetedDelegate()方法设置代理。
    那么如何判断上下左右呢?当然是根据起始触摸点和结束触摸点的坐标变化:

    var Helloworld = cc.Layer.extend({
        firstX:null,
        firstY:null,
        cardArr:null,
    
        init:function () {
            this._super();
            // 设置单点触摸
            cc.Director.getInstance().getTouchDispatcher()._addTargetedDelegate(this, 0, true);
            ...
            // 打开触摸
            this.setTouchEnabled(true);
            return true;
        },
        // onTouchBegan函数,需要返回true
        onTouchBegan:function (touch, event) {
            var touchPoint = touch.getLocation();
            this.firstX = touchPoint.x;
            this.firstY = touchPoint.y;
            return true;
        },
        // onTouchEnded函数
        onTouchEnded:function (touch, event) {
            var touchPoint = touch.getLocation();
            var offsetX = this.firstX - touchPoint.x;
            var offsetY = this.firstY - touchPoint.y;
    
            if(Math.abs(offsetX) > Math.abs(offsetY)) {
                if(offsetX > 5) {
                    this.doLeft();
                    //this.doCheckGameOver();
                    //this.setScore(this.score);
                }
                else if(offsetX < -5) {
                    this.doRight();
                    //this.doCheckGameOver();
                    //this.setScore(this.score);
                }
            }
            else {
                if(offsetY > 5) {
                    this.doDown();
                    //this.doCheckGameOver();
                    //this.setScore(this.score);
                }
                else if(offsetY < -5) {
                    this.doUp();
                    //this.doCheckGameOver();
                    //this.setScore(this.score);
                }
            }
        },
        // ...other methods
    });

    6、卡片合并

    游戏2048主要玩法就是通过合并相同数字的卡片以达到2048。通过手势动作往一个方向进行合并。我们的思路就是根据手势方向,遍历每一行或每一列,将在这个方向上相邻(中间没有其他数字)且数字相同的卡片合并加倍。

    // 向上***************************************
    doUp:function(){
    var isdo = false;
    for (var x=0; x<4; ++x)
    {
        for (var y=3; y>=0; --y)
        {
            for (var y1=y-1; y1>=0; --y1)
            {
                if (this.cardArr[x][y1].getNumber() > 0)
                {
                    if (this.cardArr[x][y].getNumber() <= 0)
                    {
                        this.cardArr[x][y].setNumber(this.cardArr[x][y1].getNumber());
                        this.cardArr[x][y1].setNumber(0);
                        ++y;
                        isdo = true;
                    }
                    else if(this.cardArr[x][y].getNumber() == this.cardArr[x][y1].getNumber())
                    {
                        this.cardArr[x][y].setNumber(this.cardArr[x][y].getNumber()*2);
                        this.cardArr[x][y1].setNumber(0);
                        //this.score += this.cardArr[x][y].getNumber(); 
                        isdo = true;
                    }
                    break;
                }
            }
        }
    }
    return isdo;
    },
    // 向下***************************************
    doDown:function() {
    var isdo = false;
    for (var x=0; x<4; ++x)
    {
        for (var y=0; y<4; ++y)
        {
            for (var y1=y+1; y1<4; ++y1)
            {
                if (this.cardArr[x][y1].getNumber() > 0)
                {
                    if (this.cardArr[x][y].getNumber() <= 0)
                    {
                        this.cardArr[x][y].setNumber(this.cardArr[x][y1].getNumber());
                        this.cardArr[x][y1].setNumber(0);
                        --y;
                        isdo = true;
                    }
                    else if(this.cardArr[x][y].getNumber() == this.cardArr[x][y1].getNumber())
                    {
                        this.cardArr[x][y].setNumber(this.cardArr[x][y].getNumber()*2);
                        this.cardArr[x][y1].setNumber(0);
                        //this.score += this.cardArr[x][y].getNumber(); 
                        isdo = true;
                    }
                    break;
                }
            }
        }
    }
    return isdo;
    },
    // 向左***************************************
    doLeft:function() {
    var isdo = false;
    for (var y=0; y<4; ++y)
    {
        for(var x=0; x<4; ++x)
        {
            for(var x1=x+1; x1<4; ++x1)
            {
                if(this.cardArr[x1][y].getNumber() > 0)
                {
                    if(this.cardArr[x][y].getNumber() <= 0)
                    {
                        this.cardArr[x][y].setNumber(this.cardArr[x1][y].getNumber());
                        this.cardArr[x1][y].setNumber(0);
                        --x;
                        isdo = true;
                    }
                    else if(this.cardArr[x][y].getNumber() == this.cardArr[x1][y].getNumber())
                    {
                        this.cardArr[x][y].setNumber(this.cardArr[x][y].getNumber()*2);
                        this.cardArr[x1][y].setNumber(0);
                        //this.score += this.cardArr[x][y].getNumber();
                        isdo = true;
                    }
                    break;
                }
            }
        }
    }
    return isdo;
    },
    // 向右***************************************
    doRight:function() {
    var isdo = false;
    for (var y = 0; y < 4; ++y)
    {
        for (var x = 3; x >= 0; --x)
        {
            for (var x1 = x - 1; x1 >= 0; --x1)
            {
                if (this.cardArr[x1][y].getNumber() > 0)
                {
                    if (this.cardArr[x][y].getNumber() <= 0)
                    {
                        this.cardArr[x][y].setNumber(this.cardArr[x1][y].getNumber());
                        this.cardArr[x1][y].setNumber(0);
                        ++x;
                        isdo = true;
                    }else if(this.cardArr[x][y].getNumber() == this.cardArr[x1][y].getNumber())
                    {
                        this.cardArr[x][y].setNumber(this.cardArr[x][y].getNumber()*2);
                        this.cardArr[x1][y].setNumber(0);
                        //this.score += this.cardArr[x][y].getNumber(); 
                        isdo = true;
                    }
                    break;
                }
            }
        }
    }
    return isdo;
    }

    7、添加分数

    添加两个变量:

    score:0,  // 分数
    scoreLabel:null,  // 显示分数的控件
    然后初始化分数显示:

    // 显示分数
    var label = cc.LabelTTF.create("Score : ", "Arial", 20);
    label.setAnchorPoint(0,0);
    label.setPosition(size.width/2 - 50, size.height-50);
    this.addChild(label);
    
    this.scoreLabel = cc.LabelTTF.create("0", "Arial", 20);
    this.scoreLabel.setAnchorPoint(0,0);
    this.scoreLabel.setPosition(size.width/2 + 20, size.height-50);
    this.addChild(this.scoreLabel);
    卡片合并的时候要增加分数,然后更新分数显示:

    // 更新分数 -> this.setScore(this.score);
    setScore:function(s)
    {
    	this.scoreLabel.setString(s);
    }

    8、判断游戏结束和胜利

    每一次卡片合并操作后,我们都需要判断游戏是否胜利或者结束。利用五个条件判断游戏是否还能够继续:(1)还有空卡片 (2)还可以向右滑 (3)还可以向左滑 (4)还可以向上滑 (5)还可以向下滑。只要以上条件满足一个,游戏就可以再继续。否则,游戏不能再继续了。判断胜利则是看卡片中有没有数字达到2048。

    // 判断游戏是否结束*******************************
    doCheckGameOver:function()
    {
    	var size = cc.Director.getInstance().getWinSize();
    
    	var isGameOver = true;
    	for(var y=0; y<4; ++y)
    	{
    	    for(var x=0; x<4; ++x)
    	    {
    		if(this.cardArr[x][y].getNumber() == 0 ||
    		    (x>0&&(this.cardArr[x][y].getNumber() == this.cardArr[x-1][y].getNumber())) ||
    		    (x<3&&(this.cardArr[x][y].getNumber() == this.cardArr[x+1][y].getNumber())) ||
    		    (y>0&&(this.cardArr[x][y].getNumber() == this.cardArr[x][y-1].getNumber())) ||
    		    (y<3&&(this.cardArr[x][y].getNumber() == this.cardArr[x][y+1].getNumber())))
    		{
    		    isGameOver = false;
    		}
    	    }
    	}
    
    	if (isGameOver)  {     // if the game is over
    	    console.log("The Game Is Over!");
    	    cc.Director.getInstance().replaceScene(cc.TransitionFade.create(1, new HelloWorldScene()));
    	}
    	else {
    	    if (this.shouldCreateCardNumber()) {
    		this.autoCreateCardNumber();
    	    }
    	}
    
    	if(this.isWin())  {     // if win
    	    cc.Director.getInstance().replaceScene(cc.TransitionFade.create(1, new HelloWorldScene()));
    	}
    }
    
    // 判断是否胜利**********************
    isWin:function() {
    var Win = false;
    	for (var i=0; i<4; ++i) {
    	    for(var j=0; j<4; ++j) {
    		if (this.cardArr[i][j].getNumber() == 2048) {
    		    Win = true;
    		    break;
    		}
    	    }
    	}
    	return Win;
    }

    9、界面优化

    在一个卡片里面,当数字变成两位数、三位数的时候,就需要调整一下数字的大小,在CardSprite的setNumber方法中添加代码:

    // 设置数字大小
    if(num >= 0)
    {
        this.labelCardNumber.setFontSize(60);
    }
    if(num >= 16)
    {
        this.labelCardNumber.setFontSize(55);
    }
    if(num >= 128)
    {
        this.labelCardNumber.setFontSize(40);
    }
    if(num >= 1024)
    {
        this.labelCardNumber.setFontSize(30)
    }
    我们还需要给不同的数字设置不同的卡片颜色,同样在setNumber方法中添加代码:

    //判断数字的大小来调整颜色
    if(this.number == 0){
        this.cardColorBG.setColor(new cc.Color3B(200,190,180));
    }
    if (this.number == 2) {
        this.cardColorBG.setColor(new cc.Color3B(240,230,220));
    }
    if (this.number == 4) {
        this.cardColorBG.setColor(new cc.Color3B(240,220,200));
    }
    if (this.number == 8) {
        this.cardColorBG.setColor(new cc.Color3B(240,180,120));
    }
    if (this.number == 16) {
        this.cardColorBG.setColor(new cc.Color3B(240,140,90));
    }
    if (this.number == 32) {
        this.cardColorBG.setColor(new cc.Color3B(240,120,90));
    }
    if (this.number == 64) {
        this.cardColorBG.setColor(new cc.Color3B(240,90,60));
    }
    if (this.number == 128) {
        this.cardColorBG.setColor(new cc.Color3B(240,90,60));
    }
    if (this.number == 256) {
        this.cardColorBG.setColor(new cc.Color3B(240,200,70));
    }
    if (this.number == 512) {
        this.cardColorBG.setColor(new cc.Color3B(240,200,70));
    }
    if (this.number == 1024) {
        this.cardColorBG.setColor(new cc.Color3B(0,130,0));
    }
    if (this.number == 2048) {
        this.cardColorBG.setColor(new cc.Color3B(0,130,0));
    }

    10、最终游戏截图



    结语

    采用Cocos2D-HTML5引擎进行游戏开发,开发效率更高,加上丰富的工具集支持、实时的各种调试手段和云合作开发手段,更加直接的效果调校方式,完全没有开发环境依赖,各种优势的叠加,可以大幅度降低开发成本,提升游戏的上架速度。

    如果觉得这样还不够,我们可以使用Cocos2d-JS-v3.0,同一份JavaScript游戏代码不用修改或者小范围修改,配合Cocos2D-X JavaScript-Binding就能以Hybrid的方式直接作为本地应用发布,也可以编译到Android、iOS等移动平台。

    通过Cocos2D-HTML5或Cocos2d-JS,开发者能以一个更快、更惬意的方式实现自己的想法。




    参考:http://blog.csdn.net/column/details/love2048.html




  • 相关阅读:
    城市的划入划出效果
    文本溢出省略解决笔记css
    长串英文数字强制折行解决办法css
    Poj 2352 Star
    树状数组(Binary Indexed Trees,二分索引树)
    二叉树的层次遍历
    Uva 107 The Cat in the Hat
    Uva 10336 Rank the Languages
    Uva 536 Tree Recovery
    Uva10701 Pre, in and post
  • 原文地址:https://www.cnblogs.com/songlee/p/5738140.html
Copyright © 2011-2022 走看看