如何使用libgdx 游戏编写一个简单的游戏

libGdx是一个跨平台的2D/3D的游戏开发框架,它由Java/C/C++语言编写而成。它基于Apache License, Version 2.0协议,对商业使用和非商业使用均免费。代码托管于Github中。Libgdx中文社区于2013年5月正式上线,为了满足广大开发者更好地学习libgdx框架,Libgdx中文社区有多名知名游戏开发者组织创办。
相关内容推荐
11月21日消息,搜狐畅游推出其开源手游引擎Genesis3D。两天之后,在成都GMG…下面我就罗列出八款常见的Android游戏引擎,以供有需要者参考(收费,下载… 1.Edgelib:2D及3D中间件游戏引擎,支持iOS、Android、Windows Phone、塞…著名游戏引擎Unity的 移动基本版从今天起免费,这意味着 iOS, Android 和 B…当手机游戏摇身成为移动互联网上最炙手可热的应用, 手机浏览器巨头们围绕…游戏引擎,是一组完整的解决方案,能够在保持一定弹性的原则下,提供最大程…
您接触过或是想要学习那些手游引擎?
Unreal Development Kit
JMonkey Engine
Papaya Social Game Engine
BattryTech
CocoStudio工具集是开源游戏引擎Cocos2d-x开发团队官方推出的游戏开发工具,本...
CocoStudio工具集是基于开源跨平台游戏引擎Cocos2d-x的开发工具集,包括UI编辑...
在Android游戏开发中交互、传感器使用和音效都是非常重要的部分,专题Android游...
本专题简要地介绍了AIR在移动平台上的发展状况,分析了AIR Android开发的可行性...<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
您的访问请求被拒绝 403 Forbidden - ITeye技术社区
您的访问请求被拒绝
亲爱的会员,您的IP地址所在网段被ITeye拒绝服务,这可能是以下两种情况导致:
一、您所在的网段内有网络爬虫大量抓取ITeye网页,为保证其他人流畅的访问ITeye,该网段被ITeye拒绝
二、您通过某个代理服务器访问ITeye网站,该代理服务器被网络爬虫利用,大量抓取ITeye网页
请您点击按钮解除封锁&如何使用libgdx编写一个简单的游戏-手机开发-火龙果软件工程
每天15篇文章
不仅获得谋生技能
更可以追随信仰
如何使用libgdx编写一个简单的游戏
火龙果软件&&& 发布于&
(一)― 雏形
这个系列主要讲述了如何使用Cocos2D编写简单的游戏。稍微读读感觉不错,所以想写个libgdx版本的。
本篇文章主要讲述基本内容的编写,包括显示人物、怪兽和飞镖。
最终效果如下图:
获取libgdx
你可以从libgdx的官网下载打包好的代码,我下载的是0.98版本。
当然,你也可以从git代码仓库获取最新的版本的,或者你习惯使用的以前版本,比如0.97。
libgdx项目的创建可以有多种方式,我推荐使用setup-ui。方便易用还可以省去很多麻烦,特别是ADT升级以后的ClassNotFound问题。
如果是下载打包好的,那么就默认包含了gdx-setup-ui,双击就可以打开。
填写一些基本信息,然后选中你下载的0.98.zip那个压缩文件。这里我只生成一个桌面项目和Android项目。
桌面项目是方便调试,而Android项目是最后发布的。在整个开发中我始终用桌面项目调试,因为速度快,容易排错。同时周期性的在Android真机上测试。
点击生成项目,然后在Eclipse中导入。
一般导入进去以后Android项目会有一些问题,修改project.properties文件和AndroidManifest.xml配置文件。
运行效果如下:
本例子中用到的图片如下:
用gdx-texturepacker打包成一张大图。
我整个例子都是用的是Stage模式。所以它的坐标原点在左下角,如果是一般用Spirte直接绘制,那么原点在右上角。
首先将打包好的图册复制到assets中新建的pack文件夹。
然后我们开始动工了,首先删除setup-ui生成的多余代码,整理DartsShaSha.java文件如下:
import com.badlogic.gdx.ApplicationA
import com.badlogic.gdx.G
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.scenes.scene2d.S
public class DartsShaSha extends ApplicationAdapter {
public void create() {
stage = new Stage(480, 320, true);
public void dispose() {
stage.dispose();
public void render() {
Gdx.gl.glClearColor(1, 1, 1, 1);
Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
stage.act();
stage.draw();
这时候运行效果是一个白茫茫的画面。
注意一下这句
stage = new Stage(480, 320, true);
因为我希望屏幕的自适应有Stage自动完成,所以坐标基本可以写死。
先不着急开工,我们先添加一个现实FPS的标签。我希望这个标签显示在屏幕右下角。
在create方法中添加
LabelStyle labelStyle = new LabelStyle(new BitmapFont(), Color.BLACK); //创建一个Label样式,使用默认黑色字体
Label label = new Label("FPS:", labelStyle); //创建标签,显示的文字是FPS:
label.setName("fpsLabel"); //设置标签名称为fpsLabel
label.setY(0); //设置Y为0,即显示在最下面
label.setX(480 - label.getTextBounds().width); //设置X值,显示为最后一个字紧靠屏幕最右侧
stage.addActor(label); //将标签添加到舞台
在render方法中更新fps的值
Label label = (Label) stage.getRoot().findActor(&fpsLabel&); //获取名为fpsLabel的标签
label.setText(&FPS:& + Gdx.graphics.getFramesPerSecond());
label.setX(480 - label.getTextBounds().width);
//更新X值以保证显示位置正确性
效果如下:
现在来添加我们的主角,我希望主角显示在屏幕左侧中央。所以它的x值必然是0,但是它的y值并不是320的一半,而是160减去图片高度的一半。
主角其实就是一张图片,并没有太多特别的效果,所以我使用Image类。
首先获取图册
TextureAtlas atlas = new TextureAtlas(&pack/default.pack&);
在从图册中获取Player.png并创建Image对象。
Image man = new Image(atlas.findRegion("Player")); //获取图册中的Player.png并创建image对象
man.setX(0);
man.setY(160 - man.getHeight() / 2); //设置Y值,以让图片在中间显示
stage.addActor(man); //将主角添加到舞台
效果如下:
然后我们来添加几只怪兽。怪兽应该是随机从屏幕右侧出现,并直线移动到屏幕左侧。
同时我们还要检测怪兽的生命值什么的,或者其他效果,所以为了方便处理,我们专门建立一个Group来管理怪兽。
新建类TargetGroup,并集成Group类。
import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasR
import com.badlogic.gdx.scenes.scene2d.G
import com.badlogic.gdx.scenes.scene2d.ui.I
public class TargetGroup extends Group {
public TargetGroup(AtlasRegion region) {
因为还需要传入怪兽的图片,所以我们的创建方法保留参数AtlasRegion
怪兽的Y值因为是随机的,但是又不能超出屏幕。所以用随机数来生成。libgdx的MathUtils提供了相关方法。
int minY = 0;
int maxY = (int) (320 - region.getRegionHeight());
int tempY = MathUtils.random(minY, maxY);
这里还有一个问题需要注意,就是怪兽之间不应该出现遮挡,所以对于生成的Y值还需要进行判断。
假设我们要生成3只怪兽,那么代码应该如下:
int tempY = 0;
for (int i = 0; i < 3; i++) {
Image image = new Image(region);
image.setX(480 - image.getWidth());
// 开始判断Y值是否符合要求
boolean flag =
tempY = MathUtils.random(minY, maxY); // 生成Y值
Actor[] actors = this.getChildren().begin(); // 获取当前已有的怪兽对象
for (int j = 0; j < this.getChildren(). j++) {
Actor tempActor = actors[j];
if (tempY == tempActor.getY()) { // 如果Y值相等,比如重合,所以舍弃,重新生成
} else if (tempY = tempActor
.getY()) {
} else { // 如果生成的Y值大于当前怪兽的Y值,则判断当前怪兽的Y值加上高度后是否合适
if (tempY <= (tempActor.getY() + region
.getRegionHeight())) {
} while (flag);
image.setY(tempY);
this.addActor(image); //添加到组中
在主类的create方法中添加
TargetGroup group = new TargetGroup(atlas.findRegion("Target"));
stage.addActor(group);
效果如下:
目前怪兽还不能移动,这里需要一个简单的动画效果,libgdx中的Action可以办到。
考虑到怪兽是水平移动,即Y值不变,X值变小。
所以添加一个方法
public void AddMove(Actor actor, float time) {
actor.addAction(Actions.moveTo(0, actor.getY(), time));
怪兽的移动速度也随机一下,代码如下
image.setY(tempY);
this.AddMove(image, MathUtils.random(3f, 8f)); //怪兽移动效果
this.addActor(image); //添加到组中
效果如下:
我们的主角自然不能赤手空拳和怪兽进行搏斗,现在添加一些飞镖。
假定用户触摸屏幕以后,主角就向触摸位置发射一个飞镖。
因为飞镖的数量不一定,所以我这里创建一个专门的类ProjectileFactory来处理。
首先是飞镖的创建,和怪兽群一样的原因,我还是希望一个专门的组来管理。
创建一个专门的方法来创建飞镖
public static Image createProjectile(AtlasRegion region, Actor man,
Vector3 target) {
Image image = new Image(region);
image.setX(man.getX() + man.getWidth() / 2);
image.setY(man.getY() + man.getHeight() / 2);
image.addAction(Actions.moveTo(target.x, target.y, 2f)); //设置飞镖的移动
在主类进行一些修改以便其可以获取屏幕的触摸。
首先修改类声明为
public class DartsShaSha extends InputAdapter implements ApplicationListener
其实具体也可以两个都实现接口,主要是我觉得看着不舒服。
重写touchDown方法
public boolean touchDown(int screenX, int screenY, int pointer, int button) {
Vector3 vector3 = new Vector3(screenX, screenY, 0);
stage.getCamera().unproject(vector3); // 坐标转化
projectiles.addActor(ProjectileFactory.createProjectile(
atlas.findRegion("Projectile"), man, vector3)); // 添加新飞镖到飞镖组
在create方法中添加新的Group并设置Input响应。
stage.addActor(projectiles); //添加飞镖组到舞台
InputMultiplexer multiplexer = new InputMultiplexer(); //多输入接收器
multiplexer.addProcessor(this); //添加自身作为接收
multiplexer.addProcessor(stage); //添加舞台
Gdx.input.setInputProcessor(multiplexer); //设置多输入接收器为接收器
效果如下:
更完善的飞镖
飞镖虽然添加出来了,但是飞镖没有转动…而且飞镖没有在到达目的地后自动消失。
现在先来添加旋转效果,libgdx提供了rotateBy方法。
在创建飞镖的createProjectile方法中添加
image.addAction(Actions.repeat(50, Actions.rotateBy(360,
0.5f))); //设置飞镖的旋转
这个不方便截图,就不展示效果了。
现在来考虑如何让飞镖到达目的后消失。首先来看看我们的Image对象,它包含了两个Action,一个是旋转Action,另外一个移动Action。
我们可以检测Action的数量,如果只有一个Action,我们可以断定飞镖只是在旋转而已经到达目的地了。这个时候就可以把它删除了。
添加一个专门的方法来判断飞镖是否应该移除了
public static Boolean checkAlive(Actor projectile) {
if (projectile.getActions().size == 1) {
在render方法中添加处理代码
// 飞镖的移除判
Actor[] projectile = projectiles.getChildren().begin(); //获取Actor数组
for (int j = 0; j < projectiles.getChildren(). j++) { //移除判断
Actor actor = projectile[j];
if (!ProjectileFactory.checkAlive(actor)) {
projectiles.removeActor(actor);
效果如下:
现在飞镖可以自动消失了,并且也在旋转了。不过旋转效果很奇怪,它并不是沿中心旋转,而是沿着左下角旋转的。
重新设置中心
image.setOrigin(image.getWidth() / 2, image.getHeight() / 2);
现在一切正常了。
碰撞检测和杀敌
当然,发出飞镖的目的自然是杀敌,现在马上来添加这个功能。
我们可以把怪兽看着一个矩形,即飞镖击中任何位置都算作有效。而飞镖就以其中心为代表。
创建方法attackAlive
public static Boolean attackAlive(Actor target, Actor projectile) {
Rectangle rectangle = new Rectangle(target.getX(), target.getY(),
target.getWidth(), target.getHeight()); // 创建一个矩形
return rectangle.contains(
projectile.getX() + projectile.getWidth() / 2,
projectile.getY() + projectile.getHeight() / 2); //判断是否在矩阵中,即是否击中
在render方法中修改
// 开始处理飞镖
Actor[] projectile = projectiles.getChildren().begin();
Actor[] targets = targetGroup.getChildren().begin();
for (int i = 0; i < projectiles.getChildren(). i++) {
Actor actor = projectile[i];
for (int j = 0; j < targetGroup.getChildren(). j++) {
Actor target = targets[j];
if (ProjectileFactory.attackAlive(target, actor)) {
targetGroup.removeActor(target);
projectiles.removeActor(actor);
// 如果飞镖已经飞到则h除
projectile = projectiles.getChildren().begin();
for (int j = 0; j < projectiles.getChildren(). j++) {
Actor actor = projectile[j];
if (!ProjectileFactory.checkAlive(actor)) {
projectiles.removeActor(actor);
效果如下:
虽然实现了个大概,但是仔细看看其实问题还是很多的,后面的文章会提到进一步的修改。包括逻辑上的完善,声音效果,预加载,背景绘制,集成第三方社交和广告等等。
这片文章对应demo可以从这里下载来试试。
/share/link?shareid=328840&uk=
我用的2.2的sdk编译的,低版本没有测试。我的手机是ZTE V880,fps50上下。
ps:testin的测试结果是通过率 100.00%
ps: 代码上传到github上了,地址 /htynkn/DartsShaSha
文章对应的代码的tag为page 1。
(二)― 完善
完善飞镖逻辑
现在的飞镖可以旋转可以飞行了,但是有一个问题却没有解决。
首先飞镖的速度,如果用户触摸位置很靠近左侧,那么飞镖的速度就很慢了。
其次,如果用户触摸中间位置,默认情况下飞镖应该是朝那个方向飞行,而不是飞到触摸位置就消失了。
这里的处理办法很简单,就是根据用户触摸位置,算出一个X为480的值,这样飞镖就可以飞到最右侧,同时保持相当的速度。
在createProjectile方法中添加
float r = (target.y - image.getY()) / (target.x - image.getX()); //获取斜率
float detY = r * 480; //获取Y的变动量
image.addAction(Actions.moveTo(480 + image.getX(), detY + image.getY(),
2f)); // 设置飞镖的移动
这样基本就解决了问题。
接下来来思考飞镖的数量和相应位置。
首先飞镖的速度一定要得到限制,不然满屏幕飞镖有什么意思。这里限制飞镖的数量为5个。
在touchDown的最开始添加
if (projectiles.getChildren().size >= 5) { //限制飞镖的数量为5个
这样当屏幕上的飞镖数量大于等于5时就不会产生新的飞镖了。
还有就是触摸的位置,如果触摸的位置太靠右的话,会出现飞镖倒飞或者速度过快的问题,所以当触摸位置太靠近左侧的时候就不释放飞镖。
在touchDown方法中添加
if (vector3.x < man.getX() + 5) { //如果触摸太靠近左侧就不响应
这里的5是我随便写的,仅仅表示个意思。测试一下,觉得5还是太小了,最后我改成10了。
更带感的对手
说实话,现在的对手一动不动,只会匀速平移。我们先改进它的外貌吧。
我从http://untamed.wild-refuge.net/rmxpresources.php?characters获取到如下图片
打包以后放入assets文件夹中。
因为libgdx只有默认Animation类,但是没法办法直接在stage中使用,所以新建一个Scythe类并继承Actor类。
public Scythe(AtlasRegion atlasRegion) {
this.setWidth(titleWidth); //设置高度
this.setHeight(titleHeight); //设置宽度
TextureRegion[][] temp = atlasRegion.split(titleWidth, titleHeight); //分割图块
walkFrames = new TextureRegion[4]; //获取第二行的4帧
for (int i = 0; i < 4; i++) {
walkFrames[i] = temp[1][i];
animation = new Animation(0.1f, walkFrames); //创建动画,帧间隔0.1
因为原图宽200,高192,一共16张图,所以每一块的宽就是50,高48。使用Animation类需要手动提供相关帧,并通过Animation和当前时间获取的帧。
重写draw方法如下
public void draw(SpriteBatch batch, float parentAlpha) {
stateTime += Gdx.graphics.getDeltaTime(); //获取总时间
currentFrame = animation.getKeyFrame(stateTime, true); //获取当前关键帧
batch.draw(currentFrame, this.getX(), this.getY(), this.getWidth(),
this.getHeight()); //绘制
修改TargetGroup中有关region.getRegionHeight()的部分,全部除以4。同时修改Image类Scythe类。
最后完整的Scythe如下
import com.badlogic.gdx.G
import com.badlogic.gdx.graphics.g2d.A
import com.badlogic.gdx.graphics.g2d.SpriteB
import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasR
import com.badlogic.gdx.graphics.g2d.TextureR
import com.badlogic.gdx.scenes.scene2d.A
public class Scythe extends Actor {
TextureRegion[] walkF // 保存每一帧
A // 动画类
float stateT // 总时间
TextureRegion currentF // 当前帧
int titleWidth = 50; // 声明块宽度
int titleHeight = 48; // 声明块高度
public Scythe(AtlasRegion atlasRegion) {
this.setWidth(titleWidth); // 设置高度
this.setHeight(titleHeight); // 设置宽度
TextureRegion[][] temp = atlasRegion.split(titleWidth, titleHeight); // 分割图块
walkFrames = new TextureRegion[4]; // 获取第二行的4帧
for (int i = 0; i < 4; i++) {
walkFrames[i] = temp[1][i];
animation = new Animation(0.1f, walkFrames); // 创建动画,帧间隔0.1
public void draw(SpriteBatch batch, float parentAlpha) {
stateTime += Gdx.graphics.getDeltaTime(); // 获取总时间
currentFrame = animation.getKeyFrame(stateTime, true); // 获取当前关键帧
batch.draw(currentFrame, this.getX(), this.getY(), this.getWidth(),
this.getHeight()); // 绘制
效果如下:
我们来试试在给予怪兽血量,即有些怪兽可以承受两次伤害。这里我们将用到比较基本的东西。
首先是血条的位置,一般来看应该在怪兽正上方,以红色显示。
绘制可以用很多种方法,我不怎么习惯mesh那套,所以这里我使用Pixmap类。
先在Sythe类中添加两个变量
int margin = 2; // 血条和人物之间的间隔
int pixHeight = 5; // 血条高度
然后在绘制方法中添加
Pixmap pixmap = new Pixmap(64, 8, Format.RGBA8888); //生成一张64*8的图片
pixmap.setColor(Color.BLACK); //设置颜色为黑色
pixmap.drawRectangle(0, 0, titleWidth, pixHeight); //绘制边框
Texture pixmaptex = new Texture(pixmap); //生成图片
TextureRegion pix = new TextureRegion(pixmaptex, titleWidth, pixHeight); //切割图片
batch.draw(pix, this.getX(), this.getY() + this.titleHeight
+ this.margin); //绘制
这样我们就有了一个黑色的边框了
然后就是血量的填充了,在添加两个变量以记录总血量和当前血量
int maxHp; // 总血量
int currentHp; // 当前血量
绘制血条的代码添加到绘制完黑框的语句后面
pixmap.setColor(Color.RED); // 设置颜色为红色
pixmap.fillRectangle(0, 1, titleWidth * currentHp / maxHp,
pixHeight - 2); // 绘制血条
最后一定要释放掉pixmap
pixmap.dispose();
这是设置总血量为2,当前血量为1的效果
增加了血量以后我们的代码也需要修改了,在主类DartsShaSha中修改相关的逻辑。
为了方便起见我们将游戏的逻辑赋给控制器。
先新建一个IController
blogs.htynkn.
import com.badlogic.gdx.scenes.scene2d.G
import com.badlogic.gdx.scenes.scene2d.S
public abstract class IController extends Group {
public abstract void update(Stage stage);
然后新建TargetController类,重写update方法,在这个方法中我们处理相关逻辑,然后在主类中只需要调用方法就可以。
首先将中已有的代码拷贝过来,然后在Sythe中添加两个方法来处理受到伤害和死亡判断。
public void beAttacked(int damage) {
if (this.currentHp > damage) { // 如果血量大于伤害就扣除响应数值
currentHp = currentHp -
} else if (this.currentHp > 0) { // 如果血量小于伤害但是仍有血量就让血量归零
currentHp = 0;
public Boolean isAlive() {
return this.currentHp > 0;
然后在TargetController中添加update的相关代码
public void update(Stage stage) {
Group projectiles = (Group) stage.getRoot().findActor("projectiles"); // 获取飞镖所在Group
Actor[] projectile = projectiles.getChildren().begin();
Actor[] targets = this.getChildren().begin();
for (int i = 0; i < projectiles.getChildren(). i++) {
Actor actor = projectile[i];
for (int j = 0; j < this.getChildren(). j++) {
Actor target = targets[j];
if (ProjectileFactory.attackAlive(target, actor)) {
Scythe scythe = (Scythe)
scythe.beAttacked(1);
projectiles.removeActor(actor);
if (!scythe.isAlive()) {
this.removeActor(target);
然后在主类中修改相关的实例化代码,在render中调用update方法。
targetController.update(this.stage); //调用update方法,处理怪兽的逻辑
效果如下:
飞镖的杀伤力和手势识别
上面的代码中每一个飞镖的杀伤力是1,每一个怪兽的血量是2。
现在我们来修改一下飞镖的控制,让飞镖也采用控制器来处理。
在这里设定为触摸一下屏幕是发射一般的飞镖,杀伤力为1。而长按以后的杀伤力是2。
libgdx中提供了一个手势识别的接口,实现它就可以了。这里我选择继承GestureAdapter。
public class DartsListener extends GestureAdapter
同时修改飞镖的控制为控制器模式,新建控制器DartsController。
代码如下:
blogs.htynkn.
import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasR
import com.badlogic.gdx.math.R
import com.badlogic.gdx.scenes.scene2d.A
import com.badlogic.gdx.scenes.scene2d.S
import com.badlogic.gdx.scenes.scene2d.actions.A
blogs.htynkn.elements.D
public class DartsController extends IController {
public void update(Stage stage) {
// 如果飞镖已经飞到则h除
Actor[] projectile = this.getChildren().begin();
for (int j = 0; j = 5) { //如果飞镖数量大于等于5个就结束
float r = (dart.getTarget().y - dart.getY())
/ (dart.getTarget().x - dart.getX()); // 获取斜率
float detY = r * 480; // 获取Y的变动量
dart.addAction(Actions.moveTo(480 + dart.getX(), detY + dart.getY(), 2f)); // 设置飞镖的移动
this.addActor(dart);
public Boolean attackAlive(Actor target, Actor projectile) {
Rectangle rectangle = new Rectangle(target.getX(), target.getY(),
target.getWidth(), target.getHeight()); // 创建一个矩形
return rectangle.contains(
projectile.getX() + projectile.getWidth() / 2,
projectile.getY() + projectile.getHeight() / 2); // 判断是否在矩阵中,即是否击中
public Boolean checkAlive(Actor projectile) {
if (projectile.getActions().size == 1) {
public Dart createDart() {
return new Dart(region);
其中Dart类代码如下:
blogs.htynkn.
import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasR
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.scenes.scene2d.actions.A
import com.badlogic.gdx.scenes.scene2d.ui.I
public class Dart extends Image {
public Dart(AtlasRegion region) {
super(region);
this.setOrigin(getWidth() / 2, getHeight() / 2);
this.addAction(Actions.repeat(50, Actions.rotateBy(360, 0.5f)));
public void setTarget(Vector2 target) {
this.target =
public void setTarget(float x, float y) {
this.target = new Vector2(x, y);
public Vector2 getTarget() {
因为我们的输入接受另外有类DartsListener来处理,所以修改主类的继承如下:
public class DartsShaSha implements ApplicationListener
在multiplexer中添加我们新的手势识别器
GestureDetector gestureDetector = new GestureDetector(
new DartsListener(this.stage));
multiplexer.addProcessor(gestureDetector); // 添加手势识别
目前DartsListener中代码如下
blogs.htynkn.
import com.badlogic.gdx.input.GestureDetector.GestureA
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.scenes.scene2d.A
import com.badlogic.gdx.scenes.scene2d.S
blogs.htynkn.controller.DartsC
blogs.htynkn.elements.D
public class DartsListener extends GestureAdapter {
public DartsListener(Stage stage) {
this.stage =
public boolean touchDown(float x, float y, int pointer, int button) {
DartsController dartsController = (DartsController) stage.getRoot()
.findActor("dartsController");
if (dartsController.getChildren().size >= 5) { // 限制飞镖的数量为5个
Vector3 vector3 = new Vector3(x, y, 0);
stage.getCamera().unproject(vector3); // 坐标转化
Actor man = stage.getRoot().findActor("player");
if (vector3.x < man.getX() + 10) { // 如果触摸太靠近左侧就不响应
Dart dart = dartsController.createDart();
dart.setX(man.getX() + man.getWidth() / 2);
dart.setY(man.getY() + man.getHeight() / 2);
dart.setTarget(vector3.x, vector3.y);
dartsController.AddDarts(dart);
public boolean longPress(float x, float y) {
可能还有其他细节的修改,详细的请参考代码。
目前我们只能算是重构了一下,游戏效果并没有改变。现在来设置飞镖的杀伤力和长按的处理。
在Dart中添加属性
在实例化中添加
power = 1; //默认杀伤力为1
将TargetController中的
scythe.beAttacked(1);
scythe.beAttacked(dart.getPower());
而中的longPress方法基本和touchDown相同,只是增加了
dart.setPower(2); //设置杀伤力为2
dart.setColor(Color.RED); //设置成红色
来思考一下处理流程,用户触摸屏幕,首先会触发tap事件,然后是touchDown,最后才是longPress。
也就是目前我们的游戏长按一下会发出一个普通的飞镖,然后才是我们的红色飞镖。
要处理这个问题,我们添加一个DartsDetector类,继承GestureDetector类。
因为事件的触发顺序是tap-&touchdown-&longpress-&touchup。
所以我们的事件逻辑全部转移到touchup中,如果是longpress事件就发出红色飞镖,如果是touchdown就发出普通飞镖。
由于我们的DartsListener已经不处理任何逻辑了,所以删除其中所有代码。
GestureDetector中的代码如下
blogs.htynkn.
import com.badlogic.gdx.graphics.C
import com.badlogic.gdx.input.GestureD
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.scenes.scene2d.A
import com.badlogic.gdx.scenes.scene2d.S
blogs.htynkn.controller.DartsC
blogs.htynkn.elements.D
public class DartsDetector extends GestureDetector {
public DartsDetector(Stage stage, GestureListener listener) {
super(listener);
this.stage =
public boolean touchUp(float x, float y, int pointer, int button) {
DartsController dartsController = (DartsController) stage.getRoot()
.findActor("dartsController");
if (dartsController.getChildren().size >= 5) { // 限制飞镖的数量为5个
Vector3 vector3 = new Vector3(x, y, 0);
stage.getCamera().unproject(vector3); // 坐标转化
Actor man = stage.getRoot().findActor("player");
if (vector3.x < man.getX() + 10) { // 如果触摸太靠近左侧就不响应
return super.touchUp(x, y, pointer, button);
Dart dart = dartsController.createDart();
dart.setX(man.getX() + man.getWidth() / 2);
dart.setY(man.getY() + man.getHeight() / 2);
dart.setTarget(vector3.x, vector3.y);
if (this.isLongPressed()) { //如果是长按就变成红色飞镖
dart.setPower(2); // 设置杀伤力为2
dart.setColor(Color.RED); // 设置成红色
dartsController.AddDarts(dart);
return super.touchUp(x, y, pointer, button);
效果如下:
虽然实现了个大概,但是仔细看看其实问题还是很多的,后面的文章会提到进一步的修改。包括逻辑上的完善,声音效果,预加载,背景绘制,集成第三方社交和广告等等。
这片文章对应demo可以从这里下载来试试。
/share/link?shareid=328840&uk=
我用的2.2的sdk编译的,低版本没有测试。我的手机是ZTE V880,fps50上下。
ps:testin的测试结果是通过率 100.00%
ps: 代码上传到github上了,地址 /htynkn/DartsShaSha
文章对应的代码的tag为page 1。
(三)― 人性化
这一篇主要是添加一些让游戏更人性化的东西,比如音效和加载画面,菜单等等。
这其中用到了很多资源,主要出自以下几个网站,大家有需要也可以去上面寻找。
首先是飞镖发出时候的音效,我希望是类似&bing&的一声,要短小精炼。
我使用的是http://www.freesound.org/people/BMacZero/sounds/96132/
libgdx支持的音频主要是WAV, MP3和OGG,其他的支持需要扩展支持。
libgdx中的音频有两种,一种是sound,一种是music。
一般所谓的音效使用sound,而音乐就使用music。
在DartController中添加属性
在实例化方法中添加
this.bing = Gdx.audio.newSound(Gdx.files.internal(&audio/bing.wav&));
在AddDart方法中添加
bing.play();
在怪兽被消灭的时候的音效我选用http://www.freesound.org/people/yottasounds/sounds/174465/
在TargetController中添加
great = Gdx.audio.newSound(Gdx.files.internal(&audio/great.wav&));
最后添加一个背景音乐。我使用的是http://www.freesound.org/people/Setuniman/sounds/167453/
大小为5.7M,个人感觉有点大。
而且我需要循环播放,这就意味着末尾的这个部分的我不需要。
使用在线转换器截取并转化成ogg。
转化以后大小变成465kb了。仔细听一下,感觉很多部分是重复,所以再一次截取,只要前4秒的。
转化好以后在主类添加
backgroundMusic = Gdx.audio.newMusic(Gdx.files
.internal("audio/background.ogg"));
backgroundMusic.setLooping(true); //循环播放
backgroundMusic.setVolume(0.4f); //设置音量
backgroundMusic.play(); //播放
因为music可以在resume和pause时自动播放和暂停,所以不需要其他的处理。在游戏关闭时需要将其释放掉。
backgroundMusic.dispose();
Game-Screen模式和资源预加载
到目前为止我们的所有操作都在一个界面上,但是一个游戏中可能有很多不同的界面和场景,比如菜单,帮助,关卡1,关卡2等等。
libgdx提供了Game-Screen模式,即只有一个Game,但是Game中有很多Screen。通过setScreen来切换。
这里注意一下Game所提供的切换功能没有任何过渡效果,后面会有添加过多效果的例子。
新建DartsGame类,继承Game类。在darts-shasha-android项目和darts-shasha-desktop项目中分别修改入口代码
import android.os.B
import com.badlogic.gdx.backends.android.AndroidA
import com.badlogic.gdx.backends.android.AndroidApplicationC
public class MainActivity extends AndroidApplication {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
AndroidApplicationConfiguration cfg = new AndroidApplicationConfiguration();
cfg.useGL20 =
initialize(new DartsGame(), cfg);
import com.badlogic.gdx.backends.lwjgl.LwjglA
import com.badlogic.gdx.backends.lwjgl.LwjglApplicationC
public class Main {
public static void main(String[] args) {
LwjglApplicationConfiguration cfg = new LwjglApplicationConfiguration();
cfg.title = "darts-shasha";
cfg.useGL20 =
cfg.width = 480;
cfg.height = 320;
new LwjglApplication(new DartsGame(), cfg);
新建一个类ShaScreen,实现Screen接口,其中的代码基本和原来的主类一样。唯一的区别就是create中的代码需要移动到show中。
在DartsGame中添加代码
import com.badlogic.gdx.G
blogs.htynkn.screen.ShaS
public class DartsGame extends Game {
public void create() {
ShaScreen screen = new ShaScreen();
this.setScreen(screen);
这样我们就转化到Game-Screen模式。
当我们在游戏中使用了很多资源的时候直接new的话经常会卡顿,比如我们这个demo再加入音效后进入时间明显变长了。
所以我们这里需要一个预加载过程,而libgdx提供的AssetManager可以实现这个功能。
加载中使用一个小图标加文字形式。由于要使用中文,同时文字量很小,所以使用hiero作图。
新建一个LoadingScreen类作为加载页面。
因为AssetManager的作用很特殊,很多类都可以要调用它,所以我一般使用单例模式。
在DartsGame添加
public static AssetM
public static AssetManager getManager() {
if (manager == null) {
manager = new AssetManager();
在LoadingScreen中通过
AssetManager manager = DartsGame.getManager();
获取manager。然后通过load方法添加要加载的资源
manager.load("audio/background.ogg", Music.class);
manager.load("audio/bing.wav", Sound.class);
manager.load("audio/great.wav", Sound.class);
manager.load("pack/sha/default.pack", TextureAtlas.class);
这里只是告诉manager要加载什么资源,并没有实际加载。
在render方法中调用
DartsGame.getManager().update()
来启动加载并获取是否加载完成,如果返回值为true那么就表明加载完成。
其他类通过
DartsGame.getManager().get()
来获取资源。
LoadingScreen代码如下:
blogs.htynkn.
import com.badlogic.gdx.G
import com.badlogic.gdx.G
import com.badlogic.gdx.S
import com.badlogic.gdx.assets.AssetM
import com.badlogic.gdx.audio.M
import com.badlogic.gdx.audio.S
import com.badlogic.gdx.graphics.C
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.g2d.BitmapF
import com.badlogic.gdx.graphics.g2d.TextureA
import com.badlogic.gdx.scenes.scene2d.S
import com.badlogic.gdx.scenes.scene2d.actions.A
import com.badlogic.gdx.scenes.scene2d.ui.I
import com.badlogic.gdx.scenes.scene2d.ui.L
import com.badlogic.gdx.scenes.scene2d.ui.Label.LabelS
blogs.htynkn.DartsG
public class LoadingScreen implements Screen {
public void render(float delta) {
Gdx.gl.glClearColor(0, 0, 0, 1);
Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
if (DartsGame.getManager().update()) {
game.setScreen(new ShaScreen());
stage.act();
stage.draw();
public void resize(int width, int height) {
// TODO Auto-generated method stub
public void show() {
AssetManager manager = DartsGame.getManager();
manager.load("audio/background.ogg", Music.class);
manager.load("audio/bing.wav", Sound.class);
manager.load("audio/great.wav", Sound.class);
manager.load("pack/sha/default.pack", TextureAtlas.class);
stage = new Stage(480, 320, true);
BitmapFont font = new BitmapFont(
Gdx.files.internal("pack/loading/font.fnt"), false);
LabelStyle labelStyle = new LabelStyle(font, Color.WHITE);
Label label = new Label("游戏加载中...", labelStyle);
label.setPosition(160, 150);
stage.addActor(label);
Image image = new Image(
new TextureAtlas(Gdx.files
.internal("pack/loading/default.pack"))
.findRegion("ninja_attack"));
image.setOrigin(image.getWidth() / 2, image.getHeight() / 2);
image.addAction(Actions.repeat(1000, Actions.rotateBy(360, 3f)));
image.setPosition(100, 140);
stage.addActor(image);
public void hide() {
// TODO Auto-generated method stub
public void pause() {
// TODO Auto-generated method stub
public void resume() {
// TODO Auto-generated method stub
public void dispose() {
// TODO Auto-generated method stub
public LoadingScreen(Game game) {
this.game =
效果如下:
当我们不需要某个资源时通过
manager.unload
来销毁。或者使用
manager.clear();
来清空所有资源。
添加友盟统计
统计功能对于一个应用是必须的。你可以获取到用户的信息,比如分辨率,系统版本,还可以快速获取到错误等等。
libgdx是一个游戏框架,没有自带的统计功能,这里我选用友盟统计。
首先申请key
申请完成以后获取Appkey并下载SDK,拷贝到android项目的libs文件夹中。
在AndroidManifest.xml中添加相关信息
详细的情况参考友盟的文档。
现在来看一下友盟文档,最常用的是
MobclickAgent.onResume(this);
MobclickAgent.onPause(this);
为了方便开发,我们新建一个接口IStatisticsService。
blogs.htynkn.public interface IStatisticsService {
void onResume();
void onPause();}
然后在DartsGame类添加
public static IStatisticsService statisticsS
然后在Android项目中添加AndroidStatisticsService
blogs.htynkn.
import android.content.C
import com.umeng.analytics.MobclickA
public class AndroidStatisticsService implements IStatisticsService {
public AndroidStatisticsService(Context context) {
this.context =
public void onResume() {
MobclickAgent.onResume(context);
public void onPause() {
MobclickAgent.onPause(context);
修改入口文件为
blogs.import android.os.Bimport com.badlogic.gdx.backends.android.AndroidAimport com.badlogic.gdx.backends.android.AndroidApplicationCblogs.htynkn.extension.AndroidStatisticsSpublic class MainActivity extends AndroidApplication {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
AndroidApplicationConfiguration cfg = new AndroidApplicationConfiguration();
cfg.useGL20 =
DartsGame dartsGame = new DartsGame();
DartsGame.setStatisticsService(new AndroidStatisticsService(this));
initialize(dartsGame, cfg);
同样的,在桌面项目中添加DesktopStatisticsService
blogs.htynkn.import android.content.Cimport com.umeng.analytics.MobclickApublic class AndroidStatisticsService implements IStatisticsService {
public AndroidStatisticsService(Context context) {
this.context =
public void onResume() {
MobclickAgent.onResume(context);
public void onPause() {
MobclickAgent.onPause(context);
修改入口文件
blogs.import com.badlogic.gdx.backends.lwjgl.LwjglAimport com.badlogic.gdx.backends.lwjgl.LwjglApplicationCblogs.htynkn.extension.DesktopStatisticsS public class Main {
public static void main(String[] args) {
LwjglApplicationConfiguration cfg = new LwjglApplicationConfiguration();
cfg.title = &飞镖杀杀&;
cfg.useGL20 =
cfg.width = 480;
cfg.height = 320;
DartsGame.setStatisticsService(new DesktopStatisticsService());
new LwjglApplication(new DartsGame(), cfg);
因为桌面项目并没有统计功能,所以以输出log替代。
然后在游戏相应位置调用即可。
其他需要的功能办法基本相同,就是实现相应接口,然后不同的平台传入不同实现。没有实现的就用mock输出。
依照这种方法还可以添加其他第三方功能。
不过很遗憾友盟SDK升级以后我死活用不上了…现在换成百度APP统计了。原理是一样的,具体参考源码。
比如在Android的启动文件中重写onPause和onResume
blogs.import android.os.Bimport com.badlogic.gdx.backends.android.AndroidAimport com.badlogic.gdx.backends.android.AndroidApplicationCblogs.htynkn.extension.AndroidStatisticsSpublic class MainActivity extends AndroidApplication {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
AndroidApplicationConfiguration cfg = new AndroidApplicationConfiguration();
cfg.useGL20 =
DartsGame dartsGame = new DartsGame();
DartsGame.setStatisticsService(new AndroidStatisticsService(this));
initialize(dartsGame, cfg);
protected void onResume() {
super.onResume();
DartsGame.getStatisticsService().onResume();
protected void onPause() {
super.onPause();
DartsGame.getStatisticsService().onPause();
实话,友盟的统计比百度的好很多,但是不知为嘛我用不起了。
本来这一篇写好很久了,但是我一直想加入一个菜单和游戏结束的界面,不过最近实在没有时间,先发出来这个部分吧。
如果有时间以后会补上的。
更多课程...&&&
每天2个文档/视频
扫描微信二维码订阅
订阅技术月刊
获得每月300个技术资源
|&京ICP备号&京公海网安备号

我要回帖

更多关于 libgdx 消除游戏 的文章

 

随机推荐