使用相机观察世界

191 阅读7分钟

我在Phaser3中的世界提到,随着Phaser.Game的初始化,一个Game World便随之被构建出来,然后我们可以通过canvas去窥探这个世界的人事物。随着进一步的学习,我发现其实我们是通过camera对这个世界进行观察,camera对着哪里,我们就可以看到哪里,这时canvas更像是camera的显示屏。

Camera

// camera的构造函数
// x - viewport的横坐标
// y - viewport的纵坐标
// width - viewport的宽度
// height - viewport的高度
Camera(x, y, width, height)

camera有两个重要属性,viewportscroll。我们可以把一个camera想象成生活中常见的电子监控,其中viewport可以看成是监控屏幕,我们可以从这里看到摄像头正对着的地方,而scroll则用来控制摄像头地视角,通过修改这个值,我们可以实现把摄像头朝上下左右四个方向进行调整。

viewport

viewport用来向我们展示camera正在观察地东西,它有两个重要的属性,分别是positionsize,其中position表示我们要把“屏幕”放在canvas的哪个位置,而size则表示我们的“屏幕”有多大。我们可以通过setPosition(x, y)setSize(width, height)来调整cameraviewport。实际上camera构造函数的四个参数就是在设置其viewport

scroll

scroll用于调整相机的视角,我们可以使用setScroll(scrollX, scrollY)方法,来调整我们想观察的位置,我们的可见范围是以(scrollX, scrollY)为左上角,宽度与高度分别为viewport.widthviewport.height的矩形。初始情况下,scrollXscrollY都为0。


main camera

camera的任务是负责渲染游戏中的对象,换句话说,不使用camera我们就无法在canvas上看到任何东西。那么之前的代码我们也没有创建camera,为什么可以看到那些矩形呢?

因为,在游戏初始化的过程中,Phaser会自动创建一个默认的camera,其viewport的大小与配置的canvas大小一致,position设置为(0, 0)canvas左上角),并且scroll的位置也是(0, 0)。于是我们在scene中创建的所有位于画布范围内的东西都可以看到(在Phaser3中的世界中画的红色矩形),我们可以通过以下代码访问这个默认创建的camera

class Example extends Phaser.Scene
{
    constructor ()
    {
        super();
    }

    create ()
    {
        const mainCamera = this.cameras.main;

        console.log(`main camera viewport position: (${mainCamera.x}, ${mainCamera.y})`);
        console.log(`main camera viewport size: ${mainCamera.width} * ${mainCamera.height}`);
        console.log(`main camera scroll at (${mainCamera.scrollX}, ${mainCamera.scrollY})`)
    }
}

const config = {
    type: Phaser.AUTO,
    parent: 'phaser-example',
    width: 800,
    height: 600,
    scene: Example
};

const game = new Phaser.Game(config);

打开控制台就可以看到各个数据

main camera viewport position: (0, 0)
main camera viewport size: 800 * 600
main camera scroll at (0, 0)

操纵相机视角

Phaser3中的世界文末的例子中,绿色矩形完全不可见,蓝色矩形有一半在可见范围外。我们可以尝试修改代码,通过方向键控制camera的视角,从而看到原先看不到的绿色矩形。

class Example extends Phaser.Scene
{
    constructor ()
    {
        super();
    }

    create ()
    {
        this.drawRectange(0, 0, 0xff0000); // 红色
        this.drawRectange(-100, 0, 0x00ff00); // 绿色
        this.drawRectange(750, 0, 0x0000ff); // 蓝色

        const mainCamera = this.cameras.main;

        this.input.keyboard.on('keydown-LEFT', () => {
            mainCamera.scrollX -= 10;
        });

        this.input.keyboard.on('keydown-RIGHT', () => {
            mainCamera.scrollX += 10;
        });
        
        this.input.keyboard.on('keydown-UP', () => {
            mainCamera.scrollY -= 10;
        });
        
        this.input.keyboard.on('keydown-DOWN', () => {
            mainCamera.scrollY += 10;
        });
    }

    drawRectange(x, y, color)
    {
        // setOrigin将矩形定位原点设置为左上角
        return this.add.rectangle(x, y, 100, 100, color).setOrigin(0);
    }
}

const config = {
    type: Phaser.AUTO,
    parent: 'phaser-example',
    width: 800,
    height: 600,
    scene: Example
};

const game = new Phaser.Game(config);

通过方向键的上下左右调整视角之后,我们就可以看到隐藏在canvas外的绿色矩形了。当我们一直按左方向键的时候,我们可以看到,左边慢慢出现绿色矩形,而右边的蓝色矩形在慢慢消失。

图示

bounds

