如何用Cocos2d-android写一个小游戏
Cocos2d-android是Cocos2dx家族中的一员,优点是使用Java语言进行游戏代码的编写,不像Cocos2dx需要使用C++ 、Lua,方便安卓程序员上手。缺点也显而易见,Cocos2dx本身使用C++开发的,Cocos2dx-android相当于做了一次Java到C的本地调用封装,因此执行效率上肯定会比较差。 开发环境:Eclipse + SDK17 屏幕左侧会有一个“忍者”,从屏幕右侧会跑出许多“敌人”向屏幕左侧移动。忍者发射飞镖射击敌人。如果忍者击毙了30个敌人则游戏成功结束,如果有敌人一直跑到屏幕左侧都没有被飞镖打死,则游戏结束。 step1 创建安卓项目 protected CCGLSurfaceView _glSurfaceView;
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(
WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);
getWindow().setFlags(
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON,WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
_glSurfaceView = new CCGLSurfaceView(this);
setContentView(_glSurfaceView);
}
这段代码中绝大部分都是使用的android原生代码。设定MainActivity界面不显示TitleBar,完全全屏且保持亮度。比较不一样的代码是声明了一个属性_glSurfaceView,属性类型为CCGLSurfaceView,CC前缀代表这是一个Cocos2d-android中的类型,该类继承自安卓的GLSurfaceView。通过调用构造器创建了_glSurfaceView将其传入了setContentView方法中。因此MainActivity连布局文件都不需要了。 @Override
public void onPause()
{
super.onPause();
CCDirector.sharedDirector().pause();
}
@Override
public void onResume()
{
super.onResume();
CCDirector.sharedDirector().resume();
}
@Override
public void onStop()
{
super.onStop();
CCDirector.sharedDirector().end();
}
这里反复出现了一个CCDirector类,通过sharedDirector方法获取CCDirector的单例对象,并执行相关方法。 public void onStart()
{
super.onStart();
CCDirector.sharedDirector().attachInView(_glSurfaceView);
CCDirector.sharedDirector().setDeviceOrientation(CCDirector.kCCDeviceOrientationLandscapeLeft);
CCDirector.sharedDirector().setDisplayFPS(true);
CCDirector.sharedDirector().setAnimationInterval(1.0f / 60.0f);
CCScene scene = GameLayer.scene();
CCDirector.sharedDirector().runWithScene(scene);
}
在Activity的onStart生命周期方法中,CCDirector类继续进行一些常规性的设置,这些都是常规性的代码,包括添加_glSurfaceView,屏幕横屏显示,显示FPS信息,设置刷新率为每秒60次。 public class GameLayer extends CCColorLayer {
......
public static CCScene scene()
{
//获取幕(CCScene)对象
CCScene scene = CCScene.node();
//创建GameLayer对象
CCColorLayer layer = new GameLayer(ccColor4B.ccc4(255,255,255));
//将Layer放到Scene中
scene.addChild(layer);
//返回Scene
return scene;
}
//构造器
protected GameLayer(ccColor4B color)
{
super(color);
//打开触屏事件
this.setIsTouchEnabled(true);
//视口大小
CGSize winSize =
CCDirector.sharedDirector().displaySize();
//精灵(忍者)
CCSprite player =
CCSprite.sprite("Player.png");
//设定忍者精灵在视口中的位置
player.setPosition(
CGPoint.ccp(
player.getContentSize().width / 2.0f,winSize.height / 2.0f));
//将忍者精灵添加到Layer上
addChild(player);
......
}
......
}
我们的GameLayer通过继承CCColorLayer而成为一个Layer,可以添加各种的精灵。但Layer不能单独的显示,最终要把Layer放到一个Scene中才可以。 super(color);
然后允许Layer可触摸。这样一旦手指在Layer上产生触摸,会触发Layer的相应回调方法。 this.setIsTouchEnabled(true);
接下来获取显示区域的大小: CGSize winSize = CCDirector.sharedDirector().displaySize();
创建一个精灵: CCSprite player = CCSprite.sprite("Player.png");
该精灵的样子就是Player.png,而Player.png需要放在我们安卓项目的assets文件夹下: 这些图片都将是精灵的形象。Player是忍者,Target是敌人,Projectile是忍者发射的飞镖,fps_images是用来显示帧数(FPS)信息的。需要注意的是,根据下载或导入的Cocos2d-android的来源不同,有的Cocos2d-android中已经包含了fps_images图片了,就不需要额外导入了。 player.setPosition(
CGPoint.ccp(
player.getContentSize().width / 2.0f,winSize.height / 2.0f));
这里需要注意两点: 最后将已经生成好的精灵添加到Layer中 addChild(player);
这样Layer的构造器就算完成了,在创建Layer对象的时候我们还设置了Layer的背景并添加了一个精灵。 public static CCScene scene()
{
CCScene scene = CCScene.node();
CCColorLayer layer =
new GameLayer(ccColor4B.ccc4(
255,255));
scene.addChild(layer);
return scene;
}
因为Layer不可以单独显示,所以scene方法就是创建Scene对象,通过调用GameLayer的构造器创建Layer对象后放到Scene中,并将该Scene对象作为方法的返回值返回。 this.schedule("gameLogic",1.0f);
该代码的作用就是利用计时器(Timer),每间隔1秒钟执行一次gameLogic方法。 public void gameLogic(float dt)
{
addTarget();
}
gameLogic回调方法必须有一个float类型的参数,该参数的值为本次回调距离上一次该方法被回调时的时间间隔是多少。所以对于gameLogic方法来说,每一次被调用时dt参数的值应该都是在1左右。 protected void addTarget()
{
Random rand = new Random();
//添加精灵
CCSprite target =
CCSprite.sprite("Target.png");
CGSize winSize =
CCDirector.sharedDirector().displaySize();
int minY = (int)(target.getContentSize().height / 2.0f);
int maxY = (int)(winSize.height - target.getContentSize().height / 2.0f);
int rangeY = maxY - minY;
int actualY = rand.nextInt(rangeY) + minY;
target.setPosition(winSize.width + (target.getContentSize().width / 2.0f),actualY);
addChild(target);
target.setTag(1);
......
int minDuration = 2;
int maxDuration = 4;
int rangeDuration = maxDuration - minDuration;
int actualDuration =
rand.nextInt(rangeDuration) + minDuration;
CCMoveTo actionMove =
CCMoveTo.action(
actualDuration,CGPoint.ccp(
-target.getContentSize().width / 2.0f,actualY));
CCCallFuncN actionMoveDone =
CCCallFuncN.action(this,"spriteMoveFinished");
CCSequence actions =
CCSequence.actions(actionMove,actionMoveDone);
target.runAction(actions);
}
addTarget方法中完成了两件事情: target.setTag(1);
这里为创建的敌人精灵设置一个tag属性,以后可以通过该tag值判定一个精灵的类型是什么。如果取到一个精灵的tag值为1,则可以知道该精灵为敌人精灵。 CCMoveTo actionMove =
CCMoveTo.action(
actualDuration,CGPoint.ccp(
-target.getContentSize().width / 2.0f,actualY));
actualDuration为利用Random生成的完成移动的时间,第二个参数是移动结束时希望敌人精灵所处的位置。终止位置的纵坐标与敌人精灵初始位置的纵坐标一致,横坐标为敌人精灵移出屏幕最左侧一半身位的位置,也就是敌人精灵的最右侧恰好贴在屏幕的最左侧边缘,刚好看不见。 public void spriteMoveFinished(Object sender)
{
CCSprite sprite = (CCSprite)sender;
if (sprite.getTag() == 1)
{
......
}
......
this.removeChild(sprite,true);
}
之前说过spriteMoveFinished有一个Object类型的参数,该参数就是触发该回调的精灵。我们通过调用该精灵的getTag方法来判定该精灵究竟是什么精灵,如果tag为1则该精灵是敌人精灵,我们可以针对敌人精灵在做更多的操作。最后将该精灵从Layer上去掉。 this.removeChild(sprite,true);
此时再运行我们的代码,就会看到从屏幕右侧每隔一秒钟会产生一个水平移动的敌人精灵,因为移动的时长是随机数,因此敌人精灵从屏幕右侧向左侧移动时的速度是不一样的。 public boolean ccTouchesEnded(MotionEvent event)
{
// 将坐标点从安卓坐标系转为Cocos2d坐标系
CGPoint location =
CCDirector.sharedDirector().
convertToGL(CGPoint.ccp(
event.getX(),event.getY()));
CGSize winSize =
CCDirector.sharedDirector().displaySize();
CCSprite projectile =
CSprite.sprite("Projectile.png");
projectile.setPosition(20,winSize.height / 2.0f);
//计算飞镖的终点坐标
int offX =
(int)(location.x - projectile.getPosition().x);
int offY =
(int)(location.y - projectile.getPosition().y);
if (offX <= 0)
return true;
addChild(projectile);
projectile.setTag(2);
......
int realX = (int)(winSize.width + (projectile.getContentSize().width / 2.0f));
float ratio = (float)offY / (float)offX;
int realY = (int)((realX * ratio) + projectile.getPosition().y);
//飞镖的终点坐标
CGPoint realDest = CGPoint.ccp(realX,realY);
int offRealX =
(int)(realX - projectile.getPosition().x);
int offRealY =
(int)(realY - projectile.getPosition().y);
float length =
(float)Math.sqrt(
(offRealX * offRealX) +
(offRealY * offRealY));
float velocity =
480.0f / 1.0f; // 480 pixels / 1 sec
float realMoveDuration = length / velocity;
//移动飞镖
projectile.runAction(CCSequence.actions(
CCMoveTo.action(
realMoveDuration,realDest),CCCallFuncN.action(
this,"spriteMoveFinished")));
......
return true;
}
当手指抬起后,会从初始位置飞镖精灵的初始位置(20,屏幕高度一半)向手指抬起的方向进行移动,但是移动时需要注意的是飞镖移动的终点并不是手指抬起的点,而是应该顺着手指抬起的点一直延伸到屏幕之外: public void spriteMoveFinished(Object sender)
{
CCSprite sprite = (CCSprite)sender;
if (sprite.getTag() == 1)
{
//敌人精灵的处理逻辑
......
}
else if (sprite.getTag() == 2)
//飞镖精灵的处理逻辑
.....
//无论是什么精灵,移动完毕就从Layer上移除
this.removeChild(sprite,true);
}
接下来,处理飞镖击中敌人时的逻辑。飞镖击中敌人实际就是飞镖精灵和敌人精灵发生了碰撞,此时应该让敌人精灵从屏幕上消失,当然击中了敌人的飞镖精灵也应该消失掉。 this.schedule("update");
这与之前添加的this.schedule(“gameLogic”,1.0f);类似,也是每间隔一段时间就调用一次update方法,只不过这一次的调用间隔与我们在构造器中设置的刷新率是一致的,也就是1/60秒就调用一次update方法。 public void update(float dt)
{
......
CGRect projectileRect =
CGRect.make(projectile.getPosition().x -
(projectile.getContentSize().width / 2.0f),projectile.getPosition().y -
(projectile.getContentSize().height / 2.0f),projectile.getContentSize().width,projectile.getContentSize().height);
......
CGRect targetRect =
CGRect.make(target.getPosition().x -
(target.getContentSize().width),target.getPosition().y -
(target.getContentSize().height),target.getContentSize().width,target.getContentSize().height);
......
if (CGRect.intersects(projectileRect,targetRect))
{
......
removeChild(target,true);
removeChild(projectile,true);
......
}
......
}
update方法中保留了碰撞检测的核心代码: // 获取上下文对象
Context context = CCDirector.sharedDirector().getActivity();
SoundEngine.sharedEngine().preloadEffect(context,R.raw.pew_pew_lei);
SoundEngine.sharedEngine().playSound(context,R.raw.background_music_aac,true);
使用SoundEngine的preloadEffect方法来缓冲音效文件,用playSound方法来设定循环播放背景音乐。 public boolean ccTouchesEnded(MotionEvent event)
{
......
// 播放音效
Context context =
CCDirector.sharedDirector().getActivity();
SoundEngine.sharedEngine().playEffect(
context,R.raw.pew_pew_lei);
......
return true;
}
这样每当发射飞镖时,都会伴随着pew pew的音效声了。 public void spriteMoveFinished(Object sender)
{
CCSprite sprite = (CCSprite)sender;
if (sprite.getTag() == 1)
{
_targets.remove(sprite);
_projectilesDestroyed = 0;
CCDirector.sharedDirector().replaceScene(GameOverLayer.scene("You Lose :("));
}
else if (sprite.getTag() == 2)
......
this.removeChild(sprite,true);
}
如果是敌人精灵逃脱后触发的spriteMoveFinished方法,则利用CCDirector进行幕的切换,从当前幕切换到由GameOverLayer.scene()方法产生的幕。 public class GameOverLayer extends CCColorLayer {
protected CCLabel _label;
public static CCScene scene(String message)
{
CCScene scene = CCScene.node();
GameOverLayer layer = new GameOverLayer(
ccColor4B.ccc4(255,255));
layer.getLabel().setString(message);
scene.addChild(layer);
return scene;
}
public CCLabel getLabel()
{
return _label;
}
protected GameOverLayer(ccColor4B color)
{
super(color);
this.setIsTouchEnabled(true);
CGSize winSize =
CCDirector.sharedDirector().displaySize();
//参数为:文字内容,字体名称,文字大小
_label = CCLabel.makeLabel(
"Won't See Me","DroidSans",32);
_label.setColor(ccColor3B.ccBLACK);
_label.setPosition(
winSize.width / 2.0f,winSize.height / 2.0f);
addChild(_label);
this.runAction(CCSequence.actions(
CCDelayTime.action(3.0f),CCCallFunc.action(this,"gameOverDone")));
}
public void gameOverDone()
{
CCDirector.sharedDirector().
replaceScene(GameLayer.scene());
}
@Override
public boolean ccTouchesEnded(MotionEvent event)
{
gameOverDone();
return true;
}
}
GameOverLayer的代码与GameLayer的代码有很多类似的地方,首先也是继承自CCColorLayer因此可以设定白色背景,在构造器中也创建了一个精灵,只不过这次的精灵即不是一个图片甚至也不是一个CCSprite类型,而是一个显示文字的CCLabel对象。但是前面提到过任何显示在屏幕上的东西都是一个精灵,因此CCLabel对象也是一个精灵。 完整代码下载:源码 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |