码迷,mamicode.com
首页 > 其他好文 > 详细

Libgdx之正交相机 OrthographicCamera

时间:2016-08-09 22:19:41      阅读:529      评论:0      收藏:0      [点我收藏+]

标签:

本翻译自Libgdx Wiki
本文主要介绍OrthographicCamera相机类和用法。OrthographicCamera是正交相机,用在2D游戏开发中,无论游戏物体放在游戏世界中的那个位置,用正交相机看到的物体都不会被缩放。

描述

正交相机的操作非常简单,就像我们在现实世界中操作相机一样,文章中主要介绍:

  • 相机的移动和旋转
  • 相机的放到和缩小
  • 改变相机的视窗大小
  • 在窗口(widow)坐标系和(世界)坐标系之间切换点的位置

    使用正交相机可以在不必去操作矩阵的情况下非常方便的来移动游戏世界,所以的投影矩阵和视图矩阵都在后台实现。
    下面的代码示例展示了怎样通过照相机在改变游戏世界

import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.backends.lwjgl.LwjglApplication;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.MathUtils;

public class OrthographicCameraExample implements ApplicationListener {

    static final int WORLD_WIDTH = 100;
    static final int WORLD_HEIGHT = 100;

    private OrthographicCamera cam;
    private SpriteBatch batch;

    private Sprite mapSprite;
    private float rotationSpeed;

    @Override
    public void create() {
        rotationSpeed = 0.5f;

        mapSprite = new Sprite(new Texture(Gdx.files.internal("sc_map.png")));
        mapSprite.setPosition(0, 0);
        mapSprite.setSize(WORLD_WIDTH, WORLD_HEIGHT);

        float w = Gdx.graphics.getWidth();
        float h = Gdx.graphics.getHeight();

        // Constructs a new OrthographicCamera, using the given viewport width and height
        // Height is multiplied by aspect ratio.
        cam = new OrthographicCamera(30, 30 * (h / w));

        cam.position.set(cam.viewportWidth / 2f, cam.viewportHeight / 2f, 0);
        cam.update();

        batch = new SpriteBatch();
    }

    @Override
    public void render() {
        handleInput();
        cam.update();
        batch.setProjectionMatrix(cam.combined);

        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

        batch.begin();
        mapSprite.draw(batch);
        batch.end();
    }

    private void handleInput() {
        if (Gdx.input.isKeyPressed(Input.Keys.A)) {
            cam.zoom += 0.02;
        }
        if (Gdx.input.isKeyPressed(Input.Keys.Q)) {
            cam.zoom -= 0.02;
        }
        if (Gdx.input.isKeyPressed(Input.Keys.LEFT)) {
            cam.translate(-3, 0, 0);
        }
        if (Gdx.input.isKeyPressed(Input.Keys.RIGHT)) {
            cam.translate(3, 0, 0);
        }
        if (Gdx.input.isKeyPressed(Input.Keys.DOWN)) {
            cam.translate(0, -3, 0);
        }
        if (Gdx.input.isKeyPressed(Input.Keys.UP)) {
            cam.translate(0, 3, 0);
        }
        if (Gdx.input.isKeyPressed(Input.Keys.W)) {
            cam.rotate(-rotationSpeed, 0, 0, 1);
        }
        if (Gdx.input.isKeyPressed(Input.Keys.E)) {
            cam.rotate(rotationSpeed, 0, 0, 1);
        }

        cam.zoom = MathUtils.clamp(cam.zoom, 0.1f, 100/cam.viewportWidth);

        float effectiveViewportWidth = cam.viewportWidth * cam.zoom;
        float effectiveViewportHeight = cam.viewportHeight * cam.zoom;

        cam.position.x = MathUtils.clamp(cam.position.x, effectiveViewportWidth / 2f, 100 - effectiveViewportWidth / 2f);
        cam.position.y = MathUtils.clamp(cam.position.y, effectiveViewportHeight / 2f, 100 - effectiveViewportHeight / 2f);
    }

    @Override
    public void resize(int width, int height) {
        cam.viewportWidth = 30f;
        cam.viewportHeight = 30f * height/width;
        cam.update();
    }

    @Override
    public void resume() {
    }

    @Override
    public void dispose() {
        mapSprite.getTexture().dispose();
        batch.dispose();
    }

    @Override
    public void pause() {
    }

