mieki256's diary



2013/12/05(木) [n年前の日記]

#3 [dxruby][game] DXRubyで二足歩行する敵っぽい動きを作ってみる

今日も DXRuby を使って何かしら書いてみます。

今回も多関節ネタということで、二足歩行する敵っぽい動きを試してみようかなと。

なんで二足歩行なのかというと…。個人的に、ちょっと思い入れがありまして…。

昔、先輩から、「このゲームの、この中ボスは、こうやって動かしてるんだよ」と説明してもらったことがありまして。「うわー、面白そう。自分もそういうのやってみたい」と思ったものの、その後、そんな機会には恵まれず。

当時は、その手のゲームを作るとなるとアセンブラで書くのが当たり前だったので、なかなか気軽に試すわけにもいかなくて。また、CGツールも無かった時代ですので、絵描きさんに「こういうの描いてくれませんか?」とお願いする必要もあって。 *1 なので、お遊びでチョチョイッと、てなわけにはいかなかったのです。 *2

だけど今なら、気軽に試せる DXRuby がある。CGツールだって、好きなだけ使える。ということで、当時、指をくわえて眺めていたあの動きを、自分もちょっと試しに書いてみようかな、と。

とりあえず、こんな感じになりました。

二足歩行その1
# 二足歩行する敵モドキ

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は、以下のような状態ですが…。 このプログラムでは、以下のような状態です。 制限だらけですね…。とはいえ、さすがに、これだけ制限があれば、昔の非力なCPUでもIKが実現できたわけです。実際、スーパーファミコンやメガドライブの某タイトルでは、こういう仕組みでボス敵が動いてました。…スーパーファミコンのCPUはとにかく遅くてプログラマー泣かせだったらしいですけど、プログラマーの工夫次第で、こういうこともできたわけですね。

さて、実際の処理内容ですが…。 ここまで分かれば、中間点の座標、sin、cos、膝があるはずの角度、膝までの距離、を使って膝座標が得られますね。

ちょっと分かりづらいかな…?。余裕があったら説明図も後で描いときます。

(※ 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 の場合…。
  • 3Dで描画するから、見た目からしてリアル。
  • 多関節キャラを扱うのは当たり前。そもそも、人体モデルが多関節キャラですから…。
  • 多関節キャラの動きを、3DCGソフトを使って確認しながら作り込める。トライ&エラーが簡単。
  • モーションの合成も、基本機能として持っている。
  • IKも、基本機能として持っている…。
てな感じの、大変便利なツールですので…。何も今から、Unityと同じことをしなくてもいいんじゃないか、車輪の再発明をしなくても、ということになりそうですよね。

ただ、Unityも、習得に要する時間というものがあるわけで…。

習得時間に関しては、DXRuby 等、2Dゲームライブラリは、比較的短いので…。やはり、「ちょっと試してみる」「この手のプログラムの書き方を勉強してみる」という点では、メリットがあるようにも思うのでした。

*1: 当時は、国民機 PC-9801 を使ってた時代ですから…。16色しか出ませんから…。TVゲーム機用の色を出すには、自社設計のハードウェアをPC-9801に追加して、とかやってた時代ですから…。プログラマーが、余技? 趣味? でCGツールも使わせてもらう、なんてことは、ちょっと難しかったのです。…いや、やる気ビンビンな先輩方は「俺にもCGツール使わせて!」とかやってましたけど。自分は引っ込み思案、かつ、プログラム書くだけで手一杯でそこまでは…。
*2: そもそも自分、当時はまともなPCを持ってなかった気がする。X1 turbo は部屋にあったかもしれないけど、電源すら入れてなかったような…。X68Kでも持ってたら違ったのかもしれないけど。

以上です。

過去ログ表示

Prev - 2013/12 - Next
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31

カテゴリで表示

検索機能は Namazu for hns で提供されています。(詳細指定/ヘルプ


注意: 現在使用の日記自動生成システムは Version 2.19.6 です。
公開されている日記自動生成システムは Version 2.19.5 です。

Powered by hns-2.19.6, HyperNikkiSystem Project