使用OGEngine开发2048游戏机源码解析
最近有一款2048的游戏非常火,本文将来介绍一下使用OGEngine游戏引擎开发游戏2048。
OGEngine引擎是开源的,我们很容易找到,搭建起来也很方便,我们只需在Android工程下添加OGEngine的jar包或者直接引用源码就可以了。
OGEngine引擎官网
2048游戏源码下载
1.创建游戏的主Activity 类
创建的游戏主Activity入口类继承于GameActivity类,需要重写相关方法。
(1) 重写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不变,根据实际手机屏幕分辨率的宽高比改变镜头的高。
(2) 重写 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");
}
(3) 重写 onLoadComplete(). 此类在上面onLoadComplete()方法中加载资源完成后执行,通常此时可以跳转到相关游戏场景。
@Override
protected void onLoadComplete() {
// 加载资源完成后
this.startScene(GameScene.class);
}
创建场景的类可以继承于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);
.....
}
(1) Scene 类是游戏中非常重要的一个类,在Scene场景中,利用attachChild(IEntity)来添加实体。
(2) 由GameActivity 类跳转到Scene 或者Scene 于 Scene 之间的跳转使用
public Scene startScene(Class<? extends Scene> pSceneCls)
public Scene startScene(Class<? extends Scene> pSceneCls, SceneBundle bundle)
pSceneCls:需要跳转的场景的Class
bundle用于传递场景之间的数据
(3) Scene中包含生命周期,在Scene被添加到引擎渲染后、Activity执行对应生命周期时、Scene会重新显示时执行
public void onSceneCreate(SceneBundle bundle)
public void onSceneResume()
public void onScenePause()
public void onSceneDestroy()
(4) 要关闭一个Scene 使用 finish() 方法即可。
(5) 需要灵活运用实体中有关设置坐标位置和获取坐标位置的方法来设定实体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) ;
(6) 显示、更新分数。这里显示分数使用到了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);
}
}
() 按钮及按钮点击事件监听,btnHelp 和 btnExit 均为ButtonSprite,通过
setOnClickListener(onClickListener); 设置点击事件监听。
/**
* 按钮点击监听
*/
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();
}
}
};
手游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,下面我们再把它组合成一个整体。
(1) 建立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();
}
(2)具体实现创建卡片函数,代码如下:
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));
}
(4)在游戏场景GameScene 类中创建GameGroup
// 中间游戏主体部分
mGameGroup = new GameGroup(this);
// 设置改Group的中心位置在镜头的中心点上
mGameGroup.setCentrePosition(this.getCameraCenterX(),
this.getCameraCenterY());
this.attachChild(mGameGroup);
(5)运行效果
运行效果如下,中间部分则为GameGroup 中展示的界面