您的位置:首页 > 其它

A simple libgdx game (一个简单的游戏)

2014-07-20 13:45 281 查看
在深入钻研libGDX提供的api之前,让我们创建一个简单的小游戏来初步接触一个每个模块。这里将会主要介绍一些设计思想,而非细节。
我们将会看到如下内容:
1.主要的文件操作
2.清屏
3.绘制图片
4.使用相机
5.主要的输入处理
6.播放声音效果
工程的创建就不在赘述了。
The Game (游戏)
游戏的idea很简单:
1.用桶抓住雨滴
2.桶在屏幕的下方
3.雨滴在屏幕的上面随机出现并且垂直下落
4.玩家可以通过鼠标或者触摸或者键盘方向键来水平移动桶
5.游戏没有结束条件。。。

The Assets (资源)

使用到的资源有水滴,桶,还有声音文件。这些文件需要被放在工程目录下面的assets文件夹里。

Configuring the Starter Classes (配置启动者类)

桌面项目中Main.java配置如下:
package com.badlogic.drop;

import com.badlogic.gdx.backends.lwjgl.LwjglApplication;
import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration;

public class Main {
public static void main(String[] args) {
LwjglApplicationConfiguration config = new LwjglApplicationConfiguration();
config.title = "Drop";
config.width = 800;
config.height = 480;
new LwjglApplication(new Drop(), config);
}
}
上面代码配置了config对象的属性,窗口的大小为480*800,标题为“Drop”。
然后再来看android工程。我们要让游戏运行在横屏模式,所以需要在AndroidManifest.xml文件中配置如下:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.badlogic.drop"
android:versionCode="1"
android:versionName="1.0" >

<uses-sdk android:minSdkVersion="8" android:targetSdkVersion="19" />
<uses-feature android:glEsVersion="0x00020000" android:required="true" />

<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:screenOrientation="landscape"
android:configChanges="keyboard|keyboardHidden|orientation">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

</manifest>
上面文件中android:screenOritation被设置成了“landscape”,如果要让游戏运行在竖屏模式,那么就需要设置这个属性值为“portrait”。
我们还想能够保护电池,所以要让加速计和指南正不可用。我们在AndroidLauncher.java中来做这件事,代码如下:
package com.badlogic.drop;

import android.os.Bundle;

import com.badlogic.gdx.backends.android.AndroidApplication;
import com.badlogic.gdx.backends.android.AndroidApplicationConfiguration;

public class AndroidLauncher extends AndroidApplication {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

AndroidApplicationConfiguration config= new AndroidApplicationConfiguration();
config.useAccelerometer = false; //设置加速计不可用
config.useCompass = false;  //设置指南正不可用

initialize(new Drop(), config);
}
}
我们不能定义Acitivity的分辨率,因为它是Android系统设置的。

The Code (代码)

我们把代码分成几个部分讲解。为了简单化,我们把所有的代码都放在了核心项目的Drop.java文件中。

Loading the Assets (载入资源)

