使用OGEngine开发2048游戏机源码解析



2048 的游戏非常火,本文将来介绍一下使用OGEngine 游戏引擎开发游戏2048。



引擎是开源的,我们很容易找到,搭建起来也很方便,我们只需在 Android 工程下添加 OGEngine 的 jar 包或者直接引用源码就可以了。





OGEngine引擎官网



2048游戏源码下载



1.创建游戏的主Activity 类

Activity 入口类继承于 GameActivity 类,需要重写相关方法。



onCreatePixelPerfectEngineOptions().  此类主要是设置引擎相关参数。



@Override
        protected PixelPerfectEngineOptions onCreatePixelPerfectEngineOptions() {
                PixelPerfectEngineOptions pixelPerfectEngineOptions = new PixelPerfectEngineOptions(
                                this, ZoomCamera.class);
                // 设置竖屏
                pixelPerfectEngineOptions
                                .setScreenOrientation(ScreenOrientation.PORTRAIT_FIXED); 
                // 适配模式,这里设置为“保持宽度不变,改变高”
                pixelPerfectEngineOptions
                                .setPixelPerfectMode(PixelPerfectMode.CHANGE_HEIGHT);
                // 参考尺寸
                pixelPerfectEngineOptions.setDesiredSize(ConstantUtil.DESIRED_SIZE);
                return pixelPerfectEngineOptions;
        }



解析:



ScreenOrientation.PORTRAIT_FIXED  这个参数表示竖屏, ScreenOrientation.LANDSCAPE_FIXED  这参数表上横屏,我这里设置成了竖屏。



PixelPerfectMode.CHANGE_HEIGHT  表上“保持宽度不变,改变高”。



480 ,表示保持镜头的宽为 480 不变,根据实际手机屏幕分辨率的宽高比改变镜头的高。



 



onLoadResources().  此类主要用于在此加载相关资源。



 



@Override
        protected void onLoadResources() {
                // 加载图片资源
                RegionRes.loadTexturesFromAssets(Res.ALL_XML);
                
                // 加载字体资源
                FontRes.loadFont(128, 128, Typeface.create(Typeface.DEFAULT, Typeface.BOLD), 32, true, Color.BLACK, ConstantUtil.FONT_CARD_NUM);
                FontRes.loadFont(128, 128, Typeface.create(Typeface.DEFAULT, Typeface.NORMAL), 25, true, Color.WHITE, ConstantUtil.FONT_SCORE_NUM);

                // 加载音效资源
                SoundFactory.setAssetBasePath("mfx/");
                SoundRes.loadSoundFromAssets(ConstantUtil.SOUND_SELECT, "select.mp3");
                SoundRes.loadSoundFromAssets(ConstantUtil.SOUND_SETPOS, "setpos.mp3");
                SoundRes.loadSoundFromAssets(ConstantUtil.SOUND_MERGE, "merge.mp3");
        }



onLoadComplete().  此类在上面 onLoadComplete() 方法中加载资源完成后执行,通常此时可以跳转到相关游戏场景。



@Override
        protected void onLoadComplete() {
                // 加载资源完成后
                this.startScene(GameScene.class);
        }


2.创建游戏场景 GameScene 类


Scene  类,场景 Scene 是 Entity 的子类,该类用来创建游戏中的场景。 Scene 是屏幕上所有对象的根容器。



onSceneCreate(SceneBundle bundle) 方法里面创建各种实体,比如  EntityGroup 、 Sprite 、 Text 、 Layer 。



