2013/12/05(木) [n年前の日記]
#3 [dxruby][game] DXRubyで二足歩行する敵っぽい動きを作ってみる
今日も DXRuby を使って何かしら書いてみます。
今回も多関節ネタということで、二足歩行する敵っぽい動きを試してみようかなと。
なんで二足歩行なのかというと…。個人的に、ちょっと思い入れがありまして…。
昔、先輩から、「このゲームの、この中ボスは、こうやって動かしてるんだよ」と説明してもらったことがありまして。「うわー、面白そう。自分もそういうのやってみたい」と思ったものの、その後、そんな機会には恵まれず。
当時は、その手のゲームを作るとなるとアセンブラで書くのが当たり前だったので、なかなか気軽に試すわけにもいかなくて。また、CGツールも無かった時代ですので、絵描きさんに「こういうの描いてくれませんか?」とお願いする必要もあって。 *1 なので、お遊びでチョチョイッと、てなわけにはいかなかったのです。 *2
だけど今なら、気軽に試せる DXRuby がある。CGツールだって、好きなだけ使える。ということで、当時、指をくわえて眺めていたあの動きを、自分もちょっと試しに書いてみようかな、と。
とりあえず、こんな感じになりました。
ざっくりと解説を。
一般的に、2Dゲームの多関節処理というと、各関節毎に角度を管理・制御して動きをつけていくのですけど。このプログラムでは、少し違うことをしています。
このプログラムでは、各関節の角度ではなく、根元と先っぽ(足先)の座標だけを管理・制御しています。そして、膝だか肘だかの座標は、計算して求めます。
この仕組みを作ってしまえば、根元と先っぽだけを制御すればいいので、比較的自由に動きをつけていけますし、時々、プログラマーが意図してなかったポーズになったりもして、見た目が面白い場面も出てきたり…するかもしれません。
MMD(MikuMikuDance)や3DCGソフトを触ったことがある人なら、ピンときたのではないでしょうか。「それって、IK(インバースキネマティクス)じゃね?」。そうです。これ“も”、IKです。
MMDや3DCGソフトのIKは、以下のような状態ですが…。
さて、実際の処理内容ですが…。
ちょっと分かりづらいかな…?。余裕があったら説明図も後で描いときます。
(※ 2013/12/06追記。説明図を追加してみました。ちょっとだけ…分かりやすくなった…のかどうか…)