我们的第一个任务是要载入资源并且保存他们的引用。资源通常在ApplicationListener.create()方法中载入。所以我们这样做:
public class Drop implements ApplicationListener {
private Texture dropImage;
private Texture bucketImage;
private Sound dropSound;
private Music rainMusic;

@Override
public void create() {
// load the images for the droplet and the bucket, 64x64 pixels each
dropImage = new Texture(Gdx.files.internal("droplet.png"));
bucketImage = new Texture(Gdx.files.internal("bucket.png"));

// load the drop sound effect and the rain background "music"
dropSound = Gdx.audio.newSound(Gdx.files.internal("drop.wav"));
rainMusic = Gdx.audio.newMusic(Gdx.files.internal("rain.mp3"));

// start the playback of the background music immediately
rainMusic.setLooping(true);
rainMusic.play();

... more to come ...
}

// rest of class omitted for clarity
对于每一个资源,我们在Drop类中都有一个变量,所以我们以后可以随时使用它。最开始两行加载了两张图片。一个Texture代表了一个被夹在到图像随机存储器中的图片。一个Texture可以通过向t它的构造函数传递一个指向某个资源文件的FileHandle来加载。这个FileHandle实例通过Gdx.files中的方法获得。有不多不同的文件种类,我们使用“internal”文件类型来指向我们的资源。内部文件就是在Android工程中assets文件下的资源。
下一步,我们加载了音效和背景音乐,liGDX认为sound和music是不同的,sound被存储到内存中,而music不管是否被存储都被当做流来处理。Music通常太大而不能再内从中完全存储。作为一个不成文的规矩,你应该让小于10秒的声音资源是一个Sound,而更长的资源是一个music。
sound和music的加载是通过Gdx.audio.newSound()和Gdx.audio.newMusic()完成的。这两个方法都使用了FileHandle。
在create(0方法的最后,我们设置了music实例循环播放,并且让它立刻播放。

A camera and aSpriteBatch (摄像机和画笔)

下一步,我们要创建一个摄像机和SpriteBatch。我们将使用前者来实现无论设备的屏幕分辨率是多少,我们都是用480*800的分辨率来绘制。SpriteBatch是一个用来绘制2D图片的类,比如绘制我们加载的Texture。
添加两个变量:
private OrthographicCamera camera;
private SpriteBatch batch;
在create()方法中,我们首先创建相机如下:
camera = new OrthographicCamera();
camera.setToOrtho(false, 800, 480);
这将保证相机始终向我们展示一个拥有480*800个单元的一个区域。可以把它想象成一个虚拟的世界。我们先把这些个单元叫做像素,来方便后面的开发。这样就没有什么能阻止我们使用使用其他的单元。Cameras是一个非常给力的工具,你可以使用它来做很多事,这里不做太多的介绍。
下一步,创建一个SpriteBatch,也是在create()方法中。
batch = new SpriteBatch();
这样我们就几乎完成了游戏所需要的所有的初始化操作。

Adding the Bucket (添加桶)

最后我们就要让桶和雨滴表现出来。让我们来想象在代码中需要表现出什么:
1.他们需要有一个在我们的480*800的世界中的坐标位置。
2.他们需要有宽和高
3.他们需要有一个图形来显示,即我们加载的texture
所以,要描述他们,我们需要保存他们的位置和大小。libGDX提供了一个rectangle类,它可以让我们达到目的。添加一个新的变量:
private Rectangle bucket;
在create()方法中,我们实例化它,并且指定它的初始化信息。
bucket = new Rectangle();
bucket.x = 800 / 2 - 64 / 2;
bucket.y = 20;
bucket.width = 64;
bucket.height = 64;
我们让桶水平居中,并且放在距离屏幕底端20像素的位置。等一下,为什么bucket.y设置成了20,不应该是480-20么?在默认情况下,所有在libGDX中的渲染(跟在OpenGL中一样)都是以左下角为起点。

Rendering the Bucket(渲染桶)

是时候渲染桶了。我们要做的第一件事是使用蓝黑色清空屏幕:
@Override
public void render() {
Gdx.gl.glClearColor(0, 0, 0.2f, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

... more to come here ...
}
这两行是唯一关于OpenGL你所要知道的。第一句话我们设置清空颜色为蓝色。参数为argb,每一个值范围在0-1之间。下一句话让OpenGL真正的来清空屏幕。
下一步,我们需要告诉相机来确保它更新了。相机使用了数学上的矩阵来描述绘制的坐标系统。这些矩阵需要在我们改变相机参数的时候进行从新计算,比如改变它的坐标。我们不在这个简单的例子中做这件事了,但是在每一帧来更新相机是很好的事。
camera.update();
现在,我们可以绘制桶了:
batch.setProjectionMatrix(camera.combined);
batch.begin();
batch.draw(bucketImage, bucket.x, bucket.y);
batch.end();
第一句话告诉了SpriteBatch使用相机的坐标系统。像上面描述的一样,这是通过一个叫矩阵的东西完成的,更明确一点,是一个投影矩阵。camera.combined变量就是一个矩阵。此后,SpriteBatch就将绘制前面在坐标系统中所描述的一切东西。
下一步,我们告诉SpriteBatch来开始一个新的批次。为什么我们需要这个,还有什么是batch?
OpenGL 最讨厌的就是告诉它一个一个图片。它想被一次被告诉尽可能多的图片。
SpriteBatch类让OpenGL感到高兴。它将记录在SpriteBatch.begin()和SpriteBatch.end()直接的所有绘制请求,可以在一定程度上加快渲染效率。这开始的时候看起来很笨重,但是如果在每秒60帧渲染500个精灵的情况下和在每秒20帧渲染100个精灵的情况下,就能够看出差别了。
Making the Bucket Move (Keyboard)通过键盘让桶移动
在桌面和浏览器游戏中,我们能够接受键盘输入。我们让桶能够在键盘上左右方向键被按下的时候让桶来移动。
我们让桶的移动不具有加速度,移动速度为每秒200个像素。为了实现基于时间的移动,我们需要知道上一帧和当前帧的时间间隔:
if(Gdx.input.isKeyPressed(Keys.LEFT)) bucket.x -= 200 * Gdx.graphics.getDeltaTime();
if(Gdx.input.isKeyPressed(Keys.RIGHT)) bucket.x += 200 * Gdx.graphics.getDeltaTime();
Gdx.input.isKeyPressed()方法告诉我们某个指定的按键是否被按下。Keys是枚举变量,它包括了所有libGDX支持的按键代码。Gdx.graphics.getDeltaTime()方发返回了上一帧和当前帧的时间间隔。
保证桶在屏幕范围内
if(bucket.x < 0) bucket.x = 0;
if(bucket.x > 800 - 64) bucket.x = 800 - 64;

Adding the Raindrops (添加雨滴)

对于雨滴,我们使用了一个Rectangle的List集合,保存了每一个雨滴的位置和大小。添加变量:
private Array<Rectangle> raindrops;
Array类是libGDX提供的工具来,它代替了java中标准的ArrayList集合。后者的问题是它会以不同的方式产生垃圾。Array类则尽可能的最小化垃圾的产生。liGDX也提供了许多其他的能够回收垃圾的集合,比如hash-maps和sets。
我们还需要记录我们产生一个雨滴的最后时间,所以添加一个变量:
private long lastDropTime;
我们将用纳秒(十亿分之一秒)为单位来保存这个时间,所以使用long类型。
为了帮助雨滴的产生,我们写了一个叫做spawnRaindrop()的方法,这个方法实例化了一个新的Rectangle,设置它的位置为屏幕上方的随机位置,然后把它加入到raindrops集合中。
private void spawnRaindrop() {
Rectangle raindrop = new Rectangle();
raindrop.x = MathUtils.random(0, 800-64);
raindrop.y = 480;
raindrop.width = 64;
raindrop.height = 64;
raindrops.add(raindrop);
lastDropTime = TimeUtils.nanoTime();
}
MathUtils类是一个libGDX提供的一个提供许多和数学相关的静态方法的类。在上面的代码中,它将返回一个0到800-64直接的随机数。TimeUtils是另一个libGDX提供的一个提供许多跟时间有关的静态方法的工具类。在上面的代码中,我们记录了纳秒级别的当前时间,以此来决定是否产生一个新的雨滴。
现在,在create()方法中实例化雨滴集合并且产生第一个雨滴:
raindrops = new Array<Rectangle>();
spawnRaindrop();
下一步,我们在render()函数中添加如下代码来判断自产生上一个雨滴起过了多长时间,如果必要,则产生下一个雨滴:
if(TimeUtils.nanoTime() - lastDropTime > 1000000000) spawnRaindrop();
我们也需要让我们的雨滴移动,速度还是200像素每秒。如果雨滴出到了屏幕下面,那么就从集合中删除:
Iterator<Rectangle> iter = raindrops.iterator();
while(iter.hasNext()) {
Rectangle raindrop = iter.next();
raindrop.y -= 200 * Gdx.graphics.getDeltaTime();
if(raindrop.y + 64 < 0) iter.remove();
}
雨滴也需要渲染:
batch.begin();
batch.draw(bucketImage, bucket.x, bucket.y);
for(Rectangle raindrop: raindrops) {
batch.draw(dropImage, raindrop.x, raindrop.y);
}
batch.end();
最后一道工序:如果雨滴碰撞到了桶,我们要播放drop音效并且把雨滴从集合中删除:
if(raindrop.overlaps(bucket)) {
dropSound.play();
iter.remove();
}
Rectangle.overlaps()方法检测一个rectangle是否和另一个rectangle有重叠的部分。

Clieaning Up (清理)

用户可以在任何时候关闭游戏。对于这个简单的例子,没有什么需要做的。然而,帮助操作系统清理我们产生的混乱总是好的。
在libGDX中任何一个实现了Disposable接口的类都有一个dispose()方法来在它不需要再使用的时候释放掉它占用的资源。所以我们实现了ApplicationListener#dispose()方法:
@Override
public void dispose() {
dropImage.dispose();
bucketImage.dispose();
dropSound.dispose();
rainMusic.dispose();
batch.dispose();
}
一旦释放了一个资源,就不能再使用它了。
可处理的废物资源通常是本地资源,这些资源不能够被java的垃圾回收期处理。这就是为什么我们需要手动清理它们的原因。

Handling Pausing/Resuming (处理暂停、继续)

Android在每次用户有电话进来或者按下了Home键的时候都有暂停和继续的标记。ligGDX会在这种情况下为你自动做很多事情,比如重新加载可能丢失的资源,暂停或者继续音频流等等。
在我们的游戏当中,我们不需要处理暂停和继续。只要用户再次回到游戏中,游戏就会从离开的地方继续运行。

The Full Source (全部代码)

package com.badlogic.drop;import java.util.Iterator;import com.badlogic.gdx.ApplicationListener;import com.badlogic.gdx.Gdx;import com.badlogic.gdx.Input.Keys;import com.badlogic.gdx.audio.Music;import com.badlogic.gdx.audio.Sound;import com.badlogic.gdx.graphics.GL20;import com.badlogic.gdx.graphics.OrthographicCamera;import com.badlogic.gdx.graphics.Texture;import com.badlogic.gdx.graphics.g2d.SpriteBatch;import com.badlogic.gdx.math.MathUtils;import com.badlogic.gdx.math.Rectangle;import com.badlogic.gdx.math.Vector3;import com.badlogic.gdx.utils.Array;import com.badlogic.gdx.utils.TimeUtils;public class Drop implements ApplicationListener {private Texture dropImage;private Texture bucketImage;private Sound dropSound;private Music rainMusic;private SpriteBatch batch;private OrthographicCamera camera;private Rectangle bucket;private Array<Rectangle> raindrops;private long lastDropTime;@Overridepublic void create() {// load the images for the droplet and the bucket, 64x64 pixels eachdropImage = new Texture(Gdx.files.internal("droplet.png"));bucketImage = new Texture(Gdx.files.internal("bucket.png"));// load the drop sound effect and the rain background "music"dropSound = Gdx.audio.newSound(Gdx.files.internal("drop.wav"));rainMusic = Gdx.audio.newMusic(Gdx.files.internal("rain.mp3"));// start the playback of the background music immediatelyrainMusic.setLooping(true);rainMusic.play();// create the camera and the SpriteBatchcamera = new OrthographicCamera();
camera.setToOrtho(false, 800, 480);batch = new SpriteBatch();// create a Rectangle to logically represent the bucketbucket = new Rectangle();bucket.x = 800 / 2 - 64 / 2; // center the bucket horizontallybucket.y = 20; // bottom left corner of the bucket is 20 pixels above the bottom screen edgebucket.width = 64;bucket.height = 64;// create the raindrops array and spawn the first raindropraindrops = new Array<Rectangle>();
spawnRaindrop();}private void spawnRaindrop() {
Rectangle raindrop = new Rectangle();
raindrop.x = MathUtils.random(0, 800-64);
raindrop.y = 480;
raindrop.width = 64;
raindrop.height = 64;
raindrops.add(raindrop);
lastDropTime = TimeUtils.nanoTime();
}@Overridepublic void render() {// clear the screen with a dark blue color. The// arguments to glClearColor are the red, green// blue and alpha component in the range [0,1]// of the color to be used to clear the screen.Gdx.gl.glClearColor(0, 0, 0.2f, 1);Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);// tell the camera to update its matrices.camera.update();// tell the SpriteBatch to render in the// coordinate system specified by the camera.batch.setProjectionMatrix(camera.combined);// begin a new batch and draw the bucket and// all dropsbatch.begin();batch.draw(bucketImage, bucket.x, bucket.y);for(Rectangle raindrop: raindrops) {batch.draw(dropImage, raindrop.x, raindrop.y);}batch.end();// process user inputif(Gdx.input.isTouched()) {Vector3 touchPos = new Vector3();touchPos.set(Gdx.input.getX(), Gdx.input.getY(), 0);camera.unproject(touchPos);bucket.x = touchPos.x - 64 / 2;}if(Gdx.input.isKeyPressed(Keys.LEFT)) bucket.x -= 200 * Gdx.graphics.getDeltaTime();
if(Gdx.input.isKeyPressed(Keys.RIGHT)) bucket.x += 200 * Gdx.graphics.getDeltaTime();// make sure the bucket stays within the screen boundsif(bucket.x < 0) bucket.x = 0;
if(bucket.x > 800 - 64) bucket.x = 800 - 64;// check if we need to create a new raindropif(TimeUtils.nanoTime() - lastDropTime > 1000000000) spawnRaindrop();// move the raindrops, remove any that are beneath the bottom edge of// the screen or that hit the bucket. In the later case we play back// a sound effect as well.Iterator<Rectangle> iter = raindrops.iterator();while(iter.hasNext()) {Rectangle raindrop = iter.next();raindrop.y -= 200 * Gdx.graphics.getDeltaTime();if(raindrop.y + 64 < 0) iter.remove();if(raindrop.overlaps(bucket)) {
dropSound.play();
iter.remove();
}}}@Overridepublic void dispose() {// dispose of all the native resourcesdropImage.dispose();bucketImage.dispose();dropSound.dispose();rainMusic.dispose();batch.dispose();}@Overridepublic void resize(int width, int height) {}@Overridepublic void pause() {}@Overridepublic void resume() {}}

                                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