@Override
        public void onSceneCreate(SceneBundle bundle) {
                super.onSceneCreate(bundle);
                initView();
        }



        private void initView() {
                // 游戏背景
                AnimatedSprite game_bg = new AnimatedSprite(0, 0, Res.GAME_BG,
                                getVertexBufferObjectManager());
                this.attachChild(game_bg);
                // 中间游戏主体部分
                mGameGroup = new GameGroup(this);
                // 设置改Group的中心位置在镜头的中心点上
                mGameGroup.setCentrePosition(this.getCameraCenterX(),
                                this.getCameraCenterY());
                this.attachChild(mGameGroup);

                // 2048 LOGO
                AnimatedSprite game_logo = new AnimatedSprite(20, 20, Res.GAME_LOGO,
                                getVertexBufferObjectManager());
                this.attachChild(game_logo);

                // 最佳得分背景
                bestScoreBg = new AnimatedSprite(0, 20, Res.GAME_SCORE_BG_BEST,
                                getVertexBufferObjectManager());
                // 设置bestScoreBg右边x坐标的位置在镜头的右边减20的位置
                bestScoreBg.setRightPositionX(this.getCameraRightX() - 20);
                this.attachChild(bestScoreBg);

                tBestScore = new Text(0, bestScoreBg.getY() + 50,
                                FontRes.getFont(ConstantUtil.FONT_SCORE_NUM),
                                SharedUtil.getBestScore(getActivity()) + "", 4,
                                getVertexBufferObjectManager());
                // 设置 tBestScore 的X坐标上的中点在bestScoreBg的X坐标中点上
                tBestScore.setCentrePositionX(bestScoreBg.getCentreX());
                this.attachChild(tBestScore);

                // 当前得分背景
                currScoreBg = new AnimatedSprite(0, bestScoreBg.getY(),
                                Res.GAME_SCORE_BG_NOW, getVertexBufferObjectManager());
                // 设置currScoreBg的右边X坐标点在bestScoreBg左边的X坐标减20的位置上
                currScoreBg.setRightPositionX(bestScoreBg.getLeftX() - 20);
                this.attachChild(currScoreBg);
.....
        }



类是游戏中非常重要的一个类,在 Scene 场景中,利用 attachChild(IEntity) 来添加实体。



由 GameActivity  类跳转到 Scene  或者 Scene  于  Scene  之间的跳转使用



public Scene startScene(Class<? extends Scene> pSceneCls)



public Scene startScene(Class<? extends Scene> pSceneCls, SceneBundle bundle)



:需要跳转的场景的 Class



用于传递场景之间的数据



中包含生命周期,在 Scene 被添加到引擎渲染后、 Activity 执行对应生命周期时、 Scene 会重新显示时执行



public void onSceneCreate(SceneBundle bundle) 
    
 
    

      public void onSceneResume() 
    
 
    

      public void onScenePause() 
    
 
    

      public void onSceneDestroy()



要关闭一个 Scene  使用  finish()  方法即可。



需要灵活运用实体中有关设置坐标位置和获取坐标位置的方法来设定实体 Entity 的位置。下面是部分相关方法。



 



        //获取左X坐标
        public float getLeftX() ;
//获取右X坐标
        public float getRightX();
        //设置右上X位置
        public void setRightPositionX(float pX);
//获取底部Y位置
        public float getBottomY();
//设置底部Y位置
        public void setBottomPositionY(float pY) ;
        // 获取中心X坐标
        public float getCentreX() ;
        // 获取中心Y坐标
        public float getCentreY() ;
        // 设置中心X位置
        public void setCentrePositionX(float pCentreX) ;
        // 设置中心Y位置
        public void setCentrePositionY(float pCentreY);
        // 设置中心位置
        public void setCentrePosition(float pCentreX, float pCentreY) ;



Text  文本类 tBestScore、tBestScore均为 Text



/**
         * 更新最高纪录
         * 
         * @param pBestScore
         */
        private void updateBestScore(int pBestScore) {
                tBestScore.setText(pBestScore + "");
// 设置居中
                tBestScore.setCentrePositionX(bestScoreBg.getCentreX());
        }

        /**
         * 增加当前分数
         * 
         * @param pAddScore
         *            所增加的分数
         */
        public void addCurrScore(int pAddScore) {
                if (pAddScore != 0) {
                        // 播放音效
                        SoundRes.playSound(ConstantUtil.SOUND_MERGE);
                }
                currScore += pAddScore;
                tCurrScore.setText(currScore + "");
                tCurrScore.setCentrePositionX(currScoreBg.getCentreX());

                // 当前分数大于所保存的最佳分数时,更新最佳分数
                if (currScore > SharedUtil.getBestScore(getActivity())) {
                        SharedUtil.setBestScore(getActivity(), currScore);
                        updateBestScore(currScore);
                }
        }



ButtonSprite ,通过



设置点击事件监听。



/**
         * 按钮点击监听
         */
        private OnClickListener onClickListener = new OnClickListener() {

                @Override
                public void onClick(ButtonSprite pButtonSprite, float pTouchAreaLocalX,
                                float pTouchAreaLocalY) {
                        if (pButtonSprite == btnHelp) {
                                // 点击了帮助按钮
                                attachChild(helpLayer);
                        } else if (pButtonSprite == btnExit) {
                                // 点击了退出游戏按钮
                                showDialog();
                        }

                }
        };


