libgdx示例-SuperJumper分析 2. 界面与触屏事件

上一节已经得出 MainMenuScreen 为主菜单类,也就是游戏开始后第一个显示的Screen,先看看主菜单的界面,截图如下:

libgdx示例-SuperJumper分析 2. 界面与触屏事件_第1张图片

下面就来分析一下MainMenuScreen类,代码如下:
public class MainMenuScreen extends Screen {
    OrthographicCamera guiCam;
    SpriteBatch batcher;
    Rectangle soundBounds;
    Rectangle playBounds;
    Rectangle highscoresBounds;
    Rectangle helpBounds;
    Vector3 touchPoint;

    public MainMenuScreen (Game game) {
        super(game);
        guiCam = new OrthographicCamera(320, 480);
        guiCam.position.set(320 / 2, 480 / 2, 0);
        batcher = new SpriteBatch();
        soundBounds = new Rectangle(0, 0, 64, 64);
        playBounds = new Rectangle(160 - 150, 200 + 18, 300, 36);
        highscoresBounds = new Rectangle(160 - 150, 200 - 18, 300, 36);
        helpBounds = new Rectangle(160 - 150, 200 - 18 - 36, 300, 36);
        touchPoint = new Vector3();
    }

    @Override public void update (float deltaTime) {
        if (Gdx.input.justTouched()) {
            guiCam.unproject(touchPoint.set(Gdx.input.getX(), Gdx.input.getY(), 0));

            if (OverlapTester.pointInRectangle(playBounds, touchPoint.x, touchPoint.y)) {
                Assets.playSound(Assets.clickSound);
                game.setScreen(new GameScreen(game));
                return;
            }
            if (OverlapTester.pointInRectangle(highscoresBounds, touchPoint.x, touchPoint.y)) {
                Assets.playSound(Assets.clickSound);
                game.setScreen(new HighscoresScreen(game));
                return;
            }
            if (OverlapTester.pointInRectangle(helpBounds, touchPoint.x, touchPoint.y)) {
                Assets.playSound(Assets.clickSound);
                game.setScreen(new HelpScreen(game));
                return;
            }
            if (OverlapTester.pointInRectangle(soundBounds, touchPoint.x, touchPoint.y)) {
                Assets.playSound(Assets.clickSound);
                Settings.soundEnabled = !Settings.soundEnabled;
                if (Settings.soundEnabled)
                    Assets.music.play();
                else
                    Assets.music.pause();
            }
        }
    }

    @Override public void present (float deltaTime) {
        GLCommon gl = Gdx.gl;
        gl.glClearColor(1, 0, 0, 1);
        gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
        guiCam.update();
        batcher.setProjectionMatrix(guiCam.combined);

        batcher.disableBlending();
        batcher.begin();
        batcher.draw(Assets.backgroundRegion, 0, 0, 320, 480);
        batcher.end();
        batcher.enableBlending();

        batcher.begin();
        batcher.draw(Assets.logo, 160 - 274 / 2, 480 - 10 - 142, 274, 142);
        batcher.draw(Assets.mainMenu, 10, (int)(200 - 110 / 2), 300, 110);
        batcher.draw(Settings.soundEnabled ? Assets.soundOn : Assets.soundOff, 0, 0, 64, 64);
        batcher.end();
    }

    @Override public void pause () {
    Settings.save();
    }

    @Override public void resume () {
    }

    @Override public void dispose () {
    }
}