    public static void main(String[] args) {
        new LwjglApplication(new OrthographicCameraExample());
    }
}

上面代码展示了Libgdx程序如何通过正交相机来移动游戏世界。注意: 我们的游戏世界可以是任意单位,在此列中我们设置为100*100
当谈论到游戏世界时人们通常错误的认为游戏单位是像素,这也是不可避免发生的。如果有这种奇怪的每个单位中有多少像素的想法在你游戏代码中,这将会导致一些不必要的乘以或者除以常量,这种错误的理解方式是你困惑不解。当停止这种思考方式时许多其它问题将会得以避免
这些单位是什么?它们意味着什么?我怎样设计游戏对象的大小?在屏幕上应该展示多少个单位?我们很快就会学习到。

    private OrthographicCamera cam;  *1
    private SpriteBatch batch;       *2

    private Sprite mapSprite;        *3
    private float rotationSpeed;     *4

*1 - OrthographicCamera的实例,我们控制它来观察游戏世界
*2 - SpriteBatch 渲染游戏世界
*3 - Sprite 在游戏世界中渲染的地图
*4 - 旋转相机的速度


@Override
public void create() {
    rotationSpeed = 0.5f;  *1
    mapSprite = new Sprite(new Texture(Gdx.files.internal("sc_map.png")));   *2
    mapSprite.setPosition(0, 0);   *3                                       
    mapSprite.setSize(WORLD_WIDTH, WORLD_HEIGHT);   *4                      
    float w = Gdx.graphics.getWidth();   *5
    float h = Gdx.graphics.getHeight();  *6
    cam = new OrthographicCamera(30, 30 * (h / w));  *7
    cam.position.set(cam.viewportWidth / 2f, cam.viewportHeight / 2f, 0);    *8
    cam.update();    *9

    batch = new SpriteBatch();    *10
}

当我们创建ApplicationListener的实例的时候会调用create方法,在此处我们初始化变量。
*1 - 设置当前旋转速度为 0.5 弧度
*2 - 初始化Sprite,并且赋给它纹理:sc_map.png,点击此处下载纹理,并且复制到 assets 目录下面
*3 - 设置mapSprite的位置为(0, 0)。(并不需要严格按照代码来,因为Sprite的默认位置也是0,0)
*4 - 设置mapSprite的大小为世界的大小,sprite有一个100*100的维度,也就是世界大小
*5 - 创建局部变量 width 表示应用显示屏的宽度
*6 - 创建局部变量height 表示应用显示屏的高度
*7 - 创建正交相机,2个参数规定了视窗的高宽,也觉得了在各个轴上能看到的游戏世界的大小。
在示例代码中,设置视窗宽为30, 视窗高为 30 * (h/w)。视窗宽度没啥好说的,即在x轴能看到游戏世界为30个单位。视窗高度我们设置为30 * 屏幕纵横比。这也是游戏能画出来正确比例。想象一下,如果我们忽略纵横比,设置视窗的高宽为30 * 30,当渲染一个30*30的物体时,在屏幕上渲染的物体会发生变形,成为压扁的正方形,除非屏幕也是一个正方形,但这基本不可能。30*30的物体为什么会发生变形,因为我们设置了视窗的高宽为30 * 30,这不符合屏幕的纵横比

  1. 如果创建的相机视窗高宽为100*100(new OrthographicCamera(100, 100)),并且相机居中,我们能看到整张地图。
  2. 如果创建的相机视窗高宽为100*50(new OrthographicCamera(100, 50)),并且相机居中,我们能看到半张地图。
  3. 如果创建的相机视窗高宽为50*50(new OrthographicCamera(50, 50)),并且相机居中,我们能看到1/4张地图。

*8 - 设置相机初始位置位于地图左下角,但是…相机位置是位于相机中心,所以我们移动相机位置,相机位置.x + half viewport width, 相机位置.y + half viewport height,位置移动之后,相机位于0,0
*9 - 更新相机,无论任何时候操作相机之后到要 update 来更新相机里的矩阵
*10 - 创建SpriteBatch实例
创建完成了,开始渲染和操作相机吧!


@Override
public void render() {
    handleInput();                             *1
    cam.update();                              *2                             
    batch.setProjectionMatrix(cam.combined);   *3

    Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);  *4

    batch.begin();                             *5
    mapSprite.draw(batch);                     *6
    batch.end();                               *7
}