3.创建卡片类


2048 中,卡片是一个重要的单元体,移动的其实是卡片,下面我们来介绍一下卡片类的实现。



(1) 稍微扩展一下卡片精灵



2048 游戏中,每种数字所对应的卡片颜色不一样,这里我们使用卡片的原图是一白色的方块图,通过相关设置颜色的方法可以改变卡片的颜色。观察 OGEngine 引擎源码可以发现 Entity  类里有个  setColor  的方法,它可以使设定的 RGB 颜色值叠加在原图上形成新的颜色,我们这里的原图使用的是白色的方块图,所以我们可以找到想要变成的颜色的图,拿到它的 RGB 值就好办了。



Entity  中的  setColor



/**
         * @param pRed
         *            from <code>0.0f</code> to <code>1.0f</code>
         * @param pGreen
         *            from <code>0.0f</code> to <code>1.0f</code>
         * @param pBlue
         *            from <code>0.0f</code> to <code>1.0f</code>
         */
        @Override
        public void setColor(final float pRed, final float pGreen, final float pBlue) {
                if (this.mColor.setChecking(pRed, pGreen, pBlue)) { 
                    this.onUpdateColor();
                }
        }



RGB 各个颜色值的范围是 0~1( 如下面的源码注释 ) ,而我们用取色器获取得到的 RGB 是 0~255 的,不太方便使用,所以我们扩展一下。这里我们在卡片精灵 CardSprite  类中扩展一下。



public class CardSprite extends AnimatedSprite {

        public CardSprite(VertexBufferObjectManager pVertexBufferObjectManager) {
                super(0, 0, Res.GAME_ROUNDRECT, pVertexBufferObjectManager);
        }

        /**
         * 设置RGB 0 到 255
        /**
        public void setRGB(float pRed, float pGreen, float pBlue) {
                this.setColor(pRed / 255, pGreen / 255, pBlue / 255);
        }

        /**
         * 设置数组形式的 RGB 0 到 255
         * @param pRGBs 数组形式的RGB
         */
        public void setRGB(float[] pRGBs) {
                this.setColor(pRGBs[0] / 255, pRGBs[1] / 255, pRGBs[2] / 255);
        }
}



(2) 创建卡片精灵和卡片上面显示的数字的组合类



OGEngine 中要把精灵、文本实体等组合成一个整体我们通常使用到 EntityGroup  类。



public class CardGroup extends EntityGroup {

        private CardSprite cardSprite;// 卡片背景
        private int number = 0;// 数字
        private Text tNum; // 数字文本

        // ============get&set===================
        // 获取数字
        public int getNumber() {
                return number;
        }
        // 设置数字
        public void setNumber(int number) {
                this.number = number;
                onDrawNum(number);
        }
        // ======================================
        public CardGroup(float pX, float pY, Scene pScene) {
// 卡片资源原图的大小为90*90,所以这个组合的宽高可以设置为90*90
                super(pX, pY, 90, 90, pScene);
                // 初始化View
                initView();
                // 自动计算成自适应宽高
                this.setWrapSize();
        }

private void initView() {
                // 创建卡片精灵
                cardSprite = new CardSprite(this.getVertexBufferObjectManager());
                this.attachChild(cardSprite);
                // 创建文本实体用于显示卡片上的数字,文本是可变的,这里设置文本的默认值为空字符串,最大的显示位数为4
                tNum = new Text(0, 0, FontRes.getFont(ConstantUtil.FONT_CARD_NUM), "",
                                4, this.getVertexBufferObjectManager());
                // 设置文本的中心Y坐标在cardSprite的Y坐标中点上
                tNum.setCentrePositionY(cardSprite.getCentreY());
                this.attachChild(tNum);
                // 画卡片上的数字并根据数字显示颜色,默认值为0
                onDrawNum(0);
        }









