[libgdx游戏开发教程]使用Libgdx进行游戏开发(9)-场景过渡
本章主要讲解场景过渡效果的使用。这里将用到Render to Texture(RTT)技术。
Libgdx提供了一个类,实现了各种常见的插值算法,不仅适合过渡效果,也适合任意特定行为。
在本游戏里面,我们将实现3种转场效果:fade, slide和slice.
和前面提到的多场景管理一样,我们也需要这样的结构来统一管理转场特效:
首先创建接口ScreenTransition:
package com.packtpub.libgdx.canyonbunny.screens.transitions; import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.SpriteBatch; public interface ScreenTransition {
public float getDuration(); public void render(SpriteBatch batch, Texture currScreen,
Texture nextScreen, float alpha);
}
OpenGL有个叫做Framebuffer Objects(FBO)的特性,它允许在内存中把离屏画面渲染到纹理中。
要使用这一特性,实例化Libgdx的Framebuffer类即可,一般的用法是:
// ...
Framebuffer fbo;
fbo = new Framebuffer(Format.RGB888, width, height, false);
fbo.begin(); // set render target to FBO's texture buffer
Gdx.gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // solid black
Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT); // clear FBO batch.
draw(someTextureRegion, 0, 0); // draw (to FBO)
fbo.end(); // revert render target back to normal
// retrieve result
Texture fboTexture = fbo.getColorBufferTexture();
// ...
其实这个特性是OpenGL ES 2.0模式下的,我们假设现在的硬件都支持了。哈哈。
为了让我们的Project支持OpenGL ES 2.0,需要做以下改动:
main方法:cfg.useGL20 = true;
把Android工程里的AndroidManifest.xml增加一行:
<uses-feature android:glEsVersion="0x00020000" android:required="true" />
还有MainActivity里修改cfg.useGL20 = true;
HTML5项目就不用修改了,因为GWT一直就是用的OpenGL ES 2.0模式在渲染。
接下来,我们添加一个新的类DirectedGame来管理场景使用过渡的情况(替换掉Libgdx的Game类):
package com.packtpub.libgdx.canyonbunny.screens; import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Pixmap.Format;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.glutils.FrameBuffer;
import com.packtpub.libgdx.canyonbunny.screens.transitions.ScreenTransition; public abstract class DirectedGame implements ApplicationListener {
private boolean init;
private AbstractGameScreen currScreen;
private AbstractGameScreen nextScreen;
private FrameBuffer currFbo;
private FrameBuffer nextFbo;
private SpriteBatch batch;
private float t;
private ScreenTransition screenTransition; public void setScreen(AbstractGameScreen screen) {
setScreen(screen, null);
} public void setScreen(AbstractGameScreen screen,
ScreenTransition screenTransition) {
int w = Gdx.graphics.getWidth();
int h = Gdx.graphics.getHeight();
if (!init) {
currFbo = new FrameBuffer(Format.RGB888, w, h, false);
nextFbo = new FrameBuffer(Format.RGB888, w, h, false);
batch = new SpriteBatch();
init = true;
}
// start new transition
nextScreen = screen;
nextScreen.show(); // activate next screen
nextScreen.resize(w, h);
nextScreen.render(0); // let screen update() once
if (currScreen != null)
currScreen.pause();
nextScreen.pause();
Gdx.input.setInputProcessor(null); // disable input
this.screenTransition = screenTransition;
t = 0;
}
}
重写ApplicationListener接口的方法:
@Override
public void render() {
// get delta time and ensure an upper limit of one 60th second
float deltaTime = Math.min(Gdx.graphics.getDeltaTime(), 1.0f / 60.0f);
if (nextScreen == null) {
// no ongoing transition
if (currScreen != null)
currScreen.render(deltaTime);
} else {
// ongoing transition
float duration = 0;
if (screenTransition != null)
duration = screenTransition.getDuration();
// update progress of ongoing transition
t = Math.min(t + deltaTime, duration);
if (screenTransition == null || t >= duration) {
// no transition effect set or transition has just finished
if (currScreen != null)
currScreen.hide();
nextScreen.resume();
// enable input for next screen
Gdx.input.setInputProcessor(nextScreen.getInputProcessor());
// switch screens
currScreen = nextScreen;
nextScreen = null;
screenTransition = null;
} else {
// render screens to FBOs
currFbo.begin();
if (currScreen != null)
currScreen.render(deltaTime);
currFbo.end();
nextFbo.begin();
nextScreen.render(deltaTime);
nextFbo.end();
// render transition effect to screen
float alpha = t / duration;
screenTransition.render(batch, currFbo.getColorBufferTexture(),
nextFbo.getColorBufferTexture(), alpha);
}
}
} @Override
public void resize(int width, int height) {
if (currScreen != null)
currScreen.resize(width, height);
if (nextScreen != null)
nextScreen.resize(width, height);
} @Override
public void pause() {
if (currScreen != null)
currScreen.pause();
} @Override
public void resume() {
if (currScreen != null)
currScreen.resume();
} @Override
public void dispose() {
if (currScreen != null)
currScreen.hide();
if (nextScreen != null)
nextScreen.hide();
if (init) {
currFbo.dispose();
currScreen = null;
nextFbo.dispose();
nextScreen = null;
batch.dispose();
init = false;
}
}
最后,我们要把先前用的Game类的地方,替换成DirectedGame.
首先修改AbstractGameScreen:
protected DirectedGame game; public AbstractGameScreen(DirectedGame game) {
this.game = game;
} public abstract InputProcessor getInputProcessor();
然后修改CanyonBunnyMain然它继承DirectedGame。
还有MenuScreen的构造函数改成public MenuScreen (DirectedGame game),同时添加:
@Override
public void show() {
stage = new Stage();
rebuildStage();
}
@Override
public InputProcessor getInputProcessor() {
return stage;
}
同样,GameScreen相应修改构造函数参数类型Game为DirectedGame,还有添加:
@Override
public InputProcessor getInputProcessor() {
return worldController;
}
最后,修改worldcontroller里的game为DirectedGame,移除init中的InputProcessor:
private void init () {
cameraHelper = new CameraHelper();
lives = Constants.LIVES_START;
livesVisual = lives;
timeLeftGameOverDelay = 0;
initLevel();
}
应用的代码已经修改完成了,但是我们的转场特效现在还是空的,接下来一步步实现它。
这里有必要普及一下理论知识,转场特效的核心就是插值算法,Libgdx已经实现了很多线性和非线性的插值算法,我们来看看这些算法图:
通俗的讲,这些图就相当于每一个供查询的表,用户提供一个阿尔法值(x轴),通过表就得到一个结果值(y轴)。
在其他的游戏引擎中,比如Cocos2D引擎,已经封装了很多转场效果供开发者调用,比如Cocos里的FadeIn和FadeOut,就是对应于fade图的阿尔法的取值范围0-0.5 和 0.5-1。
在Libgdx中,这些特效都是不固定的,开发者可以自由组合。通常用法是这样的:
float alpha = 0.25f;
float interpolatedValue = Interpolation.elastic.apply(alpha);
这里的阿尔法除了可以理解为参数以外,还可以理解为是整个动作进行的百分比。
下面我们来创建fade, slide 和 slice效果。
fade就是当前场景从不透明到完全透明,同时新场景从透明到完全不透明。
创建类ScreenTransitionFade:
package com.packtpub.libgdx.canyonbunny.screens.transitions; 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.math.Interpolation; public class ScreenTransitionFade implements ScreenTransition {
private static final ScreenTransitionFade instance = new ScreenTransitionFade();
private float duration; public static ScreenTransitionFade init(float duration) {
instance.duration = duration;
return instance;
} @Override
public float getDuration() {
return duration;
} @Override
public void render(SpriteBatch batch, Texture currScreen,
Texture nextScreen, float alpha) {
float w = currScreen.getWidth();
float h = currScreen.getHeight();
alpha = Interpolation.fade.apply(alpha);
Gdx.gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
batch.begin();
batch.setColor(1, 1, 1, 1);
batch.draw(currScreen, 0, 0, 0, 0, w, h, 1, 1, 0, 0, 0,
currScreen.getWidth(), currScreen.getHeight(), false, true);
batch.setColor(1, 1, 1, alpha);
batch.draw(nextScreen, 0, 0, 0, 0, w, h, 1, 1, 0, 0, 0,
nextScreen.getWidth(), nextScreen.getHeight(), false, true);
batch.end();
}
}
现在,我们把play按钮的clicked代码修改一下,用上fade的效果:
ScreenTransition transition = ScreenTransitionFade.init(0.75f);
game.setScreen(new GameScreen(game), transition);
创建类ScreenTransitionSlide:
package com.packtpub.libgdx.canyonbunny.screens.transitions; 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.math.Interpolation; public class ScreenTransitionSlide implements ScreenTransition {
public static final int LEFT = 1;
public static final int RIGHT = 2;
public static final int UP = 3;
public static final int DOWN = 4;
private static final ScreenTransitionSlide instance = new ScreenTransitionSlide();
private float duration;
private int direction;
private boolean slideOut;
private Interpolation easing; public static ScreenTransitionSlide init(float duration, int direction,
boolean slideOut, Interpolation easing) {
instance.duration = duration;
instance.direction = direction;
instance.slideOut = slideOut;
instance.easing = easing;
return instance;
} @Override
public float getDuration() {
return duration;
} @Override
public void render(SpriteBatch batch, Texture currScreen,
Texture nextScreen, float alpha) {
float w = currScreen.getWidth();
float h = currScreen.getHeight();
float x = 0;
float y = 0;
if (easing != null)
alpha = easing.apply(alpha);
// calculate position offset
switch (direction) {
case LEFT:
x = -w * alpha;
if (!slideOut)
x += w;
break;
case RIGHT:
x = w * alpha;
if (!slideOut)
x -= w;
break;
case UP:
y = h * alpha;
if (!slideOut)
y -= h;
break;
case DOWN:
y = -h * alpha;
if (!slideOut)
y += h;
break;
}
// drawing order depends on slide type ('in' or 'out')
Texture texBottom = slideOut ? nextScreen : currScreen;
Texture texTop = slideOut ? currScreen : nextScreen;
// finally, draw both screens
Gdx.gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
batch.begin();
batch.draw(texBottom, 0, 0, 0, 0, w, h, 1, 1, 0, 0, 0,
currScreen.getWidth(), currScreen.getHeight(), false, true);
batch.draw(texTop, x, y, 0, 0, w, h, 1, 1, 0, 0, 0,
nextScreen.getWidth(), nextScreen.getHeight(), false, true);
batch.end();
}
}
然后在worldcontroller的backmenu里使用这个效果:
private void backToMenu() {
// switch to menu screen
ScreenTransition transition = ScreenTransitionSlide.init(0.75f,
ScreenTransitionSlide.DOWN, false, Interpolation.bounceOut);
game.setScreen(new MenuScreen(game), transition);
}
创建类ScreenTransitionSlice:
package com.packtpub.libgdx.canyonbunny.screens.transitions; 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.math.Interpolation;
import com.badlogic.gdx.utils.Array; public class ScreenTransitionSlice implements ScreenTransition {
public static final int UP = 1;
public static final int DOWN = 2;
public static final int UP_DOWN = 3;
private static final ScreenTransitionSlice instance = new ScreenTransitionSlice();
private float duration;
private int direction;
private Interpolation easing;
private Array<Integer> sliceIndex = new Array<Integer>(); public static ScreenTransitionSlice init(float duration, int direction,
int numSlices, Interpolation easing) {
instance.duration = duration;
instance.direction = direction;
instance.easing = easing;
// create shuffled list of slice indices which determines
// the order of slice animation
instance.sliceIndex.clear();
for (int i = 0; i < numSlices; i++)
instance.sliceIndex.add(i);
instance.sliceIndex.shuffle();
return instance;
} @Override
public float getDuration() {
return duration;
} @Override
public void render(SpriteBatch batch, Texture currScreen,
Texture nextScreen, float alpha) {
float w = currScreen.getWidth();
float h = currScreen.getHeight();
float x = 0;
float y = 0;
int sliceWidth = (int) (w / sliceIndex.size);
Gdx.gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
batch.begin();
batch.draw(currScreen, 0, 0, 0, 0, w, h, 1, 1, 0, 0, 0,
currScreen.getWidth(), currScreen.getHeight(), false, true);
if (easing != null)
alpha = easing.apply(alpha);
for (int i = 0; i < sliceIndex.size; i++) {
// current slice/column
x = i * sliceWidth;
// vertical displacement using randomized
// list of slice indices
float offsetY = h
* (1 + sliceIndex.get(i) / (float) sliceIndex.size);
switch (direction) {
case UP:
y = -offsetY + offsetY * alpha;
break;
case DOWN:
y = offsetY - offsetY * alpha;
break;
case UP_DOWN:
if (i % 2 == 0) {
y = -offsetY + offsetY * alpha;
} else {
y = offsetY - offsetY * alpha;
}
break;
}
batch.draw(nextScreen, x, y, 0, 0, sliceWidth, h, 1, 1, 0, i
* sliceWidth, 0, sliceWidth, nextScreen.getHeight(), false,
true);
}
batch.end();
}
}
在CanyonBunnyMain中使用这个特效:
@Override
public void create () {
// Set Libgdx log level
Gdx.app.setLogLevel(Application.LOG_DEBUG);
// Load assets
Assets.instance.init(new AssetManager());
// Start game at menu screen
ScreenTransition transition = ScreenTransitionSlice.init(2,
ScreenTransitionSlice.UP_DOWN, 10, Interpolation.pow5Out);
setScreen(new MenuScreen(this), transition);
}
ok,增加了转场效果之后,游戏是不是漂亮了许多?
在下一章,我们将添加音乐和音效。
[libgdx游戏开发教程]使用Libgdx进行游戏开发(9)-场景过渡的更多相关文章
- [libGDX游戏开发教程]使用libGDX进行游戏开发(12)-Action动画
前文章节列表: 使用libGDX进行游戏开发(11)-高级编程技巧 使用libGDX进行游戏开发(10)-音乐音效不求人,程序员也可以DIY 使用libGDX进行游戏开发(9)-场景过渡 ...
- [libGDX游戏开发教程]使用libGDX进行游戏开发(1)-游戏设计
声明:<使用Libgdx进行游戏开发>是一个系列,文章的原文是<Learning Libgdx Game Development>,大家请周知.后续的文章连接在这里 使用Lib ...
- 使用Html5+C#+微信 开发移动端游戏详细教程: (四)游戏中层的概念与设计
众所周知,网站的前端页面结构一般是由div组成,父div包涵子div,子div包涵各种标签和项, 同理,游戏中我们也将若干游戏模块拆分成层,在后续的代码维护和游戏程序逻辑中将更加清晰和便于控制. We ...
- 微信小程序开发教程 #043 - 在小程序开发中使用 npm
本文介绍了如何在微信小程序开发中使用 npm 中包的功能,大大提高微信小程序的开发效率,同时也是微信小程序系列教程的视频版更新. 微信小程序在发布之初没有对 npm 的支持功能,这也是目前很多前端开发 ...
- PythonWeb开发教程(一),开发之前需要准备什么
什么是web开发呢,其实就是开发一个网站了.那开发网站需要用到哪些知识呢 1.python基础,因为用python开发的,所以python指定要会,最起码你也得会条件判断,循环,函数,类这些知识: 2 ...
- [libgdx游戏开发教程]使用Libgdx进行游戏开发(11)-高级编程技巧 Box2d和Shader
高级编程技巧只是相对的,其实主要是讲物理模拟和着色器程序的使用. 本章主要讲解利用Box2D并用它来实现萝卜雨,然后是使用单色着色器shader让画面呈现单色状态:http://files.cnblo ...
- [libgdx游戏开发教程]使用Libgdx进行游戏开发(10)-音乐和音效
本章音效文件都来自于公共许可: http://files.cnblogs.com/mignet/sounds.zip 在游戏中,播放背景音乐和音效是基本的功能. Libgdx提供了跨平台的声音播放功能 ...
- [libgdx游戏开发教程]使用Libgdx进行游戏开发(2)-游戏框架搭建
让我们抛开理论开始code吧. 入口类CanyonBunnyMain的代码: package com.packtpub.libgdx.canyonbunny; import com.badlogic. ...
- [libgdx游戏开发教程]使用Libgdx进行游戏开发(7)-屏幕布局的最佳实践
管理多个屏幕 我们的菜单屏有2个按钮,一个play一个option.option里就是一些开关的设置,比如音乐音效等.这些设置将会保存到Preferences中. 多屏幕切换是游戏的基本机制,Libg ...
随机推荐
- Zebra - zebra command to get printer status
/// <summary> /// determine whether the network printer is in pause. /// </summary> /// ...
- [转]掌握 Dojo 工具包,第 2 部分: XHR 框架与 Dojo
作者:secooler 快乐的DBA Ajax 的兴起改变了传统的 B/S 结构应用程序中以页面为单位的交互模式,Ajax 引入的局部刷新机制带来了更好的用户体验,促使浏览器中的页面开始向应用程序发展 ...
- hdu 3500 Fling (dfs)
Fling Time Limit: 6000/3000 MS (Java/Others) Memory Limit: 65535/65535 K (Java/Others)Total Submi ...
- P1196 [NOI2002]银河英雄传说
题目描述 公元五八○一年,地球居民迁至金牛座α第二行星,在那里发表银河联邦创立宣言,同年改元为宇宙历元年,并开始向银河系深处拓展. 宇宙历七九九年,银河系的两大军事集团在巴米利恩星域爆发战争.泰山压顶 ...
- TestRedis
import org.junit.Before; import org.junit.Test; import redis.clients.jedis.Jedis; import java.util.H ...
- 链接oracle数据库 生成表对应的javabean
package com.databi.utils; import java.io.File; import java.io.FileOutputStream; import java.io.IOExc ...
- JVM内存模型 三
本文章节: 1.JMM简介 2.堆和栈 3.本机内存 4.防止内存泄漏 1.JMM简介 i.内存模型概述 Java平台自动集成了线程以及多处理器技术,这种集成程度比Java以前诞生的计算机语言要厉 ...
- mysql root设置密码 linux
成功方案 mysqld_safe --user=mysql --skip-grant-tables --skip-networking & [root@localhost ~]# mysql ...
- codechef T6 Pishty and tree dfs序+线段树
PSHTTR: Pishty 和城堡题目描述 Pishty 是生活在胡斯特市的一个小男孩.胡斯特是胡克兰境内的一个古城,以其中世纪风格 的古堡和非常聪明的熊闻名全国. 胡斯特的镇城之宝是就是这么一座古 ...
- 关于集合的size的操作
1.创建集合: 创建指定大小的集合:(大小为5) db.createCollection(}) 2.插入五条数据: > db.colle1.insert({name:}) WriteResult ...