分类: libGDX

  • maven和libGDX

    maven和libGDX

    这一篇是关于maven和libgdx的。本来我准备用gradle(现已有gradle模板了),不过暂时有点小问题,而同时libgdx官方提供了maven支持,为了快速上手还是选用maven了。

    为什么要用maven

    其实原因很简单…我讨厌每次新建项目结构。

    从最开始接触libgdx手动建立Java项目和对应的Android项目到后面的使用setup-ui工具。

    手动建立不说了…这个建立步骤甚至挡住了一些人使用libgdx的步伐。

    setup-ui的确很棒,但是每次重复工作还是很麻烦,特别是需要一个完整的zip包…我本地一般不保存,所以每次都要下载。

    对我而言,在用libgdx开发游戏的时候用maven有三个优点:

    • 不需要去下载相关的包,不管是libgdx本身的,还是你自己使用的第三方包。

    • 省去了建立初始项目结构的麻烦,一个mvn archetype:generate就搞定了。

    • 各种环境不需要配置了,特别是GWT那个。

    虽然很多人觉得maven老了,但是我觉得能用就行了。

    不说多了,直接开始动手吧。这个系列都是以我最近在做的一个东西为例子,名字叫单词泡泡,大概是一个陪单词的(不要在意细节)。

    安装libgdx-maven-archetype

    刚才说到libgdx官方有支持,地址是:https://github.com/libgdx/libgdx-maven-archetype

    先把它clone下来,命令

    git clone git://github.com/libgdx/libgdx-maven-archetype.git

    进入目录,运行

    mvn install

    运行效果:

    安装好了就可以删除clone下来的文件了,我这里安装的是0.99版本。

    然后进入你的工作目录,

    mvn archetype:generate -DarchetypeGroupId=com.badlogic.gdx -DarchetypeArtifactId=gdx-archetype -DarchetypeVersion=0.9.9-SNAPSHOT

    然后输入一些信息,比如groupId,artifactId啥的,最后建立完成,目录结构如下:

    一些小修改

    建立工作就完成了…但是很遗憾,要让它正常工作还需要修改一下小配置。

    主要的问题集中在android项目上,桌面问题不大。

    尝试运行命令:

    mvn integration-test –Pdesktop

    效果如下:

    6

    Html项目使用

    mvn integration-test -Phtml

    这个会下载很多东西,然后本身的执行速度也比较慢,耐心多等等。

    完成后访问http://localhost:8080/可以看到结果。

    Android这块主要是SDK配置的问题。

    我使用的SDK 2.3.3,对应的编号是10。而libgdx-maven-archetype默认的是11和17。

    先打开android目录下的default.properties文件,改成target=android-10。

    然后打开pom文件,找到有关android-maven-plugin的配置。修改SDK版本为10。

    然后在sdk节点中添加一个path值,指向sdk安装位置。

    运行

    mvn install –Pandroid

    如果这时候报错,提示什么0 devices的话可以连接一个调试用的真机或者开个虚拟机,也可以在配置文件中做设置,如果你对android-maven-plugin熟悉的话。

    我一般开个虚拟机。

    如果有需要还可以指定签名文件进行签名。

    导入eclipse

    因为本身就是maven项目了,给eclipse安装上m2e和m2e-android就行了。

    导入已经存在的maven项目导入你需要的项目(反正我是不用ios那块的)。

    导入的结构和setup-ui差不多,一个core项目,然后是各个平台的。

    如果有小红叉的可以右键maven—Update Project一下。

    然后可以在eclipse添加常用maven命令。

    其他问题

    1.如果在eclipse中运行相关maven命令遇到Fatal error compiling: tools.jar not found的问题,直接在Preferences -> Java -> Installed JREs – > Execution Environment中添加一个jdk。

    2.默认带的那张图片是200*250的,如果运行报错,自行换一张符合要求的图片。

  • 如何使用libgdx编写一个简单的游戏(三)— 人性化

    如何使用libgdx编写一个简单的游戏(三)— 人性化

    这一篇主要是添加一些让游戏更人性化的东西,比如音效和加载画面,菜单等等。

    这其中用到了很多资源,主要出自以下几个网站,大家有需要也可以去上面寻找。

    http://www.freesound.org/

    添加音效

    首先是飞镖发出时候的音效,我希望是类似”bing”的一声,要短小精炼。

    我使用的是http://www.freesound.org/people/BMacZero/sounds/96132/

    libgdx支持的音频主要是WAV, MP3和OGG,其他的支持需要扩展支持。

    libgdx中的音频有两种,一种是sound,一种是music。

    一般所谓的音效使用sound,而音乐就使用music。

    在DartController中添加属性

    Sound bing;

    在实例化方法中添加

    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项目中分别修改入口代码

    package com.cnblogs.htynkn;
    
    import android.os.Bundle;
    
    import com.badlogic.gdx.backends.android.AndroidApplication;
    import com.badlogic.gdx.backends.android.AndroidApplicationConfiguration;
    
    publicclass MainActivity extends AndroidApplication {
    @Override
    publicvoidonCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    
    AndroidApplicationConfiguration cfg =new AndroidApplicationConfiguration();
    cfg.useGL20 =false;
    
    initialize(new DartsGame(), cfg);
    }
    }

    package com.cnblogs.htynkn;
    
    import com.badlogic.gdx.backends.lwjgl.LwjglApplication;
    import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration;
    
    publicclass Main {
    publicstaticvoidmain(String[] args) {
    LwjglApplicationConfiguration cfg =new LwjglApplicationConfiguration();
    cfg.title ="darts-shasha";
    cfg.useGL20 =false;
    
    cfg.width =480;
    cfg.height =320;
    
    new LwjglApplication(new DartsGame(), cfg);
    }
    }

    新建一个类ShaScreen,实现Screen接口,其中的代码基本和原来的主类一样。唯一的区别就是create中的代码需要移动到show中。

    在DartsGame中添加代码

    package com.cnblogs.htynkn;
    
    import com.badlogic.gdx.Game;
    import com.cnblogs.htynkn.screen.ShaScreen;
    
    publicclass DartsGame extends Game {
    @Override
    publicvoidcreate() {
    ShaScreen screen =new ShaScreen();
    this.setScreen(screen);
    }
    }

    这样我们就转化到Game-Screen模式。

    当我们在游戏中使用了很多资源的时候直接new的话经常会卡顿,比如我们这个demo再加入音效后进入时间明显变长了。

    所以我们这里需要一个预加载过程,而libgdx提供的AssetManager可以实现这个功能。

    加载中使用一个小图标加文字形式。由于要使用中文,同时文字量很小,所以使用hiero作图。

    03

    新建一个LoadingScreen类作为加载页面。

    因为AssetManager的作用很特殊,很多类都可以要调用它,所以我一般使用单例模式。

    在DartsGame添加

    publicstatic AssetManager manager;
    publicstatic AssetManagergetManager() {
    if (manager ==null) {
    manager =new AssetManager();
    }
    return manager;
    }

    在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代码如下:

    package com.cnblogs.htynkn.screen;
    
    import com.badlogic.gdx.Game;
    import com.badlogic.gdx.Gdx;
    import com.badlogic.gdx.Screen;
    import com.badlogic.gdx.assets.AssetManager;
    import com.badlogic.gdx.audio.Music;
    import com.badlogic.gdx.audio.Sound;
    import com.badlogic.gdx.graphics.Color;
    import com.badlogic.gdx.graphics.GL10;
    import com.badlogic.gdx.graphics.g2d.BitmapFont;
    import com.badlogic.gdx.graphics.g2d.TextureAtlas;
    import com.badlogic.gdx.scenes.scene2d.Stage;
    import com.badlogic.gdx.scenes.scene2d.actions.Actions;
    import com.badlogic.gdx.scenes.scene2d.ui.Image;
    import com.badlogic.gdx.scenes.scene2d.ui.Label;
    import com.badlogic.gdx.scenes.scene2d.ui.Label.LabelStyle;
    import com.cnblogs.htynkn.DartsGame;
    
    publicclass LoadingScreen implements Screen {
    
    Game game;
    Stage stage;
    
    @Override
    publicvoidrender(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();
    }
    
    @Override
    publicvoidresize(int width,int height) {
    // TODO Auto-generated method stub
    
    }
    
    @Override
    publicvoidshow() {
    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);
    }
    
    @Override
    publicvoidhide() {
    // TODO Auto-generated method stub
    
    }
    
    @Override
    publicvoidpause() {
    // TODO Auto-generated method stub
    
    }
    
    @Override
    publicvoidresume() {
    // TODO Auto-generated method stub
    
    }
    
    @Override
    publicvoiddispose() {
    // TODO Auto-generated method stub
    
    }
    
    publicLoadingScreen(Game game) {
    this.game = game;
    }
    }

    效果如下:

    04

    当我们不需要某个资源时通过

    manager.unload

    来销毁。或者使用

    manager.clear();

    来清空所有资源。

    添加友盟统计

    统计功能对于一个应用是必须的。你可以获取到用户的信息,比如分辨率,系统版本,还可以快速获取到错误等等。

    libgdx是一个游戏框架,没有自带的统计功能,这里我选用友盟统计。

    首先申请key

    申请完成以后获取Appkey并下载SDK,拷贝到android项目的libs文件夹中。

    在AndroidManifest.xml中添加相关信息。

    详细的情况参考友盟的文档。

    现在来看一下友盟文档,最常用的是

    MobclickAgent.onResume(this);
    MobclickAgent.onPause(this);

    为了方便开发,我们新建一个接口IStatisticsService。

    package com.cnblogs.htynkn.extension;
    
    publicinterface IStatisticsService {
    void onResume();
    void onPause();
    }

    然后在DartsGame类添加

    publicstatic IStatisticsService statisticsService;

    然后在Android项目中添加AndroidStatisticsService

    package com.cnblogs.htynkn.extension;
    
    import android.content.Context;
    
    import com.umeng.analytics.MobclickAgent;
    
    publicclass AndroidStatisticsService implements IStatisticsService {
    
    Context context;
    
    publicAndroidStatisticsService(Context context) {
    this.context = context;
    }
    
    @Override
    publicvoidonResume() {
    MobclickAgent.onResume(context);
    }
    
    @Override
    publicvoidonPause() {
    MobclickAgent.onPause(context);
    }
    
    }

    修改入口文件为

    package com.cnblogs.htynkn;
    
    import android.os.Bundle;
    
    import com.badlogic.gdx.backends.android.AndroidApplication;
    import com.badlogic.gdx.backends.android.AndroidApplicationConfiguration;
    import com.cnblogs.htynkn.extension.AndroidStatisticsService;
    
    publicclass MainActivity extends AndroidApplication {
    @Override
    publicvoidonCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    AndroidApplicationConfiguration cfg =new AndroidApplicationConfiguration();
    cfg.useGL20 =false;
    DartsGame dartsGame =new DartsGame();
    DartsGame.setStatisticsService(new AndroidStatisticsService(this));
    initialize(dartsGame, cfg);
    }
    }

    同样的,在桌面项目中添加DesktopStatisticsService

    package com.cnblogs.htynkn.extension;
    
    import com.badlogic.gdx.Gdx;
    
    publicclass DesktopStatisticsService implements IStatisticsService{
    
    @Override
    publicvoidonResume() {
    Gdx.app.log("StatisticsService","onResume");
    }
    
    @Override
    publicvoidonPause() {
    Gdx.app.log("StatisticsService","onPause");
    }
    
    }

    修改入口文件

    package com.cnblogs.htynkn;
    
    import com.badlogic.gdx.backends.lwjgl.LwjglApplication;
    import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration;
    import com.cnblogs.htynkn.extension.DesktopStatisticsService;
    
    publicclass Main {
    publicstaticvoidmain(String[] args) {
    LwjglApplicationConfiguration cfg =new LwjglApplicationConfiguration();
    cfg.title ="飞镖杀杀";
    cfg.useGL20 =false;
    cfg.width =480;
    cfg.height =320;
    DartsGame.setStatisticsService(new DesktopStatisticsService());
    new LwjglApplication(new DartsGame(), cfg);
    }
    }

    因为桌面项目并没有统计功能,所以以输出log替代。

    然后在游戏相应位置调用即可。

    其他需要的功能办法基本相同,就是实现相应接口,然后不同的平台传入不同实现。没有实现的就用mock输出。

    依照这种方法还可以添加其他第三方功能。

    不过很遗憾友盟SDK升级以后我死活用不上了…现在换成百度APP统计了。原理是一样的,具体参考源码。

    比如在Android的启动文件中重写onPause和onResume

    package com.cnblogs.htynkn;
    
    import android.os.Bundle;
    
    import com.badlogic.gdx.backends.android.AndroidApplication;
    import com.badlogic.gdx.backends.android.AndroidApplicationConfiguration;
    import com.cnblogs.htynkn.extension.AndroidStatisticsService;
    
    publicclass MainActivity extends AndroidApplication {
    @Override
    publicvoidonCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    AndroidApplicationConfiguration cfg =new AndroidApplicationConfiguration();
    cfg.useGL20 =false;
    DartsGame dartsGame =new DartsGame();
    DartsGame.setStatisticsService(new AndroidStatisticsService(this));
    initialize(dartsGame, cfg);
    }
    
    @Override
    protectedvoidonResume() {
    super.onResume();
    DartsGame.getStatisticsService().onResume();
    }
    
    @Override
    protectedvoidonPause() {
    super.onPause();
    DartsGame.getStatisticsService().onPause();
    }
    }

    统计结果

     


    我还实现了其他方法,比如事件记录等等。

    说实话,友盟的统计比百度的好很多,但是不知为嘛我用不起了。

    写在最后

    本来这一篇写好很久了,但是我一直想加入一个菜单和游戏结束的界面,不过最近实在没有时间,先发出来这个部分吧。

    如果有时间以后会补上的。

    Github仓库地址:https://github.com/htynkn/DartsShaSha 对应的tag是page3。

    apk下载地址:http://pan.baidu.com/share/link?shareid=385272&uk=4127624209

  • 如何使用libgdx编写一个简单的游戏(二)— 完善

    如何使用libgdx编写一个简单的游戏(二)— 完善

    上一篇介绍游戏雏形的编写,这一篇将完善部分逻辑并添加更多效果。

    例子代码在https://github.com/htynkn/DartsShaSha,如有需要请自行在tag中下载对应部分。

    完善飞镖逻辑

    现在的飞镖可以旋转可以飞行了,但是有一个问题却没有解决。

    首先飞镖的速度,如果用户触摸位置很靠近左侧,那么飞镖的速度就很慢了。

    其次,如果用户触摸中间位置,默认情况下飞镖应该是朝那个方向飞行,而不是飞到触摸位置就消失了。

    这里的处理办法很简单,就是根据用户触摸位置,算出一个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个
    returnfalse;
    }

    这样当屏幕上的飞镖数量大于等于5时就不会产生新的飞镖了。

    还有就是触摸的位置,如果触摸的位置太靠右的话,会出现飞镖倒飞或者速度过快的问题,所以当触摸位置太靠近左侧的时候就不释放飞镖。

    在touchDown方法中添加

    if (vector3.x < man.getX() +5) {//如果触摸太靠近左侧就不响应
    returnfalse;
    }

    这里的5是我随便写的,仅仅表示个意思。测试一下,觉得5还是太小了,最后我改成10了。

    更带感的对手

    说实话,现在的对手一动不动,只会匀速平移。我们先改进它的外貌吧。

    我从http://untamed.wild-refuge.net/rmxpresources.php?characters获取到如下图片

    scythe

    打包以后放入assets文件夹中。

    因为libgdx只有默认Animation类,但是没法办法直接在stage中使用,所以新建一个Scythe类并继承Actor类。

    publicScythe(AtlasRegion atlasRegion) {
    super();
    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方法如下

    @Override
    publicvoiddraw(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如下

    package com.cnblogs.htynkn;
    
    import com.badlogic.gdx.Gdx;
    import com.badlogic.gdx.graphics.g2d.Animation;
    import com.badlogic.gdx.graphics.g2d.SpriteBatch;
    import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasRegion;
    import com.badlogic.gdx.graphics.g2d.TextureRegion;
    import com.badlogic.gdx.scenes.scene2d.Actor;
    
    publicclass Scythe extends Actor {
    TextureRegion[] walkFrames;// 保存每一帧
    Animation animation;// 动画类
    float stateTime;// 总时间
    TextureRegion currentFrame;// 当前帧
    int titleWidth =50;// 声明块宽度
    int titleHeight =48;// 声明块高度
    
    publicScythe(AtlasRegion atlasRegion) {
    super();
    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
    }
    
    @Override
    publicvoiddraw(SpriteBatch batch,float parentAlpha) {
    stateTime += Gdx.graphics.getDeltaTime();// 获取总时间
    currentFrame = animation.getKeyFrame(stateTime,true);// 获取当前关键帧
    batch.draw(currentFrame,this.getX(),this.getY(),this.getWidth(),
    this.getHeight());// 绘制
    }
    }

    效果如下:

    01

    添加血条

    我们来试试在给予怪兽血量,即有些怪兽可以承受两次伤害。这里我们将用到比较基本的东西。

    首先是血条的位置,一般来看应该在怪兽正上方,以红色显示。

    绘制可以用很多种方法,我不怎么习惯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的效果

    03

    控制转换

    增加了血量以后我们的代码也需要修改了,在主类DartsShaSha中修改相关的逻辑。

    为了方便起见我们将游戏的逻辑赋给控制器。

    先新建一个IController

    package com.cnblogs.htynkn.controller;
    
    import com.badlogic.gdx.scenes.scene2d.Group;
    import com.badlogic.gdx.scenes.scene2d.Stage;
    
    publicabstractclass IController extends Group {
    publicabstractvoidupdate(Stage stage);
    }

    然后新建TargetController类,重写update方法,在这个方法中我们处理相关逻辑,然后在主类中只需要调用方法就可以。

    首先将中已有的代码拷贝过来,然后在Sythe中添加两个方法来处理受到伤害和死亡判断。

    publicvoidbeAttacked(int damage) {
    if (this.currentHp > damage) {// 如果血量大于伤害就扣除响应数值
    currentHp = currentHp - damage;
    }elseif (this.currentHp >0) {// 如果血量小于伤害但是仍有血量就让血量归零
    currentHp =0;
    }
    }
    
    public BooleanisAlive() {
    returnthis.currentHp >0;
    }

    然后在TargetController中添加update的相关代码

    publicvoidupdate(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().size; i++) {
    Actor actor = projectile[i];
    for (int j =0; j <this.getChildren().size; j++) {
    Actor target = targets[j];
    if (ProjectileFactory.attackAlive(target, actor)) {
    Scythe scythe = (Scythe) target;
    scythe.beAttacked(1);
    projectiles.removeActor(actor);
    if (!scythe.isAlive()) {
    this.removeActor(target);
    }
    break;
    }
    }
    }
    }

    然后在主类中修改相关的实例化代码,在render中调用update方法。

    targetController.update(this.stage);//调用update方法,处理怪兽的逻辑

    效果如下:

    04

    飞镖的杀伤力和手势识别

    上面的代码中每一个飞镖的杀伤力是1,每一个怪兽的血量是2。

    现在我们来修改一下飞镖的控制,让飞镖也采用控制器来处理。

    在这里设定为触摸一下屏幕是发射一般的飞镖,杀伤力为1。而长按以后的杀伤力是2。

    libgdx中提供了一个手势识别的接口,实现它就可以了。这里我选择继承GestureAdapter。

    publicclass DartsListener extends GestureAdapter

    同时修改飞镖的控制为控制器模式,新建控制器DartsController。

    代码如下:

    package com.cnblogs.htynkn.controller;
    
    import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasRegion;
    import com.badlogic.gdx.math.Rectangle;
    import com.badlogic.gdx.scenes.scene2d.Actor;
    import com.badlogic.gdx.scenes.scene2d.Stage;
    import com.badlogic.gdx.scenes.scene2d.actions.Actions;
    import com.cnblogs.htynkn.elements.Dart;
    
    publicclass DartsController extends IController {
    
    AtlasRegion region;
    
    @Override
    publicvoidupdate(Stage stage) {
    // 如果飞镖已经飞到则刪除
    Actor[] projectile =this.getChildren().begin();
    for (int j =0; j <this.getChildren().size; j++) {
    Actor actor = projectile[j];
    if (!this.checkAlive(actor)) {
    this.removeActor(actor);
    }
    }
    }
    
    publicDartsController(AtlasRegion region) {
    this.region = region;
    }
    
    publicvoidAddDarts(Dart dart) {
    if (this.getChildren().size >=5) {//如果飞镖数量大于等于5个就结束
    return;
    }
    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 BooleanattackAlive(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 BooleancheckAlive(Actor projectile) {
    if (projectile.getActions().size ==1) {
    returnfalse;
    }
    returntrue;
    }
    
    public DartcreateDart() {
    returnnew Dart(region);
    }
    }

    其中Dart类代码如下:

    package com.cnblogs.htynkn.elements;
    
    import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasRegion;
    import com.badlogic.gdx.math.Vector2;
    import com.badlogic.gdx.scenes.scene2d.actions.Actions;
    import com.badlogic.gdx.scenes.scene2d.ui.Image;
    
    publicclass Dart extends Image {
    
    Vector2 target;
    
    publicDart(AtlasRegion region) {
    super(region);
    this.setOrigin(getWidth() /2, getHeight() /2);
    this.addAction(Actions.repeat(50, Actions.rotateBy(360,0.5f)));
    }
    
    publicvoidsetTarget(Vector2 target) {
    this.target = target;
    }
    
    publicvoidsetTarget(float x,float y) {
    this.target =new Vector2(x, y);
    }
    
    public Vector2getTarget() {
    return target;
    }
    }

    因为我们的输入接受另外有类DartsListener来处理,所以修改主类的继承如下:

    publicclass DartsShaSha implements ApplicationListener

    在multiplexer中添加我们新的手势识别器

    GestureDetector gestureDetector =new GestureDetector(
    new DartsListener(this.stage));
    multiplexer.addProcessor(gestureDetector);// 添加手势识别

    目前DartsListener中代码如下

    package com.cnblogs.htynkn.listener;
    
    import com.badlogic.gdx.input.GestureDetector.GestureAdapter;
    import com.badlogic.gdx.math.Vector3;
    import com.badlogic.gdx.scenes.scene2d.Actor;
    import com.badlogic.gdx.scenes.scene2d.Stage;
    import com.cnblogs.htynkn.controller.DartsController;
    import com.cnblogs.htynkn.elements.Dart;
    
    publicclass DartsListener extends GestureAdapter {
    
    Stage stage;
    
    publicDartsListener(Stage stage) {
    this.stage = stage;
    }
    
    @Override
    publicbooleantouchDown(float x,float y,int pointer,int button) {
    DartsController dartsController = (DartsController) stage.getRoot()
    .findActor("dartsController");
    if (dartsController.getChildren().size >=5) {// 限制飞镖的数量为5个
    returnfalse;
    }
    Vector3 vector3 =new Vector3(x, y,0);
    stage.getCamera().unproject(vector3);// 坐标转化
    Actor man = stage.getRoot().findActor("player");
    if (vector3.x < man.getX() +10) {// 如果触摸太靠近左侧就不响应
    returnfalse;
    }
    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);
    returntrue;
    }
    
    @Override
    publicbooleanlongPress(float x,float y) {
    returntrue;
    }
    }

    可能还有其他细节的修改,详细的请参考代码。

    目前我们只能算是重构了一下,游戏效果并没有改变。现在来设置飞镖的杀伤力和长按的处理。

    在Dart中添加属性

    int power;

    在实例化中添加

    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中的代码如下

    package com.cnblogs.htynkn.listener;
    
    import com.badlogic.gdx.graphics.Color;
    import com.badlogic.gdx.input.GestureDetector;
    import com.badlogic.gdx.math.Vector3;
    import com.badlogic.gdx.scenes.scene2d.Actor;
    import com.badlogic.gdx.scenes.scene2d.Stage;
    import com.cnblogs.htynkn.controller.DartsController;
    import com.cnblogs.htynkn.elements.Dart;
    
    publicclass DartsDetector extends GestureDetector {
    
    Stage stage;
    
    publicDartsDetector(Stage stage, GestureListener listener) {
    super(listener);
    this.stage = stage;
    }
    @Override
    publicbooleantouchUp(float x,float y,int pointer,int button) {
    DartsController dartsController = (DartsController) stage.getRoot()
    .findActor("dartsController");
    if (dartsController.getChildren().size >=5) {// 限制飞镖的数量为5个
    returnfalse;
    }
    Vector3 vector3 =new Vector3(x, y,0);
    stage.getCamera().unproject(vector3);// 坐标转化
    Actor man = stage.getRoot().findActor("player");
    if (vector3.x < man.getX() +10) {// 如果触摸太靠近左侧就不响应
    returnsuper.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);
    returnsuper.touchUp(x, y, pointer, button);
    }
    }

    效果如下:

    05

    写在最后

    这一篇修改了很多细节,可能部分很小但却关键的修改没有在文中标明。比如为Actor设置名称以便通过findActor方法获取。

    如果直接复制有问题,可以从git库获取https://github.com/htynkn/DartsShaSha,对应的tag为page2。

    apk地址是http://pan.baidu.com/share/link?shareid=331455&uk=4127624209

    下一篇将会添加一些声音效果和资源加载,然后会添加一个统计功能。

  • 如何使用libgdx编写一个简单的游戏(一)— 雏形

    如何使用libgdx编写一个简单的游戏(一)— 雏形

    写这几篇文章主要是看了这个系列的文章:http://www.raywenderlich.com/352/how-to-make-a-simple-iphone-game-with-cocos2d-tutorial

    这个系列主要讲述了如何使用Cocos2D编写简单的游戏。稍微读读感觉不错,所以想写个libgdx版本的。

    本篇文章主要讲述基本内容的编写,包括显示人物、怪兽和飞镖。

    最终效果如下图:

    01_thumb1

    获取libgdx

    你可以从libgdx的官网下载打包好的代码,我下载的是0.98版本。

    02_thumb4

    当然,你也可以从git代码仓库获取最新的版本的,或者你习惯使用的以前版本,比如0.97。

    创建项目

    libgdx项目的创建可以有多种方式,我推荐使用setup-ui。方便易用还可以省去很多麻烦,特别是ADT升级以后的ClassNotFound问题。

    如果是下载打包好的,那么就默认包含了gdx-setup-ui,双击就可以打开。

    03_thumb1

    填写一些基本信息,然后选中你下载的0.98.zip那个压缩文件。这里我只生成一个桌面项目和Android项目。

    桌面项目是方便调试,而Android项目是最后发布的。在整个开发中我始终用桌面项目调试,因为速度快,容易排错。同时周期性的在Android真机上测试。

    04_thumb1

    点击生成项目,然后在Eclipse中导入。

    05_thumb1

    一般导入进去以后Android项目会有一些问题,修改project.properties文件和AndroidManifest.xml配置文件。

    运行效果如下:

    06_thumb1

    准备工作

    本例子中用到的图片如下:

    Player_thumb Projectile_thumb Target_thumb

    用gdx-texturepacker打包成一张大图。

    07_thumb1

    我整个例子都是用的是Stage模式。所以它的坐标原点在左下角,如果是一般用Spirte直接绘制,那么原点在右上角。

    首先将打包好的图册复制到assets中新建的pack文件夹。

    然后我们开始动工了,首先删除setup-ui生成的多余代码,整理DartsShaSha.java文件如下:

    package com.cnblogs.htynkn;
    
    import com.badlogic.gdx.ApplicationAdapter;
    import com.badlogic.gdx.Gdx;
    import com.badlogic.gdx.graphics.GL10;
    import com.badlogic.gdx.scenes.scene2d.Stage;
    
    publicclass DartsShaSha extends ApplicationAdapter {
    Stage stage;
    @Override
    publicvoidcreate() {
    stage =new Stage(480,320,true);
    }
    
    @Override
    publicvoiddispose() {
    stage.dispose();
    }
    
    @Override
    publicvoidrender() {
    Gdx.gl.glClearColor(1,1,1,1);
    Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
    stage.act();
    stage.draw();
    }
    }

    这时候运行效果是一个白茫茫的画面。

    09_thumb1

    注意一下这句

    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值以保证显示位置正确性

    效果如下:

    10_thumb1

    添加忍者

    现在来添加我们的主角,我希望主角显示在屏幕左侧中央。所以它的x值必然是0,但是它的y值并不是320的一半,而是160减去图片高度的一半。

    因为我们指定的x、y值其实相对图片的左下角的。所以要补上多余或者不足的部分。

    主角其实就是一张图片,并没有太多特别的效果,所以我使用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);//将主角添加到舞台

    效果如下:

    11_thumb1

    添加怪兽

    然后我们来添加几只怪兽。怪兽应该是随机从屏幕右侧出现,并直线移动到屏幕左侧。

    同时我们还要检测怪兽的生命值什么的,或者其他效果,所以为了方便处理,我们专门建立一个Group来管理怪兽。

    新建类TargetGroup,并集成Group类。

    package com.cnblogs.htynkn;
    
    import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasRegion;
    import com.badlogic.gdx.scenes.scene2d.Group;
    import com.badlogic.gdx.scenes.scene2d.ui.Image;
    
    publicclass TargetGroup extends Group {
    publicTargetGroup(AtlasRegion region) {
    super();
    }
    }

    因为还需要传入怪兽的图片,所以我们的创建方法保留参数AtlasRegion region。

    怪兽的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 =false;
    do {
    flag =false;
    tempY = MathUtils.random(minY, maxY);// 生成Y值
    
    Actor[] actors =this.getChildren().begin();// 获取当前已有的怪兽对象
    for (int j =0; j <this.getChildren().size; j++) {
    Actor tempActor = actors[j];
    if (tempY == tempActor.getY()) {// 如果Y值相等,比如重合,所以舍弃,重新生成
    flag =true;
    break;
    }elseif (tempY < tempActor.getY()) {// 如果生成的Y值小于当前怪兽的Y值,则判断生成的Y值加上高度后是否合适
    if ((tempY + region.getRegionHeight()) >= tempActor
    .getY()) {
    flag =true;
    break;
    }
    }else {// 如果生成的Y值大于当前怪兽的Y值,则判断当前怪兽的Y值加上高度后是否合适
    if (tempY

    在主类的create方法中添加

    TargetGroup group =new TargetGroup(atlas.findRegion("Target"));
    stage.addActor(group);

    效果如下:

    12_thumb1

    目前怪兽还不能移动,这里需要一个简单的动画效果,libgdx中的Action可以办到。

    考虑到怪兽是水平移动,即Y值不变,X值变小。

    所以添加一个方法

    publicvoidAddMove(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);//添加到组中

    效果如下:

    13_thumb1

    添加武器

    我们的主角自然不能赤手空拳和怪兽进行搏斗,现在添加一些飞镖。

    假定用户触摸屏幕以后,主角就向触摸位置发射一个飞镖。

    因为飞镖的数量不一定,所以我这里创建一个专门的类ProjectileFactory来处理。

    首先是飞镖的创建,和怪兽群一样的原因,我还是希望一个专门的组来管理。

    创建一个专门的方法来创建飞镖

    publicstatic ImagecreateProjectile(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));//设置飞镖的移动
    return image;
    }

    在主类进行一些修改以便其可以获取屏幕的触摸。

    首先修改类声明为

    publicclass DartsShaSha extends InputAdapter implements ApplicationListener

    其实具体也可以两个都实现接口,主要是我觉得看着不舒服。

    重写touchDown方法为

    publicbooleantouchDown(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));// 添加新飞镖到飞镖组
    returntrue;
    }

    在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,我们可以断定飞镖只是在旋转而已经到达目的地了。这个时候就可以把它删除了。

    添加一个专门的方法来判断飞镖是否应该移除了

    publicstatic BooleancheckAlive(Actor projectile) {
    if (projectile.getActions().size ==1) {
    returnfalse;
    }
    returntrue;
    }

    在render方法中添加处理代码

    // 飞镖的移除判
    Actor[] projectile = projectiles.getChildren().begin();//获取Actor数组
    for (int j =0; j < projectiles.getChildren().size; j++) {//移除判断
    Actor actor = projectile[j];
    if (!ProjectileFactory.checkAlive(actor)) {
    projectiles.removeActor(actor);
    }
    }

    效果如下:

    现在飞镖可以自动消失了,并且也在旋转了。不过旋转效果很奇怪,它并不是沿中心旋转,而是沿着左下角旋转的。

    重新设置中心

    image.setOrigin(image.getWidth() /2, image.getHeight() /2);

    现在一切正常了。

    碰撞检测和杀敌

    当然,发出飞镖的目的自然是杀敌,现在马上来添加这个功能。

    我们可以把怪兽看着一个矩形,即飞镖击中任何位置都算作有效。而飞镖就以其中心为代表。

    创建方法attackAlive

    publicstatic BooleanattackAlive(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().size; i++) {
    Actor actor = projectile[i];
    for (int j =0; j < targetGroup.getChildren().size; j++) {
    Actor target = targets[j];
    if (ProjectileFactory.attackAlive(target, actor)) {
    targetGroup.removeActor(target);
    projectiles.removeActor(actor);
    break;
    }
    }
    }
    
    // 如果飞镖已经飞到则刪除
    projectile = projectiles.getChildren().begin();
    for (int j =0; j < projectiles.getChildren().size; j++) {
    Actor actor = projectile[j];
    if (!ProjectileFactory.checkAlive(actor)) {
    projectiles.removeActor(actor);
    }
    }

    效果如下:

    16_thumb1

    写在最后

    虽然实现了个大概,但是仔细看看其实问题还是很多的,后面的文章会提到进一步的修改。包括逻辑上的完善,声音效果,预加载,背景绘制,集成第三方社交和广告等等。

    这片文章对应demo可以从这里下载来试试。

    http://pan.baidu.com/share/link?shareid=328840&uk=4127624209

    我用的2.2的sdk编译的,低版本没有测试。我的手机是ZTE V880,fps50上下。

    ps:testin的测试结果是通过率100.00%

    ps: 代码上传到github上了,地址https://github.com/htynkn/DartsShaSha

    文章对应的代码的tag为page 1。

  • android游戏开发框架libgdx的使用(完)—杂谈Libgdx

    写在最前面

    仔细看看,上一篇博文是10月5号了,有很长的时间没有写博客了。

    libgdx这个系列的文章开篇一年有余了,期间得到了很多朋友的鼓励和意见,总的来说感觉还是蛮不错的。

    想了很久,我决定给这个系列的文章画上一个句号。

    就原因来说是多方面的:

    第一是libgdx的版本从0.92到了今天的0.97版本,虽然总体架构变化不大,但是细节上的接口还是有很多差异了,早期的一些文章的实例代码可能无法直接运行了,我又没有精力细细检查和修改,可能新入手的朋友们会遇到一些问题。

    第二就是这个系列本身的问题,我开始写的时候也没有想什么具体的东西,就是有一篇写一篇的,整个系列来看没有一种整体的驾驭性,继续写下去我感觉意义不大。

    第三就是个人原因了,我其实并没有做过太多的游戏开发,当初接触libgdx也是相当偶然的原因。一些对于游戏开发更深入的东西我没有研究,不敢随意写出来。

    文章接下来的内容是一些极为零碎的东西,希望能够有所用处。

    Libgdx是什么

    我最感到庆幸的就是这个系列的文章的标题【android游戏开发框架libgdx的使用】,虽然最开始了解不深入,但是这个标题却是正确的。

    Libgdx是一个游戏开发框架,它不是一个游戏引擎。

    它的主要代码是java,其中对于效率要求较高的部分是由C和C++写成的。

    它是跨平台的,支持Windows、Mac OS、Linux、Android(1.5+)、GWT,它对IOS的支持是基于MonoTouchIKVM

    它是免费的,基于Apache License 2.0协议。

    Libgdx的优势是什么?我为什么选择它?

    说实话,现在的游戏框架和引擎有很多,选择的面也很广。比如庞大的cocos2d家族。

    如果你的主要语言是Java,那么推荐你使用Libgdx。

    如果你主要涉足Android游戏领域,那么推荐你使用Libgdx。它可以方便的在桌面环境调试你的游戏逻辑,你可以轻松地和那个缓慢的模拟器说拜拜了。

    如果你希望更自由的开发,你希望控制和掌握你的代码,那么推荐你使用Libgdx。它的封装比较低级,你可以基于它开发自己的游戏引擎,而不是接受其他引擎的那套模式和理论。当然你不想太底层但又希望一定的自由,2D的UI库和Stage给出了一套简单的解决方法。

    如果你追求高效,那么推荐你使用Libgdx。也许你带着有色的眼光…这货是Java的吧!好吧,我也没有做过相关的测试,不过推荐你看看这篇文章,它比较了主流的游戏框架的方方面面。

    Libgdx好用吗?比起XX怎么样?

    我经常在QQ和Email里面看到这样的询问,说实话,我觉着这种问题意义不大。首先我没有深入的研究过其他的游戏框架和引擎,不敢断言。其次个人的喜欢和需求是不同的,也许你喜欢Libgdx的这套API设计,也许你就是看不惯某种设计模式,这种问题很难有标准的答案,甚至常常引起争端。

    如果你想知道哪款开发框架或引擎适合你,你可以都试试再做出选择。如果你觉得没有时间或者这个功夫,那就双眼一闭,随便选一个吧。

    Libgdx的使用广吗?有哪些游戏使用了它?

    我清楚问这个问题的人的意思和顾虑。但是我觉得一款游戏的好坏和它使用什么技术手段开发没有大的关系。游戏的美工,构思设计还有宣传等等都会有很大的影响。

    当然使用广泛与否一定程度上关系着反馈的快捷和获取帮助的难易程度。我只能告诉你社区很活跃,任何问题都不会拖过半天(起码我遇到的情况是这样的)。我原来看过一个国外的网站,它统计了市场中的游戏使用了第三方库的情况,Libgdx的排名在20位以内,具体的数据和网址我记不清楚了。

    如果你想知道什么游戏使用了Libgdx,你可以去社区看看,有一个列表专门收集基于Libgdx的游戏。如果你硬要我举个例子,不知道Google最近吸引不少眼球的Ingress能不能满足你的心理期待。

    Libgdx支持中文吗?

    很遗憾的告诉你不完全支持。不完全支持的意思是原生不支持,但是你可以通过一些办法实现。你可以使用Herio或者TTF字库。

    Libgdx的缺点那么那么多…

    如果你真心实意的问这个问题,那么我只能说你可以选择别的,或者发挥你的实力改造它。

    但是有些时候请先仔细琢磨琢磨再开口。我不止一次的看到了类似的问题:

    • Libgdx使用的图片宽与高皆为2的整数次幂,好麻烦
    • Libgdx的文档好少
    • Libgdx没有场景过渡效果
    • Libgdx没有XX功能

    我应该如何开始

    群上比较常见的问题就是 我是新手,我应该怎么学习Libgdx。

    我个人来看最好的例子就是官方的Wiki,从理念到搭建环境再到每个细节都有说明和介绍,可以先从那里入手有个基本的了解。

    然后就是官方的Demo,可以看看怎么编写一个个完整的项目。不过这里注意一个问题,Demo的风格不敢说迥异,但也算各有不同。参考的时候一定多注意。

    哪里可以获取帮助

    遇到问题的时候我推荐自己多想想,如果要获取帮助的话可以去论坛问,如果不习惯英文可以去群上问:

    Libgdx游戏引擎(1群)(148848483)

    Libgdx游戏引擎(3群)(187378034)

    从字面看应该有2群的,不过我没有加…还有其他群的话欢迎大家在下面留言。

    总的来说QQ群还是比较活跃的。

    除了我之前的文章可以参考意外,我推荐Mkey libgdx系列教程,虽然没有很完整的体系,但是给出很多细节的解决方法。同时推荐这个项目https://code.google.com/p/c2d-engine/,它是基于Libgdx的,感觉看着不错,起码它给出了一部分人特别想要的场景切换的解决方案。

    一些其他想法

    这是以前文章的一个离线版本(不是我打包的哈),大家不想看在线看的话可以下载,不过我觉得效果不怎么好就是了。

    下载地址:http://pan.baidu.com/share/link?shareid=168280&uk=4127624209

  • android游戏开发框架libgdx的使用(二十四)—physics-body-editor配合Box2D加快开发

    android游戏开发框架libgdx的使用(二十四)—physics-body-editor配合Box2D加快开发

    Box2d是一个很出名的2d物理引擎,有C++、Java等等版本。libgdx对Box2D进行了很高效的封装,极大的降低了学习和使用成本。

    但是我觉得和Libgdx的UI库比起来,Box2D的使用始终比较复杂,单纯的物体降落什么的还好说,要是遇上一个特别复杂的物体,有时候就有点繁琐了。

    在网上游荡时发现了physics-body-editor,绝对堪称神器。

    physics-body-editor

    项目主页:http://code.google.com/p/box2d-editor/

    libgdx-box2d-1

    复杂对象的创建只需要用鼠标点点就行了。

    工具可以到处Json格式的文件,还可以用于其他环境中。

    对于Libgdx而言,该项目有一个专门的loader可供使用。

    第一个小瓶子

    我想了半天都没有找到一个合适的例子可以用做演示,干脆就用官方提供了那个瓶子的图片做演示。

    启动编辑器,新建一个项目,导入我们要的图片。

    libgdx-box2d-2

    点点鼠标把边描绘出来。

    libgdx-box2d-4

    编辑器还可以进行测试,点击鼠标拖动就可以出现不同大小和速度的小球。

    libgdx-box2d-3

    保存项目,你可以获得一个Json格式的文件。

    将压缩包中的loader引入项目。

    使用

    BodyEditorLoader loader =new BodyEditorLoader(Gdx.files.internal("box2d/b.json"));

    读入配置文件。然后创建一个动态对象:

    BodyDef bd =new BodyDef();
    bd.type = BodyType.DynamicBody;

    指定密度等等

    FixtureDef fd =new FixtureDef();
    fd.density =1;
    fd.friction =0.5f;
    fd.restitution =0.3f;

    创建Body

    bottleModel = world.createBody(bd);

    使用loader创建细节

    loader.attachFixture(bottleModel,"p", fd, BOTTLE_WIDTH);
    bottleModelOrigin = loader.getOrigin("p", BOTTLE_WIDTH).cpy();

    其中的那个“p”是我的图片的名称,大家根据自己的情况更改。

    为了方便测试,我们创建几个小球。

    BodyDef ballBodyDef =new BodyDef();
    ballBodyDef.type = BodyType.DynamicBody;
    
    CircleShape shape =new CircleShape();
    shape.setRadius(0.15f);
    
    FixtureDef fd =new FixtureDef();
    fd.density =1;
    fd.friction =0.5f;
    fd.restitution =0.5f;
    fd.shape = shape;
    
    ballModels =new Body[100];
    for (int i=0; i<100; i++) {
    ballModels[i] = world.createBody(ballBodyDef);
    ballModels[i].createFixture(fd);
    }

    小球的半径为0.15f,一共100个。

    绘制的时候通过

    bottleModel.getPosition().sub(bottleModelOrigin)

    获取坐标,赋给对象。

    效果:

    libgdx-box2d-5

    libgdx-box2d-6

    libgdx-box2d-7

    physics-body-editor到底生成了什么

    physics-body-editor最后导出的是一个Json格式的文件。找一个Json查看器看看。

    libgdx-box2d-8

    可以看到,万变不离其宗。生成的Json文件其实记录下了每一个点的坐标,在Loader读入的时候进行转换和声明。

    具体可以参考一下Libgdx的Loader,根据其思路可以将这个工具用到其他语言和环境中去。

    写在最后

    如果想在Stage中使用的话要注意相机的处理。同时不要把Stage的大小和World中的大小混为一谈,最好有一个转换比例。一般10~100。

    最后附上用到的两张图:

    ballbottle

    资源来自:http://www.aurelienribon.com

  • android游戏开发框架libgdx的使用(二十三)—使用Universal Tween Engine实现动画效果

    android游戏开发框架libgdx的使用(二十三)—使用Universal Tween Engine实现动画效果

    libgdx的ui库可以实现一些动画效果,但是做游戏来说可能有些不足。Universal Tween Engine是一个纯java实现的动画库。

    地址:http://code.google.com/p/java-universal-tween-engine/

    只要能够用float表示的一切java对象它可以让它动画化,可以使用于Libgdx、Android、Swing等等。

    Universal Tween Engine使用一般流程

    使用Universal Tween Engine最重要的一个步骤就是实现TweenAccessor接口,这个接口定义了getValues和setValues方法。

    然后Engine中注册对应的接口。然后定义一些动画效果并添加到管理器中。最后用update方法更新时间。

    具体的可以参考一下Wiki:http://code.google.com/p/java-universal-tween-engine/wiki/GetStarted

    在libgdx中实现简单动画

    我比较喜欢使用Stage,所以下面的例子都是Stage中的。

    首先实现TweenAccessor接口,我没有区分对待,比如给Image写一个,再给Button写个啥的。我直接给Actor写了一个,这样都可以用。

    getValues和setValues中我定义了3中操作:只修改X值;只修改Y值;修改X和Y值。

    publicstaticfinalint POSITION_X =1;
    publicstaticfinalint POSITION_Y =2;
    publicstaticfinalint POSITION_XY =3;

     

    这里注意一下getValues的返回值,你修改或者操作了几个值就返回几。

    代码如下:

    package com.cnblogs.htynkn;
    
    import aurelienribon.tweenengine.TweenAccessor;
    
    import com.badlogic.gdx.scenes.scene2d.Actor;
    
    publicclass ActorAccessor implements TweenAccessor {
    
    publicstaticfinalint POSITION_X =1;
    publicstaticfinalint POSITION_Y =2;
    publicstaticfinalint POSITION_XY =3;
    
    @Override
    publicintgetValues(Actor target,int tweenType,float[] returnValues) {
    switch (tweenType) {
    case POSITION_X:
    returnValues[0] = target.x;
    return1;
    case POSITION_Y:
    returnValues[0] = target.y;
    return1;
    case POSITION_XY:
    returnValues[0] = target.x;
    returnValues[1] = target.y;
    return2;
    default:
    assertfalse;
    return -1;
    }
    }
    
    @Override
    publicvoidsetValues(Actor target,int tweenType,float[] newValues) {
    switch (tweenType) {
    case POSITION_X:
    target.x = newValues[0];
    break;
    case POSITION_Y:
    target.y = newValues[0];
    break;
    case POSITION_XY:
    target.x = newValues[0];
    target.y = newValues[1];
    break;
    default:
    assertfalse;
    break;
    }
    }
    }

    然后来写具体的动画和绘制部分。为了方便演示我编写一个随着点击移动的小图标的例子。

    我的图标是news。声明image和stage的绘制和原来一样。

    先声明一个动画管理器

    private TweenManager tweenManager =new TweenManager();

    然后将我们的Image注册一下

    Tween.registerAccessor(Image.class,new ActorAccessor());

    同时实现InputProcessor接口以接收触碰事件。

    在touchDown方法中添加

    @Override
    publicbooleantouchDown(int x,int y,int pointer,int button) {
    Vector3 vector3 =new Vector3(x, y,0);
    stage.getCamera().unproject(vector3);
    
    Tween.to(image, ActorAccessor.POSITION_XY,1.0f).ease(Bounce.OUT)
    .target(vector3.x, vector3.y).start(tweenManager);
    returnfalse;
    }

    说明一下,因为Stage的坐标和默认的Input的坐标不一致,所以通过unproject转化一下。

    Tween.to(image, ActorAccessor.POSITION_XY, 1.0f)代表操作image对象移动。target(vector3.x, vector3.y)代表移动的目标。

    ease(Bounce.OUT)声明了缓冲效果,具体的效果可以参考http://robertpenner.com/easing/easing_demo.html

    start(tweenManager)启动管理器。

    在render方法中添加

    tweenManager.update(Gdx.graphics.getDeltaTime());

    让管理器的时间更新。

    完整代码:

    package com.cnblogs.htynkn;
    
    import aurelienribon.tweenengine.Tween;
    import aurelienribon.tweenengine.TweenManager;
    import aurelienribon.tweenengine.equations.Bounce;
    
    import com.badlogic.gdx.ApplicationListener;
    import com.badlogic.gdx.Gdx;
    import com.badlogic.gdx.InputMultiplexer;
    import com.badlogic.gdx.InputProcessor;
    import com.badlogic.gdx.graphics.GL10;
    import com.badlogic.gdx.graphics.g2d.TextureAtlas;
    import com.badlogic.gdx.math.Vector3;
    import com.badlogic.gdx.scenes.scene2d.Stage;
    import com.badlogic.gdx.scenes.scene2d.ui.Image;
    
    publicclass App implements ApplicationListener, InputProcessor {
    Stage stage;
    private TweenManager tweenManager =new TweenManager();
    Image image;
    
    @Override
    publicvoidcreate() {
    stage =new Stage(Gdx.graphics.getWidth(), Gdx.graphics.getHeight(),
    true);
    TextureAtlas atlas =new TextureAtlas("packer/test.pack");
    image =new Image(atlas.findRegion("news"));
    image.x =20;
    image.y =20;
    stage.addActor(image);
    Tween.registerAccessor(Image.class,new ActorAccessor());
    
    InputMultiplexer multiplexer =new InputMultiplexer();
    multiplexer.addProcessor(this);
    multiplexer.addProcessor(stage);
    Gdx.input.setInputProcessor(multiplexer);
    }
    
    @Override
    publicvoiddispose() {
    
    }
    
    @Override
    publicvoidrender() {
    tweenManager.update(Gdx.graphics.getDeltaTime());
    
    Gdx.gl.glClearColor(1,1,1,1);
    Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
    stage.act(Gdx.graphics.getDeltaTime());
    stage.draw();
    }
    
    @Override
    publicvoidresize(int width,int height) {
    }
    
    @Override
    publicvoidpause() {
    }
    
    @Override
    publicvoidresume() {
    }
    
    @Override
    publicbooleankeyDown(int keycode) {
    // TODO Auto-generated method stub
    returnfalse;
    }
    
    @Override
    publicbooleankeyUp(int keycode) {
    // TODO Auto-generated method stub
    returnfalse;
    }
    
    @Override
    publicbooleankeyTyped(char character) {
    // TODO Auto-generated method stub
    returnfalse;
    }
    
    @Override
    publicbooleantouchDown(int x,int y,int pointer,int button) {
    Vector3 vector3 =new Vector3(x, y,0);
    stage.getCamera().unproject(vector3);
    
    Tween.to(image, ActorAccessor.POSITION_XY,1.0f).ease(Bounce.OUT)
    .target(vector3.x, vector3.y).start(tweenManager);
    returnfalse;
    }
    
    @Override
    publicbooleantouchUp(int x,int y,int pointer,int button) {
    // TODO Auto-generated method stub
    returnfalse;
    }
    
    @Override
    publicbooleantouchDragged(int x,int y,int pointer) {
    // TODO Auto-generated method stub
    returnfalse;
    }
    
    @Override
    publicbooleantouchMoved(int x,int y) {
    // TODO Auto-generated method stub
    returnfalse;
    }
    
    @Override
    publicbooleanscrolled(int amount) {
    // TODO Auto-generated method stub
    returnfalse;
    }
    }

    因为是动画效果,这里就不贴出了,文章末尾会有一个小视频的。

    使用TimeLine实现更多动画效果

    上面只是一个简单的移动效果,但就动画而言这个显然是不够的。如果希望实现一个渐渐显示的效果怎么办?

    还是想想TweenAccessor接口,只要float类型的值就行了。所以同样的我们可以实现修改透明程度、大小等等实现更多的效果。

    我最终选用了六种效果:

    publicstaticfinalint POS_XY =1;
    publicstaticfinalint CPOS_XY =2;
    publicstaticfinalint SCALE_XY =3;
    publicstaticfinalint ROTATION =4;
    publicstaticfinalint OPACITY =5;
    publicstaticfinalint COLOR =6;

    实现修改X和Y值,修改X和Y值(包括对象自身大小),修改缩放,修改旋转,修改透明,修改颜色。

    代码如下:

    package com.cnblogs.htynkn;
    
    import aurelienribon.tweenengine.TweenAccessor;
    
    import com.badlogic.gdx.graphics.Color;
    import com.badlogic.gdx.scenes.scene2d.Actor;
    
    publicclass ActorAccessor implements TweenAccessor {
    publicstaticfinalint POS_XY =1;
    publicstaticfinalint CPOS_XY =2;
    publicstaticfinalint SCALE_XY =3;
    publicstaticfinalint ROTATION =4;
    publicstaticfinalint OPACITY =5;
    publicstaticfinalint TINT =6;
    
    @Override
    publicintgetValues(Actor target,int tweenType,float[] returnValues) {
    switch (tweenType) {
    case POS_XY:
    returnValues[0] = target.x;
    returnValues[1] = target.y;
    return2;
    
    case CPOS_XY:
    returnValues[0] = target.x + target.width /2;
    returnValues[1] = target.y + target.height /2;
    return2;
    
    case SCALE_XY:
    returnValues[0] = target.scaleX;
    returnValues[1] = target.scaleY;
    return2;
    
    case ROTATION:
    returnValues[0] = target.rotation;
    return1;
    case OPACITY:
    returnValues[0] = target.color.a;
    return1;
    
    case TINT:
    returnValues[0] = target.color.r;
    returnValues[1] = target.color.g;
    returnValues[2] = target.color.b;
    return3;
    
    default:
    assertfalse;
    return -1;
    }
    }
    
    @Override
    publicvoidsetValues(Actor target,int tweenType,float[] newValues) {
    switch (tweenType) {
    case POS_XY:
    target.x = newValues[0];
    target.y = newValues[1];
    break;
    case CPOS_XY:
    target.x = newValues[0] - target.width /2;
    target.y = newValues[1] - target.height /2;
    break;
    case SCALE_XY:
    target.scaleX = newValues[0];
    target.scaleY = newValues[1];
    break;
    case ROTATION:
    target.rotation = newValues[0];
    break;
    
    case OPACITY:
    Color c = target.color;
    c.set(c.r, c.g, c.b, newValues[0]);
    target.color = c;
    break;
    
    case TINT:
    c = target.color;
    c.set(newValues[0], newValues[1], newValues[2], c.a);
    target.color = c;
    break;
    
    default:
    assertfalse;
    }
    }
    }

    因为Actor中的color是final,所以不能修改,自己改一下源代码吧。

    TimeLine是Universal Tween Engine中的一大利器,可以实现平行和顺序动画。

    比如

    Timeline.createSequence()
    .beginSequence()
    .push(Tween.to(image, ActorAccessor.POS_XY,1.0f).target(100,
    100))
    .push(Tween.to(image, ActorAccessor.POS_XY,1.0f).target(200,
    20)).start(tweenManager);

    就表示先移动到100,100处在移动到200,20处。

    再比如

    Timeline.createParallel()
    .beginParallel()
    .push(Tween.to(image, ActorAccessor.CPOS_XY,1.0f).target(
    vector3.x, vector3.y))
    .push(Tween.to(image, ActorAccessor.ROTATION,1.0f).target(360))
    .push(Tween.to(image, ActorAccessor.SCALE_XY,1.0f).target(
    1.5f,1.5f)).end().start(tweenManager);

    实现的就是一般移动一般旋转和放大的效果。

    效果:

  • android游戏开发框架libgdx的使用(二十二)—利用TABLELAYOUT进行布局

    android游戏开发框架libgdx的使用(二十二)—利用TABLELAYOUT进行布局

    前面有一篇文章介绍了用TWL进行布局,最近做其他java的东西接触到了table-layout,用着很不错,仔细看来一下文档,原来还支持libgdx。

    简单试用了一下,比TWL好使多了。

    TABLELAYOUT简介

    tablelayout-logo

    TABLELAYOUT是一个轻量级的UI组件布局库。使用表格实现,有点像HTML的TABLE。

    它支持libgdx, Swing, Android和TWL。支持Java API和配置文件两种方式。

    同时有个配套的编辑器http://table-layout.googlecode.com/svn/wiki/jws/editor.jnlp

    使用配置文件

    其实最好的方法应该是使用配置文件,这样便于修改,还可以利用工具进行可视化编辑。但是libgdx对于配置文件的支持好像没有很到位(或者是版本修改等等问题)。

    所以先介绍配置文件方式。配置文件的具体书写格式请参考table-layout主页。

    打开编辑器,绘制一个简单的游戏界面,包含一个开始按钮和设置按钮。

    libgdx-layout-1

    配置文件如下:

    debug
    *spacing:12 padding:5 align:center
    
    ---
    [StartButton] width:200 height:60
    ---
    [PrefButton] width:200 height:60

    那个debug是显示边框的,方便调试。空间以对应的类型为结尾,比如StartButton就是按钮。

    第二行定义的是全局样式。还有很多其他样式可以定义,我个人觉得很Css盒子模型很接近。

    将文件保存为main.layout。

    因为TableLayout是在Stage中使用的,所以很其他的Actor的用法雷同。先添加一个table,在从table中获取TableLayout。然后定义控件,并和配置文件中的名字一一对应注册进去,最后加载配置文件。

    先新建一个table并添加到stage中

    Table table =new Table(skin);
    stage.addActor(table);

    然后获取TableLayout

    TableLayout layout = table.getTableLayout();

    创建控件以后进行注册

    layout.register("StartButton", StartButton);

    其中第一个是配置文件中的名称,第二个是你代码中的名称。

    最后加载配置文件

    layout.parse(Gdx.files.internal("layout/main.layout").readString());

     

    看一下完整的代码

    package com.cnblogs.htynkn;
    
    import com.badlogic.gdx.ApplicationListener;
    import com.badlogic.gdx.Gdx;
    import com.badlogic.gdx.graphics.GL10;
    import com.badlogic.gdx.graphics.g2d.BitmapFont;
    import com.badlogic.gdx.graphics.g2d.stbtt.TrueTypeFontFactory;
    import com.badlogic.gdx.scenes.scene2d.Stage;
    import com.badlogic.gdx.scenes.scene2d.ui.Skin;
    import com.badlogic.gdx.scenes.scene2d.ui.TextButton;
    import com.badlogic.gdx.scenes.scene2d.ui.tablelayout.Table;
    import com.badlogic.gdx.scenes.scene2d.ui.tablelayout.TableLayout;
    
    publicclass App implements ApplicationListener {
    Stage stage;
    @Override
    publicvoidcreate() {
    stage =new Stage(Gdx.graphics.getWidth(), Gdx.graphics.getHeight(),
    true);
    
    Skin skin =new Skin(Gdx.files.internal("skin/uiskin.json"));
    Table table =new Table(skin);
    table.width = Gdx.graphics.getWidth();
    table.height = Gdx.graphics.getHeight();
    
    stage.addActor(table);
    TableLayout layout = table.getTableLayout();
    TextButton StartButton =new TextButton("Start game", skin);
    layout.register("StartButton", StartButton);
    
    TextButton PrefButton =new TextButton("Settings", skin);
    layout.register("PrefButton", PrefButton);
    
    layout.parse(Gdx.files.internal("layout/main.layout").readString());
    }
    
    @Override
    publicvoiddispose() {
    stage.dispose();
    }
    
    @Override
    publicvoidrender() {
    Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
    stage.act(Gdx.graphics.getDeltaTime());
    stage.draw();
    }
    
    @Override
    publicvoidresize(int width,int height) {
    }
    
    @Override
    publicvoidpause() {
    }
    
    @Override
    publicvoidresume() {
    }
    }

    运行效果如下:

    libgdx-layout-2

    使用API配置

    使用代码控制其实和配置文件差不多,我比较推荐先用编辑器生成配置文件,然后在根据配置文件写代码。

    通过table.defaults()设置全局样式,就是配置中的第二行。参照刚才的配置文件,我们的代码应该是:

    table.defaults().space(12).align("center").pad(5);

    通过table.add()添加

    table.add(StartButton).width(200).height(60);

    配置文件中的—-代码新起一行,代码中用table.row()实现。

    完整代码如下:

    package com.cnblogs.htynkn;
    
    import com.badlogic.gdx.ApplicationListener;
    import com.badlogic.gdx.Gdx;
    import com.badlogic.gdx.graphics.GL10;
    import com.badlogic.gdx.graphics.g2d.BitmapFont;
    import com.badlogic.gdx.graphics.g2d.stbtt.TrueTypeFontFactory;
    import com.badlogic.gdx.scenes.scene2d.Stage;
    import com.badlogic.gdx.scenes.scene2d.ui.Skin;
    import com.badlogic.gdx.scenes.scene2d.ui.TextButton;
    import com.badlogic.gdx.scenes.scene2d.ui.tablelayout.Table;
    
    publicclass App implements ApplicationListener {
    Stage stage;
    
    @Override
    publicvoidcreate() {
    stage =new Stage(Gdx.graphics.getWidth(), Gdx.graphics.getHeight(),
    true);
    
    Skin skin =new Skin(Gdx.files.internal("skin/uiskin.json"));
    Table table =new Table(skin);
    table.width = Gdx.graphics.getWidth();
    table.height = Gdx.graphics.getHeight();
    table.defaults().space(12).align("center").pad(5);
    TextButton StartButton =new TextButton("Start game", skin);
    table.add(StartButton).width(200).height(60);
    table.row();
    TextButton PrefButton =new TextButton("Settings", skin);
    table.add(PrefButton).width(200).height(60);
    
    stage.addActor(table);
    }
    
    @Override
    publicvoiddispose() {
    stage.dispose();
    }
    
    @Override
    publicvoidrender() {
    Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
    stage.act(Gdx.graphics.getDeltaTime());
    stage.draw();
    }
    
    @Override
    publicvoidresize(int width,int height) {
    }
    
    @Override
    publicvoidpause() {
    }
    
    @Override
    publicvoidresume() {
    }
    }

    效果是一样的:

    libgdx-layout-3

    再谈Skin中的中文支持

    参考前文使用TTF字库实现了中文支持,但是Skin默认支持的是Hiero。

    要使用TTF字库也可以,不过就不能使用配置文件模式了。

    我一般比较偷懒,先读入配置文件,在新建一个Skin,将其中的Font改成TTF字库的Font就可以了。

    Skin skin1 =new Skin(Gdx.files.internal("skin/uiskin.json"));
    Skin skin =new Skin();
    skin.addResource("default-font", font);
    TextButtonStyle buttonStyle = skin1.getStyle("default",
    TextButtonStyle.class);
    buttonStyle.font = font;
    skin.addStyle("default", buttonStyle);

    效果:

    libgdx-layout-4

    说实话这个效果真心没有英文看着好,所以推荐还是制作Hiero来用吧。

    写在最后

    使用TABLELAYOUT好处相当明显,它使用逻辑性的表格而不是像素定位,极大的方便了开发。同时也增加了自适应性。而且一次学习可以用到多个地方,比如Android和Swing。

    Libgdx是一个相当活跃的项目,很多代码和包都发生了变化。本博客的代码一定是运行成功以后截图分享给大家的,但是不能保证随着时间推移它依然有效。比如Skin部分就发生了非常大的变化。

    遇到找不到包或者没有对应方法的时候请先看看官方说明,重要的变动会在声明中提到的,比如0.96就变动了本文介绍的TABLELAYOUT和SCENCE2D。或者在社区搜索一下,一般都有人提问。

    本文使用的Libgdx是0.96发行版…不要使用nightly,变动太多了。

    参考:http://www.badlogicgames.com/wordpress/?p=2483

  • android游戏开发框架libgdx的使用(二十一)—使用TTF字库支持中文

    android游戏开发框架libgdx的使用(二十一)—使用TTF字库支持中文

    好久没有更新这个系列的文章了。今天下午在群上讨论libgdx对中文的支持问题。本来Hiero做是最好的,但是LIbgdx的BitmapFont不支持多图,常用汉字3500个,是在做不到一张图上。

    libgdx很早之前就有ttf的支持了,不过那个时候有点问题。经测试新版本(0.96)完全支持,特地总结出来和大家分享。

    TTF字库

    TTF(TrueTypeFont)是Apple公司和Microsoft公司共同推出的字体文件格式,随着windows的流行,已经变成最常用的一种字体文件表示方式,应用范围非常广。

    如果是Windows操作系统,可以从Fonts文件夹中找到很多字库。也可以从网上下载。

    我推荐YaHei.Consolas字库,看着很爽,反正我的eclipse就是用的这个字库。

    gdx-setup-ui的使用

    从libgdx下载0.96版本,可以看到文件结构如下:

    libgdx-chinese-1

    强烈运行gdx-setup-ui.jar,这一个项目初始化工具。

    运行界面如下:

    libgdx-chinese-2

    项目名字,包名什么的按情况填写。在中间的Required配置中可以直接下载最新的稳定版本和Nightly版本。不过推荐选用本地的附件。

    libgdx-chinese-3

    不用解压,直接选择zip文件,它会自动抽取需要的文件。

    我使用的Eclipse,直接Import即可。

    libgdx-chinese-4

    我只生成了桌面项目,一般可以把Android和Web的一起生成了。

    注意一下,默认的Android项目对应的SDK是15,可以根据需要更改。有时候导入的项目会有红色感叹号,添加一个assets文件夹即可。

    libgdx-chinese-5

    其中back-libgdx项目是我们的核心,一切逻辑什么的都在这里实现。back-libgdx-XXX是对应的启动而已。

    libgdx使用TTF字库

    准备一个TTF字库改名为font.ttf复制到assets文件夹。从压缩包中找到gdx-stb-truetype-natives.jar和gdx-stb-truetype.jar。其中gdx-stb-truetype-natives.jar添加到桌面项目,gdx-stb-truetype.jar添加到核心项目中去。如图:

    libgdx-chinese-6

    现在来看看最关键的方法TrueTypeFontFactory.createBitmapFont。

    我们通过它创建一个支持ttf字库的BitmapFont出来。它需要一个FileHandle和要支持的文字。当然还有一些参数,具体意义可以参考API文档。

    也就是说我们需要使用TTF字库的哪些文字必须声明出来。不过问题也不到,3500常用汉字基本可以满足要求。网上还有一个常用500字,不过那个完全不够用。

    我这里随便写几个作为演示。

    publicstaticfinal String FONT_CHARACTERS ="世界人民爱好的美好中文支持再看看";

    然后使用初始化

    font = TrueTypeFontFactory.createBitmapFont(
    Gdx.files.internal("font.ttf"), FONT_CHARACTERS,12.5f,7.5f,
    1.0f, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());

    剩下的工作和使用Hiero一样的了。

    publicclass App implements ApplicationListener {
    BitmapFont font;
    SpriteBatch batch;
    publicstaticfinal String FONT_CHARACTERS ="世界人民爱好的美好中文支持再看看";
    
    @Override
    publicvoidcreate() {
    font = TrueTypeFontFactory.createBitmapFont(
    Gdx.files.internal("font.ttf"), FONT_CHARACTERS,12.5f,7.5f,
    1.0f, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
    font.setColor(1f,0f,0f,1f);
    batch =new SpriteBatch();
    }
    
    @Override
    publicvoiddispose() {
    font.dispose();
    }
    
    @Override
    publicvoidrender() {
    Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
    batch.begin();
    font.draw(batch,"中文支持",10,10);
    batch.end();
    }
    
    @Override
    publicvoidresize(int width,int height) {
    }
    
    @Override
    publicvoidpause() {
    }
    
    @Override
    publicvoidresume() {
    }
    }

    效果如下:

    libgdx-chinese-7

    在Stage中使用TTF字库原理是一样,需要修改的只有BitmapFont的实例化方法。

    写在最后

    使用TTF字库解决libgdx显示中文无疑是一种很好的选择,基本不存在显示不出文字的情况。但是TTF字库的大小一般都比较大,比如我使用的字库是13.8M..小一点的也有3M。

    对于Android应用这个大小有时候还是难以接受的。

    android系统自带的字库位于/system/fonts/中,可以使用绝对路径访问…不过不推荐就是了。

    使用Herio是一个高效(程序效率,不是开发效率)的方法,但是libgdx不支持Herio的多图。

    有文档提到了使用SpriteSheetPacker,但是我一直没有找到。google code的代码自从迁移到Github之后就没有了,不知道是移除了还是怎么的了。

  • android游戏开发框架libgdx的使用(二十)—资源预加载与AssetManager的使用

    本文使用的libgdx是0.92版本,和现在的最新版可能有一些不一样的地方。全文内容仅供参考。

    好久没有写libgdx相关的东西了,主要是我不知道改写些什么了。

    最近收到了很多邮件,大多是关于资源加载的,特别是异步资源加载,还有什么Loading窗口的实现什么的,这篇博文就做个大致的回答吧。

    什么时候需要资源预加载

    资源预加载的目的很明确,提升用户体验。当然就开发者而言,比较好的预加载实现方式还可以方便管理。

    如果游戏很简单简单,资源很少很少,那么弄资源预加载就是费事,但是以下几种情况的话还是推荐使用:

    1.资源数量大

    音乐、图片、视频什么的,如果你的游戏这些东西太多,推荐使用

    2.部分资源反复使用

    有些资源会反复使用到,比如一些背景音乐,一些小图标

    Libgdx中的AssetManager

    AssetManager是libgdx提供的资源加载方案,demo中有个例子,详细的可以细致参考。

     

    AssetManager使用很简单,先实例化一个。

    AssetManager assetManager =new AssetManager();

    加载资源时需要制定资源位置和资源类型,目前支持Pixmaps、Textures、BitmapFonts、TextureAtlases、TiledAtlases、TileMapRenderers、Music、Sound这些类型的资源。

    使用方法:

    assetManager.load("data/img/bg.png", Texture.class);assetManager.load("data/font/chinese.fnt", BitmapFont.class);assetManager.load("data/music/op_head.ogg", Music.class);assetManager.load("data/pack/task1.pack", TextureAtlases.class);

    加载的时候还支持自定义参数,比如加载Texture时

    TextureParametertextureParameter = new TextureParameter();textureParameter.minFilter = TextureFilter.Linear;textureParameter.genMipMaps =true;manager.load("data/img/bg.png", Texture.class, textureParameter);

    大多数情况还是不用考虑自定义参数的。

     

    执行了Load方法其实只是将预加载的资源载入了队列,实质上并没有加载什么,使用Update方法才会加载资源。

    该方法有个返回值,当其为True时即加载完成,为False就表示还在加载。

    如果需要一个比较准确的返回,比如当前加载到百分之多少了以方便绘制进度条,可以使用getProgress方法,该方法返回一个0到1之间的数字。

    获取资源时候使用assetManager.get(位置,类型)即可,比如

    assetManager.get("data/img/bg.png", Texture.class);assetManager.get("data/font/chinese.fnt", BitmapFont.class);

    如果有需要也可以先检查一下

    assetManager.isLoaded("data/img/bg.png")

    使用时的一些注意事项

    如果要取消预加载的话,调用finishLoading方法。

    如果要销毁使用完的资源的话,调用unload方法。

    还有一个比较重大的问题就是游戏被打断以后,部分资源需要重新加载,不然的话会出现无数报错⊙﹏⊙b汗