up:: Fyrox
Character controller - Fyrox Book
cargo init 名称
で初期化、fyroxを依存関係に追加。
一回ここでrun。自分の時はなぜかmain.rsが書き換えられてちょっと詰まった。
runで出来たmain.rsにエンジン初期化処理を書く。
細かいインターフェースをスキップで消す解説用処理。
アセットを追加し、src/player/mod.rs
にキャラクタを生成して場所やスケールやピボットアタッチ、カメラアタッチなどの初期処理を用意しておく。
次はcamera.rs。
// Import everything we need for the tutorial.
use fyrox::{
core::{
algebra::{UnitQuaternion, Vector3},
pool::Handle,
},
engine::resource_manager::ResourceManager,
event::DeviceEvent,
resource::texture::TextureWrapMode,
scene::{
base::BaseBuilder,
camera::{CameraBuilder, SkyBox, SkyBoxBuilder},
graph::Graph,
node::Node,
transform::TransformBuilder,
pivot::PivotBuilder
},
};
async fn create_skybox(resource_manager: ResourceManager) -> SkyBox {
// Load skybox textures in parallel.
let (front, back, left, right, top, bottom) = fyrox::core::futures::join!(
resource_manager.request_texture("data/textures/skybox/front.jpg"),
resource_manager.request_texture("data/textures/skybox/back.jpg"),
resource_manager.request_texture("data/textures/skybox/left.jpg"),
resource_manager.request_texture("data/textures/skybox/right.jpg"),
resource_manager.request_texture("data/textures/skybox/up.jpg"),
resource_manager.request_texture("data/textures/skybox/down.jpg")
);
// Unwrap everything.
let skybox = SkyBoxBuilder {
front: Some(front.unwrap()),
back: Some(back.unwrap()),
left: Some(left.unwrap()),
right: Some(right.unwrap()),
top: Some(top.unwrap()),
bottom: Some(bottom.unwrap()),
}
.build()
.unwrap();
// Set S and T coordinate wrap mode, ClampToEdge will remove any possible seams on edges
// of the skybox.
let cubemap = skybox.cubemap();
let mut data = cubemap.as_ref().unwrap().data_ref();
data.set_s_wrap_mode(TextureWrapMode::ClampToEdge);
data.set_t_wrap_mode(TextureWrapMode::ClampToEdge);
skybox
}
ここまではモジュールとスカイボックス。
pub struct CameraController {
pivot: Handle<Node>,
hinge: Handle<Node>,
camera: Handle<Node>,
}
impl CameraController {
pub async fn new(graph: &mut Graph, resource_manager: ResourceManager) -> Self {
let camera;
let hinge;
let pivot = PivotBuilder::new(BaseBuilder::new()
.with_children(&[{
hinge = PivotBuilder::new(BaseBuilder::new()
.with_local_transform(
TransformBuilder::new()
.with_local_position(Vector3::new(0.0, 0.55, 0.0))
.build(),
)
.with_children(&[{
camera = CameraBuilder::new(
BaseBuilder::new().with_local_transform(
TransformBuilder::new()
.with_local_position(Vector3::new(0.0, 0.0, -2.0))
.build(),
),
)
.with_z_far(48.0)
.with_skybox(create_skybox(resource_manager).await)
.build(graph);
camera
}]))
.build(graph);
hinge
}]))
.build(graph);
Self {
pivot,
hinge,
camera,
}
}
}
camera.rsここまで。
カメラ、ヒンジ、ピボットを用意。
ピボットにヒンジ、ヒンジにカメラを取り付ける。カメラはヒンジを中心に回転させる。これによりプレイヤーの頭を常に映すようにする。
次はlevel.rs。
use fyrox::{
core::pool::Handle,
engine::resource_manager::{ResourceManager},
scene::{node::Node, Scene},
};
pub struct Level {
root: Handle<Node>,
}
impl Level {
pub async fn new(resource_manager: ResourceManager, scene: &mut Scene) -> Self {
let root = resource_manager
.request_model("data/levels/level.rgs")
.await
.unwrap()
.instantiate(scene);
Self { root }
}
}
level.rgsをロードするだけのコード。
最後にmain.rsを変更する。
use crate::{level::Level, player::Player};
use fyrox::{
core::{color::Color, futures::executor::block_on, pool::Handle},
engine::{resource_manager::ResourceManager, executor::Executor},
event::{Event, WindowEvent},
event_loop::ControlFlow,
plugin::{Plugin, PluginConstructor, PluginContext},
scene::{Scene},
};
mod level;
mod player;
struct Game {
scene: Handle<Scene>,
level: Level,
player: Player,
}
struct GameConstructor;
impl PluginConstructor for GameConstructor {
fn create_instance(&self, _: Handle<Scene>, context: PluginContext) -> Box<dyn Plugin> {
Box::new(Game::new(context))
}
}
impl Game {
fn new(context: PluginContext) -> Self {
let mut scene = Scene::new();
scene.ambient_lighting_color = Color::opaque(150, 150, 150);
let player = block_on(Player::new(context.resource_manager.clone(), &mut scene));
Self {
player,
level: block_on(Level::new(context.resource_manager.clone(), &mut scene)),
scene: context.scenes.add(scene),
}
}
}
impl Plugin for Game {
fn update(&mut self, context: &mut PluginContext, _: &mut ControlFlow) {
}
fn on_os_event(
&mut self,
event: &Event<()>,
_context: PluginContext,
_control_flow: &mut ControlFlow,
) {
}
}
fn main() {
let mut executor = Executor::new();
executor.add_plugin_constructor(GameConstructor);
executor.get_window().set_title("RPG");
executor.run();
}
クレートとしてlevelとplayerの構造体を追加、モジュールを読みに行った後、Game構造体に追加。
Gameメソッドでplayerとlevelを追加。block_onは読み込みが終わるまで呼び出し元をブロックする関数。非同期な読み込みで読みミスを防ぐためっぽい。
次はカメラをマウスで回す処理。
camera.rsを開き、構造体にyawとpitchを追加。new関数の戻り値内で初期化。
CameraControllerメソッドにhandle_device_eventを入れてマウスで制御できるようにする。
pub fn handle_device_event(&mut self, device_event: &DeviceEvent) {
if let DeviceEvent::MouseMotion { delta } = device_event {
const MOUSE_SENSITIVITY: f32 = 0.015;
self.yaw -= (delta.0 as f32) * MOUSE_SENSITIVITY;
self.pitch = (self.pitch + (delta.1 as f32) * MOUSE_SENSITIVITY)
// Limit vertical angle to [-90; 90] degrees range
.max(-90.0f32.to_radians())
.min(90.0f32.to_radians());
}
}
delta取りつつpitchはmaxminで値を制限するだけ。
ここで変更したyawとpitchはupdate関数で適用する。
pub fn update(&mut self, graph: &mut Graph) {
// Apply rotation to the pivot.
graph[self.pivot]
.local_transform_mut()
.set_rotation(UnitQuaternion::from_axis_angle(
&Vector3::y_axis(),
self.yaw,
));
// Apply rotation to the hinge.
graph[self.hinge]
.local_transform_mut()
.set_rotation(UnitQuaternion::from_axis_angle(
&Vector3::x_axis(),
self.pitch,
));
}
この二つのメソッドはimpl playerで呼ぶ。
PlayerはCameraControllerインスタンスを所有しているので。
pub fn handle_device_event(&mut self, device_event: &DeviceEvent) {
self.camera_controller.handle_device_event(device_event)
}
pub fn update(&mut self, scene: &mut Scene) {
self.camera_controller.update(&mut scene.graph);
}
ここはまだプロキシ。最後にこいつをGameから呼ぶ。
場所はon_device_eventとon_tick。
fn on_tick(&mut self, engine: &mut Engine, dt: f32, _control_flow: &mut ControlFlow) {
let scene = &mut engine.scenes[self.scene];
self.player.update(scene);
}
fn on_device_event(
&mut self,
_engine: &mut Engine,
_device_id: DeviceId,
event: DeviceEvent,
) {
self.player.handle_device_event(&event);
}