首页 > 其他分享 >【Bevy实战】2D场景下Camera实践

【Bevy实战】2D场景下Camera实践

时间:2024-09-26 23:03:17浏览次数:6  
标签:Bevy 视口 commands 渲染 2D Camera img1

Bevy,一个用Rust构建的令人耳目一新的简单数据驱动游戏引擎。如果你是一名Rust开发者,同时又对游戏开发比较感兴趣,那么Bevy一定是你会接触甚至是使用的游戏引擎。当然,本文关注的重点并不是来介绍Bevy,以及它的一些基本概念,关于这块的内容读者完全可以到Bevy的官网、Github主页进行学习;同时,本文也不是一篇介绍Rust编程细节的文章,因此,本文面向的对象是有Rust开发经验(至少是要入门)的小伙伴。接下来就让我们开始本文的主要内容:Bevy在2D场景下的Camera实践。

基本概念

Camera摄影机驱动Bevy中的所有渲染。他们负责配置要绘制的内容、如何绘制以及在哪里绘制。为了更形象的讲解,我们先编写如下代码:

PS:假设读者的其他环境、依赖已经配置Ok

use bevy::prelude::*;

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_systems(Startup, startup)
        .run();
}


fn startup(mut commands: Commands, asset_server: Res<AssetServer>) {
    let icon: Handle<Image> = asset_server.load("img1.png");
    commands.spawn(SpriteBundle {
        texture: icon.clone(),
        ..default()
    });
}

上述的代码核心过程:

  1. 加载Bevy默认的插件
  2. 启动阶段(Startup)生成一张精灵图(来自img1.png)

此时我们运行项目,会发现仅有一个没有任何内容的,黑色背景的窗口:

010

造成这个现象的原因是:在默认的情况下,Bevy“游戏世界“中不存在摄像机Camera实体,因此无法将可渲染的内容”拍摄“下来并投射到屏幕上。为了解决这个问题也非常简单,在世界中增加一个摄像机实体即可:

fn startup(mut commands: Commands, asset_server: Res<AssetServer>) {
+   // add camera.
+   commands.spawn(Camera2dBundle::default());
    // then spawn Sprite.
    let img1: Handle<Image> = asset_server.load("img1.png");
    commands.spawn(SpriteBundle {
        texture: img1.clone(),
        ..default()
    });
}

上述代码我们添加了一个2D摄像机的实体,此时再次运行程序可以看到如下的效果:

020

你可以将上面的2D渲染想象成如下的样子:

030

此时,让我们在项目工程中再增加一张图片,并且在之前生成实体的位置再生成一个新的Sprite:

fn startup(mut commands: Commands, asset_server: Res<AssetServer>) {
    // add camera.
    commands.spawn(Camera2dBundle::default());
    // then spawn Sprite.
    let img1: Handle<Image> = asset_server.load("img1.png");
    commands.spawn(SpriteBundle {
        texture: img1.clone(),
        ..default()
    });
    // add another Sprite with img2.png
    let img2: Handle<Image> = asset_server.load("img2.png");
    commands.spawn(SpriteBundle {
        texture: img2.clone(),
        ..default()
    });
}

此时当我们再次运行程序时,可以看到img2.png的内容在img1.png之上:

040

不难理解,先生成的图片实体先渲染,后生成的图片实体后渲染。那么,假设我希望img1.png能够盖住img2.png呢?此时只需要将其位置组件的z轴设置为大于0即可(因为z轴默认是0):

fn startup(mut commands: Commands, asset_server: Res<AssetServer>) {
    // add camera.
    commands.spawn(Camera2dBundle::default());
    // then spawn Sprite.
    let img1: Handle<Image> = asset_server.load("img1.png");
    commands.spawn(SpriteBundle {
        texture: img1.clone(),
      	// add transform
        transform: Transform {
            translation: Vec3 {
                z: 1., // <- z > 0
              	x: 100.,
                ..default()
            },
            ..default()
        },
        ..default()
    });
    // add another Sprite with img2.png
		// ...
}

为了检查效果,笔者还将img1的位置进行一定的偏移。

此时再次运行,可以看到img1已经在img2之上,盖住了img2:

050

同时,通过上面x轴偏移的效果我们也不难知道,在Bevy中的2D的坐标是以视口(我们马上介绍什么是视口Viewport)中心为原点的,并非传统GUI的top-left为原点的坐标系统。

注意,视口并非窗口,只是在默认情况下,视口就是整个窗口范围。所以接下来,就让我们开始介绍视口的内容。

视口Viewport

通过查阅Camera2dBundle中的Camera组件细节,我们会发现Camera组件存在一个名为viewport的字段。根据其文档我们可以知道,这个字段可以用来配置将Camera摄取到的内容渲染到指定RenderTarget的矩形区域:

060

所以,在继续对视口相关内容进行介绍前,我们首先要知道上面提到的RenderTarget是个什么东西。继续翻阅Camera的代码内容,可以看到Camera还有一个字段target