说明:
     1. 首先在构造方法里,初始化了一个OrthographicCamera(正交相机),并把宽度和高度设置为320*480,也就是屏幕的大小。随后将OrthographicCamera的位置,也就是position设置在屏幕的中点 (因为此为2D游戏,所以不需要考虑Z轴)。
     然后就是一个SpriteBatch,用于绘制游戏画面。
     接着是一系列的Rectangle(矩形),这些矩形由(x,y,width,height)定义,用来在触屏事件中判断,相应的选项是否被选中。
     最后是一个名为touchPoint 的 Vector3 变量。这是一个包含空间中 x , y ,z 三个方向坐标的点类,用来记录触屏事件中点的坐标。

     2. 构造方法执行完毕后,将会先调用update()方法再调用present()。其中updata()是根据触屏事件进行屏幕的切换和更新,而present()方法则是绘制屏幕画面。为什么会是调用这个两个方法?因为 MainMenuScreen  是 作为 Game类的一个成员变量存在的,真正的绘制方法是Game类中实现了ApplicationListener接口而复写的render()方法,在其中规定了真正的绘图方法就是调用screen 的 update 和 present方法,而所传递进去的参数 Gdx.graphics.getDeltaTime() 是一个float型的参数,它表示每次绘制的时间间隔,也就是刷新的时间,代码如下:
@Override public void render () {
    screen.update(Gdx.graphics.getDeltaTime());
    screen.present(Gdx.graphics.getDeltaTime());
}

