↗ベクトル演算ライブラリを作る
2Dアクションゲームには二次元線形代数ライブラリが必要です。Rustの既存のライブラリ(クレート?)を持ってきてもいいのですが、Rustのウォーミングアップとしてちょうどいいので、自力で作ってしまうことにします(とかいいつつ、あとでライブラリを差し替えたりしそう……)。
Vector2
構造体を作って、impl
でゴリゴリとメソッドを定義していきます。この辺は私はこれまで何度も作ったことがあってお手のものです。なにしろ何度もゲーム開発に挫折しているので。
#[derive(Clone, Copy, PartialEq, Default)]
pub struct Vector2 {
pub x: f32,
pub y: f32,
}
impl Vector2 {
pub fn new(x: f32, y: f32) -> Vector2 {
Vector2 { x, y }
}
}
impl ops::Add<Vector2> for Vector2 {
type Output = Vector2;
fn add(self, q: Vector2) -> Vector2 {
Vector2 {
x: self.x + q.x,
y: self.y + q.y,
}
}
}
ゲームを作ってみたい高校生諸君は、ベクトルと行列は勉強しておきましょう。別に学生時代にマスターしなくてもいいですが、2Dアクションゲームを作りたくなったときにベクトルわからないとロクにプログラミングはできないと思ってください。逆に言えば2Dアクションゲーム開発はベクトルと行列の勉強に最適です。ついでに高校物理の変位/速度/加速度、摩擦力、重力、弾性力などは2Dアクションゲーム開発にめちゃくちゃ役に立つのでおすすめです。
演算子のオーバーロードをするには、impl ops::Add<Vector2> for Vector2
のようにしてadd
関数を定義すればいいようです。つまりAdd型クラスのインスタンスを定義しているわけですね(Haskell用語)。#[derive(Clone, Copy, PartialEq, Default)]
とかも、Clone
型クラスやPartialEq
型クラスのインスタンスを導出している(Haskell用語)んだなと、なんとなく見ればわかります。筆者はHaskell脳なのでRustもHaskell用語で基本的に解釈しています。トレイトとかRustの用語はよくわかりませんが、Haskellでいう型クラスのようなものだと思っておくことにします。Rustは素直な言語だと思います。
なんかRustの公式ドキュメントを見ると、現代のハードウェアでは f32
とf64
は速度はどちらもほとんど同じだから基本的にf64
を使えばいいでしょ、みたいに書いてあったのですが、まあWASM-4は厳しい環境なのでケチるに越したことはありません(そのうち説明しますが、WASM-4では普通に作っているだけでスタックが足りなくなっていろいろ対応が必要になったりします)。しかも2Dゲームだと精度もとくに必要ありません。f32
で十分でしょう。
struct
で直積型を定義して、#[derive(Clone, Copy, PartialEq, Default)]
で各種の型クラスインスタンスを導出(Rust用語では「トレイトの導出」というらしいですが、筆者の頭の中ではHaskell用語の型クラスインスタンスの導出とみなされている)。impl Vector2 { ... }
の中で関数を定義すると、メソッドっぽくなるみたいです。演算子オーバーロードをするには、impl ops::Add<Vector2> for Vector2 { ... }
の中でadd
という名前の関数を書けばいいらしい。type Output = Vector2;
ってなんだろうと思いつつも、なんとなく雰囲気で書き進めます。
⏹AABBを定義する
ゲーム開発でベクトルや行列のほかに重要な概念として、AABBがあります。AABBとは Axis-Aligned Bounding Boxesのことで、つまり画面に対して傾いていない矩形の領域のことをいいます。2Dアクションゲームでは、地形やプレイヤーキャラクターの衝突の判定をこのAABBで考えると高速かつシンプルに処理することができます。ゲーム開発以外ではあまり馴染みのない概念かもしれませんが……。
AABBは要するに矩形なので、画面に対して左上の基準点と、幅、高さをあわせて、次のような構造体で表すことができます。
#[derive(Clone, Copy)]
pub struct AABB {
pub x: f32,
pub y: f32,
pub w: f32,
pub h: f32,
}
AABBはただの矩形(Rectangle)とは違って、傾いていないということがポイントです。そのため、矩形同士が斜めに交差するということがないので、次のようにしてシンプルに交差判定ができます。
pub fn collesion(&self, b: AABB) -> bool {
(self.x < b.r() && self.r() > b.x) && (self.y < b.b() && self.b() > b.y)
}
これが、もし傾きを考慮しないといけない矩形だと交差判定が大変です。もちろんより厳密な物理演算が必要なゲームでは傾いた矩形を含む図形の交差を判定しなければなりませんが、今回作る2Dドット絵プラットフォーマーなら、AABBだけで判定したほうがずっとシンプルです。
左右方向のみを表す型
このほか、キャラクターが左向きか右向きかを表すデータ型を定義しておくと便利です。これはまあ左向きか右向きかの二つに一つなので bool
型で表すこともできますし、i32
の1
と-1
で表すようなこともできるのですが、Rustにはせっかくenum
があるのでこれを使っておきます。
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
pub enum Direction {
Left,
Right,
}
impl Direction {
pub fn delta(&self) -> i32 {
if *self == Direction::Left {
-1
} else {
1
}
}
...
このあたりは好みもあると思うのですが、筆者はなるべく不正な状態にならないようなデータ型にしたほうが堅牢になると信じるタイプの人なので、積極的にenum
を使っていきます。代数的データ型最高!
🔜次回予告
WASM-4は非常に簡素なフレームワークなので、こんな感じで自力でゲーム開発に必要な基礎的な関数を定義したりすることもあるので、それはそれで勉強になります。次回はこのベクトル演算ライブラリを利用しつつ、プラットフォーマーに不可欠な「衝突判定」を実装していきます。