070

查看RenderTarget定义如下:

080

通过其文档我们得知,“target”指的是Camera摄取的内容应该渲染到哪个目标上。已知的有:

  1. Window窗体。这是默认的目标。
  2. Image图片。也就是说我们有望将游戏世界中的内容摄取并输送到一张图片上。
  3. 纹理视图。这个暂时不在本文中进行讲解。

在了解了上述的RenderTarget后,我们再回到视口viewport的相关内容。此时我们不难推断出,通过配置viewport,我们可以将Camera摄取到的内容输送到相关渲染目标的某个区域(毕竟无论是Window窗体还是Image图像等,它们都是一个平面2D的接收图像的区域罢了)。所以,接下来我们实践一下。将视口配置为一个 400 x 200 的区域:

fn startup(mut commands: Commands, asset_server: Res<AssetServer>) {
    // add camera.
    commands.spawn(
        Camera2dBundle {
            camera: Camera {
              	// 配置视口的代码
                viewport: Some(Viewport {
                    physical_position: UVec2::new(50, 50),
                    physical_size: UVec2::new(400, 200),
                    ..default()
                }),
                ..default()
            },
            ..default()
        }
    );
    // then spawn Sprite.
		// ...
    // add another Sprite with img2.png
 		// ...
}

上述代码中,我们给对应的Camera配置了视口:在渲染目标(目前就是当前这个窗口)的左上角(50,50)位置开始,宽400,高200的矩形区域。在其余代码保持不变的情况下,运行该程序能够得到如下的效果:

090

此时,我们可以用一张图例来更加形象的表达上述目前的情形:

100

关于2D实体的位置等处理

实际上,如果你对上面的内容了解了以后,就很容易知道应该如何处理2D实体的位置、缩放等。一般来说,我们可以有两种方式:

  1. 调整世界中的实体的变换
  2. 调整摄像机的变换(transform)

假设整个世界中只有一个带有变换组件(Transform)的实体,假设我们希望对这个物体旋转180度,我们即可对这个实体的变换组件进行180旋转变换,也可以对摄像机进行180度的旋转变换。不过,从Bevy这一层次来说,要控制某个实体的位置大小等,最直观的还是对这个实体本身进行变换。

多个Camera

一般情况下,我们很容易的产生多个Camera:

110

在前面的代码基础上,我们又Spawn了另一个Camera,这个Camera的视口我们设置到了另一个区域。此时运行程序,我们会发现效果如下:

120

如果你理解了前面的视口流程图,那么这个地方也不难理解:两个摄像机都在“摄取”游戏世界中的2D内容,但由于两个摄像机有不同的视口配置,因此将摄取到的内容渲染到渲染目标上Window上的时候,会将内容渲染到不同的位置:

130

当然,从上图你会发现控制台有这样一句不断打印的警告:

WARN bevy_render::camera::camera: Camera order ambiguities detected for active cameras with the following priorities: {(0, Some(Window(NormalizedWindowRef(Entity { index: 0, generation: 1 }))))}. To fix this, ensure there is exactly one Camera entity spawned with a given order for a given RenderTarget. Ambiguities should be resolved because either (1) multiple active cameras were spawned accidentally, which will result in rendering multiple instances of the scene or (2) for cases where multiple active cameras is intentional, ambiguities could result in unpredictable render results.

核心意思是,如果场景中存在多个Camera且对应的渲染对象都是一个(比如本例中的同一个Window),需要每一个Camera一个特定的顺序,否则这种歧义可能会造成不可预测的渲染结果(Bevy内部的机制导致的,这里不深究)。所以,为了消除这一警告,我们只需要为不同的Camera设置不同的order

140

不同的摄像机渲染不同的实体

有的时候,我们确实需要有多个Camera,且它们各自需要“摄取”并渲染不同的实体。此时,为了实现这个效果,我们需要引入一个组件:RenderLayer。

150

我们将第一个Camera对应的实体和img1对应的实体都添加了RenderLayer::layer(1),标识摄像机将只会“摄取”layer为1的所有实体。同样的,我们针对第二个Camera和img2添加RenderLayer::layer(2)

160

就绪以后,运行程序,我们能看到如下效果:

170

左上角视口的Camera#1,只摄取了img1的内容;而右边的Camera#2则只摄取了img2的内容。

关于多Camera的实际应用

上述内容,我们介绍了多Camera的一些效果。那么对于多Camera有什么实际的应用呢?笔者能想到有:

  1. ui和游戏世界渲染的分离
  2. 某些小地图的呈现

对于第一点来说,最直观的就是,也许我们制作的游戏类似这样的布局:

180

上述是游戏《Caves of Quad》的游戏界面,整体来看,我们就完全可以使用2个以上的Camera来分别渲染左侧的游戏部分和右侧的状态部分。

对于第二点来说,像CS2中的投掷物预览效果,就可以单独用一个Camera来呈现:

190

写在最后

本文的代码仓库为:w4ngzhen/bevy_examples (github.com)。同时,后续我会添加更多的示例供读者参考。

相关阅读以及参考

Cameras - Unofficial Bevy Cheat Book (bevy-cheatbook.github.io)

bevy/examples at latest · bevyengine/bevy (github.com)

标签:Bevy,视口,commands,渲染,2D,Camera,img1
From: https://www.cnblogs.com/w4ngzhen/p/18434696

相关文章

  • P8908 [USACO22DEC] Palindromes P 题解
    P8908[USACO22DEC]PalindromesP题解算是好题,虽然没什么人做(简单地,我们考虑如何将一个字符串改变为回文串。显然如果我们判定所有\(\texttt{G}\)组成的是回文串,那么整个串一定是回文的。于是我们只考虑改变\(\texttt{G}\)的位置。那么由这类题的套路不难知道最优的变换......
  • 探索Chat2DB:下一代智能数据库客户端工具
    在快速发展的软件行业中,开发者们总是在寻找能够提高效率、简化工作流程的工具。今天,我们要介绍的是一款新兴的数据库管理工具——Chat2DB,它不仅支持多种数据库平台,还集成了人工智能技术,让数据库操作变得更加直观和高效。官网网址在文章最后~Chat2DB支持MySQL、PostgreSQL、Micr......
  • Camera ITS场景0_test_solid_color_test_pattern测试失败
    也会导致cts中CtsSensorPrivacyTestCases模块中两个单项报错,testOpStartsRunningAfterStartedWithSensoryPrivacyEnabledtestOpGetsRecordedAfterStartedWithSensorPrivacyEnabled这两项metadata加上MTK_SENSOR_TEST_PATTERN_MODE_OFF,MTK_SENSOR_TEST_PATTERN_MODE_BLACK就......
  • P8907 [USACO22DEC] Making Friends P 题解
    P8907[USACO22DEC]MakingFriendsP题解我们考虑维护每个\(i\),在\(i\)的后面有多少个点和它有朋友关系。初步的想法是每删掉一个人就给集合里所有的点连边。但是我们发现这样太不优了,有很多边会重复连很多次。优化的想法是对于\(i\),删去之后连的边就成了一个完全图,于是......
  • mini-lsm通关笔记Week2Day2
    项目地址:https://github.com/skyzh/mini-lsm个人实现地址:https://gitee.com/cnyuyang/mini-lsmSummary在本章中,您将:要将测试用例复制到启动器代码中并运行它们,实现一个simpleleveled合并策略,并在合并模拟器上进行仿真。将compaction作为后台任务启动,并在系统中实现一个......
  • P8906 [USACO22DEC] Breakdown P 题解
    P8906[USACO22DEC]BreakdownP题解显然的套路是删边转化为加边。考虑到维护整条路径不好维护,于是考虑转化维护\(f_{i,k},g_{i,k}\)分别表示\(1,n\)到\(i\)走了\(k\)步时的最短路。那么此时\(k\le4\)。我们先考虑\(f\)的转移,\(g\)的转移是等价的。那么对于\((......
  • cameralink卡设计原理图:287-基于FMC接口的1路Base cameralink输入1路Base cameralink
    基于FMC接口的1路Basecameralink输入1路Basecameralink输出子卡  一、板卡概述      该板卡是我公司自主研发的1路Basecameralink输入,1路Basecameralink输出的FMC子卡,LPC-FMC连接器。FMC连接器是一种高速多pin的互连器件,广泛应用于板卡对接的设......
  • OpenCV_自定义线性滤波(filter2D)应用详解
    OpenCVfilter2D将图像与内核进行卷积,将任意线性滤波器应用于图像。支持就地操作。当孔径部分位于图像之外时,该函数根据指定的边界模式插值异常像素值。卷积核本质上是一个固定大小的系数数组,数组中的某个元素被作为锚点(一般是数组的中心)。上面讲了线性滤波的实质就是计算相......
  • [Paper Reading] CAPE: Camera View Position Embedding for Multi-View 3D Object De
    目录名称TL;DRMethodKeyPositionEmbeddingConstructionQueryPositionEmbeddingConstructionKey/QueryPositionEmbedding两者结合关系参考下图temporalmodelingExperiment总结与发散相关链接资料查询名称link时间:23.03机构:Baidu/华科TL;DR提出CAPE(CAmeraviewPosi......
  • 基于MicroPython的ESP8266控制GP2D12红外测距传感器模块的设计方案
       以下是一个基于MicroPython的ESP8266控制GP2D12红外测距传感器模块的设计方案:一、硬件准备:1. ESP8266开发板(如NodeMCU)2. GP2D12红外测距传感器模块3. 杜邦线若干4.3.3V和5V直流电源二、硬件连接:1. 将ESP8266开发板的VCC和GND引脚,通过杜邦线,分别连接到3.......