*1 - 根据不同的按键操作相机的位置,缩放,旋转
*2 - 更新相机
*3 - 根据相机的视图和投影矩阵更新SpriteBatch实例
*4 - 清屏
*5 *6 *7 - 渲染地图


深入handleInput()方法,看如何来操作相机

     private void handleInput() {
        if (Gdx.input.isKeyPressed(Input.Keys.A)) {
            cam.zoom += 0.02;
            //If the A Key is pressed, add 0.02 to the Camera‘s Zoom
        }
        if (Gdx.input.isKeyPressed(Input.Keys.Q)) {
            cam.zoom -= 0.02;
            //If the Q Key is pressed, subtract 0.02 from the Camera‘s Zoom
        }
        if (Gdx.input.isKeyPressed(Input.Keys.LEFT)) {
            cam.translate(-3, 0, 0);
            //If the LEFT Key is pressed, translate the camera -3 units in the X-Axis
        }
        if (Gdx.input.isKeyPressed(Input.Keys.RIGHT)) {
            cam.translate(3, 0, 0);
            //If the RIGHT Key is pressed, translate the camera 3 units in the X-Axis
        }
        if (Gdx.input.isKeyPressed(Input.Keys.DOWN)) {
            cam.translate(0, -3, 0);
            //If the DOWN Key is pressed, translate the camera -3 units in the Y-Axis
        }
        if (Gdx.input.isKeyPressed(Input.Keys.UP)) {
            cam.translate(0, 3, 0);
            //If the UP Key is pressed, translate the camera 3 units in the Y-Axis
        }
        if (Gdx.input.isKeyPressed(Input.Keys.W)) {
            cam.rotate(-rotationSpeed, 0, 0, 1);
            //If the W Key is pressed, rotate the camera by -rotationSpeed around the Z-Axis
        }
        if (Gdx.input.isKeyPressed(Input.Keys.E)) {
            cam.rotate(rotationSpeed, 0, 0, 1);
            //If the E Key is pressed, rotate the camera by rotationSpeed around the Z-Axis
        }

        cam.zoom = MathUtils.clamp(cam.zoom, 0.1f, 100/cam.viewportWidth);

        float effectiveViewportWidth = cam.viewportWidth * cam.zoom;
        float effectiveViewportHeight = cam.viewportHeight * cam.zoom;

        cam.position.x = MathUtils.clamp(cam.position.x, effectiveViewportWidth / 2f, 100 - effectiveViewportWidth / 2f);
        cam.position.y = MathUtils.clamp(cam.position.y, effectiveViewportHeight / 2f, 100 - effectiveViewportHeight / 2f);
    }

从代码可以看到会主动查询Key事件,相机会做相应的操作,当具体按键按下时。
最后5行确保相机不跑出游戏世界。
需要确保相机的zomm不能增长或者缩小的值使我们的世界翻转,也不能过多的显示游戏世界。所以我们要计算 effectiveViewportWidtheffectiveViewportHeight ,仅仅需要viewportWidth/Height * zoom,然后使用clamp 取我们需要的值。
最后2行需要确保相机不会translate出游戏世界 0 < x < 100


当游戏的屏幕大小发生变化应该怎么做?当处理不同屏幕比时我们应该实现怎样的策略。下面是作者整理的基本思路。
如果想要简单的测试可以参考Wiki的Viewport篇。
下面的resize策略确保无论屏幕分辨率怎样变化,总是在x-轴看到30个单元

    @Override
    public void resize(int width, int height) {
        cam.viewportWidth = 30f;                 // Viewport of 30 units!
        cam.viewportHeight = 30f * height/width; // Lets keep things in proportion.
        cam.update();
    }

下面的resize策略将会根据分辨率多少会将游戏世界显示不全

    @Override
    public void resize(int width, int height) {
        cam.viewportWidth = width/32f;  //We will see width/32f units!
        cam.viewportHeight = cam.viewportWidth * height/width;
        cam.update();
    }

启动实现的主要方法如下:

public static void main(String[] args) {
        new LwjglApplication(new OrthographicCameraExample());
    }

最终效果如下:
技术分享

Libgdx之正交相机 OrthographicCamera

标签:

原文地址:http://blog.csdn.net/zqiang_55/article/details/52155274

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!