Bevyを使った個人でゲーム開発を行なっている登尾(のぼりお)です。
前回以下の記事でBevyを使ったProcessingライクな描画について記事を書きましたが、Mesh2dを使った描画方法で、あまりProcessing感がないなと反省しましたので、今回はさらにProcessingに寄せた図形の描画方法を解説してきます。
![]() |
![]() |
今回は以下のようなコードで図形を描画していてだいぶProcessing風な書き方ができていると思います。
fn user_sketch() {
ellipse(200.0, 200.0, 400.0, 400.0, random_color());
line(0.0, 0.0, 200.0, 200.0, Color::linear_rgb(1.0, 0.0, 0.0));
rect(100.0, 100.0, 150.0, 100.0, Color::linear_rgb(1.0, 1.0, 0.0));
ellipse(300.0, 250.0, 150.0, 250.0, Color::linear_rgb(1.0, 0.0, 1.0));
triangle(
100.0,
250.0,
50.0,
350.0,
300.0,
350.0,
Color::linear_rgb(1.0, 0.5, 0.0),
);
for _n in 0..11 {
let mut r = rand::rng();
let x1 = r.random_range(0.0..400.0);
let y1 = r.random_range(0.0..400.0);
let x2 = r.random_range(0.0..400.0);
let y2 = r.random_range(0.0..400.0);
line(x1, y1, x2, y2, random_color());
}
}
実装の流れ
今回はコードの行数が300行近くになりましたので、先に全体のコードを先にご紹介します。
上記コードのポイントをかいつまんで紹介して行きます。
座標系の変換
前回同様キャンバスのサイズは400x400とし、また左上が起点になるようcanvas_to_worldを用意しています。
const CANVAS_W: f32 = 400.0;
const CANVAS_H: f32 = 400.0;
fn canvas_to_world(p: Vec2) -> Vec2 {
Vec2::new(p.x - CANVAS_W * 0.5, CANVAS_H * 0.5 - p.y)
}
Processing風APIの用意
以下の部分でProcessing風なAPIのコマンドの受け皿を用意しています。今回は、line、Rect、Ellipse、Triangleの4つのみです。
#[derive(Clone)]
enum ProcessingCommand {
Line {
x1: f32,
y1: f32,
x2: f32,
y2: f32,
thickness: f32,
color: Color,
},
Rect {
x: f32,
y: f32,
w: f32,
h: f32,
color: Color,
},
Ellipse {
cx: f32,
cy: f32,
w: f32,
h: f32,
color: Color,
},
Triangle {
x1: f32,
y1: f32,
x2: f32,
y2: f32,
x3: f32,
y3: f32,
color: Color,
},
}
それぞれを実際の関数として用意し、以下の場合であればlineの例で、この関数が呼ばれるたびにその内容を送信しています。
thread_local! {
static TX: RefCell<Option<Sender<ProcessingCommand>>> = RefCell::new(None);
}
fn install_tx(sender: Sender<ProcessingCommand>) {
TX.with(|c| *c.borrow_mut() = Some(sender));
}
fn send(cmd: ProcessingCommand) {
TX.with(|c| {
if let Some(tx) = &*c.borrow() {
let _ = tx.send(cmd);
}
});
}
pub fn line(x1: f32, y1: f32, x2: f32, y2: f32, color: Color) {
send(ProcessingCommand::Line {
x1,
y1,
x2,
y2,
thickness: 4.0,
color: color,
});
}
APIの呼び出し
先ほど定義したlineを呼び出している例です。Color部分はBevyのColorをそのまま利用できる様にしています。
fn user_sketch() {
line(0.0, 0.0, 200.0, 200.0, Color::linear_rgb(1.0, 0.0, 0.0));
}
受信した情報をもとに描画
与えられたコマンドをもとに実際のMesh2dをspawnします。line関数であれば、以下の様にマッチしてMesh2dをspawnしています。
for cmd in drained {
match cmd {
ProcessingCommand::Line {
x1,
y1,
x2,
y2,
thickness,
color,
} => {
let a = canvas_to_world(Vec2::new(x1, y1));
let b = canvas_to_world(Vec2::new(x2, y2));
let delta = b - a;
let len = delta.length();
let angle = delta.to_angle();
let line_mesh = meshes.add(Rectangle {
half_size: Vec2::new(len * 0.5, thickness * 0.5),
});
commands.spawn((
Mesh2d(line_mesh),
MeshMaterial2d(materials.add(color)),
Transform {
translation: Vec3::new((a.x + b.x) * 0.5, (a.y + b.y) * 0.5, 0.0),
rotation: Quat::from_rotation_z(angle),
..default()
},
));
}
ProcessingCommand::Rect { x, y, w, h, color } => {
...
おしまい
今回もこれまで同様以下の個人リポジトリで公開しています。コード量が多めでしたのでざっくり解説となりましたので、実際のコードと比較しながらの方が分かりやすいかと思います。
cloneした後に、
% cargo run --example processing_like2
で挙動を起動できます。
前回の記事よりもProcessingライクな使い方はできたかなと思いますが、あくまでもごく一部のAPIのみですので、ここから先はよりProcessing風APIをはやしたり、逆にBevyのAPIで描画することに興味を持ってもらえると幸いです。