RubySketch ゲームエンジン化計画 〜 物理演算付きスプライトクラスの追加など
この記事は GameEngineDev Advent Calendar 2022 の10日目の記事です。
こんにちは、今日は私が趣味で公開している RubySketch という iOS アプリについて、今後どんな機能を追加していく予定があるのかを書いてみたいと思います。
RubySketch のストアページ
もし iPhone や iPad をお持ちで、RubySketch が面白そうなアプリだなと思っていただけたら、是非お手元でダウンロードして遊んでみていただけたらとても嬉しいです。(無料アプリです!)
そもそも RubySketch とは?
いきなり RubySketch の未来の話をする前にそもそも何それ?という方がほとんどだと思いますので、まずは RubySketch がどんなアプリなのかを軽く紹介[1]させてください。
Processing 開発環境としての RubySketch
このアプリは、まず一言で説明すると 「スマホで手軽に Ruby + Processing[2] しよう」 というアプリです。
要はアプリ上で Ruby スクリプトが書けてそれをそのまま実行することができ、よく知られたグラフィックス系の API が組み込まれてるので簡単にグラフィカルなプログラミングが楽しめるというアプリになっています。
コーディング画面と実行画面
例えばこんなふうな短いスクリプトを書くだけで、
setup do
background 0 # 背景を黒でクリア
noStroke # 枠線の描画は無し
colorMode HSB, 1 # 色は HSB で指定するモードに
ellipseMode CENTER # 円は中心座標を指定して描画するモードに
end
# 画面タッチ時とドラッグ時にその座標を使って brush() 関数を呼ぶ
mousePressed { brush mouseX, mouseY }
mouseDragged { brush mouseX, mouseY }
# ブラシを描画する関数
def brush(x, y)
blendMode ADD # 加算合成モードに
fill (frameCount / 100.0), 0.8, 1, 0.1 # 経過時間に応じて色を変化させる
circle x, y, 100 # 指定の座標に丸を描画する
end
簡単なペイントソフトのようなものが作れてしまいます。
BrightBrush.rb を実行した様子
ゲームエンジンとしての RubySketch
画像を任意の位置に描画でき、それを毎秒最大60フレームで全画面更新できるとなると2Dゲームが作れてしまいます。そのため RubySketch には、サンプルスクリプトとしていくつか簡単なゲームのソースコードも同梱されています。
RubySketch に同梱されたサンプルゲームたち
現状サウンド周りの API がないので大きく機能不足ではあるものの、ここまでゲームが作れるプログラミング環境であればゲームエンジンと呼ぶことも可能でしょう。ということでここからは RubySketch をゲームエンジンとしてとらえて話を進めていきたいと思います。
RubySketch の開発ロードマップ
現在公開している RubySketch の最新バージョンは v2.1 です。今年の8月25日に公開しています。
バージョン 2.1 の履歴
履歴を見ると AppStore での最初のリリースが2020年11月7日なのでそこから2年経っているわけですが、12回バージョンアップをしているのでまあまあの更新頻度ですね。本当はもっとバンバン機能追加して頻繁に更新していきたいところですが、開発に充てられる時間が平日の深夜にだいたい1、2時間程度と考えるとまあこんなものでしょう。
今後も開発する時間が増える予定は無いため、この限られた時間で今後どんな機能追加をしていく予定なのか、ここから書いていきたいと思います。
直近の予定
まずはここから1ヶ月くらいの予定です。この辺はほぼ確実な範囲で書きます。
v2.2
次のリリースは v2.2 です。これはもう 98%完成しているのでほぼこのまま出します。あとは README やストア情報を書くだけです。[3]
- Shader クラスを追加
- 以下の関数を追加
- Examples/FilterCamera.rb サンプルスクリプトを追加
- 本家 Processing と BUTT と SQUARE が逆だったので修正
- 本家 Processing に合わせて BUTT を PROJECT にリネーム
- テキストの描画品質を改善
- setUniform() 関数が数値の配列、ベクトル、画像を受け取れるように
- pushStyle() が textAlign、tint、filter の状態も管理するように
- push/pushMatrix/pushStyle にブロックを渡したとき、特定の状況で pop/popMatrix/popStyle が呼ばれないことがある不具合を修正
前回の更新から 4ヶ月経ってるだけあって、シェーダー(GLSL)周りの機能追加と大きな改修になっています。
こんな感じで自由にフラグメントシェーダーを書いて描画時に使うことができるようになります。
fragmentShader = <<EOS
varying vec4 vertTexCoord; // テクスチャ座標
uniform sampler2D texMap; // テクスチャ
void main() {
vec4 color = texture2D(texMap, vertTexCoord.st); // テクスチャから色を取得
vec3 rgb = 1.0 - color.rgb; // RGB を反転する
gl_FragColor = vec4(rgb, 1.0); // 結果を RGB で指定する
}
EOS
# シェーダーを作成
s = createShader nil, fragmentShader
# 画像を HTTP で取得する
img = loadImage "https://xord.org/rubysketch/images/rubysketch128.png"
# 作成したシェーダーを有効化して画像を描画する
shader s
image img, 0, 0
左が RGB 反転なしの描画で、右がシェーダーによる RGB 反転ありの描画
プログラミングアプリということでただでさえ簡単ではないアプリの中、シェーダーというさらに上級者向けの機能になるので喜ぶ人も限られるかもしれませんが、シェーダーが使えると画面全体のエフェクトを手軽に書けたりとゲームの開発環境としても大きな意味のあるアップデートとなりそうです。
v2.3
続く v2.3 はまだ具体的になってませんが、Discord サーバーを作ってアプリサポート用とする予定なのでその招待リンクをアプリに組み込もうと思ってます。
- アプリサポート用 Discord サーバーへのリンクを追加
- ほか細かいバグ修正など
Discord とりあえずサーバー作成だけしました。招待リンク貼ってみるので興味があればどうぞ
これから1年以内に追加予定の機能
でここからが本題です。ゲームエンジンとして機能強化する計画を書いていきます。
スプライトクラスの追加
本当はシェーダーより先にこっちを作ろうと思ってたんですが、ついシェーダー周りに手を付けてしまい深みにハマり時間をかけてしまったので、次こそはスプライトクラスを追加したいところです。
イメージ的にはこんなふうに使えるクラスにしたいなと思ってます。
# 画像の一部(16x16)を使ってスプライトを作成
s = createSprite(image, 100, 200, 16, 16)
s.update do
# 時間経過でスプライトの移動速度を減速
s.velocity *= 0.95
end
draw do
# スプライトを描画
image(s, s.x, s.y)
end
mousePressed do
# 画面タップしたらスプライトにランダムな速度を設定
s.velocity = Vector.random2D() * 5
end
アプリ同梱のサンプルゲームでも毎回以下のようなクラスを書いているので、それを組み込みの Sprite クラスでまかなえるようにしようという意図です。
class GameObj
def initialize(x, y, w, h, vx = 0, vy = 0)
@w, @h = w, h
@pos = createVector x, y
@vel = createVector vx, vy
end
attr_accessor :pos, :w, :h, :vel
def x() = pos.x
def y() = pos.y
def vx() = vel.x
def vy() = vel.y
def l() = x # left
def t() = y # top
def r() = x + w # right
def b() = y + h # bottom
def center() = createVector(x + w / 2, y + h / 2)
def intersect?(o) =
o && overlap?(l, r, o.l, o.r) && overlap?(t, b, o.t, o.b)
def overlap?(a1, a2, b1, b2) = a1 <= b2 && b1 <= a2
def update() = @pos.add @vel
def draw() = rect 0, 0, w, h
end
物理演算機能の追加
スプライトクラスを追加したら、次はそのスプライトに物理演算機能を組み込む[4]計画です。
このような感じで書くとスプライト同士が自動で衝突判定されるイメージです。
player = createSprite(...) # プレイヤーオブジェクト
enemy = createSprite(...) # 敵オブジェクト
enemy.contact do |o| # 敵オブジェクトがなにかと衝突したら呼ばれる
gameover! if o == player # 衝突相手がプレイヤーだったらゲームオーバー
end
mousePressed do
bullet = createSprite(player.pos, ...) # 画面タッチで自機から弾丸を発射
bullet.velocity = createVector(0, -50) # 画面上に向けて移動速度を与える
bullet.contact do |o| # 弾丸となにかが衝突したら呼ばれる
enemy.destroy! if o == enemy # 衝突相手が敵だったら敵を破壊
end
end
プラットフォーマー系ゲームが作りたければ、ステージとして床や壁などの位置固定のスプライトを配置し、自由に移動可能なスプライトとして自機や敵機を配置するだけでそれなりに動くものが割と簡単に作れてしまうかもしれません。
そういうゲームだと、上に乗ることはできるけど下からぶつかると衝突せずすり抜けられるような床がよくありますが、そういう判定も Ruby で手軽に記述できるようにしたいなぁと妄想してます。
# 床スプライトを作る
floor = createSprite(...)
# 他オブジェクトと重なったタイミングで呼ばれる
# 重なったことで衝突したとするなら true、衝突としないなら false を返す
floor.contact? do |o|
# 相手がプレイヤーオブジェクトで Y 方向の速度が0もしくは下向きなら衝突と判定(= 床に乗れる)
# つまりジャンプ中(速度が上向き)なら衝突しないようにする
o == player && o.velocity.y >= 0
end
簡易ドット絵編集機能の追加
ゲームエンジンのゲーム開発支援機能として、ドット絵編集機能も入れたいです。イメージとしては PICO-8 を始めとする Fantasy Console のスプライト・エディターみたいなやつ。
左から PICO-8、TIC-80、pyxel のスプライト・エディター画面
ガチガチの Fantasy Console を作りたいわけではないのでスクリーンサイズや色数、スプライトの最大数など制限は特に入れないつもりですが、最初は 16x16 ドット専用のシンプルなドット絵編集機能を、これもサンプルスクリプトとして組み込もうかと思ってます。サンプルスクリプトとして同梱することで利用者が改造できるのがまた面白そうです。機能は敢えてそこまで豪華ににせず、ユーザに改造して遊んでもらうのもありかなと。
ファイル一覧画面から、特定の拡張子だったら特定のスクリプトを起動するような仕組みを入れて、例えば 16x16 サイズの PNG ファイルを選択したらドット絵編集スクリプトが起動[5]するようになどしたいと思ってます。
さらにその先の予定(もしくは妄想)
もっと先、1年以上かかりそうだけど追加したいと思ってる機能もつらつらと書いてみます。
テキストエディターの機能強化
iPhone でプログラミングするというと、やはりテキストエディター画面の出来がよくないとなかなか継続して使おうという気持ちにならないと思います。なのでコード入力周りの機能強化は継続的にすすめていく予定です。
なかでも以下の2つは、大きめの機能追加になりますが需要は大きいと思うので、できれば早めに追加したいと思っています。
- コード補完機能の追加[6]
- リファレンス表示機能の追加
こちらは RubyMine のコード補完のようす
サウンド関連 API の追加
現状サウンド関連の API が有りません。なので無音のゲームしか作れません。これは早いうちになんとかしたいなと思っています。
一応ベースにしてるライブラリの方にはサウンド周りの基本部分は入れているのでサイン波を鳴らす程度のことはできるのですが、それを Processing API レイヤーにどう組み込もうかというところで手が止まっている状況です。
ベース部分は OpenAL と Synthesis Tool Kit を組み込んであるので Web Audio API のサブセットみたいなモノなら作れるはずなのですがどこまで作り込むかはまだ悩んでる感じです。
Web Audio の本も買いました
Fantasy Console 系機能の強化
ドット絵編集機能のところでも書きましたが、Fantasy Console は大体以下の機能を持ってると思います。なのでできればこれ系も追加していきたいと思っています。
- マップ/ステージ編集機能の追加
- 簡易効果音作成機能の追加
- 簡易音楽作成機能の追加
TIC-80 の効果音作成画面と音楽作成画面
効果音と音楽作成機能はサウンド周りが整備できたら、ですね。
アプリ内で作成したゲームスクリプトを単体の iOS アプリとしてエクスポートする機能の追加
そしてこれがいよいよ妄想レベルの機能ですw
まずはアプリ内で Xcode のプロジェクトを生成して保存できる機能を入れたいですね。XcodeGen のファイルと必要なファイルを生成して zip に固めて iOS の File アプリから見れる場所に保存する、ぐらいの程度なら無理なく実現できるかなと思ってます。
ただこれだと、ビルドから AppStore へのサブミットまで結局 Mac が無いと出来ないので中途半端です。なのでここから更に妄想ですがクラウドでビルド・サブミットまでできるようにしたら面白くなりそうとか思ってます。iPhone ひとつあればゲーム作って AppStore に公開するところまで持って行けるっていうのは他であんまり無さそうかなと。
ビルドするクラウド環境どうするかなとなると個人運営で経済的にもそこまでリッチなサーバー環境使えないので、GitHub Actions とかで賄えないだろうかとか考えたりしてます。アプリから GitHub ログインしてもらって Git で push まで出来れば、あとは GitHub Actions の設定ファイルを入れておけば利用者の利用枠内でビルドできるよなと。で Fastlane でサブミットするとこまで組んでおけば[7]クラウドで AppStore サブミットまで行けるのでは、みたいな妄想です。
中高生あたりだと iPhone はあるけど自由に使える Mac が近場に無いとかは割とありそうな気もするので、これができれば面白がってくれる人も居そう[8]なので、ここまでの仕組みいつか作ってみたいですね。
おわりに
ということで、RubySketch というアプリの将来についてざーっと書いてみました。この記事を読んでもし興味をもっていただけたら RubySketch 是非ダウンロードして使ってみてください!(何度も言いますが無料アプリです!)
関連リンク
最後に、私が過去のアドベントカレンダーに書いた記事も参考のため載せておきますね。
これらの記事で触れている内容もすべて RubySketch につながる内容になっていますのでよければご覧ください。
- Ruby で Processing する gem(とアプリ)の紹介 (2020)
- iPhone の「カメラ入力」でおもしろビデオカメラを作ろう (2020)
- CRuby でゲームを作って iPhone で動かそう (2015)
- Ruby 用自作 GUI ツールキットに Box2D を組み込んだらブロック崩しゲームが簡単に作れた話 (2014)
- CRuby インタプリタの CocoaPod を作ったので紹介します (2014)
-
ちなみにこのアプリ、第14回フクオカRuby大賞で優秀賞をいただいており、表彰状・トロフィーのほかなんと副賞として10万円もの賞金をいただいております。これなにげにアプリに入れてる2年分の広告収入より多いですw ↩︎
-
Processing は、クリエイティブコーディングやジェネラティブアート等で有名なグラフィックスプログラミング環境で、シンプルな関数呼び出しで丸や矩形、線分、画像などを簡単に描画することができる統合開発環境です ↩︎
-
と思って作業してるとついバグを見つけて修正に時間取られて公開がダラダラ遅くなっていくとかよくあるんですが ↩︎
-
アプリの内部的にはすでに物理演算エンジンとして Box2D が組み込んであるので、そこからどういうインターフェイスの API として公開していこうかなという話になります ↩︎
-
スクリプト側はファイル名が ARGV に入ってくるのでそのファイルを開くイメージ ↩︎
-
Ruby 界隈で最近流行ってる静的型検査周りをうまくつかって実現出来ないかなと ↩︎
-
RubySketch 自体のビルド周りでかなりの範囲は Fastlane で自動化済みなので、そのへんを再利用すれば割と実現できそう ↩︎
-
それでも AppStore 公開まで行くには Apple 税で毎年1万円強を納める必要があるので中学生とかだときびしいでしょうか ↩︎
Discussion