        // 画卡片上的数字,并根据数字设置卡片的颜色
        private void onDrawNum(int number) {
                float[] mRGBs;
                switch (number) {
                case 0:
                        mRGBs = ConstantUtil.RGBS_0;
                        break;
                case 2:
                        mRGBs = ConstantUtil.RGBS_2;
                        break;
                case 4:
                        mRGBs = ConstantUtil.RGBS_4;
                        break;
                case 8:
                        mRGBs = ConstantUtil.RGBS_8;
                        break;

                // 此次省略了16、32、64、128、256、512 的设置

                case 1024:
                        mRGBs = ConstantUtil.RGBS_1024;
                        break;
                case 2048:
                        mRGBs = ConstantUtil.RGBS_2048;
                        break;
                default:
                        mRGBs = ConstantUtil.RGBS_0;
                        break;
                }
                // 设置精灵颜色,传入的是RGB的数组形式
                cardSprite.setRGB(mRGBs);
                // 设置文本显示,设置数字相对于卡片精灵X坐标居中
                if (number == 0) {
                        tNum.setText("");
                } else {
                        tNum.setText(number + "");
                        tNum.setCentrePositionX(cardSprite.getCentreX());
                }
        }



// 对比当前卡片与传进来的卡片上的数字是否相等
        public boolean equals(CardGroup pCardGroup) {
                return getNumber() == pCardGroup.getNumber();
        }



这样整个卡片类就创建好了,代码注释得比较详细,就不再多解析了。




4.把卡片添加到游戏主体界面部分(GameGroup)

CardGroup ,下面我们再把它组合成一个整体。



建立 GameGroup 类,声明一些常量,构造器。



public class GameGroup extends EntityGroup {
        private GameScene mGameScene;
        /**手指滑动的最小响应距离**/
        private final static int FLING_MIN_DISTANCE =10;
        /**卡片之间的间隔**/
        private static final float INTERVAL = 15;
        /**卡片行列数量**/
        private final static int mCount = 4;
        /**卡片尺寸**/
        private final static float CARD_SIZE = 90;
        /**卡片数组**/
        private CardGroup[][] cardArrays = new CardGroup[4][4];
        /**用于标记还有哪些空的位置**/
        private List<Point> emptyPoints = new ArrayList<Point>();
/**随机生成的数字2**/
        private final static int mSamllNum = 2;
/**随机生成的数字4**/
        private final static int mBignum = 4;
        public GameGroup(GameScene pGameScene) {
                super(0, 0, 435, 435, pGameScene);
                // 设置可以监听触碰事件
                this.setIgnoreTouch(false);
                this.mGameScene = pGameScene;
                initView();
        }



具体实现创建卡片函数,代码如下:



        private void initView() {
                // 创建背景
                AnimatedSprite rectBg = new AnimatedSprite(0, 0, Res.GAME_RECT_BG, this.getVertexBufferObjectManager());
                this.attachChild(rectBg);
                // 创建 4*4 单元格 卡片
                for (int row = 0; row < mCount; row++) {
                        for (int column = 0; column < mCount; column++) {
                                cardArrays[row][column]=new CardGroup((column+1)*INTERVAL+column*CARD_SIZE, (row+1)*INTERVAL+row*CARD_SIZE, getScene());
                                this.attachChild(cardArrays[row][column]);
                                
                        }
                }
                // 在随机一处剩余的空白单元随机生成数字,2或者4
                addRandomNum();
                addRandomNum();
        }



(3)游戏生成随机卡片数字addRandomNum()



/**
         * 在随机一处剩余的空白单元随机生成数字,2或者4
         */
        private void addRandomNum() {
                // 播放音效
                SoundRes.playSound(ConstantUtil.SOUND_SETPOS);
                emptyPoints.clear();
                for (int x = 0; x < mCount; x++) { 
                        for (int y = 0; y < mCount; y++) {
                                if (cardArrays[x][y].getNumber() <= 0) {
                                        emptyPoints.add(new Point(x, y));
                                }
                        }
                }
                Point p = emptyPoints
                                .remove((int) (Math.random() * emptyPoints.size()));
                cardArrays[p.x][p.y].setNumber(Math.random() > 0.1f ? mSamllNum
                                : mBignum);
// 生成卡片的一些动作效果,就是一个由0到1倍的缩放过程
                cardArrays[p.x][p.y].registerEntityModifier(new ScaleModifier(0.2f, 0.0f, 1.0f));
        }



在游戏场景 GameScene  类中创建 GameGroup



// 中间游戏主体部分
                mGameGroup = new GameGroup(this);
                // 设置改Group的中心位置在镜头的中心点上
                mGameGroup.setCentrePosition(this.getCameraCenterX(),
                                this.getCameraCenterY());
                this.attachChild(mGameGroup);



运行效果



GameGroup  中展示的界面