分类: libGDX

  • android游戏开发框架libgdx的使用(十)—双舞台

    android游戏开发框架libgdx的使用(十)—双舞台

    游戏屏幕最常见的就是一个变化较少的背景加上一系列和用户交互的角色和部件。为了方便管理你还可以为背景建个Group方便管理。

    但是有时候写的时候没有想到这个问题,或者是背景不是单纯的一个图片什么的,背景和角色还有一些混合逻辑分布在两个Stage里。

    我重写太麻烦,想想反正都是SpritBatch绘制出来的,用双舞台大不了多个摄像头。马上试试还真行。

    先看看Stage的draw方法:

    /** Renders the stage */
    publicvoiddraw () {
    camera.update();
    if (!root.visible)return;
    batch.setProjectionMatrix(camera.combined);
    batch.begin();
    root.draw(batch,1);
    batch.end();
    }

    batch的话两个舞台可以共用。用Stage(width, height, stretch, batch)实例化第二个舞台。

    代码如下:

    package com.cnblogs.htynkn.game;
    
    import com.badlogic.gdx.ApplicationListener;
    import com.badlogic.gdx.Gdx;
    import com.badlogic.gdx.InputProcessor;
    import com.badlogic.gdx.graphics.GL10;
    import com.badlogic.gdx.graphics.Texture;
    import com.badlogic.gdx.graphics.g2d.TextureRegion;
    import com.badlogic.gdx.scenes.scene2d.Stage;
    import com.badlogic.gdx.scenes.scene2d.ui.Image;
    
    publicclass JavaGame implements ApplicationListener {
    
    Stage stage1;
    Stage stage2;
    float width;
    float height;
    
    @Override
    publicvoidcreate() {
    width = Gdx.graphics.getWidth();
    height = Gdx.graphics.getHeight();
    stage1 =new Stage(width, height,true);
    stage2 =new Stage(width, height,true,stage1.getSpriteBatch());
    Image image =new Image(new TextureRegion(new Texture(Gdx.files
    .internal("img/sky.jpg")),50,50,480,320));
    stage1.addActor(image);
    Image image2 =new Image(new TextureRegion(new Texture(Gdx.files
    .internal("img/baihu.png")),217,157));
    image2.x=(width-image2.width)/2;
    image2.y=(height-image2.height)/2;
    stage2.addActor(image2);
    }
    
    @Override
    publicvoiddispose() {
    // TODO Auto-generated method stub
    
    }
    
    @Override
    publicvoidpause() {
    // TODO Auto-generated method stub
    
    }
    
    @Override
    publicvoidrender() {
    Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
    stage1.act(Gdx.graphics.getDeltaTime());
    stage2.act(Gdx.graphics.getDeltaTime());
    stage1.draw();
    stage2.draw();
    }
    
    @Override
    publicvoidresize(int width,int height) {
    // TODO Auto-generated method stub
    
    }
    
    @Override
    publicvoidresume() {
    // TODO Auto-generated method stub
    
    }
    }

    效果:

    image1

    如果你对于效率追求比较极致,可以考虑对于SpritBatch的缓冲数进行修改。

    还有一个需要注意,背景舞台应该先绘制,其他部件后绘制,不然效果就是下图:

    image2

    关于舞台的输入控制,不能简单的使用:

    Gdx.input.setInputProcessor(stage1); Gdx.input.setInputProcessor(stage2);

    应该这样做:

    InputMultiplexer inputMultiplexer=new InputMultiplexer(); inputMultiplexer.addProcessor(stage1); inputMultiplexer.addProcessor(stage2);
  • android游戏开发框架libgdx的使用(九)—在libgdx中使用Box2d

    android游戏开发框架libgdx的使用(九)—在libgdx中使用Box2d

    在游戏开发中难免会需要模拟现实情况,一般常见的就是模拟物理世界。

    比如物体碰撞,车辆前进,物体下落等。也许有人会觉得原来有实现过一些效果没有看到用什么物理知识呢。

    在我看来引入物理引擎并不是必须的,但是这样可以让游戏更逼真。物理引擎的话首选Box2d,但是它是C++写的,直接用不方便。

    在Android游戏开发中使用物理引擎一般有三个比较好的选择:

    1.JBox2d

    Box2d的Java移植版本,速度有点慢。不过对于不熟悉C++的朋友而言这个可以拿来看看原理。

    2.Havok

    这个就不多说了,我怎么没有用过。

    3.Libgdx

    libgdx用了jni封装了box2d,速度和易用性都不错。

    下面我们来看看一个示例,原来没有接触过box2d的朋友最好先找资料看看。

    代码如下:

    package com.cnblogs.htynkn.listener;
    
    import com.badlogic.gdx.ApplicationListener;
    import com.badlogic.gdx.Gdx;
    import com.badlogic.gdx.graphics.GL10;
    import com.badlogic.gdx.graphics.OrthographicCamera;
    import com.badlogic.gdx.math.Vector2;
    import com.badlogic.gdx.physics.box2d.Body;
    import com.badlogic.gdx.physics.box2d.BodyDef;
    import com.badlogic.gdx.physics.box2d.Box2DDebugRenderer;
    import com.badlogic.gdx.physics.box2d.CircleShape;
    import com.badlogic.gdx.physics.box2d.World;
    import com.badlogic.gdx.physics.box2d.BodyDef.BodyType;
    
    publicclass DemoGame implements ApplicationListener {
    
    protected OrthographicCamera camera;
    protected Box2DDebugRenderer renderer;// 测试用绘制器
    private World world;
    
    @Override
    publicvoidcreate() {
    camera =new OrthographicCamera(48,32);
    camera.position.set(0,15,0);
    renderer =new Box2DDebugRenderer();
    
    world =new World(new Vector2(0, -9.8f),true);// 一般标准重力场
    BodyDef bd =new BodyDef();//声明物体定义
    bd.position.set(2f,2f);
    bd.type=BodyType.DynamicBody;
    Body b = world.createBody(bd);//通过world创建一个物体
    CircleShape c =new CircleShape();//创建一个形状(圆)
    c.setRadius(1f);//设置半径
    b.createFixture(c,1f);//将形状和密度赋给物体
    }
    
    @Override
    publicvoiddispose() {
    
    renderer.dispose();
    world.dispose();
    
    renderer =null;
    world =null;
    }
    
    @Override
    publicvoidpause() {
    // TODO Auto-generated method stub
    
    }
    
    @Override
    publicvoidrender() {
    world.step(Gdx.app.getGraphics().getDeltaTime(),3,3);
    GL10 gl = Gdx.app.getGraphics().getGL10();
    gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
    camera.update();
    camera.apply(gl);
    renderer.render(world, camera.combined);
    }
    
    @Override
    publicvoidresize(int width,int height) {
    // TODO Auto-generated method stub
    
    }
    
    @Override
    publicvoidresume() {
    // TODO Auto-generated method stub
    
    }
    
    }

    我们先创建了一个世界,重力为9.8,方向朝下。

    然后声明了一个物体定义,物体为动态物体(就是可以动那种),如果创建一个圆形,将形状赋给物体。

    然后调用进行模拟

    world.step(Gdx.app.getGraphics().getDeltaTime(),3,3);

    效果如下:

    box2d

    其实用法什么没有大的区别。libgdx并没有AABB那个类,要这只边界的话用

    world.QueryAABB(callback, lowerX, lowerY, upperX, upperY)

    就行了。

    形状的话其实有很多,圆形,方形,矩形,多边形,可以具体参考Box2d的帮助文件。

    有人觉得使用box2d很复杂,很难和现有的东西联系起来。其实我觉得box2d用着很方便,特别是和Stage联系起来很方便。

    Body.setUserData(userData)

    将Actor的名字赋给Body,然后在绘制时用

    Stage.findActor(name)

    改变actor的位置和状体就行了。

    写在最后:

    这里想说几个问题:

    1.Box2d是一个物理引擎,不是一个游戏引擎。它模拟物理世界和法则,但是你的图像绘制什么的都是自己处理。

    2.Box2d的单位是米,秒。注意单位换算,不推荐直接用1像素=1米,这样有些东西会很奇怪的,比例看情况,用个30,,50就行了。

    3.Stage的坐标是左下角,Box2d一般是重心,注意变化。

    4.调试的时候可以用Box2DDebugRenderer,可以清楚看到边界和重心。

  • android游戏开发框架libgdx的使用(八)–相机和观察点

    android游戏开发框架libgdx的使用(八)–相机和观察点

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

    相机也可以成为观察者,最简单的例子就是魂斗罗。

    魂斗罗

    玩家操作角色前进,后退,跳跃等,显示的世界(就是后面的背景)会不停变化。其实就是相机的观察点(视角)的改变引起的。

    也许游戏的世界(背景地图)很大,比如10240480,而设备的分辨率是800480。现在游戏的角色在地图的最左侧,那么它的观察点就是(400,240)。

    随着角色的向前移动,观察点也变化,通常是y值变大。

    角色后退时x值变小。

    角色跳跃时,y值变化。(其实地图可能要稍微高一点,比如500,这样角色的跳跃才会有效果)。

    可以说相机是游戏开发的一个基本组件。而Libgdx的Stage类中就默认包含了一个Camera。

    Camera类按照功能而言也有很多种,最常用的是OrthographicCamera(正投影相机),Stage中默认Camera的实现类为该类。

    OrthographicCamera实现以下功能:

    1.移动和旋转镜头

    2.放大和缩小

    3.改变观察点(视角)

    4.窗体和世界的点的转化

    相机的最大好处就是无需手动操作矩阵就可以移动游戏世界,所有的矩阵投影和观察点运算就是对开发者隐藏的。

    相机的使用一般配合着mesh。mesh绘制一个矩形区域,然后将地图贴图在其上。

    接下来的例子将使用一张图:

    xk

    大小2048*2048.

    代码如下:

    package com.cnblogs.htynkn.listener;
    
    import com.badlogic.gdx.ApplicationListener;
    import com.badlogic.gdx.Gdx;
    import com.badlogic.gdx.graphics.GL10;
    import com.badlogic.gdx.graphics.Mesh;
    import com.badlogic.gdx.graphics.OrthographicCamera;
    import com.badlogic.gdx.graphics.Texture;
    import com.badlogic.gdx.graphics.VertexAttribute;
    import com.badlogic.gdx.graphics.VertexAttributes;
    import com.badlogic.gdx.graphics.VertexAttributes.Usage;
    import com.badlogic.gdx.math.Rectangle;
    
    publicclass FirstGame implements ApplicationListener {
    
    private OrthographicCamera cam;
    private Texture texture;
    private Mesh mesh;
    private Rectangle glViewport;
    
    @Override
    publicvoidcreate() {
    //创建一个静态的,由两个三角形构成的4个顶点的矩形
    //矩形分为3*2块。网格有3个位置参数(x,y,z)
    //网格的位置属性有两个值
    mesh =new Mesh(true,4,6,new VertexAttribute(
    VertexAttributes.Usage.Position,3,"attr_Position"),
    new VertexAttribute(Usage.TextureCoordinates,2,
    "attr_texCoords"));
    texture =new Texture(Gdx.files.internal("img/xk.jpg"));
    //设置对应的边点
    //以第一个为例-2048f,-2048f,0是位置参数,因为是二维的,所以Z值为0
    //0,1对应顶点坐标
    mesh.setVertices(newfloat[] { -2048f, -2048f,0,0,1,2048f, -2048f,
    0,1,1,2048f,2048f,0,1,0, -2048f,2048f,0,0,0 });
    //设置索引,这个有点纠结,看文章详解
    mesh.setIndices(newshort[] {0,1,2,2,3,0 });
    
    float WIDTH = Gdx.graphics.getWidth();
    float HEIGHT = Gdx.graphics.getHeight();
    
    cam =new OrthographicCamera(WIDTH, HEIGHT);
    cam.position.set(WIDTH /2, HEIGHT /2,0);
    
    glViewport =new Rectangle(0,0, WIDTH, HEIGHT);
    }
    
    @Override
    publicvoiddispose() {
    
    }
    
    @Override
    publicvoidpause() {
    // TODO Auto-generated method stub
    
    }
    
    @Override
    publicvoidrender() {
    
    GL10 gl = Gdx.graphics.getGL10();
    // 相机移动 ...这里我傻逼了...各位看官忽略吧
    gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
    gl.glViewport((int) glViewport.x, (int) glViewport.y,
    (int) glViewport.width, (int) glViewport.height);
    
    cam.update();
    cam.apply(gl);
    // 贴图
    gl.glActiveTexture(GL10.GL_TEXTURE0);
    gl.glEnable(GL10.GL_TEXTURE_2D);
    texture.bind();
    
    mesh.render(GL10.GL_TRIANGLES);
    }
    
    @Override
    publicvoidresize(int width,int height) {
    // TODO Auto-generated method stub
    
    }
    
    @Override
    publicvoidresume() {
    // TODO Auto-generated method stub
    
    }
    
    }

    注意这一句:

    mesh.setIndices(newshort[] {0,1,2,2,3,0 });

    这是设置索引,我用一张图来说明。

    x

    0,1,2是指右上角的三角形

    而2,3,0是左下角的三角形

  • android游戏开发框架libgdx的使用(七)–开发框架总观

    android游戏开发框架libgdx的使用(七)–开发框架总观

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

    上几篇文章介绍了libgdx框架的一些基本类的用法,也夹杂着浏览了一下部分源代码,但是如果使用libgdx进实际开发,
    仅仅了解几个类是不够的,还需要对框架有具体和宏观的了解。

    1.应用的生命周期

    游戏应该高效和稳定,特别是对于android平台。目前的开发都是面向手机和平板。如果有效的管理资源,如何高效的运行都是非常重要的。

    试想一个制作优美,可玩性高的游戏运行于你的android手机上却10秒钟崩溃一次,运行时卡的如同在看漫画,这样的游戏是有合格的吗?

    application_lifecycle_diagram

    Android一般不用管resize(),我们在create中实例化所需的对象,在render()中进行绘制。pause()时可以保持当前的灵活数据。dispose()报销对象。

    而在resume()中根据保持的数据对游戏进行还原。

    其实说不负责一点,dispose()你可以不管,android系统会自动回收一些,用户对于退出后的短暂停滞也是有接受能力的。

    create()你也可以水一些,大不了进入游戏或者初始化相关场景时速度慢一些罢了。

    但是pause()和resume()你必须认真处理。电话是最常见的意外性中断,不会有用户偏爱一个电话就可以让记录或者进度消失的游戏的。

    2.游戏构架

    对于是否该称架构我一直有所疑惑,你可以理解为游戏的组成。一个游戏不论大小,不论复杂程度都应该具有的大致框架。

    架构

    这里稍微解释一下。

    输入只是用户的响应,可以使点击(Touch)或者点击(Click),拖拽(Drag)等。

    输出一般是图片和声音或者影像,当时也有文件或者其他数据(比如网络的存储)。

    而在输入中的文件一般为图片和声音,逻辑由数学逻辑和物理逻辑构成。

    而libgdx对于物理上处理是Box2D的封装,可以满足一般需求了。对于数学的封装了一些常见的结构和少量算式,不过可以基于此开发自己需要的算法。

    libgdx对于图像和声音等文件的处理比较好,可以直接使用。

    3.游戏容器

    最原始的容器自然是Application了。

    图表2

    然后是Game,它管理着若干个Screen,比如游戏场景,积分场景,帮助场景,高分榜场景。而场景之下有着若干舞台,舞台之中是演员。

  • android游戏开发框架libgdx的使用(六)–演员和演出

    android游戏开发框架libgdx的使用(六)–演员和演出

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

    上一篇说到UI,然后之前说到舞台,现在我们可以很轻松的构建一出戏了。

    因为actor类在绘制是以x,y值为基准,所以我们可以通过控制x,y值变化演员的位置,但是演员的其他效果需要配合Action类进行操作。

    Action类是一个抽象类,所有的具体实现都在com.badlogic.gdx.scenes.scene2d.actions包中。

    actions

    而包中的类依功能而言可以分为两类:

    1. 控制Action
    2. 表现Action

    控制Action没有直接表现效果,它操作的对象是表现Action。

    比如Delay。

    表现Action就是直接的表现效果,继承自AnimationAction,操作对象是Actor。

    比如MoveTo。

    现在挨着说吧:

    控制类:

    Delay $ (Action action,float duration)

    延迟duration秒执行action。

    Forever $ (Action action)

    一直执行action。

    Parallel $ (Action... actions)

    并行(同时)执行actions。

    Repeat $ (Action action,int times)

    重复action times次。

    Sequence $ (Action... actions)

    按顺序执行actions。

    Remove $ ()

    删除所有Action。

    表现类:

    FadeIn $ (float duration)
    
    FadeOut $ (float duration)

    淡入淡出效果

    FadeTo $ (float alpha,float duration)

    duration秒改变至alpha。

    MoveTo $ (float x,float y,float duration)
    
    MoveBy $ (float x,float y,float duration)

    用duration移动到x,y处。

    RotateBy $ (float rotation,float duration)
    
    RotateTo $ (float rotation,float duration)

    用duration秒旋转rotation度。

    ScaleTo $ (float scaleX, float scaleY, float duration)

    缩放x到scaleX,y到scaleY,用时duration秒。

    一个个写例子太麻烦了,而且实际运用中也多是多个组合运用,下面来看一个综合性的示例:

    我们的主角是star

    通过action的组合实现闪烁,飞动,旋转等效果。

    代码如下:

    package com.cnblogs.htynkn.listener;
    
    import com.badlogic.gdx.ApplicationListener;
    import com.badlogic.gdx.Gdx;
    import com.badlogic.gdx.graphics.GL10;
    import com.badlogic.gdx.graphics.Texture;
    import com.badlogic.gdx.graphics.Texture.TextureFilter;
    import com.badlogic.gdx.math.MathUtils;
    import com.badlogic.gdx.scenes.scene2d.Action;
    import com.badlogic.gdx.scenes.scene2d.Stage;
    import com.badlogic.gdx.scenes.scene2d.actions.FadeIn;
    import com.badlogic.gdx.scenes.scene2d.actions.FadeOut;
    import com.badlogic.gdx.scenes.scene2d.actions.MoveBy;
    import com.badlogic.gdx.scenes.scene2d.actions.MoveTo;
    import com.badlogic.gdx.scenes.scene2d.actions.Parallel;
    import com.badlogic.gdx.scenes.scene2d.actions.Repeat;
    import com.badlogic.gdx.scenes.scene2d.actions.RotateTo;
    import com.badlogic.gdx.scenes.scene2d.actions.Sequence;
    import com.badlogic.gdx.scenes.scene2d.actors.Image;
    
    publicclass FirstGame implements ApplicationListener {
    
    private Stage stage;
    private Texture texture;
    
    @Override
    publicvoidcreate() {
    stage =new Stage(Gdx.graphics.getWidth(), Gdx.graphics.getHeight(),
    true);
    texture =new Texture(Gdx.files.internal("star.png"));
    texture.setFilter(TextureFilter.Linear, TextureFilter.Linear);
    float duration =4f;
    int maxwidth = Gdx.graphics.getWidth() - texture.getWidth();
    int maxheight = Gdx.graphics.getHeight() - texture.getHeight();
    
    for (int i =0; i <20; i++) {
    Image image =new Image("star" + i, texture);
    image.x = MathUtils.random(0, maxwidth);
    image.y = MathUtils.random(0, Gdx.graphics.getHeight());//随机出现
    Action moveAction = Sequence.$(MoveTo.$(MathUtils.random(0,
    maxwidth), MathUtils.random(0, maxheight), duration /2),
    MoveBy.$(MathUtils.random(0, maxwidth), MathUtils.random(0,
    maxheight), duration /2));//移动方向和地点随机
    Action rotateAction = RotateTo.$(360, duration);//旋转
    Action fadeAction = Repeat.$(Sequence.$(FadeOut.$(duration /20),
    FadeIn.$(duration /20)),10);//闪烁,重复10次
    image.action(Parallel.$(moveAction, rotateAction, fadeAction));//所有action并行
    stage.addActor(image);
    }
    Gdx.input.setInputProcessor(stage);
    }
    
    @Override
    publicvoiddispose() {
    texture.dispose();
    stage.dispose();
    }
    
    @Override
    publicvoidpause() {
    // TODO Auto-generated method stub
    
    }
    
    @Override
    publicvoidrender() {
    Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
    stage.act(Gdx.graphics.getDeltaTime());
    stage.draw();
    }
    
    @Override
    publicvoidresize(int width,int height) {
    // TODO Auto-generated method stub
    
    }
    
    @Override
    publicvoidresume() {
    // TODO Auto-generated method stub
    
    }
    
    }

    效果(图片有点大…多等等):

    xingxing

    其实每个action的用法都很简单,主要问题是怎么组合排列来显示一种符合需求的效果。

    我发现libgdx的更新不是一般快,每天都有几个版本的。那个通过文件配置UI样式让我觉得非常有意思,但是具体操作中有诸多问题。

  • android游戏开发框架libgdx的使用(五)–舞台和常用UI类

    android游戏开发框架libgdx的使用(五)–舞台和常用UI类

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

    常用的UI类包括标签,按钮,勾选框,下拉框,图片,输入框,列表,滑动面板,滑条,分割面板。它们都在com.badlogic.gdx.scenes.scene2d.ui包中,都属于Actor,可以方便的纳入舞台的管理中。

    其实仔细看看UI类的实现代码不难发现其实它们都是大部分继承自Widget或者Table,如果需要自定义UI可以继承以上两个类(它们继承自Actor),这里要说明一下libgdx的布局部分使用了TWL,有兴趣的朋友可以去看看。

    在介绍每个控件之前我们先来看一下NinePatch,这是最近的一个比较重大的更新。

    何为NinePatch?其实android原生即有NinePatch类,常在按钮中使用。

    NinePatch

    如图,将图片分成九份。中间部分可以根据需要扩大,使按钮的大小内容变动不受图片的限制。

    而在libgdx的NinePatch其实就是九个TextureRegion对象。

    常用的实例化方法有两个:

    publicNinePatch (Texture texture,int left,int right,int top,int bottom)
    
    publicNinePatch (TextureRegion region,int left,int right,int top,int bottom)

    关于其中的四个int型参数如何取值我们可以参考一下源码:

    publicNinePatch (TextureRegion region,int left,int right,int top,int bottom) {
    int middleWidth = region.getRegionWidth() - left - right;
    int middleHeight = region.getRegionHeight() - top - bottom;
    this.patches =new TextureRegion[] {new TextureRegion(region,0,0, left, top),
    new TextureRegion(region, left,0, middleWidth, top),new TextureRegion(region, left + middleWidth,0, right, top),
    new TextureRegion(region,0, top, left, middleHeight),new TextureRegion(region, left, top, middleWidth, middleHeight),
    new TextureRegion(region, left + middleWidth, top, right, middleHeight),
    new TextureRegion(region,0, top + middleHeight, left, bottom),
    new TextureRegion(region, left, top + middleHeight, middleWidth, bottom),
    new TextureRegion(region, left + middleWidth, top + middleHeight, right, bottom)};
    }

    先计算中间部分的宽度和高度。然后开始切图,首先取顶部的最左边的那个,即图中编号1的那块,然后去它右边的,然后再右边的。

    取完最上边的那行,然后取中间的那行,然后取最后一行的。

    由上自下,由左自右。

    而在绘制时又是如何处理的呢?看源码:

    publicvoiddraw (SpriteBatch batch,float x,float y,float width,float height) {
    float centerColumnX = x;
    if (patches[BOTTOM_LEFT] !=null)
    centerColumnX += patches[BOTTOM_LEFT].getRegionWidth();
    elseif (patches[MIDDLE_LEFT] !=null)
    centerColumnX += patches[MIDDLE_LEFT].getRegionWidth();
    elseif (patches[TOP_LEFT] !=null)//
    centerColumnX += patches[TOP_LEFT].getRegionWidth();
    
    float rightColumnX = x + width;
    if (patches[BOTTOM_RIGHT] !=null)
    rightColumnX -= patches[BOTTOM_RIGHT].getRegionWidth();
    elseif (patches[MIDDLE_RIGHT] !=null)
    rightColumnX += patches[MIDDLE_RIGHT].getRegionWidth();
    elseif (patches[TOP_RIGHT] !=null)//
    rightColumnX += patches[TOP_RIGHT].getRegionWidth();
    
    float middleRowY = y;
    if (patches[TOP_LEFT] !=null)
    middleRowY += patches[TOP_LEFT].getRegionHeight();
    elseif (patches[TOP_CENTER] !=null)
    middleRowY += patches[TOP_CENTER].getRegionHeight();
    elseif (patches[TOP_RIGHT] !=null)//
    middleRowY += patches[TOP_RIGHT].getRegionHeight();
    
    float topRowY = y + height;
    if (patches[TOP_LEFT] !=null)
    topRowY -= patches[TOP_LEFT].getRegionHeight();
    elseif (patches[TOP_CENTER] !=null)
    topRowY -= patches[TOP_CENTER].getRegionHeight();
    elseif (patches[TOP_RIGHT] !=null)//
    topRowY -= patches[TOP_RIGHT].getRegionHeight();
    
    // Bottom row
    if (patches[BOTTOM_LEFT] !=null) batch.draw(patches[BOTTOM_LEFT], x, y, centerColumnX - x, middleRowY - y);
    if (patches[BOTTOM_CENTER] !=null)
    batch.draw(patches[BOTTOM_CENTER], centerColumnX, y, rightColumnX - centerColumnX, middleRowY - y);
    if (patches[BOTTOM_RIGHT] !=null)
    batch.draw(patches[BOTTOM_RIGHT], rightColumnX, y, x + width - rightColumnX, middleRowY - y);
    
    // Middle row
    if (patches[MIDDLE_LEFT] !=null) batch.draw(patches[MIDDLE_LEFT], x, middleRowY, centerColumnX - x, topRowY - middleRowY);
    if (patches[MIDDLE_CENTER] !=null)
    batch.draw(patches[MIDDLE_CENTER], centerColumnX, middleRowY, rightColumnX - centerColumnX, topRowY - middleRowY);
    if (patches[MIDDLE_RIGHT] !=null)
    batch.draw(patches[MIDDLE_RIGHT], rightColumnX, middleRowY, x + width - rightColumnX, topRowY - middleRowY);
    
    // Top row
    if (patches[TOP_LEFT] !=null) batch.draw(patches[TOP_LEFT], x, topRowY, centerColumnX - x, y + height - topRowY);
    if (patches[TOP_CENTER] !=null)
    batch.draw(patches[TOP_CENTER], centerColumnX, topRowY, rightColumnX - centerColumnX, y + height - topRowY);
    if (patches[TOP_RIGHT] !=null)
    batch.draw(patches[TOP_RIGHT], rightColumnX, topRowY, x + width - rightColumnX, y + height - topRowY);
    }

    先计算左右栏的宽度,在计算中间和顶部的高度。然后从下自上的绘制。说实话我觉得这段代码看着很好玩的。

    现在来说说几个常用的控件的使用吧。先构建一个舞台。

    先来试试Label吧,label是有缓存的,所以替换显示内容不是用setText方法,而是使用setWrappedText方法。

    代码如下:

    package com.cnblogs.htynkn.listener;
    
    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.BitmapFont.HAlignment;
    import com.badlogic.gdx.scenes.scene2d.Stage;
    import com.badlogic.gdx.scenes.scene2d.actors.Label;
    
    publicclass FirstGame implements ApplicationListener {
    
    private Stage stage;
    Label label;
    
    @Override
    publicvoidcreate() {
    stage =new Stage(Gdx.graphics.getWidth(), Gdx.graphics.getHeight(),
    true);
    label =new Label("fpsLabel",new BitmapFont(Gdx.files.internal("cf.fnt"),Gdx.files.internal("cf.png"),false),"label1");
    label.x=5;
    label.y=Gdx.graphics.getHeight()-label.height-5;
    stage.addActor(label);
    Gdx.input.setInputProcessor(stage);
    }
    
    @Override
    publicvoiddispose() {
    stage.dispose();
    }
    
    @Override
    publicvoidpause() {
    // TODO Auto-generated method stub
    
    }
    
    @Override
    publicvoidrender() {
    Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
    label.setWrappedText("FPS: "+Gdx.graphics.getFramesPerSecond(),
    HAlignment.CENTER);
    stage.act(Gdx.graphics.getDeltaTime());
    stage.draw();
    }
    
    @Override
    publicvoidresize(int width,int height) {
    // TODO Auto-generated method stub
    
    }
    
    @Override
    publicvoidresume() {
    // TODO Auto-generated method stub
    
    }
    }

    效果:

    FPS

    然后再看看Button吧,实例化需要一个ButtonStyle,定义了按钮三种状态对应的图片样式,按下和松开时的X,Y偏移还有Button中文字绘制所需的BitmapFont和Color。

    按钮的三种状态的图片我就省了,只用一张图片。

    06

    修改代码如下:

    package com.cnblogs.htynkn.listener;
    
    import com.badlogic.gdx.ApplicationListener;
    import com.badlogic.gdx.Gdx;
    import com.badlogic.gdx.graphics.Color;
    import com.badlogic.gdx.graphics.GL10;
    import com.badlogic.gdx.graphics.Texture;
    import com.badlogic.gdx.graphics.g2d.BitmapFont;
    import com.badlogic.gdx.graphics.g2d.NinePatch;
    import com.badlogic.gdx.graphics.g2d.BitmapFont.HAlignment;
    import com.badlogic.gdx.scenes.scene2d.Stage;
    import com.badlogic.gdx.scenes.scene2d.actors.Label;
    import com.badlogic.gdx.scenes.scene2d.ui.Button;
    import com.badlogic.gdx.scenes.scene2d.ui.Button.ButtonStyle;
    
    publicclass FirstGame implements ApplicationListener {
    
    private Stage stage;
    Label label;
    Texture texture;
    Button button;
    
    @Override
    publicvoidcreate() {
    stage =new Stage(Gdx.graphics.getWidth(), Gdx.graphics.getHeight(),
    true);
    texture =new Texture(Gdx.files.internal("06.png"));
    NinePatch n1 =new NinePatch(texture,7,7,9,9);
    BitmapFont bitmapFont =new BitmapFont(Gdx.files.internal("cf.fnt"),
    Gdx.files.internal("cf.png"),false);
    label =new Label("fpsLabel", bitmapFont,"label1");
    label.x =5;
    label.y = Gdx.graphics.getHeight() - label.height -5;
    stage.addActor(label);
    button =new Button("button",new ButtonStyle(n1, n1, n1,0f,0f,0f,
    0f, bitmapFont,new Color(1,1,0,0.5f)),"button");
    button.x=10;
    button.y=10;
    button.width=100f;
    button.height=32f;
    stage.addActor(button);
    Gdx.input.setInputProcessor(stage);
    }
    
    @Override
    publicvoiddispose() {
    stage.dispose();
    }
    
    @Override
    publicvoidpause() {
    // TODO Auto-generated method stub
    
    }
    
    @Override
    publicvoidrender() {
    Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
    label.setWrappedText("FPS: " + Gdx.graphics.getFramesPerSecond(),
    HAlignment.CENTER);
    stage.act(Gdx.graphics.getDeltaTime());
    stage.draw();
    }
    
    @Override
    publicvoidresize(int width,int height) {
    // TODO Auto-generated method stub
    
    }
    
    @Override
    publicvoidresume() {
    // TODO Auto-generated method stub
    
    }
    }

    效果:

    libgdx按钮

    按钮自然应该有点击事件,通过setClickListener来设置

    button.setClickListener(new ClickListener() {
    @Override
    publicvoidclick(Actor actor) {
    Gdx.app.log("Info","点击事件触发了");
    }
    });

    然后再看看CheckBox。CheckBox的样式定义在CheckBoxStyle中,需要4个参数,两种状态的各一张图片,一个BitmapFont和Color。

    这里我再添加一张图片

    07

    原理差不多,直接贴代码了。

    package com.cnblogs.htynkn.listener;
    
    import android.graphics.Paint.Align;
    
    import com.badlogic.gdx.ApplicationListener;
    import com.badlogic.gdx.Gdx;
    import com.badlogic.gdx.graphics.Color;
    import com.badlogic.gdx.graphics.GL10;
    import com.badlogic.gdx.graphics.Texture;
    import com.badlogic.gdx.graphics.g2d.BitmapFont;
    import com.badlogic.gdx.graphics.g2d.NinePatch;
    import com.badlogic.gdx.graphics.g2d.TextureRegion;
    import com.badlogic.gdx.graphics.g2d.BitmapFont.HAlignment;
    import com.badlogic.gdx.scenes.scene2d.Actor;
    import com.badlogic.gdx.scenes.scene2d.Stage;
    import com.badlogic.gdx.scenes.scene2d.actors.Label;
    import com.badlogic.gdx.scenes.scene2d.ui.Button;
    import com.badlogic.gdx.scenes.scene2d.ui.CheckBox;
    import com.badlogic.gdx.scenes.scene2d.ui.ClickListener;
    import com.badlogic.gdx.scenes.scene2d.ui.Button.ButtonStyle;
    import com.badlogic.gdx.scenes.scene2d.ui.CheckBox.CheckBoxStyle;
    
    publicclass FirstGame implements ApplicationListener {
    
    private Stage stage;
    Label label;
    Texture texture1;
    Texture texture2;
    CheckBox checkBox;
    
    @Override
    publicvoidcreate() {
    stage =new Stage(Gdx.graphics.getWidth(), Gdx.graphics.getHeight(),
    true);
    texture1 =new Texture(Gdx.files.internal("06.png"));
    texture2 =new Texture(Gdx.files.internal("07.png"));
    NinePatch n1 =new NinePatch(texture1,7,7,9,9);
    BitmapFont bitmapFont =new BitmapFont(Gdx.files.internal("cf.fnt"),
    Gdx.files.internal("cf.png"),false);
    label =new Label("fpsLabel", bitmapFont,"label1");
    label.x =5;
    label.y = Gdx.graphics.getHeight() - label.height -5;
    CheckBoxStyle style =new CheckBoxStyle(new TextureRegion(texture1),
    new TextureRegion(texture2), bitmapFont,new Color(1,1,1,
    0.5f));
    
    checkBox =new CheckBox("checkbox", style,"checkbox");
    checkBox.x =100;
    checkBox.y =100;
    checkBox.width =158f;
    checkBox.height =32f;
    checkBox.setText("Yes");
    checkBox.setClickListener(new ClickListener() {
    
    @Override
    publicvoidclick(Actor actor) {
    if (checkBox.isChecked) {
    checkBox.setText("Yes");
    }else {
    checkBox.setText("NO");
    }
    }
    });
    stage.addActor(checkBox);
    stage.addActor(label);
    Gdx.input.setInputProcessor(stage);
    }
    
    @Override
    publicvoiddispose() {
    stage.dispose();
    }
    
    @Override
    publicvoidpause() {
    // TODO Auto-generated method stub
    
    }
    
    @Override
    publicvoidrender() {
    Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
    label.setWrappedText("FPS: " + Gdx.graphics.getFramesPerSecond(),
    HAlignment.CENTER);
    stage.act(Gdx.graphics.getDeltaTime());
    stage.draw();
    }
    
    @Override
    publicvoidresize(int width,int height) {
    // TODO Auto-generated method stub
    
    }
    
    @Override
    publicvoidresume() {
    // TODO Auto-generated method stub
    
    }
    }

    效果:

    noyes

    其他的UI大致用法差不多,显示的样式在对应的Style或者Skin中定义。但是要注意有些UI类需要手动设置width和height,不然有些显示会很奇怪的。

    最后说一下Slider的用法。

    SliderStyle需要一个NinePath和Texture,我最初没有想通为什么不是两个NinePath,仔细看一下源码才了解到,NinePath是作为背景,而Texture那个是中间的那个滑动的方块。

    关于用配置文件设置Style的问题,google code的wiki上似乎没有写,但是在libgdx的论坛里面有,比如

    somePatch1:[ { height: 13, width: 9, x: 761, y: 78 }, { height: 13, width: 1, x: 770, y: 78 }, { height: 13, width: 9, x: 771, y: 78 }, { height: 1, width: 9, x: 761, y: 91 }, { height: 1, width: 1, x: 770, y: 91 }, { height: 1, width: 9, x: 771, y: 91 }, { height: 13, width: 9, x: 761, y: 92 }, { height: 13, width: 1, x: 770, y: 92 }, { height: 13, width: 9, x: 771, y: 92 }]
    或者
    somePatch2:[ { height: 13, width: 9, x: 761, y: 78 },]
  • android游戏开发框架libgdx的使用(四)–舞台和演员

    android游戏开发框架libgdx的使用(四)–舞台和演员

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

    我们先看几个游戏截图再来理解何为舞台。

    12942M3Q423F-45025

    请仔细观察图片中的元素,有些东西是不能动,有些可以动,有些有特效,有些没有。有些是按钮,有些是图片,但是其实它们都可以统一称为演员(Actor)。

    而整个游戏界面就是我们的舞台。

    12942M3Q423F-45025 - 副本

    再看一个射击类游戏

    f2316c3c29ce2660f93dce72a0944453

    而其中的演员是

    f2316c3c29ce2660f93dce72a0944453

    演员是游戏设计中常用的一个对象,它接受舞台的统一管理,拥有一些公共的事件,比如触摸,点击,但是同时还有自身的响应和属性。

    而舞台就是容纳演员的场所。它统一管理所有演员,接受输入,同时提供一个方便的框架操作演员的时间变化。

    我们来看一下Stage类:

    protectedfinal Group root;
    protectedfinal SpriteBatch batch;
    protected Camera camera;

    它拥有一个Group,一个SpriteBatch,还有一个相机。

    SpriteBatch我们在前几篇说过,这里就不再重复了。

    Group是一个类,用于容纳和控制演员。但是这里要注意Group本身其实也是继承自Actor。

    相机我们这里跳过,以后再说,可以暂时理解成一个控制观察视角和指标转化的工具。

    当我们拥有一个演员后就可以调用addActor方法加入舞台。

    舞台可以获取输入,但是需要设置。

    Gdx.input.setInputProcessor(stage);

    下面来个列子,控制一个人物前进。

    actor1

    控制人物的按钮:

    narrow

    将所需的图片放到assert中

    所需资源

    新建三个类:

    FirstGame,实现接口ApplicationListener

    FirstActor,继承Actor

    NarrowButton,继承Actor

    先看一下FirstGame

    声明一个Stage,然后实例化FirstActor和NarrowButton,将二者加入舞台中,最后设置输入响应为Stage。

    package com.cnblogs.htynkn.listener;
    
    import java.util.Date;
    import java.util.Random;
    
    import javax.microedition.khronos.opengles.GL;
    
    import android.util.Log;
    
    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.scenes.scene2d.Stage;
    import com.cnblogs.htynkn.domain.FirstActor;
    import com.cnblogs.htynkn.domain.NarrowButton;
    
    publicclass FirstGame implements ApplicationListener {
    
    private Stage stage;
    private FirstActor firstActor;
    private NarrowButton button;
    
    @Override
    publicvoidcreate() {
    stage =new Stage(Gdx.graphics.getWidth(), Gdx.graphics.getHeight(),
    true);
    firstActor =new FirstActor("renwu");
    button =new NarrowButton("narrow");
    stage.addActor(firstActor);
    stage.addActor(button);
    Gdx.input.setInputProcessor(stage);
    }
    
    @Override
    publicvoiddispose() {
    stage.dispose();
    }
    
    @Override
    publicvoidpause() {
    // TODO Auto-generated method stub
    
    }
    
    @Override
    publicvoidrender() {
    Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
    stage.act(Gdx.graphics.getDeltaTime());
    stage.draw();
    }
    
    @Override
    publicvoidresize(int width,int height) {
    // TODO Auto-generated method stub
    
    }
    
    @Override
    publicvoidresume() {
    // TODO Auto-generated method stub
    
    }
    }

    再看一下FirstActor。

    声明一个Texture用于绘制。在构造方法中获取到高度和宽度,以便于后期的hit时间判断。

    package com.cnblogs.htynkn.domain;
    
    import com.badlogic.gdx.Gdx;
    import com.badlogic.gdx.graphics.Texture;
    import com.badlogic.gdx.graphics.g2d.SpriteBatch;
    import com.badlogic.gdx.scenes.scene2d.Actor;
    
    publicclass FirstActor extends Actor {
    
    Texture texture;
    
    @Override
    publicvoiddraw(SpriteBatch batch,float parentAlpha) {
    batch.draw(texture,this.x,this.y);
    }
    
    @Override
    public Actorhit(float x,float y) {
    if (x >0 && y >0 &&this.height > y &&this.width > x) {
    returnthis;
    }else {
    returnnull;
    }
    }
    
    @Override
    publicbooleantouchDown(float x,float y,int pointer) {
    // TODO Auto-generated method stub
    returnfalse;
    }
    
    @Override
    publicvoidtouchDragged(float x,float y,int pointer) {
    // TODO Auto-generated method stub
    
    }
    
    @Override
    publicvoidtouchUp(float x,float y,int pointer) {
    // TODO Auto-generated method stub
    
    }
    
    publicFirstActor(String name) {
    super(name);
    texture =new Texture(Gdx.files.internal("actor1.gif"));
    this.height = texture.getHeight();
    this.width = texture.getWidth();
    }
    }

    NarrowButton中代码绘制部分和上面的以下,主要是有个点击后控制人物行动的问题。

    修改touchDown事件:

    通过Group获取到FirstActor,控制x值。

    publicbooleantouchDown(float x,float y,int pointer) {
    Actor actor =this.parent.findActor("renwu");
    actor.x +=10;
    returnfalse;
    }

    效果:

    ui类1ui类2

    到此为止一个最简单的人物控制我们已经实现了。但是这个有实例还有很多可以改进的地方,比如方向按钮没有点击效果,人物没有移动效果。

    我们可以使用Animation来实现。添加一张图片

    actor2

    具体的原理我们看一下Animation类:

    publicclass Animation {
    final TextureRegion[] keyFrames;
    publicfloat frameDuration;
    
    /** Constructor, storing the frame duration and key frames.
    *
    *@param frameDuration the time between frames in seconds.
    *@param keyFrames the {@link TextureRegion}s representing the frames. */
    publicAnimation (float frameDuration, List keyFrames) {
    this.frameDuration = frameDuration;
    this.keyFrames =new TextureRegion[keyFrames.size()];
    for(int i =0, n = keyFrames.size(); i < n; i++) {
    this.keyFrames[i] = (TextureRegion)keyFrames.get(i);
    }
    }
    
    /** Constructor, storing the frame duration and key frames.
    *
    *@param frameDuration the time between frames in seconds.
    *@param keyFrames the {@link TextureRegion}s representing the frames. */
    publicAnimation (float frameDuration, TextureRegion... keyFrames) {
    this.frameDuration = frameDuration;
    this.keyFrames = keyFrames;
    }
    
    /** Returns a {@link TextureRegion} based on the so called state time. This is the amount of seconds an object has spent in the
    * state this Animation instance represents, e.g. running, jumping and so on. The mode specifies whether the animation is
    * looping or not.
    *@param stateTime the time spent in the state represented by this animation.
    *@param looping whether the animation is looping or not.
    *@return the TextureRegion representing the frame of animation for the given state time. */
    public TextureRegiongetKeyFrame (float stateTime,boolean looping) {
    int frameNumber = (int)(stateTime / frameDuration);
    
    if (!looping) {
    frameNumber = Math.min(keyFrames.length -1, frameNumber);
    }else {
    frameNumber = frameNumber % keyFrames.length;
    }
    return keyFrames[frameNumber];
    }
    }

    可以看出所谓的动画其实是一张一张的图片不断切换(其实所有的动画都是这个样子的)。

    我们构造一个图片列表然后根据事件变动不停取出,重新绘制就形成动画了。

    注意一下传入的时间和图片列表大小的问题,修改FirstActor代码如下:

    package com.cnblogs.htynkn.domain;
    
    import com.badlogic.gdx.Gdx;
    import com.badlogic.gdx.graphics.Texture;
    import com.badlogic.gdx.graphics.g2d.Animation;
    import com.badlogic.gdx.graphics.g2d.SpriteBatch;
    import com.badlogic.gdx.graphics.g2d.TextureRegion;
    import com.badlogic.gdx.scenes.scene2d.Actor;
    
    publicclass FirstActor extends Actor {
    
    Texture texture1;
    Texture texture2;
    Animation animation;
    TextureRegion[] walksFrame;
    float stateTime;
    
    @Override
    publicvoiddraw(SpriteBatch batch,float parentAlpha) {
    stateTime += Gdx.graphics.getDeltaTime();
    TextureRegion currentFrame = animation.getKeyFrame(stateTime,true);
    batch.draw(currentFrame,this.x,this.y);
    }
    
    @Override
    public Actorhit(float x,float y) {
    Gdx.app.log("INFO", x +" " +this.width);
    if (x >0 && y >0 &&this.height > y &&this.width > x) {
    returnthis;
    }else {
    returnnull;
    }
    }
    
    @Override
    publicbooleantouchDown(float x,float y,int pointer) {
    // TODO Auto-generated method stub
    returnfalse;
    }
    
    @Override
    publicvoidtouchDragged(float x,float y,int pointer) {
    // TODO Auto-generated method stub
    
    }
    
    @Override
    publicvoidtouchUp(float x,float y,int pointer) {
    // TODO Auto-generated method stub
    
    }
    
    publicFirstActor(String name) {
    super(name);
    texture1 =new Texture(Gdx.files.internal("actor1.gif"));
    texture2 =new Texture(Gdx.files.internal("actor2.gif"));
    this.height = texture1.getHeight();
    this.width = texture1.getWidth();
    TextureRegion region1;
    TextureRegion region2;
    region1 =new TextureRegion(texture1);
    region2 =new TextureRegion(texture2);
    walksFrame =new TextureRegion[30];
    for (int i =0; i <30; i++) {
    if (i %2 ==0) {
    walksFrame[i] = region1;
    }else {
    walksFrame[i] = region2;
    }
    }
    animation =new Animation(0.25f, walksFrame);
    }
    }

    效果:

    2011111423585672

    这里注意一下,为什么我们要Texture转为TextureRegion。这是因为在实际开发中的图片是集成在一起的,比如所有角色要用的图片都是放在一张图里,然后分割截取的,对应的辅助方法TextureRegion.split。

    另外我们可以发现NarrowButton和FirstActor中有大量代码重复了,可能有朋友觉得应该提取一下,其实libgdx已经帮我们做了,可以参考

    ui类

    这里有一些常用的UI控件,估计下一篇可以讲到。

  • android游戏开发框架libgdx的使用(三)–中文显示与汉字绘制

    android游戏开发框架libgdx的使用(三)–中文显示与汉字绘制

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

    本来这篇想写场景的,但是昨天和群里一个朋友讨论了一下libgdx显示汉字的问题。以前没有注意到这个问题,发现还是蛮严重的,要是不支持中文,libgdx用着就有点不愉快了。

    我们来看看BitmapFont类,这是有关文字绘制的。看一下源码:

    publicBitmapFont () {
    this(Gdx.files.classpath("com/badlogic/gdx/utils/arial-15.fnt"),
    Gdx.files.classpath("com/badlogic/gdx/utils/arial-15.png"),false,true);
    }

    这是默认的构造函数,可以看出它加载了两个文件arial-15.fnt和arial-15.png。

    arial-15.fnt文件的部分内容:

    infoface="Arial"size=15bold=0italic=0charset=""unicode=0stretchH=100smooth=1aa=1padding=0,0,0,0spacing=1,1commonlineHeight=18base=14scaleW=256scaleH=256pages=1packed=0pageid=0file="arial-15.png"charscount=189charid=32x=0y=0width=0height=0xoffset=0yoffset=14xadvance=4page=0chnl=0 charid=255x=0y=0width=8height=19xoffset=-1yoffset=0xadvance=7page=0chnl=0 charid=254x=8y=0width=9height=19xoffset=1yoffset=0xadvance=8page=0chnl=0 charid=253x=17y=0width=8height=19xoffset=-1yoffset=0xadvance=7page=0chnl=0 charid=252x=25y=0width=9height=19xoffset=1yoffset=0xadvance=8page=0chnl=0 charid=251x=34y=0width=9height=19xoffset=1yoffset=0xadvance=8page=0chnl=0 charid=250x=43y=0width=9height=19xoffset=1yoffset=0xadvance=8page=0chnl=0 charid=249x=52y=0width=9height=19xoffset=1yoffset=0xadvance=8page=0chnl=0 charid=248x=61y=0width=9height=19xoffset=1yoffset=0xadvance=8page=0chnl=0 charid=247x=70y=0width=9height=19xoffset=1yoffset=0xadvance=8page=0chnl=0 charid=246x=79y=0width=9height=19xoffset=1yoffset=0xadvance=8page=0chnl=0 charid=245x=88y=0width=9height=19xoffset=1yoffset=0xadvance=8page=0chnl=0 charid=244x=97y=0width=9height=19xoffset=1yoffset=0xadvance=8page=0chnl=0 charid=243x=106y=0width=9height=19xoffset=1yoffset=0xadvance=8page=0chnl=0 charid=242x=115y=0width=9height=19xoffset=1yoffset=0xadvance=8page=0chnl=0 charid=241x=124y=0width=9height=19xoffset=1yoffset=0xadvance=8page=0chnl=0 charid=240x=133y=0width=9height=19xoffset=1yoffset=0xadvance=8page=0chnl=0 charid=239x=142y=0width=5height=19xoffset=0yoffset=0xadvance=3page=0chnl=0 …kerningscount=374kerningfirst=49second=49amount=-1kerningfirst=121second=44amount=-1kerningfirst=121second=46amount=-1kerningfirst=119second=44amount=-1kerningfirst=119second=46amount=-1kerningfirst=118second=44amount=-1kerningfirst=118second=46amount=-1kerningfirst=114second=44amount=-1kerningfirst=114second=46amount=-1kerningfirst=89second=44amount=-2kerningfirst=89second=45amount=-1kerningfirst=89second=46amount=-2kerningfirst=89second=58amount=-1kerningfirst=89second=59amount=-1kerningfirst=89second=65amount=-1kerningfirst=89second=97amount=-1kerningfirst=89second=101amount=-1…

     

    再看看arial-15.png:

    arial-15

    可以很明显看出,libgdx的文字绘制是根据fnt文件获取对应文字的在png中的坐标位置,然后截取图片的相应部分进行绘制。

    那么要让libgdx支持中文思路就很简单了,我们自己构造fnt和png文件,其中包含我们要使用的中文即可。

    作者给我们提供了一个对应的工具:Hiero

    下载后双击运行,在右侧列表中选择一个可以用的字体,然后输入需要的中文,最好保留自动生成的英文和符号。

    hiero界面

    在右侧的Effects中可以设置效果:

    hiero界面效果

    点File—Save as BMFont Files,生成两个文件,将它们拷贝到asserts文件夹。

    生成的文件

    使用

    bitmapFont =new BitmapFont(Gdx.files.internal("cf.fnt"), Gdx.files.internal("cf.png"),false);

    指定我们生成的文件作为绘制的参考,然后绘制:

    bitmapFont.draw(spriteBatch,"FPS" + Gdx.graphics.getFramesPerSecond(),5, Gdx.graphics.getHeight() -10);
    bitmapFont.draw(spriteBatch,"祝大家光棍节快乐",0, Gdx.graphics.getHeight()/2-8);

    效果:

    libgdx显示汉字效果图

    关于多行文字:

    可以调用

    public TextBoundsdrawMultiLine (SpriteBatch spriteBatch, CharSequence str,float x,float y)

    或者

    public TextBoundsdrawMultiLine (SpriteBatch spriteBatch, CharSequence str,float x,float y,float alignmentWidth,HAlignment alignment)
  • android游戏开发框架libgdx的使用(二)–图形绘制

    android游戏开发框架libgdx的使用(二)–图形绘制

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

    首先了解一下何为texture。按照英文解释来理解:一个图片从原始格式解码并上传到GPU就被称为纹理。(说实话我不是很清楚这个的定义哈,求指点)

    为了绘制texture,常常使用几何来描述,通过几何对应的顶点来描述纹理。比如要描述一个矩形,可以通过描述每个顶点来描述矩形。

     

    要绘图时,首先要绑定纹理,然后传递一个几何描述给OpenGL进行绘制。而绘图的大小和位置由几何描述和OpenGL的viewport的设置共同决定。

    当然大部分的游戏都会让viewport的大小和屏幕一致。这就意味使用像素更容易让纹理绘制在合适的大小和位置。

     

    绘制一个矩形的几何图形是非常常见的,同样让同一个纹理在不同位置以不同大小位置也是非常常见的,比如漫天的弹幕。但是每次都传递每个形状到GPU进行绘制的效率是较低的。

    所以许多相同纹理可以一起描述并一起送入GPU,这就是SpriteBatch类所要做的。

     

    SpriteBatch被赋予了纹理和坐标以便每个图形的绘制。它(SpriteBatch)汇集了很多图形而没有直接提交给GPU。如果它被赋予的纹理不同于原有的,它将保持原有的图形,并获取新的图形。

    上一篇文章其实就使用了SpriteBatch,但是没有绘制图形,现在我们来试试绘制。

    先找张图片来,分辨率必须是2的次方(如3232,256512)。

    我截取了我的桌面的一部分,分辨率调成512*512。

    image1

    拷贝到assets文件夹中,图片文件最好都是放在这个里面哈。

    SpxImage

    然后修改代码

    private Texture texture;
    实例化texture,texture=new Texture(Gdx.files.internal("image1.jpg"));
    然后来说一下为什么要将图片放在assets文件夹中。
    Gdx.files是libgdx的文件模块,主要提供以下5大功能。
    • 读取文件
    • 写文件
    • 复制文件
    • 移动文件
    • 列出文件和目录

    而获取操作文件的FileHandle有4种方法。

    1.Classpath路径相对于classpath,文件通常为只读。

    2.Internal内部文件路径相对于程序根目录或者android 的assets文件夹。

    3.External外部文件路径是相对于SD卡根目录

    4.Absolute

    assets文件夹本身就是存储资源的文件夹,而且相比resource文件夹,它其中的资源不会生成R中的ID,用来放图片很是合适。

    所以用Gdx.files.internal(“image1.jpg”)获取图片,然后调用batch.draw(texture,20,10);绘制图形,20,10是坐标,笛卡尔座标,以左下角为原点。

    完整代码:

    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.Texture;
    import com.badlogic.gdx.graphics.g2d.SpriteBatch;
    
    publicclass FirstGame implements ApplicationListener {
    //绘图用的SpriteBatch
    private SpriteBatch batch;
    //纹理
    private Texture texture;
    @Override
    publicvoidcreate() {
    batch =new SpriteBatch();//实例化
    texture=new Texture(Gdx.files.internal("image1.jpg"));
    }
    
    @Override
    publicvoiddispose() {
    // TODO Auto-generated method stub
    
    }
    
    @Override
    publicvoidpause() {
    // TODO Auto-generated method stub
    
    }
    
    @Override
    publicvoidrender() {
    Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);//清屏
    batch.begin();
    batch.draw(texture,20,10);
    batch.end();
    }
    
    @Override
    publicvoidresize(int width,int height) {
    // TODO Auto-generated method stub
    
    }
    
    @Override
    publicvoidresume() {
    // TODO Auto-generated method stub
    
    }
    
    }

    效果:

    SpxImage

    可以看到图片不能完整显示,而实际操作中我们也经常使用图片的一部分,或者将多个图片资源集合在一个图片文件中。

    而要显示图片的一部分就可以使用TextureRegion类了。

    最常用的方法是draw(TextureRegion region, float x, float y, float width, float height)

    指定初始点和长宽。

    修改代码:

    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.Texture;
    import com.badlogic.gdx.graphics.g2d.SpriteBatch;
    import com.badlogic.gdx.graphics.g2d.TextureRegion;
    
    publicclass FirstGame implements ApplicationListener {
    //绘图用的SpriteBatch
    private SpriteBatch batch;
    //纹理
    private Texture texture;
    //区域
    private TextureRegion region;
    @Override
    publicvoidcreate() {
    batch =new SpriteBatch();//实例化
    texture=new Texture(Gdx.files.internal("image1.jpg"));
    region=new TextureRegion(texture,30,80,200,200);
    }
    
    @Override
    publicvoiddispose() {
    // TODO Auto-generated method stub
    
    }
    
    @Override
    publicvoidpause() {
    // TODO Auto-generated method stub
    
    }
    
    @Override
    publicvoidrender() {
    Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);//清屏
    batch.begin();
    batch.draw(region,0,0);
    batch.end();
    }
    
    @Override
    publicvoidresize(int width,int height) {
    // TODO Auto-generated method stub
    
    }
    
    @Override
    publicvoidresume() {
    // TODO Auto-generated method stub
    
    }
    
    }

    效果:

    SpxImage

    也许你觉得TextureRegion不够强大,没有关系,还可以使用Sprite。
    Sprite不光包含TextureRegion的功能,还可以指定位置和颜色。
    关键代码:

    sprite=new Sprite(texture,80,80,400,300);
    sprite.setPosition(10,10);//位置
    sprite.setRotation(15);

    稍微想一下前几个例子就可以发现,其实Sprite的功能就是以上的集合。但是Sprite更方便,它用一个对象描述了一切。

    完整代码如下:

    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.Texture;
    import com.badlogic.gdx.graphics.g2d.Sprite;
    import com.badlogic.gdx.graphics.g2d.SpriteBatch;
    
    publicclass FirstGame implements ApplicationListener {
    //绘图用的SpriteBatch
    private SpriteBatch batch;
    //纹理
    private Texture texture;
    //精灵
    private Sprite sprite;
    @Override
    publicvoidcreate() {
    batch =new SpriteBatch();//实例化
    texture=new Texture(Gdx.files.internal("image1.jpg"));
    sprite=new Sprite(texture,80,80,400,300);
    sprite.setPosition(10,10);//位置
    sprite.setRotation(15);//旋转
    }
    
    @Override
    publicvoiddispose() {
    // TODO Auto-generated method stub
    
    }
    
    @Override
    publicvoidpause() {
    // TODO Auto-generated method stub
    
    }
    
    @Override
    publicvoidrender() {
    Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);//清屏
    batch.begin();
    sprite.draw(batch);
    batch.end();
    }
    
    @Override
    publicvoidresize(int width,int height) {
    // TODO Auto-generated method stub
    
    }
    
    @Override
    publicvoidresume() {
    // TODO Auto-generated method stub
    
    }
    
    }

    效果:

    SpxImage

    同时可以通过sprite的setColor方法为图形着色。

    setColor(float r,float g,float b,float a)

    其中颜色的表述都是介于0,1之间的数。

    SpxImageSpxImage

    绘制的内容基本就这么多,下一篇是关于关于2D场景的。

    写在最后:

    1.关于混合问题,默认是开启混合了的。这意味着图形绘制完成时半透明的部分已经被混合了。当混合被关闭是任何已经在场景上的东西都会被纹理代替,这适合绘制大背景。

    batch.disableBlending();
    backgroundSprite.draw(batch);
    batch.enableBlending();

    2.关于性能优化

    SpriteBatch 有个构造函数可以指定最大缓冲数目。如果数值过低会造成额外的GPU调用;过高的话将占用过多的内存。

    在SpriteBatch有个字段为maxSpritesInBatch,可以先设置一个很高的缓冲数目,然后观察maxSpritesInBatch的值以确定合适的缓冲值。

    还有一个字段renderCalls,在end被调用时,它的值表示在begin和end之间几何声明被送入GPU的次数。

    还有一个构造函数可以指定缓冲大小和数量,合理的设置可以极大地提供性能。

  • android游戏开发框架libgdx的使用(一)–环境搭建

    android游戏开发框架libgdx的使用(一)–环境搭建

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

    Libgdx是一款支持2D与3D游戏开发的游戏类库,兼容大多数微机平台(标准JavaSE实现,能运行在Mac、Linux、Windows等系统)与Android平台(Android1.5以上即可使用,Android2.1以上可满功率发挥)。

    logo:logo

    地址:http://code.google.com/p/libgdx/

    首先下载最新的jar,我下载的是libgdx-0.9.2。

    SpxImage

    新建一个Android项目,File -> New -> Project -> Android Project。我选的SDK是1.6的,最低选择应该是1.5.

    新建一个文件夹libs(这个名字是固定的,不能随意改),将下载到的jar复制进去,android平台只需要复制gdx.jar和gdx-backend-android.jar。

    增加引用,然后将armeabi和armeabi-v7a两个文件夹复制到libs文件夹中。

    SpxImage

    然后写个小东西测试一下环境有没有问题。

    新建类HelloGameActivity,继承AndroidApplication类。

    package com.cnblogs.htynkn;
    
    import com.badlogic.gdx.backends.android.AndroidApplication;
    
    import android.os.Bundle;
    
    publicclass HelloGameActivity extends AndroidApplication {
    @Override
    publicvoidonCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    initialize(new FirstGame(),false);
    }
    }

     

    initialize(new FirstGame(),false);

    就是启动游戏的关键,FirstGame是一个实现了ApplicationListener的类。

    代码如下

    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.SpriteBatch;
    
    publicclass FirstGame implements ApplicationListener {
    //绘图用的SpriteBatch
    private SpriteBatch batch;
    @Override
    publicvoidcreate() {
    batch =new SpriteBatch();//实例化
    }
    
    @Override
    publicvoiddispose() {
    // TODO Auto-generated method stub
    
    }
    
    @Override
    publicvoidpause() {
    // TODO Auto-generated method stub
    
    }
    
    @Override
    publicvoidrender() {
    Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);//清屏
    batch.begin();
    batch.end();
    }
    
    @Override
    publicvoidresize(int width,int height) {
    // TODO Auto-generated method stub
    
    }
    
    @Override
    publicvoidresume() {
    // TODO Auto-generated method stub
    
    }
    
    }

    因为没有写实际的东西,所以效果是一个黑黑的框。
    SpxImage

    环境搭建就说到这里,下一篇是关于图片绘制的。

    注意:

    如果仔细观察Logcat,就会发现一个error:

    E/libEGL(382): couldn’t load <libhgl.so> library (Cannot load library: load_library[984]: Library ‘libhgl.so’ not found)

    这个没什么关系,这是系统尝试加载硬件OpenGL驱动失败了,然后回到软件的处理方法上,不是缺陷或者错误。