注:在实际的libgdx类库中,Screen接口并没有 updata 和 present方法 ,只剩下一个 render(float time)方法,并且在Game抽象类中的render方法,也只是调用了成员变量screen的render方法。这也就是说,在定义一个自己的Screen时,应该把绘图逻辑放在render方法中。至于触屏和按键的监听,既可以使用libgdx的Input类,也可使用Android 本身的API ,看什么情况下具体选用(需要注意的是,libgdx 使用的坐标系与android的是不同的)。

     3. 先说present()方法,present()方法的代码和之前例子中的代码很像,无非就是清空屏幕和绘制游戏画面。有几点说明一下:

         GLCommon gl = Gdx.gl;

        GLCommon是一个GL10, GL11 和GL20的 公共接口,它包含了所有的公共方法。也就是说可以不需要区分GL10, GL11 和GL20 来调用 GLCommon 里的方法。

        guiCam.update();
        batcher.setProjectionMatrix( guiCam.combined);

        因为自定义了OrthographicCamera ,所以要将OrthographicCamera 的 投影矩阵传递给 batcher。其中 guiCam.combined 是Camer(OrthographicCamera 的父类)类中的 Matrix4 类型的成员变量,而 Matrix4  代表一个4阶矩阵,这里的就是投影矩阵。
   
         batcher. disableBlending();
        batcher.begin();
        batcher.draw(Assets.backgroundRegion, 0, 0, 320, 480);
        batcher.end();
        batcher. enableBlending();

        这是绘制背景图片,如同作者在介绍中提到的那样,在绘制一幅比较大的图片的时候,应该先disableBlending (禁用混合) 绘制完后再 enableBlending(启用混合)。 
       其他就是绘制 Assets中对应的图片资源,都比较简单。

     4. 再来就是update()方法:

        if (Gdx.input. justTouched()) {
             guiCam.unproject(touchPoint.set(Gdx.input. getX(), Gdx.input. getY(), 0));

       首先就是判断是否有新的触屏事件发生,然后得到这个触摸点的 x y 坐标将其设置给 touchPoint, 然后调用 guiCam.unProject方法,进行反投影。(反投影这步没看懂有什么用的·····)

       if ( OverlapTester.pointInRectangle(playBounds, touchPoint.x, touchPoint.y)) {
             Assets.playSound(Assets.clickSound);   
             game.setScreen(new GameScreen(game));
             return;
       }

      得到触摸点的位置后,就是要判断到底触碰了哪个个选项。这里作者定义了一个名为: OverlapTester的类,里面包含了三个判断点是否在矩形内,矩形与矩形是否相交的静态方法。通过对比矩形位置大小和点的位置,来判断点击了哪项。
比如,上面就是点击了PLAY项后,播放音效,并将Screnn 切换成 GameScrenn(游戏主画面)。

       注:对于切换画面,这里的game被设置在Screen类的成员变量中,而实际的libgdx类库中,Screen 被做成了接口。也就是说,应当把对应的Game类的引用放在具体的Screen接口的实现类中,通过调用Game类的setScreen方法来切换游戏画面。

       同样的,对于HelpScreen 2,3,4,5(帮助界面) 还有 HighscoresScreen(最高分界面),实现的原理都是一样的。
       注:这里其实可以为所有的Screen定义一个父类Screen直接实现Screen接口,将相同的变量和方法封装起来,所有的子Screen再继承这个父Screen。


说到HighscoresScreen,就得提及libgdx中对于字符的处理。在libgdx中,所有的字符其实也是一张图片,或者说是一张图片的一部分。libgdx提供了一个BitmapFont的类来处理文字的绘制。构造BitmapFont时需要一个描述文字构成的fnt文件,和一张提供文字的png图片文件,另外在Libgdx的com.badlogic.gdx.utils包下有提供内置字库,目前仅支持英文、数字和常见符号。配合SpriteBatch就能够完成一些基础的文字绘制。
例如在游戏初始化的时候,Assets类中就已经构造了类型为BitmapFont 的 font变量,代码如下:

          public static BitmapFont font;
          font = new BitmapFont(Gdx.files.internal(" data/font.fnt"), Gdx.files.internal(" data/font.png"), false);

其中data/font.fnt 文件就是构造文件,而data/font.png就是具体的字符图片,第三个参数默认为false即可。
这两个文件都可以使用工具生成,这里推荐一个非常强大的工具:
http://www.ogre3d.org/forums/viewtopic.php?f=11&t=47802

以下就是font.png图片:
(同样的背景色是后来添加上去的)
libgdx示例-SuperJumper分析 2. 界面与触屏事件_第2张图片

可以看出其实每个字符就是图片中的一个小区域,BitmapFont做的工作就是根据 .fnt 文件的描述将.png文件分割成一个个小的图片区域块。

调用BItmapFont.draw(SpritBatch,CharSequence, x,  y) 方法就可以实现在指定位置绘制特定字符。
例如,在HighscoresScreen中绘制最高前五名的分数,代码如下:
float y = 230;

for (int i = 4; i >= 0; i--) {   
    Assets.font.draw(batcher, highScores[i], xOffset, y);
    y += Assets.font.getLineHeight();   // 换行  
}

同时BitmapFont 还可以提供了一些方法,可以得出给定字符串所占用的宽度和高度,例如,HighscoresScreen计算最高分的X坐标,代码如下:
for (int i = 0; i < 5; i++) {
    highScores[i] = (i + 1) + ". " + Settings.highscores[i];
    xOffset = Math.max(Assets.font.getBounds(highScores[i]).width, xOffset);
}

xOffset = 160 - xOffset / 2 + Assets.font.getSpaceWidth() / 2;    //计算居中显示时的X坐标

这里先把为Setting类中读取到的分数加上序号,然后调用 Assets.font.getBounds(CharSequence)返回指定字符串的 TextBounds 类,而TextBounds类是BitmapFont 的一个内部类,它包含了指定字符串所占用的宽度和高度。通过对比这5个最高分所形成的字符串的宽度,来计算出绘制时,让所有的分数都能够居中显示的X坐标。
最终显示的效果如下截图:



最后再提一个小技巧,先看一下HelpScreen的截图,如下:

libgdx示例-SuperJumper分析 2. 界面与触屏事件_第3张图片

对比后发现,屏幕下方的箭头方向改变了,但是观察游戏加载图片资源的时候,在items.png中只有一个向左的箭头,并没有向右的箭头。其实,这两个箭头都是同一张图片,只是绘制时的参数不同,使得它们的方向相反了,下面是在HelpScreen类中,绘制箭头的代码:
    batcher.begin();          
    batcher.draw(Assets.arrow, 320, 0, -64, 64);
    batcher.end();

      这里把绘制的起点设置在(320,0) 也就是屏幕的右下方,高度设为64pix,而宽度被设为了-64 pix。这就实现了箭头换向的绘制。可以使用这个技巧来处理同一张素材图片的不同需求。
 
转自: http://tonmly.blog.163.com/blog/static/174712856201162845421796/
 

你可能感兴趣的:(游戏,android,vector,工具,float,Matrix)