[案例研究]—superJumper 2.界面与触屏事件

注:请务必结合代码理解!

上一节已经得出 MainMenuScreen 为主菜单类,也就是游戏开始后第一个显示的Screen,先看看主菜单的界面,截图如下:
[案例研究]—superJumper   2.界面与触屏事件 - tonmyWu - tonmyWu

下面就来分析一下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图片:
(同样的背景色是后来添加上去的)
[案例研究]—superJumper   2.界面与触屏事件 - tonmyWu - tonmyWu
可以看出其实每个字符就是图片中的一个小区域,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坐标。
最终显示的效果如下截图 :
[案例研究]—superJumper   2.界面与触屏事件 - tonmyWu - tonmyWu
 

最后再提一个小技巧,先看一下HelpScreen的截图,如下:
[案例研究]—superJumper   2.界面与触屏事件 - tonmyWu - tonmyWu
 
对比后发现,屏幕下方的箭头方向改变了,但是观察游戏加载图片资源的时候,在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/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值