ところで。
DXRuby は、スプライトの回転拡大縮小半透明描画ができますので…。何もわざわざ、複数のスプライトを数珠繋ぎにして、足を描かなくてもいいような気がしてきました。
ということで、足を丸々画像にして、回転させて描画してみましょうか。blender でレンダリングした画像を使ってみました。
ただ、見た目をリアルにしていくのは楽しい作業なのですが…。これはちょっと、落とし穴がありそうな気もしますね。
まあ、今回は、「昔憧れたあの動きを、DXRubyという便利ライブラリを使って気軽に実験」という話でやってますので、コレはコレでOKかなと。
ソースと画像も置いときますね。Public Domainってことで。
_walkenemy.zip
さて、明日も何か書いてみようかな…と思ったけれど、今日のコレで時間ギリギリだったので、二日に1回ぐらいのペースで書くかもしれないし書かないかもしれません。まあ、この手の記事を書く練習ってことで、気楽に、気楽に…。
今回も多関節ネタということで、二足歩行する敵っぽい動きを試してみようかなと。
なんで二足歩行なのかというと…。個人的に、ちょっと思い入れがありまして…。
昔、先輩から、「このゲームの、この中ボスは、こうやって動かしてるんだよ」と説明してもらったことがありまして。「うわー、面白そう。自分もそういうのやってみたい」と思ったものの、その後、そんな機会には恵まれず。
当時は、その手のゲームを作るとなるとアセンブラで書くのが当たり前だったので、なかなか気軽に試すわけにもいかなくて。また、CGツールも無かった時代ですので、絵描きさんに「こういうの描いてくれませんか?」とお願いする必要もあって。 *1 なので、お遊びでチョチョイッと、てなわけにはいかなかったのです。 *2
だけど今なら、気軽に試せる DXRuby がある。CGツールだって、好きなだけ使える。ということで、当時、指をくわえて眺めていたあの動きを、自分もちょっと試しに書いてみようかな、と。
とりあえず、こんな感じになりました。
# 二足歩行する敵モドキ
require 'dxruby'
$angadd = 2
# 足先の座標を求めるクラス
class Foot
def initialize(start_ang)
@ang = start_ang
@x = 0
@y = 0
self.update
end
attr_accessor :x, :y
def update
# 円運動をさせてみる。
# 本当は放物線運動のほうがいいと思う…けど
# 面倒臭いから今回は安直に円運動
ang = @ang * Math::PI / 180.0
@x = -128 * Math.cos(ang)
@y = 96 * Math.sin(ang)
@y = 0 if @y >= 0
@ang += $angadd
end
end
# 棒状の部分を複数のスプライトを使って描く
def draw_foot_sub(bx, by, tx, ty, img)
count = 10
count.times do |i|
x = bx + ((tx - bx) * i / (count - 1)) - img.width / 2
y = by + ((ty - by) * i / (count - 1)) - img.height / 2
Window.draw(x, y, img)
end
end
# 足を一本分描画する
def draw_foot(bx, by, tx, ty, img, flen)
dw = tx - bx
dh = ty - by
# 腰から足の先までの長さ、の半分を求める
# 三平方の定理(ピタゴラスの定理)を使ってる
l = Math.sqrt(dw * dw + dh * dh) / 2
# 膝までの距離を求める。これも、三平方の定理を使ってる
nl = flen * flen - l * l
n = (nl <= 0)? 0 : Math.sqrt(nl)
# 腰と足先の中間点を求める
cx = (bx + tx) / 2
cy = (by + ty) / 2
# 膝があるはずの方向(角度)を求める
# 腰と足先の座標から角度を求めて、それに90度足せばいい
ang = Math.atan2(dh, dw) + Math::PI / 2
# 膝の場所を求める
lx = cx + n * Math.cos(ang)
ly = cy + n * Math.sin(ang)
# 腰から膝までと、膝から足先までを描く
draw_foot_sub(bx, by, lx, ly, img)
draw_foot_sub(lx, ly, tx, ty, img)
# 分かりやすくするために線を引いてみる
Window.drawLine(cx, cy, lx, ly, [255, 0, 0])
end
img = Image.load("ufo.png")
by = 150 # 腰の高さ
gy = 420 # 地面の高さ
# 足半分の長さ
flen = (gy - by) / 2 + 20
footl = Foot.new(0)
footr = Foot.new(180)
# メインループ
Window.loop do
break if Input.keyPush?(K_ESCAPE)
# マウスカーソルのy座標で足先の移動速度を変えてみる
$angadd = 20.0 * Input.mousePosY / Window.height - 10.0
bx = Input.mousePosX
# 右足の根元と先っぽの座標を求める
n = 24
frbx = bx - n
frby = by
frtx = bx + footr.x - n
frty = footr.y + gy
# 左足の根元と先っぽの座標を求める
flbx = bx + n
flby = by
fltx = bx + footl.x + n
flty = footl.y + gy
# 右足を描く
draw_foot(frbx, frby, frtx, frty, img, flen)
# 腰を描く
Window.drawScale(bx - img.width / 2, by - 80 , img, 2, 2)
# 左足を描く
draw_foot(flbx, flby, fltx, flty, img, flen)
# 足先の座標を更新
footl.update
footr.update
# 分かりやすくするために線を引いてみる
Window.drawLine(frbx, frby, frtx, frty, [0, 255, 255])
Window.drawLine(flbx, flby, fltx, flty, [0, 255, 255])
end
ちょっとソースが長くなってしまいましたが…。もうちょっと短く書けないのかな、コレ…。ざっくりと解説を。
一般的に、2Dゲームの多関節処理というと、各関節毎に角度を管理・制御して動きをつけていくのですけど。このプログラムでは、少し違うことをしています。
このプログラムでは、各関節の角度ではなく、根元と先っぽ(足先)の座標だけを管理・制御しています。そして、膝だか肘だかの座標は、計算して求めます。
この仕組みを作ってしまえば、根元と先っぽだけを制御すればいいので、比較的自由に動きをつけていけますし、時々、プログラマーが意図してなかったポーズになったりもして、見た目が面白い場面も出てきたり…するかもしれません。
MMD(MikuMikuDance)や3DCGソフトを触ったことがある人なら、ピンときたのではないでしょうか。「それって、IK(インバースキネマティクス)じゃね?」。そうです。これ“も”、IKです。
MMDや3DCGソフトのIKは、以下のような状態ですが…。
- 3Dで計算する。
- 各関節(?)の長さは自由。
- 計算で求める関節は、複数ある。
- 2Dで計算する。(平面上の座標計算しかしない。)
- 各関節の長さは同じであること。
- 計算で求める関節は、一つだけ。
さて、実際の処理内容ですが…。
- 根元と先っぽの座標から、根元から先っぽまでの距離を求めます。これは、三平方の定理(ピタゴラスの定理)で求められますね。
- 膝があるはずの座標は、根元と先っぽを結ぶ線の、中間の座標から垂直に延びる線上にあるはずです。ですので、中間点の座標も求めておきます。
- 膝があるはずの方向は、根元と先っぽの座標から角度を求めて、その角度に90度足した角度になるはずです。
- 膝までの距離も、三平方の定理で求められます。根元と先っぽの距離は分かってますし、関節の長さは固定のはずですから。平方根(Math.sqrt())を使うあたり、処理速度が不安ですが、今時のPCなら…ねえ…。呼ぶ回数も、1フレームで数回ですし。まあ、大丈夫じゃないかなと。
ちょっと分かりづらいかな…?。余裕があったら説明図も後で描いときます。
(※ 2013/12/06追記。説明図を追加してみました。ちょっとだけ…分かりやすくなった…のかどうか…)

