Bevy 0.6 に対応 ― 2022/01/13
ガイドに従えば0.6への移行自体はそんなに難しくはありませんが、0.5で作成した前回の記事で挙げられた点が結構改修されていますので、その辺りを主に並べてみます。
スプライトを半透明にする
SpriteSheetBundleと同じような感じで、SpriteBundleでもカラーのパラメータがSpriteに含まれるようになった。これで透明度を変えるときでも、Assets<ColorMaterial>を持ち歩かなくて済む。
スプライトの加算合成
AlphaModeなんてのがあったから、これは?と思って見てみたが、どうも2Dのスプライトに使うものでもないらしい。そもそも、ブレンドモードに加算合成が無い……需要はありそうなもんだが、何か見落としているのだろうか?
テキスト描画
Text2dBundleのTransformが、テキストの拡大率や回転角に影響するように変更された。紹介ページでは、サイズを変えたきゃ「フォントサイズ」を変えることを勧められているけど。
Web対応
標準でWeb対応されて、特にプラグインを入れることなくコマンドオプションだけでWeb用にコンパイルできる。そのためなのか、それとは別の話なのかはわからないが、Web版のテストにはwasm-server-runnerを使うようになっている。
いくつか注意点があって、wasm-server-runnerではこちらの用意したhtmlを介さないからなのかWindowDescriptorでcanvasを設定すると動かなかった。少なくともデバッグビルドでは設定を外す必要がある。
また同様に、小細工ができないので音が鳴らない。
wasm-server-runnerの設定かなんかでやりようがあるのかもしれないが、致命的な問題でもなかったのでほっといている。
最後に
コンパイル・デバッグの方法の変更でちょっと手間取ったりもしましたが、プログラム自体のBeby0.6への対応は難しくありませんでした。カプセル化されたクラスで変更点を吸収してしまうようなオブジェクト指向型のエンジンに比べると、修正箇所が多いような気もしますが。スプライトの半透明(カラー)設定なんかは、0.6ですっきりした形になって扱いやすくなりました。むしろ、0.6を待ってからゲームを作り始めていればあんな苦労は……
Rust + Bevyでゲーム作成 ― 2021/12/22
何か必要に迫られてというわけではないけれど、
Rustあたりは触っておいた方が良いかなと、
ゲームエンジンBevyを使用してミニゲームを作ってみました。
ブラウザ等、環境によっては動かないかもしれません。
開発プロジェクト(GitHub)
Bevyの基本的な使い方は紹介ページもありますし日本語でもいろいろな所で解説されていますが、実際にゲームを作るとなるとそれ以外にも必要な事が出てきたり様々な問題にぶつかったりもします。
今回の経験から、そういった点をいくつか挙げてみます。
スプライトを半透明にする
アルファ値(を含めたカラー)を設定するには、ColorMaterialのcolor:Colorを変更する。同じ大きさのスプライトを複数まとめたSpriteSheetBundleを使った場合にはTextureAtlasSpriteを持ってきて、そのメンバのcolorを変更すれば良い。
ただし SpriteBundleでは materialがHandle<ColorMaterial>なので、ResMut<Assets<ColorMaterial>も持ってきてそこからget_mutでColorMaterialを取り出す必要がある。
SpriteBundleもSpriteSheetBundleも似たようなものだと思うのだが、何でやり方が異なるのだろうか?
ちなみにSpriteBundleでもスプライト作成時に設定するのなら、ResMut<Assets<ColorMaterial>>にaddする前に変更してしまえば良い。
スプライトの加算合成
加算合成を含めた描画モードの変更の仕方だが、結局わからなかった。このゲームでは省いても何とかなったけど、エフェクトなど他のゲームではよく使われる手法なのでやり方がわからないと困りそう。
テキスト描画
テキストの描画はText2dBundleでエンティティを生成
は良いとして、
transformのscaleを変更しても、拡大率が変わらなかった。
何か使い方を間違えているのか、そういうものなのか……
場面の切替え
タイトル画面からゲームへなど場面を設定して移行する機能で、他のエンジンでは"シーン"などと呼ばれることもあるが、Bevyでは"State"となっている。SystemSetで各Stateごとにシステムを登録することによって、動作を切り替えることができるようになっている。
切り替え時には画面も変わるということでそのStateで生成したエンティティなんかを削除してまっさらにして欲しいものだが、それは自前で行う必要がある。
エンティティをすべてクリアするとか、ある時点の状態に戻すとかそういう機能があれば便利なのだが、そんなのは見つからなかったのでこのゲームでは
State開始時に存在しているエンティティのidを記録しておいて、終了時にそれ以外のエンティティを全部削除
するようにしている。あまりカタギじゃないやり方なので真似しない方が良いかも。
そのState中で作られるエンティティに目印としての構造体を insertしておくという手もあるが、面倒だし付け忘れても動作はしてしまうのが怖いところ。
また、あるStateから同じStateに直接切り替えることはできない。
GAME OVERの後、同じゲームStateを最初からやり直したかったのだが、一旦ダミーのStateに切り替えてからすぐにゲームのStateに戻すようにしている。めんどくさいことに。
あとフレーム処理絡みで問題があるのだが、それは次の項で。
フレーム処理
1秒間に60回とか30回とか、決まった間隔で処理を行う機能。ここにあるように、フレーム処理させたいシステムの前に .with_run_criteria(FixedTimestep::step( 間隔 ))
を差し込むだけ。簡単。
なのだが
一つ問題があって、これを差し込んだ後のシステムは Stateを無視して実行される(っぽい)。
他のStateの実行中に、そのStateの初期化部分も通らず繰り返し部分が実行されてしまうので当然エラーになる(むしろエラーで止まってくれて助かった)。
そのために、with_run_criteriaにchainで
現在そのStateの実行中か
という判定を挟んで対処している。なんなんだ一体。
Web対応
チートブックには2つのやり方が示されているが、bevy_webgl2_app_templateを使う方を選択。Webに対応するというよりは、Webに対応されたひな型を元にプロジェクトを作るという感じ。
ただ、ブラウザで実行させてみるとちょっと画面が荒い。なぜかサイズが1.2倍に拡大されている。どこかに設定でもあるのかと探してみたが見つからなかったので、プログラム側で対処してみた。
具体的には、
・ウィンドウ生成時のサイズを1.2分の1
・カメラのスケールを1.2に
・マウスカーソルの座標を1.2倍に
の3ヵ所。
オーディオ再生
WebではBevyの標準のサウンドシステムは使えないらしいので、ここに従ってbevy_kira_audioを組み込んでみた。featuresに"mp3"を含めるとコンパイルエラーが出るけど、どうせoggしか使っていないので他は削除して対処。
ブラウザで実行してみると音が出ない。どうも、Webページではユーザーのアクションがあるまで音を鳴らしてはいけないという規則のせいらしい。
画面をクリックしても音は出なかったが、おそらくクリック前にオーディオの初期化をしているとかそんな感じなんだろう。よくわからないが。
よくわからないまま、Googleのページのコードをhtmlに張り付けて、無事に音が出るようになった。
この記事について
いろいろと問題点やその対処法を挙げてみましたが、いかんせんRust・Bevyの初心者が見よう見まねとその場しのぎでゲームを作っただけなので、もっとシンプルでスタンダードな手法があるとか、そもそも何か勘違いしているとか怪しい点もあるかと思います。この記事はこれからBevyで何かを作ろうとする方ではなく、よくBevyをわかっている方に初心者がつまずく点ということで参考にしてもらえるとありがたいです。
Akashic Engine プログラミングでの注意点 ― 2020/04/04
なんて放送もあって、新たに Akashic Engineを使ってみようという方も出てきそうなこの頃。
しかし放送でやっていたように画像を差し替えるくらいなら良いのですが、プログラムを組んでゲームを作るとなるといろいろ注意しなければいけない点も出てきます。
一応 Akashic Engineで何本かゲームを作ってきた身として、そのあたりをいくつか挙げてみたいと思います。
なお、サンプルコードは TypeScriptで書かれています。
JavaScriptの方は型宣言等、読み飛ばしちゃってください。
1.マルチタッチ
pointDown
でタッチを見るだけなら、気にする必要はありません。pointMove
やpointUp
で移動したとき・離したときのイベントを使うときは、引数のメンバpointerId
でそれぞれ区別・対応するようにしましょう。一緒にすると、スマホだけの裏技なんてものができてしまうかもしれません。
2.シーンオブジェクトのポイントイベント
pointDownCapture
でシーン全体のポイントイベントを取得することができます。タッチした座標は引数のメンバ
point
で得られるのですが、touchable
なエンティティの上をクリックした場合、その値が画面上の座標ではなくエンティティ上での相対座標になってしまいます。例えば (100, 100) - (200, 200)の座標・大きさで
touchable
なエンティティがあったときに画面上の座標(120, 120)をクリックすると、取得される値は(20, 20)になってしまいます。画面上の座標を得るには、メンバ
target
を見てエンティティの行列で変換する必要があります。scene.pointDownCapture.add((ev: g.PointDownEvent) =>
{
let pos: g.CommonOffset;
if ( ev.target ) {
pos = ev.target.getMatrix().multiplyPoint(ev.point);
}
else {
pos = ev.point;
}
3.アンカーポイント
原点を指定するようなものなのですが、
append
で追加した子エンティティの相対座標に親のアンカーポイントは影響しません。例えば次のように、
const parent: g.FilledRect = new g.FilledRect(
{
scene: scene,
cssColor: "#ff0000",
x: 100,
y: 100,
width: 64,
height: 64,
anchorX: 0.5,
anchorY: 0.5,
});
scene.append(parent);
const child: g.FilledRect = new g.FilledRect(
{
scene: scene,
cssColor: "#0000ff",
x: 100,
y: 100,
width: 32,
height: 32,
anchorX: 0.5,
anchorY: 0.5,
});
scene.append(child);
中心をアンカーポイントとした2つのエンティティを、それぞれ同じ座標(100, 100)に表示すると
2つは同じ座標を中心として表示されます。当然ながら。
const child = new g.FilledRect(
{
scene: scene,
cssColor: "#0000ff",
x: 0,
y: 0,
width: 32,
height: 32,
anchorX: 0.5,
anchorY: 0.5,
});
parent.append(child);
そこで座標が同じなんだから相対位置は(0, 0)だろうと、青い方の位置を x: 0, y: 0にして 赤い方に appendしてみると
左上にずれます。
アンカーポイントに依らず、相対座標は親エンティティの左上を原点としているようです。
中心を合わせるには、
x: 64/2 + 0,
y: 64/2 + 0,
x: parent.width*parent.anchorX + 0,
y: parent.height*parent.anchorY + 0,
というわけで、自分が Akashic Engineを使う上で実際にぶつかった点を3つ程挙げてみました。
あとはラベルやフォントなんかで気になる点もあるのですが、よくわからないままその場しのぎで対応してたりするので、ここでは割愛致します。
最近のコメント