默认情况下,camera的视角是没有边界的,我们可以任意改变scrollXscrollY为任何值。我们也可以通过setBounds(x, y, width, height, [centerOn])方法,给camera设置边界,从而限制其可视范围,换句话说,scrollX <= bounds.x + bounds.width - viewport.width

image.png

viewport细节

如果我们用过监控系统,就可以很直观的感受到viewport是什么,它跟我们平时看到的监控画面很像,我们可以放大或者缩小画面,也可以将其移动到屏幕的任何一个位置。

image.png

比如上面这个图,我们可以把canvas看成监控室里用来显示监控画面的电视(感觉挺合理的,电视跟canvas都是有大小限制的),有四个camera在观察着不同的场景,它们将拍到的画面传送到监控室,接着我们设置四个等大的viewport,分别展示拍到的监控画面。

但是Phaser3中的camera与现实的监控系统不太一样,现实生活中的摄像头是有视野盲区的,上图的四个监控画面,我们不可能通过拉长画面从而看到更多的东西,比如右下角的手扶梯监控画面,竖着拉长并不能看到电梯的两端,除非我们调整摄像头的视角。

Phaser3中的camera的视野范围是以(scrollX, scrollY)为左上角顶点,右下角在无穷远处的矩形,换句话说,将(scrollX, scrollY)作为直角坐标系的原点,那么camera的视野范围是整个第四象限,我们可以通过增大viewportsize,从而看到更多的画面。

改变viewport大小

接下来,通过编写代码来直观感受一下上面说的那些概念。

Phaser3 Sandbox 提供了一个在线编程的环境,可以很方便的验证一些想法。

class Example extends Phaser.Scene
{
    constructor ()
    {
        super();
    }

    preload () 
    {
        // 图片大小是640 × 514
        this.load.image('einstein', 'assets/pics/ra-einstein.png');
    }

    create () 
    {
        const image = this.add.image(0, 0, 'einstein').setOrigin(0);

        const cam = this.cameras.main;

        const gui = new dat.GUI();

        gui.addFolder('Main Camera');
        gui.add(cam, 'width');
        gui.add(cam, 'height');
    }
}

const config = {
    type: Phaser.WEBGL,
    parent: 'phaser-example',
    scene: Example,
    // 将canvas大小设置成跟图片一样大
    width: 640,
    height: 514
};

const game = new Phaser.Game(config);

image.png

我们可以通过在右上角的面板输入不同的widthheight来调整viewport,当我们将width缩小到320的时候,我们只能看到爱因斯坦的左半边脸,随着我们将width增大,我们可以慢慢地看到整张脸。这一点就跟现实中的监控画面不太一样,我们可以通过放大viewport从而看到更多的画面。

image.png

改变viewport位置

接着尝试改变viewport的位置,这里有一个要点,我在一开始学习的时候就弄混了,viewport自己的位置跟它所观察的位置完全是两码事。

还是用上文的代码加以说明,我们创建了一个与图片等大的canvas,然后将图片刚好放在上面,这时候我们缩小viewport的大小为300 x 300,这时候我们只能看到图片左上角,

image.png

然后,我尝试将viewport挪到它眼睛的位置,以为就可以看到他的眼睛。其实并不能,不管我移动到哪里,viewport里的画面都保持不变。

image.png

因为决定viewport所显示的画面是通过camerascroll决定的,我们虽然改变了viewportposition,但是其scroll并没有改变。附上验证的代码

class Example extends Phaser.Scene
{
    constructor ()
    {
        super();
    }

    preload () 
    {
        // 图片大小是640 × 514
        this.load.image('einstein', 'assets/pics/ra-einstein.png');
    }

    create () 
    {
        const image = this.add.image(0, 0, 'einstein').setOrigin(0);

        const mainCamera = this.cameras.main;

        mainCamera.setSize(300, 300);

        this.input.keyboard.on('keydown-LEFT', () => {
            mainCamera.x -= 10;
        });

        this.input.keyboard.on('keydown-RIGHT', () => {
            mainCamera.x += 10;
        });
        
        this.input.keyboard.on('keydown-UP', () => {
            mainCamera.y -= 10;
        });
        
        this.input.keyboard.on('keydown-DOWN', () => {
            mainCamera.y += 10;
        });
    }
}

const config = {
    type: Phaser.WEBGL,
    parent: 'phaser-example',
    scene: Example,
    // 将canvas大小设置成跟图片一样大
    width: 640,
    height: 514
};

const game = new Phaser.Game(config);

我们可以通过方向键移动viewport的位置,但是viewport中的画面并没有改变。

image.png

总结

通过用现实生活中的监控来类比camera,会发现它们之间其实有挺多共同之处的(当然也有差异),这种类比方式可以帮助我们更好地去理解一些概念,会比较生动形象。关于camera的两个重要属性,感觉基本也就这些内容了,详细的资料可以通过camera的api文档查看。