ところで。
DXRuby は、スプライトの回転拡大縮小半透明描画ができますので…。何もわざわざ、複数のスプライトを数珠繋ぎにして、足を描かなくてもいいような気がしてきました。
ということで、足を丸々画像にして、回転させて描画してみましょうか。blender でレンダリングした画像を使ってみました。
# 二足歩行する敵モドキ。足を画像にしてみた版
require 'dxruby'
$angadd = 2
# 足先の座標を求めるクラス
class Foot
def initialize(start_ang)
@ang = start_ang
@x = 0
@y = 0
self.update
end
attr_accessor :x, :y
def update
# 円運動をさせてみる。
# 本当は放物線運動のほうがいいと思う…けど
# 面倒臭いから今回は安直に円運動
ang = @ang * Math::PI / 180.0
@x = -128 * Math.cos(ang)
@y = 96 * Math.sin(ang)
@y = 0 if @y >= 0
@ang += $angadd
end
end
# 棒状の部分を1枚のスプライトを回転させて描く
def draw_foot_sub(bx, by, tx, ty, img)
cx = (tx + bx) / 2 - img.width / 2
cy = (ty + by) / 2 - img.height / 2
dw = tx - bx
dh = ty - by
ang = Math.atan2(dh, dw) * 180.0 / Math::PI
Window.drawRot(cx, cy, img, ang)
end
# 足を一本分描画する
def draw_foot(bx, by, tx, ty, img, flen)
dw = tx - bx
dh = ty - by
# 腰から足の先までの長さ、の半分を求める
# 三平方の定理(ピタゴラスの定理)を使ってる
l = Math.sqrt(dw * dw + dh * dh) / 2
# 膝までの距離を求める。これも、三平方の定理を使ってる
nl = flen * flen - l * l
n = (nl <= 0)? 0 : Math.sqrt(nl)
# 腰と足先の中間点を求める
cx = (bx + tx) / 2
cy = (by + ty) / 2
# 膝があるはずの方向(角度)を求める
# 腰と足先の座標から角度を求めて、それに90度足せばいい
ang = Math.atan2(dh, dw) + Math::PI / 2
# 膝の場所を求める
lx = cx + n * Math.cos(ang)
ly = cy + n * Math.sin(ang)
# 腰から膝までと、膝から足先までを描く
draw_foot_sub(bx, by, lx, ly, img)
draw_foot_sub(lx, ly, tx, ty, img)
# 分かりやすくするために線を引いてみる
# Window.drawLine(cx, cy, lx, ly, [255, 0, 0])
end
img = Image.load("foot.png")
bodyimg = Image.load("body.png")
by = 150 # 腰の高さ
gy = 420 # 地面の高さ
# 足半分の長さ
flen = (gy - by) / 2 + 20
footl = Foot.new(0)
footr = Foot.new(180)
# メインループ
Window.loop do
break if Input.keyPush?(K_ESCAPE)
# マウスカーソルのy座標で足先の移動速度を変えてみる
$angadd = 20.0 * Input.mousePosY / Window.height - 10.0
bx = Input.mousePosX
# 右足の根元と先っぽの座標を求める
n = 24
frbx = bx - n
frby = by
frtx = bx + footr.x - n
frty = footr.y + gy
# 左足の根元と先っぽの座標を求める
flbx = bx + n
flby = by
fltx = bx + footl.x + n
flty = footl.y + gy
# 右足を描く
draw_foot(frbx, frby, frtx, frty, img, flen)
# 腰を描く
Window.draw(bx - 150, by - 100, bodyimg)
# 左足を描く
draw_foot(flbx, flby, fltx, flty, img, flen)
# 足先の座標を更新
footl.update
footr.update
# 分かりやすくするために線を引いてみる
# Window.drawLine(frbx, frby, frtx, frty, [0, 255, 255])
# Window.drawLine(flbx, flby, fltx, flty, [0, 255, 255])
end
うむ。実にそれらしくなりましたな。ただ、見た目をリアルにしていくのは楽しい作業なのですが…。これはちょっと、落とし穴がありそうな気もしますね。
まあ、今回は、「昔憧れたあの動きを、DXRubyという便利ライブラリを使って気軽に実験」という話でやってますので、コレはコレでOKかなと。
ソースと画像も置いときますね。Public Domainってことで。
_walkenemy.zip
さて、明日も何か書いてみようかな…と思ったけれど、今日のコレで時間ギリギリだったので、二日に1回ぐらいのペースで書くかもしれないし書かないかもしれません。まあ、この手の記事を書く練習ってことで、気楽に、気楽に…。
◎ 余談。他のツールを検討すべき境界線。 :
DXRuby に限らず、2Dゲームライブラリを使ってアレコレ作っていく際に、見た目をリアルにしていくことにリソースを割いていくと…。
どこかの時点で、「ソレ、Unity使ったほうがよくね?」となりそうな気がするのです。つまり、そこに落とし穴というか、「ここから先は別のツールを使ったほうが…」と判断できる境界線が、どこかにあるんじゃないのかな、と思えるのですね。
Unity の場合…。
ただ、Unityも、習得に要する時間というものがあるわけで…。
習得時間に関しては、DXRuby 等、2Dゲームライブラリは、比較的短いので…。やはり、「ちょっと試してみる」「この手のプログラムの書き方を勉強してみる」という点では、メリットがあるようにも思うのでした。
どこかの時点で、「ソレ、Unity使ったほうがよくね?」となりそうな気がするのです。つまり、そこに落とし穴というか、「ここから先は別のツールを使ったほうが…」と判断できる境界線が、どこかにあるんじゃないのかな、と思えるのですね。
Unity の場合…。
- 3Dで描画するから、見た目からしてリアル。
- 多関節キャラを扱うのは当たり前。そもそも、人体モデルが多関節キャラですから…。
- 多関節キャラの動きを、3DCGソフトを使って確認しながら作り込める。トライ&エラーが簡単。
- モーションの合成も、基本機能として持っている。
- IKも、基本機能として持っている…。
ただ、Unityも、習得に要する時間というものがあるわけで…。
習得時間に関しては、DXRuby 等、2Dゲームライブラリは、比較的短いので…。やはり、「ちょっと試してみる」「この手のプログラムの書き方を勉強してみる」という点では、メリットがあるようにも思うのでした。
*1: 当時は、国民機 PC-9801 を使ってた時代ですから…。16色しか出ませんから…。TVゲーム機用の色を出すには、自社設計のハードウェアをPC-9801に追加して、とかやってた時代ですから…。プログラマーが、余技? 趣味? でCGツールも使わせてもらう、なんてことは、ちょっと難しかったのです。…いや、やる気ビンビンな先輩方は「俺にもCGツール使わせて!」とかやってましたけど。自分は引っ込み思案、かつ、プログラム書くだけで手一杯でそこまでは…。
*2: そもそも自分、当時はまともなPCを持ってなかった気がする。X1 turbo は部屋にあったかもしれないけど、電源すら入れてなかったような…。X68Kでも持ってたら違ったのかもしれないけど。
*2: そもそも自分、当時はまともなPCを持ってなかった気がする。X1 turbo は部屋にあったかもしれないけど、電源すら入れてなかったような…。X68Kでも持ってたら違ったのかもしれないけど。
[ ツッコむ ]
以上です。

