[libgdx游戏开发教程]使用Libgdx进行游戏开发(11)-高级编程技巧 Box2d和Shader
高级编程技巧只是相对的,其实主要是讲物理模拟和着色器程序的使用。
本章主要讲解利用Box2D并用它来实现萝卜雨,然后是使用单色着色器shader让画面呈现单色状态:http://files.cnblogs.com/mignet/shaders.zip
如果你不知道Box2D,那你肯定玩过用它做的游戏:Angry Birds, Limbo, Tiny Wings, Crayon Physics Deluxe
Libgdx集成了Box2D,类似于其他框架,用了很薄的一层java API进行了封装,等效利用Box2D的所有功能。如果你以前学过Box2D的任何知识,Libgdx这里都可以直接使用。
一些教程:
• C++: http://www.iforce2d.net/b2dtut/
• Objective-C: http://www.raywenderlich.com/28602/intro-to-box2dwith-cocos2d-2-x-tutorial-bouncing-balls
• Flash: http://www.emanueleferonato.com/category/box2d/
• JavaScript: http://blog.sethladd.com/2011/09/box2d-collisiondamage-for-javascript.html
了解Box2D的基本概念:
首先,我们来理解一个看起来很神秘的术语:刚体。
物体,从物理上讲,只是一个物质和一些属性(比如它的位置和方向)的集合,刚体就是不会在外力的作用下变形的理想化的物体。
为简便起见,刚体(rigid body)简写为body。Box2D里提到body其实就是刚体,因为它只支持一种体。
其实Libgdx还集成了另一种物理引擎:Bullet,与Box2D相比,Box2D局限于二维空间和刚体的支持,Bullet支持全面的三维物理模拟以及同时支持刚体和软体。
我们这里专注Box2D,3D物理是个更高端的话题。哈哈
除了位置和方向,刚体还有:质量(kg)速度(m/s)旋转速度(rad/s).
刚体类型:
Static:静态的就是固定的,通常用于地板啊墙啊不动的平台啊等等。不和Static的和Kinematic的物体发生碰撞
Kinematic:可移动的就是位置会变动,可以是手动的或者根据速度变化(首选),通常用于可移动的平台比如电梯,动态物体的镜像等。不和Static的和Kinematic的物体发生碰撞
Dynamic:动态的就是位置会变动,可以是手动或者是在力的作用下(首选),Dynamic可以和所有其他类型的物体碰撞。通常用于玩家,敌人,道具等等
注意到Kinematic的物体是不受力的作用的,它只会根据自己设定的速度来移动。
使用形状:
形状描述的是几何属性,比如圆的半径,矩形的宽和高,或者是一系列的点来描述更复杂的形状的多边形,所以,这些形状定义区域,可以与其他形状进行碰撞检测。
使用夹具:
夹具是为形状增加的材质属性,比如密度,摩擦力,反弹力。夹具附加到形状然后附加到body上,所以夹具在body之间的交互上扮演了十分重要的角色。
物理世界:
world是整个物理模型模拟的一个虚拟的沙盒。每个body都需要放在world里。
Box2D是一个非常丰富的引擎,它还包括很多特性,像约束,关节,传感器,接触监听器等等,这里用不到,就不介绍了。
有个物理body编辑器可以试试:https://code.google.com/p/box2d-editor/
它有些很有用的特性:比如可以把凹边形变成凸多边形,跟踪图像的轮廓,还有个内置的碰撞测试器。
接下来,我们要开始下萝卜雨了。要添加两个新的对象到Canyon Bunny。
一个就是关卡结束的点,通关点。另一个就是一个普通胡萝卜。
当玩家到达通关点,就开始下萝卜雨。在金色的萝卜雕像旁,胡萝卜从天而降,摔倒地上,然后互相堆积。
首先把图片carrot.png,goal.png添加到images。然后打包到pack。
在Assets修改内部类:
public final AtlasRegion carrot;public final AtlasRegion goal;public AssetLevelDecoration(TextureAtlas atlas) {cloud01 = atlas.findRegion("cloud01");cloud02 = atlas.findRegion("cloud02");cloud03 = atlas.findRegion("cloud03");mountainLeft = atlas.findRegion("mountain_left");mountainRight = atlas.findRegion("mountain_right");waterOverlay = atlas.findRegion("water_overlay");carrot = atlas.findRegion("carrot");goal = atlas.findRegion("goal");}
增加Carrot类:
package com.packtpub.libgdx.canyonbunny.game.objects;import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.graphics.g2d.TextureRegion; import com.packtpub.libgdx.canyonbunny.game.Assets; import com.packtpub.libgdx.canyonbunny.game.objects.AbstractGameObject;public class Carrot extends AbstractGameObject {private TextureRegion regCarrot;public Carrot() {init();}private void init() {dimension.set(0.25f, 0.5f);regCarrot = Assets.instance.levelDecoration.carrot;// Set bounding box for collision detectionbounds.set(0, 0, dimension.x, dimension.y);origin.set(dimension.x / 2, dimension.y / 2);}public void render(SpriteBatch batch) {TextureRegion reg = null;reg = regCarrot;batch.draw(reg.getTexture(), position.x - origin.x, position.y- origin.y, origin.x, origin.y, dimension.x, dimension.y,scale.x, scale.y, rotation, reg.getRegionX(), reg.getRegionY(),reg.getRegionWidth(), reg.getRegionHeight(), false, false);} }
增加Goal类:
package com.packtpub.libgdx.canyonbunny.game.objects;import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.graphics.g2d.TextureRegion; import com.packtpub.libgdx.canyonbunny.game.Assets;public class Goal extends AbstractGameObject {private TextureRegion regGoal;public Goal() {init();}private void init() {dimension.set(3.0f, 3.0f);regGoal = Assets.instance.levelDecoration.goal;// Set bounding box for collision detectionbounds.set(1, Float.MIN_VALUE, 10, Float.MAX_VALUE);origin.set(dimension.x / 2.0f, 0.0f);}public void render(SpriteBatch batch) {TextureRegion reg = null;reg = regGoal;batch.draw(reg.getTexture(), position.x - origin.x, position.y- origin.y, origin.x, origin.y, dimension.x, dimension.y,scale.x, scale.y, rotation, reg.getRegionX(), reg.getRegionY(),reg.getRegionWidth(), reg.getRegionHeight(), false, false);} }
编辑Level图片,增加一个红色的色块表示目的点。然后修改Level类:
package com.packtpub.libgdx.canyonbunny.game;import com.badlogic.gdx.Gdx; import com.badlogic.gdx.graphics.Pixmap; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.utils.Array; import com.packtpub.libgdx.canyonbunny.game.objects.AbstractGameObject; import com.packtpub.libgdx.canyonbunny.game.objects.BunnyHead; import com.packtpub.libgdx.canyonbunny.game.objects.Carrot; import com.packtpub.libgdx.canyonbunny.game.objects.Clouds; import com.packtpub.libgdx.canyonbunny.game.objects.Feather; import com.packtpub.libgdx.canyonbunny.game.objects.Goal; import com.packtpub.libgdx.canyonbunny.game.objects.GoldCoin; import com.packtpub.libgdx.canyonbunny.game.objects.Mountains; import com.packtpub.libgdx.canyonbunny.game.objects.Rock; import com.packtpub.libgdx.canyonbunny.game.objects.WaterOverlay;public class Level {public static final String TAG = Level.class.getName();public Array<Carrot> carrots;public Goal goal;public enum BLOCK_TYPE {GOAL(255, 0, 0), // redEMPTY(0, 0, 0), // blackROCK(0, 255, 0), // greenPLAYER_SPAWNPOINT(255, 255, 255), // whiteITEM_FEATHER(255, 0, 255), // purpleITEM_GOLD_COIN(255, 255, 0); // yellowprivate int color;private BLOCK_TYPE(int r, int g, int b) {color = r << 24 | g << 16 | b << 8 | 0xff;}public boolean sameColor(int color) {return this.color == color;}public int getColor() {return color;}}public BunnyHead bunnyHead;public Array<GoldCoin> goldcoins;public Array<Feather> feathers;// objectspublic Array<Rock> rocks;// decorationpublic Clouds clouds;public Mountains mountains;public WaterOverlay waterOverlay;public Level(String filename) {init(filename);}private void init(String filename) {// player characterbunnyHead = null;// objectsrocks = new Array<Rock>();goldcoins = new Array<GoldCoin>();feathers = new Array<Feather>();carrots = new Array<Carrot>();// load image file that represents the level dataPixmap pixmap = new Pixmap(Gdx.files.internal(filename));// scan pixels from top-left to bottom-rightint lastPixel = -1;for (int pixelY = 0; pixelY < pixmap.getHeight(); pixelY++) {for (int pixelX = 0; pixelX < pixmap.getWidth(); pixelX++) {AbstractGameObject obj = null;float offsetHeight = 0;// height grows from bottom to topfloat baseHeight = pixmap.getHeight() - pixelY;// get color of current pixel as 32-bit RGBA valueint currentPixel = pixmap.getPixel(pixelX, pixelY);// find matching color value to identify block type at (x,y)// point and create the corresponding game object if there is// a match// empty spaceif (BLOCK_TYPE.EMPTY.sameColor(currentPixel)) {// do nothing }// rockelse if (BLOCK_TYPE.ROCK.sameColor(currentPixel)) {if (lastPixel != currentPixel) {obj = new Rock();float heightIncreaseFactor = 0.25f;offsetHeight = -2.5f;obj.position.set(pixelX, baseHeight * obj.dimension.y* heightIncreaseFactor + offsetHeight);rocks.add((Rock) obj);} else {rocks.get(rocks.size - 1).increaseLength(1);}}// player spawn pointelse if (BLOCK_TYPE.PLAYER_SPAWNPOINT.sameColor(currentPixel)) {obj = new BunnyHead();offsetHeight = -3.0f;obj.position.set(pixelX, baseHeight * obj.dimension.y+ offsetHeight);bunnyHead = (BunnyHead) obj;}// featherelse if (BLOCK_TYPE.ITEM_FEATHER.sameColor(currentPixel)) {obj = new Feather();offsetHeight = -1.5f;obj.position.set(pixelX, baseHeight * obj.dimension.y+ offsetHeight);feathers.add((Feather) obj);}// gold coinelse if (BLOCK_TYPE.ITEM_GOLD_COIN.sameColor(currentPixel)) {obj = new GoldCoin();offsetHeight = -1.5f;obj.position.set(pixelX, baseHeight * obj.dimension.y+ offsetHeight);goldcoins.add((GoldCoin) obj);}// goalelse if (BLOCK_TYPE.GOAL.sameColor(currentPixel)) {obj = new Goal();offsetHeight = -7.0f;obj.position.set(pixelX, baseHeight + offsetHeight);goal = (Goal) obj;}// unknown object/pixel colorelse {int r = 0xff & (currentPixel >>> 24); // red color channelint g = 0xff & (currentPixel >>> 16); // green color channelint b = 0xff & (currentPixel >>> 8); // blue color channelint a = 0xff & currentPixel; // alpha channelGdx.app.error(TAG, "Unknown object at x<" + pixelX + "> y<"+ pixelY + ">: r<" + r + "> g<" + g + "> b<" + b+ "> a<" + a + ">");}lastPixel = currentPixel;}}// decorationclouds = new Clouds(pixmap.getWidth());clouds.position.set(0, 2);mountains = new Mountains(pixmap.getWidth());mountains.position.set(-1, -1);waterOverlay = new WaterOverlay(pixmap.getWidth());waterOverlay.position.set(0, -3.75f);// free memory pixmap.dispose();Gdx.app.debug(TAG, "level '" + filename + "' loaded");}public void update(float deltaTime) {bunnyHead.update(deltaTime);for (Rock rock : rocks)rock.update(deltaTime);for (GoldCoin goldCoin : goldcoins)goldCoin.update(deltaTime);for (Feather feather : feathers)feather.update(deltaTime);for (Carrot carrot : carrots)carrot.update(deltaTime);clouds.update(deltaTime);}public void render(SpriteBatch batch) {// Draw Mountains mountains.render(batch);// Draw Goal goal.render(batch);// Draw Rocksfor (Rock rock : rocks)rock.render(batch);// Draw Gold Coinsfor (GoldCoin goldCoin : goldcoins)goldCoin.render(batch);// Draw Feathersfor (Feather feather : feathers)feather.render(batch);// Draw Carrotsfor (Carrot carrot : carrots)carrot.render(batch);// Draw Player Character bunnyHead.render(batch);// Draw Water Overlay waterOverlay.render(batch);// Draw Clouds clouds.render(batch);} }
现在,我们修改AbstractGameObject让它下萝卜雨:
public Body body; public void update(float deltaTime) {if (body == null) {updateMotionX(deltaTime);updateMotionY(deltaTime);// Move to new positionposition.x += velocity.x * deltaTime;position.y += velocity.y * deltaTime;} else {position.set(body.getPosition());rotation = body.getAngle() * MathUtils.radiansToDegrees;}}
这里的意思就是说如果我们的物体对象不是body的话,那就按照我们自己的简易物理模拟来移动,如果是的话,就通过Box2D计算反馈的值来移动。
接下来在WorldController添加:
private boolean goalReached;public World b2world;private void initPhysics() {if (b2world != null)b2world.dispose();b2world = new World(new Vector2(0, -9.81f), true);// RocksVector2 origin = new Vector2();for (Rock rock : level.rocks) {BodyDef bodyDef = new BodyDef();bodyDef.type = BodyType.KinematicBody;bodyDef.position.set(rock.position);Body body = b2world.createBody(bodyDef);rock.body = body;PolygonShape polygonShape = new PolygonShape();origin.x = rock.bounds.width / 2.0f;origin.y = rock.bounds.height / 2.0f;polygonShape.setAsBox(rock.bounds.width / 2.0f,rock.bounds.height / 2.0f, origin, 0);FixtureDef fixtureDef = new FixtureDef();fixtureDef.shape = polygonShape;body.createFixture(fixtureDef);polygonShape.dispose();}}
一直记得在world不用的时候释放掉,当然也包括PolygonShape, CircleShape这些Box2D的shape类
body物体都会有蓝色的边框,这是Box2D的类Box2DDebugRenderer的效果。我们在WorldRenderer里加上它:
private static final boolean DEBUG_DRAW_BOX2D_WORLD = false; private Box2DDebugRenderer b2debugRenderer; private void init () { batch = new SpriteBatch(); camera = new OrthographicCamera(Constants.VIEWPORT_WIDTH, Constants.VIEWPORT_HEIGHT); camera.position.set(0, 0, 0); camera.update(); cameraGUI = new OrthographicCamera(Constants.VIEWPORT_GUI_WIDTH, Constants.VIEWPORT_GUI_HEIGHT); cameraGUI.position.set(0, 0, 0); cameraGUI.setToOrtho(true); // flip y-axis cameraGUI.update(); b2debugRenderer = new Box2DDebugRenderer(); } private void renderWorld (SpriteBatch batch) { worldController.cameraHelper.applyTo(camera); batch.setProjectionMatrix(camera.combined); batch.begin(); worldController.level.render(batch); batch.end(); if (DEBUG_DRAW_BOX2D_WORLD) { b2debugRenderer.render(worldController.b2world, camera.combined); } }
增加一些常量准备:
// Number of carrots to spawn public static final int CARROTS_SPAWN_MAX = 100; // Spawn radius for carrots public static final float CARROTS_SPAWN_RADIUS = 3.5f; // Delay after game finished public static final float TIME_DELAY_GAME_FINISHED = 6;
在WorldController添加产生萝卜的代码:
private void spawnCarrots(Vector2 pos, int numCarrots, float radius) {float carrotShapeScale = 0.5f;// create carrots with box2d body and fixturefor (int i = 0; i < numCarrots; i++) {Carrot carrot = new Carrot();// calculate random spawn position, rotation, and scalefloat x = MathUtils.random(-radius, radius);float y = MathUtils.random(5.0f, 15.0f);float rotation = MathUtils.random(0.0f, 360.0f)* MathUtils.degreesToRadians;float carrotScale = MathUtils.random(0.5f, 1.5f);carrot.scale.set(carrotScale, carrotScale);// create box2d body for carrot with start position// and angle of rotationBodyDef bodyDef = new BodyDef();bodyDef.position.set(pos);bodyDef.position.add(x, y);bodyDef.angle = rotation;Body body = b2world.createBody(bodyDef);body.setType(BodyType.DynamicBody);carrot.body = body;// create rectangular shape for carrot to allow// interactions (collisions) with other objectsPolygonShape polygonShape = new PolygonShape();float halfWidth = carrot.bounds.width / 2.0f * carrotScale;float halfHeight = carrot.bounds.height / 2.0f * carrotScale;polygonShape.setAsBox(halfWidth * carrotShapeScale, halfHeight* carrotShapeScale);// set physics attributesFixtureDef fixtureDef = new FixtureDef();fixtureDef.shape = polygonShape;fixtureDef.density = 50;fixtureDef.restitution = 0.5f;fixtureDef.friction = 0.5f;body.createFixture(fixtureDef);polygonShape.dispose();// finally, add new carrot to list for updating/rendering level.carrots.add(carrot);}}
private void onCollisionBunnyWithGoal() {goalReached = true;timeLeftGameOverDelay = Constants.TIME_DELAY_GAME_FINISHED;Vector2 centerPosBunnyHead = new Vector2(level.bunnyHead.position);centerPosBunnyHead.x += level.bunnyHead.bounds.width;spawnCarrots(centerPosBunnyHead, Constants.CARROTS_SPAWN_MAX,Constants.CARROTS_SPAWN_RADIUS);} private void initLevel() {score = 0;scoreVisual = score;goalReached = false;level = new Level(Constants.LEVEL_01);cameraHelper.setTarget(level.bunnyHead);initPhysics();} private void testCollisions (float deltaTIme) { r1.set(level.bunnyHead.position.x, level.bunnyHead.position.y, level.bunnyHead.bounds.width, level.bunnyHead.bounds.height); // Test collision: Bunny Head <-> Rocks ... // Test collision: Bunny Head <-> Gold Coins ... // Test collision: Bunny Head <-> Feathers ... // Test collision: Bunny Head <-> Goal if (!goalReached) { r2.set(level.goal.bounds); r2.x += level.goal.position.x; r2.y += level.goal.position.y; if (r1.overlaps(r2)) onCollisionBunnyWithGoal(); } } public void update (float deltaTime) { handleDebugInput(deltaTime); if (isGameOver() || goalReached) { timeLeftGameOverDelay -= deltaTime; if (timeLeftGameOverDelay < 0) backToMenu(); } else { handleInputGame(deltaTime); } level.update(deltaTime); testCollisions(); b2world.step(deltaTime, 8, 3); cameraHelper.update(deltaTime); }
最后修改Rock:
@Overridepublic void update(float deltaTime) {super.update(deltaTime);floatCycleTimeLeft -= deltaTime;if (floatTargetPosition == null)floatTargetPosition = new Vector2(position);if (floatCycleTimeLeft <= 0) {floatCycleTimeLeft = FLOAT_CYCLE_TIME;floatingDownwards = !floatingDownwards;body.setLinearVelocity(0, FLOAT_AMPLITUDE* (floatingDownwards ? -1 : 1));} else {body.setLinearVelocity(body.getLinearVelocity().scl(0.98f));}/*floatTargetPosition.y += FLOAT_AMPLITUDE* (floatingDownwards ? -1 : 1);}position.lerp(floatTargetPosition, deltaTime);*/}
有人发现了上面提到的问题没?上面明明说world要dispose,但是又没释放,这不是自相矛盾吗?下面我们来释放它,首先要让worldcontroller实现dispose接口:implements Disposable
@Override public void dispose () { if (b2world != null) b2world.dispose(); }
然后修改GameScreen的hide方法:
worldController.dispose();
Ok,大功告成,跑起。。
现在,让我们把注意力放到着色器shader上来。
这也是只有OpenGL (ES) 2.0支持的功能。它就是利用叫做可编程管线的东东。着色器通常是小程序,它允许我们接管控制图形处理器渲染场景的某些阶段。因此,着色器在今天的计算机图形学领域是一个重要的组成部分,也是一个用来创建各种各样的(特殊)的其他方式很难实现的效果的极其强大的工具。为了简单起见,我们在这里将只讨论顶点和片段着色器(vertex and fragment)。
(片段着色器也称为像素着色器,不幸的是,这个名字有点误导,这种类型的着色器实际上操作的是片段而不是像素)
以下原因将告诉你为什么着色器通常是有用的以及为什么强烈建议每个(图形)的程序员把它作为必备列入工具箱
•可编程GPU的渲染管道通过着色器来创建任意复杂的效果。这意味着通过数学公式表述特效的高度灵活性。
•着色器是运行在GPU上的,这会节省CPU时间,让它可以把时间花在其他的任务上,比如做物理和一般游戏逻辑。
•重度的数学计算在gpu上通常比cpu更快的完成。
•GPU可以并行处理顶点和片段。
每个顶点的顶点着色器的操作给了GPU,一个顶点是一个在3d空间带属性(如位置,颜色和纹理坐标)的点.着色器就可以操纵这些值来达到效果,比如一个对象的变形。
通过顶点着色器计算的每个顶点的输出传输到渲染管道作为下一个阶段渲染的输入。
片段着色器计算每个片段像素的颜色,这样,很多因素都可以控制来渲染不同的材质,这些因素包含光照lighting, 透明translucency, 阴影shadows等等。
一个顶点和片段着色器的组合被称为一个着色器程序。
着色器通常是写在一个特定api的高级语言里,比如OpenGL Shading Language (GLSL) for OpenGL。语法类似于C。更多信息请google。
创建一个单色过滤着色器程序
我们先从vertex shader开始
在CanyonBunnyandroid/assets下创建一个子文件夹shaders。然后在里面新建一个文件monochrome.vs。
在里面添加代码:
attribute vec4 a_position; attribute vec4 a_color; attribute vec2 a_texCoord0; varying vec4 v_color; varying vec2 v_texCoords; uniform mat4 u_projTrans; void main() { v_color = a_color; v_texCoords = a_texCoord0; gl_Position = u_projTrans * a_position; }
前6行声明的不同的变量用GLSL的术语叫做存储限定符。
接下来创建文件monochrome.fs,添加代码:
#ifdef GL_ES precision mediump float; #endif varying vec4 v_color; varying vec2 v_texCoords; uniform sampler2D u_texture; uniform float u_amount;void main() {vec4 color = v_color * texture2D(u_texture, v_texCoords);float grayscale = dot(color.rgb, vec3(0.222, 0.707, 0.071));color.rgb = mix(color.rgb, vec3(grayscale), u_amount);gl_FragColor = color; }
接下来,在我们的游戏中使用单色过滤着色器程序。
先在常量里添加
// Shaderpublic static final String shaderMonochromeVertex = "shaders/monochrome.vs";public static final String shaderMonochromeFragment = "shaders/monochrome.fs";
修改GamePreferences的load和save:
public boolean useMonochromeShader; public void load () { showFpsCounter = prefs.getBoolean("showFpsCounter", false); useMonochromeShader = prefs.getBoolean("useMonochromeShader", false); } public void save () { prefs.putBoolean("showFpsCounter", showFpsCounter); prefs.putBoolean("useMonochromeShader", useMonochromeShader); prefs.flush(); }
修改MenuScreen:
private CheckBox chkUseMonochromeShader; private Table buildOptWinDebug () { Table tbl = new Table(); // + Title: "Debug" // + Checkbox, "Show FPS Counter" label // + Checkbox, "Use Monochrome Shader" label chkUseMonochromeShader = new CheckBox("", skinLibgdx); tbl.add(new Label("Use Monochrome Shader", skinLibgdx)); tbl.add(chkUseMonochromeShader); tbl.row(); return tbl; } private void loadSettings () { chkShowFpsCounter.setChecked(prefs.showFpsCounter); chkUseMonochromeShader.setChecked(prefs.useMonochromeShader); } private void saveSettings () { prefs.showFpsCounter = chkShowFpsCounter.isChecked(); prefs.useMonochromeShader = chkUseMonochromeShader.isChecked(); prefs.save(); }
修改WorldRenderer:
private ShaderProgram shaderMonochrome; private void init () { batch = new SpriteBatch(); camera = new OrthographicCamera(Constants.VIEWPORT_WIDTH, Constants.VIEWPORT_HEIGHT); camera.position.set(0, 0, 0); camera.update(); cameraGUI = new OrthographicCamera(Constants.VIEWPORT_GUI_WIDTH, Constants.VIEWPORT_GUI_HEIGHT); cameraGUI.position.set(0, 0, 0); cameraGUI.setToOrtho(true); // flip y-axis cameraGUI.update(); b2debugRenderer = new Box2DDebugRenderer(); shaderMonochrome = new ShaderProgram( Gdx.files.internal(Constants.shaderMonochromeVertex), Gdx.files.internal(Constants.shaderMonochromeFragment)); if (!shaderMonochrome.isCompiled()) { String msg = "Could not compile shader program: " + shaderMonochrome.getLog(); throw new GdxRuntimeException(msg); } } private void renderWorld (SpriteBatch batch) { worldController.cameraHelper.applyTo(camera); batch.setProjectionMatrix(camera.combined); batch.begin(); if (GamePreferences.instance.useMonochromeShader) { batch.setShader(shaderMonochrome); shaderMonochrome.setUniformf("u_amount", 1.0f); } worldController.level.render(batch); batch.setShader(null); batch.end(); if (DEBUG_DRAW_BOX2D_WORLD) { b2debugRenderer.render(worldController.b2world, camera.combined); } } @Override public void dispose () { batch.dispose(); shaderMonochrome.dispose(); }
跑起..
速度计的使用:
在Libgdx中,通常的用法float ax = Gdx.input.getAccelerometerX();
一个取自Android SDK开发网站的图像很好地说明了传感器坐标系统:
直接上代码,首先在常量类中增加:
// Angle of rotation for dead zone (no movement) public static final float ACCEL_ANGLE_DEAD_ZONE = 5.0f; // Max angle of rotation needed to gain max movement velocity public static final float ACCEL_MAX_ANGLE_MAX_MOVEMENT = 20.0f;
然后修改WorldController:
private boolean accelerometerAvailable; private void init () { accelerometerAvailable = Gdx.input.isPeripheralAvailable( Peripheral.Accelerometer); cameraHelper = new CameraHelper(); lives = Constants.LIVES_START; livesVisual = lives; timeLeftGameOverDelay = 0; initLevel(); } private void handleInputGame (float deltaTime) { if (cameraHelper.hasTarget(level.bunnyHead)) { // Player Movement if (Gdx.input.isKeyPressed(Keys.LEFT)) { ... } else { // Use accelerometer for movement if available if (accelerometerAvailable) { // normalize accelerometer values from [-10, 10] to [-1, 1] // which translate to rotations of [-90, 90] degrees float amount = Gdx.input.getAccelerometerY() / 10.0f; amount *= 90.0f; // is angle of rotation inside dead zone? if (Math.abs(amount) < Constants.ACCEL_ANGLE_DEAD_ZONE) { amount = 0; } else { // use the defined max angle of rotation instead of // the full 90 degrees for maximum velocity amount /= Constants.ACCEL_MAX_ANGLE_MAX_MOVEMENT; } level.bunnyHead.velocity.x = level.bunnyHead.terminalVelocity.x * amount; } // Execute auto-forward movement on non-desktop platform else if (Gdx.app.getType() != ApplicationType.Desktop) { level.bunnyHead.velocity.x = level.bunnyHead.terminalVelocity.x; } } } }
效果自己试试吧,本章到此完。
下一章将介绍Libgdx的动作和动画
转载于:https://www.cnblogs.com/mignet/p/libgdx_game_development_11.html
[libgdx游戏开发教程]使用Libgdx进行游戏开发(11)-高级编程技巧 Box2d和Shader相关推荐
- [libGDX游戏开发教程]使用LibGDX进行游戏开发(1)-游戏设计
声明:本章是一个系列的开始,英文原文是<Learning libGDX Game Development>,大家请周知. [libgdx游戏开发教程]使用Libgdx进行游戏开发(2)-游 ...
- [libGDX游戏开发教程]使用libGDX进行游戏开发(12)-Action动画
前文章节列表: 使用libGDX进行游戏开发(11)-高级编程技巧 使用libGDX进行游戏开发(10)-音乐音效不求人,程序员也可以DIY 使用libGDX进行游戏开发(9)-场景过渡 使用li ...
- 视频教程-微信小程序开发教程(第1篇)-微信开发
微信小程序开发教程(第1篇) 微信企业号星级会员.10多年软件从业经历,国家级软件项目负责人,主要从事软件研发.软件企业员工技能培训.已经取得计算机技术与软件资格考试(软考)--"信息系统项 ...
- STM32F103C8T6基础开发教程(HAL库)—开发环境配置
STM32F103C8T6基础开发教程目录 STM32F103C8T6基础开发教程(HAL库)-开发环境配置 STM32F103C8T6基础开发教程(HAL库)-Keil添加注释的快捷键 STM32F ...
- 【SQL Server】数据库开发指南(三)面向数据分析的 T-SQL 编程技巧与实践
本系列博文还在更新中,收录在专栏:#MS-SQL Server 专栏中. 本系列文章列表如下: [SQL Server] Linux 运维下对 SQL Server 进行安装.升级.回滚.卸载操作 [ ...
- python网络游戏开发教程_python 网游开发教程 | python做的游戏有哪些
python游戏开发是做什么的 现在很多的线下培训,质量参差不而且线下的培都十分高昂,加上你的线下生活费的话,学一个技术没有三万是学不下来的,你可以考虑线上的教育,只培训费就会给你省一大笔,学习时间还 ...
- python简单小游戏代码教程,Python简单小游戏代码
球球各位大神怎么用python写一个猜词小游戏的代码? key = input('请输入一个单词:')description = input('输入单词描述:')chance = 5mark = 5p ...
- Libusb开发教程三 USB设备程序开发
首先,需要指出本篇博客的基础仍然是基于 libusb V1.0 以下.主要考虑到保持与之前博客的连贯性,在上一篇博客中介绍了老版本中可以被调用的 API 以及功能介绍,对于库中 API 接口函数的使用 ...
- Kendo UI开发教程 2 准备Kendo UI开发环境
分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章.分享知识,造福人民,实现我们中华民族伟大复兴! 首先你需 ...
- [Minecraft]基于Forge的1.8.9MOD开发教程——第一讲:搭建开发环境
(第一次写教程,有点小紧张呢..) 作者的话 我一开始学习MOD开发时,也曾在各大网站搜索过一些教程,感觉国内的MOD开发教程是真心的少:CSDN没找到,优酷.爱奇艺.腾讯.B站也几乎没有(除了优酷的 ...
最新文章
- CoordinatorLayout 的jar包位置
- 快速提升网站SEO优化排名技巧有哪些?
- CV之LabelImg:图片标注工具之LabelImg(图像标注工具)的简介、安装、使用方法详细攻略
- Ajax.NET 作者发布支持Visual Web Developer 2005的模板安装
- 计算机组装与维修单招,单招职二 计算机组装与维修试卷.doc
- python判断合法整数数字_C# 判断一字符串是否为合法数字(正则表达式)
- mysql sql语句里连接符的使用_SQL中group_concat函数,用符号连接查询分组里字段值...
- 微软未来将在其开发语言整合F#语言(2007-10)
- 浅谈长尾理论--《Makers》读后感
- oracle数据库sqlloader,Oracle 的SQL*LOADER
- python常用的颜色英文表达_python常用颜色
- OA系统中的会议管理
- linux开启IP转发
- 2004年6月30日
- java计算机毕业设计会员商城管理系统源码+mysql数据库+系统+lw文档+部署
- Vuforia 虚拟按钮(Unity 2020)
- java如何让线程sheep_Java面试知识点之线程篇(三)
- 系统数据库的概念结构、逻辑结构、物理结构设计
- 盘一盘 Python 特别篇 19 - 天数计数|年限
- UCML应用框架平台的特点
热门文章
- html5 canvas类库 实例
- 《Python 学习手册4th》 第十六章 函数基础
- 学***必去的网站“***基地”
- python中和操作目录相关的函数包括_python常用的文件目录操作函数
- app登录界面背景 css_Google flutter这么火?撸一个APP登录界面(上)
- python自动化是什么意思_浅谈 Python 自动化
- Unity Transform bug
- Redis之各版本特性
- windows自动导出oracle数据库,Oracle数据库的自动导出备份脚本(windows环境)
- addu指令_大金哥的超标量处理器学习笔记之5——指令集体系