2014/01/23(木) [n年前の日記]
#1 [dxruby] 砲台はなんとかなってきた
とりあえず、砲台を動かすために書いた処理を、別ファイルにまとめたり。
_gamemath.rb
単体でも動くけど、他のファイルから呼んで使うこともできるはず。Public Domain ってことで。
ruby gamemath.rb 0 とか ruby gamemath.rb 3 とか打てば、下のような画面が出るので、マウスカーソルを動かして動作確認できるかと。0〜3 まで指定可能。
以下のページが参考になりました。ありがたや。
_[ Azure Contrail ] アズールコントレイル - Tips
_Flashゲーム講座&ASサンプル集【狙撃の計算方法について】
ちなみに。DXRuby開発版なら、外積とか内積とかそのへん求めるメソッドもあるのですが…。開発版は、常時最新版しか公開されてなくて、入手にちょっと難があるので、そのあたり、DXRuby 1.4.1 に取り込まれることを期待。
_gamemath.rb
# ゲーム関係の計算用メソッド
#
# ruby gamemath.rb 1 といった指定で、テスト種類を変えられる
require 'dxruby'
# 計算用メソッド
class GameMath
class << self
#
# 度からラジアンへの変換
#
# @param [float] deg 角度。単位は度
# @retrun [float] 角度。単位はラジアン
#
def deg2rad(deg)
return (deg * Math::PI / 180.0)
end
#
# ラジアンから度への変換
#
# @param [float] rad 角度。単位はラジアン
# @retrun [float] 角度。単位は度
#
def rad2deg(rad)
return (rad * 180.0 / Math::PI)
end
#
# 基準座標から目標座標への角度を返す
#
# @param [int] tgt_x 目標座標x
# @param [int] tgt_y 目標座標y
# @param [int] base_x 基準座標x
# @param [int] base_y 基準座標y
# @return [float] 目標座標までの角度。単位はラジアン
#
def get_dir_rad(tgt_x, tgt_y, base_x, base_y)
tx = tgt_x - base_x
ty = tgt_y - base_y
return Math.atan2(ty, tx)
end
#
# 角度をn方向に制限して返す
#
# @param [float] ang 元の角度
# @param [float] rest 何方向にするか。デフォルトは16
# @return [float] rest方向に制限した角度
#
def get_dir_rest(ang, rest=16)
d = 360.0 / rest
dir = ((ang + (d / 2)) % 360.0).div(d)
return dir * d
end
#
# 角度と速度(距離)からx,y方向の移動量を求める(度を与える版)
#
# @param [float] ang 角度。単位は度
# @param [float] spd 速度(距離)
# @param [float] bx 原点となる座標x。省略時は0
# @param [float] by 原点となる座標y。省略時は0
# @return [Array] 移動量x,y
#
def get_vec(ang, spd, bx=0, by=0)
return GameMath.get_vec_rad(GameMath.deg2rad(ang), spd, bx, by)
end
#
# 角度と速度(距離)からx,y方向の移動量を求める(ラジアンを与える版)
#
# @param [float] ang 角度。単位はラジアン
# @param [float] spd 速度(距離)
# @param [float] bx 原点となる座標x。省略時は0
# @param [float] by 原点となる座標y。省略時は0
# @return [Array] 移動量x,y
#
def get_vec_rad(rad, spd, bx=0, by=0)
x = spd * Math.cos(rad) + bx
y = spd * Math.sin(rad) + by
return x, y
end
#
# 外積を返す
#
# @param [int] tgt_x 目標ベクトルx
# @param [int] tgt_y 目標ベクトルy
# @param [int] base_x 基準ベクトルx
# @param [int] base_y 基準ベクトルy
# @return [float] 外積。
# 外積 > 0 なら、目標は右にあり、
# 外積 < 0 なら、目標は左にある
# 外積 = 0 なら、同じ方向
#
def get_cross_product(tgt_x, tgt_y, base_x, base_y)
return base_x * tgt_y - base_y * tgt_x
end
#
# 徐々に目標方向を向く角度を求める
#
# @param [float] ang 現在の角度。単位は度
# @param [float] ang_spd 角度変化量。単位は度
# @param [float] tgt_x 目標座標x
# @param [float] tgt_y 目標座標y
# @param [float] base_x 基準座標x
# @param [float] base_y 基準座標y
# @return [float] 新しい角度。単位は度
#
def get_chase_angle(ang, ang_spd, tgt_x, tgt_y, base_x, base_y)
ang %= 360
tgt_ang = GameMath.rad2deg(GameMath.get_dir_rad(tgt_x, tgt_y, base_x, base_y))
tgt_ang %= 360
d = tgt_ang - ang
d += 360 if d < -180
d -= 360 if d > 180
if d > ang_spd
ang += ang_spd
elsif d < -ang_spd
ang -= ang_spd
else
ang = tgt_ang
end
return ang
end
#
# 指定範囲に収まった角度を返す
#
# @param [float] now_ang 現在の角度。単位は度
# @param [float] base_ang 基準角度。単位は度
# @param [float] min_d 角度範囲の最小値算出用。基準角度に加算する値
# @param [float] max_d 角度範囲の最大値算出用。基準角度に加算する値
# @return [float] 範囲に収まった角度。単位は度
#
def get_dir_range(now_ang, base_ang, min_d, max_d)
base_ang %= 360
now_ang %= 360
# 現在角度が、基準角度の正反対の角度を超える角度だったら補正する
if now_ang > base_ang + 180
now_ang -= 360
elsif now_ang <= base_ang - 180
now_ang += 360
end
min_ang = base_ang + min_d
max_ang = base_ang + max_d
now_ang = min_ang if now_ang < min_ang
now_ang = max_ang if now_ang > max_ang
return now_ang
end
#
# 2点の座標間の距離が指定距離より小さいかを返す
#
# @param [int] tx 目標座標x
# @param [int] ty 目標座標y
# @param [int] bx 基準座標x
# @param [int] by 基準座標y
# @param [int] dist 判定距離
# @return {Boolean] trueなら、判定距離内に入ってる。falseなら、判定距離外
#
def check_dist(tx, ty, bx, by, dist)
w = tx - bx
h = ty - by
return (w * w + h * h <= dist * dist)? true : false
end
end
end
if $0 == __FILE__
# 動作確認
# 動作種類を変える
if ARGV.length >= 1
test_kind = ARGV[0].to_i
else
test_kind = 0
end
font = Font.new(16)
fh = 20
bx = 320
by = 240
ang = 0
base_ang = 0
Window.loop do
break if Input.keyPush?(K_ESCAPE)
px, py = Input.mousePosX, Input.mousePosY
case test_kind
when 0
# 方向(角度)に制限を加えるテスト
# n方向に制限する
n = 12
rad = GameMath.get_dir_rad(px, py, bx, by) # 本来の角度
new_deg = GameMath.get_dir_rest(GameMath.rad2deg(rad), n) # 制限した角度
# 描画
d = 360 / n
s = d / 2
n.times do |i|
nx, ny = GameMath.get_vec(s + d * i, 80, bx, by)
Window.drawLine(bx, by, nx, ny, C_RED)
end
nx, ny = GameMath.get_vec(GameMath.rad2deg(rad), 200, bx, by)
Window.drawLine(bx, by, nx, ny, C_CYAN)
rad = GameMath.deg2rad(new_deg)
nx, ny = GameMath.get_vec(GameMath.rad2deg(rad), 150, bx, by)
Window.drawLine(bx, by, nx, ny, C_YELLOW)
Window.drawFont(0, 0, "#{n}方向にする制限を加える例", font)
when 1
# 外積を使って目標を追尾するように角度を変えるテスト
# プルプル震えてしまう
ang_spd = 3
# 目標ベクトル
tx = px - bx
ty = py - by
# 基準ベクトル
vx = Math.cos(GameMath.deg2rad(ang))
vy = Math.sin(GameMath.deg2rad(ang))
# 外積を求める
cp = GameMath.get_cross_product(tx, ty, vx, vy)
# 外積の符号を見て、目標が右にあるか左にあるか判別
ang += ang_spd if cp > 0
ang -= ang_spd if cp < 0
ang %= 360
# 描画
nx, ny = GameMath.get_vec(ang, 200, bx, by)
Window.drawLine(bx, by, nx, ny, C_CYAN)
Window.drawFont(0, 0, "外積の符号で追尾する例", font)
when 2
# 目標への角度を求めて、追尾するように角度を変えるテスト
ang = GameMath.get_chase_angle(ang, 2, px, py, bx, by)
# 描画
nx, ny = GameMath.get_vec(ang, 200, bx, by)
Window.drawLine(bx, by, nx, ny, C_CYAN)
Window.drawFont(0, 0, "角度を見て追尾する例", font)
when 3
# 角度に制限を加える例
base_ang += Input.x * 2 # 基準角度を左右キーで変更
rad = GameMath.get_dir_rad(px, py, bx, by) # 本来の角度
ang = GameMath.rad2deg(rad)
# 基準角度からプラスマイナス aw の範囲に角度を制限する
aw = 90
ang = GameMath.get_dir_range(ang, base_ang, -aw, aw)
# 描画
nx, ny = GameMath.get_vec(base_ang, 250, bx, by)
Window.drawLine(bx, by, nx, ny, C_RED)
nx, ny = GameMath.get_vec(base_ang - aw, 150, bx, by)
Window.drawLine(bx, by, nx, ny, C_YELLOW)
nx, ny = GameMath.get_vec(base_ang + aw, 150, bx, by)
Window.drawLine(bx, by, nx, ny, C_YELLOW)
nx, ny = GameMath.get_vec(ang, 200, bx, by)
Window.drawLine(bx, by, nx, ny, C_CYAN)
Window.drawFont(0, 0, "角度に制限を加える例 : 左右キーで角度変更", font)
end
end
end
各スクリプトに直接書いてしまってもいいぐらいのソレばかりですが…。単体でも動くけど、他のファイルから呼んで使うこともできるはず。Public Domain ってことで。
ruby gamemath.rb 0 とか ruby gamemath.rb 3 とか打てば、下のような画面が出るので、マウスカーソルを動かして動作確認できるかと。0〜3 まで指定可能。
以下のページが参考になりました。ありがたや。
_[ Azure Contrail ] アズールコントレイル - Tips
_Flashゲーム講座&ASサンプル集【狙撃の計算方法について】
ちなみに。DXRuby開発版なら、外積とか内積とかそのへん求めるメソッドもあるのですが…。開発版は、常時最新版しか公開されてなくて、入手にちょっと難があるので、そのあたり、DXRuby 1.4.1 に取り込まれることを期待。
◎ DXRubyの便利なところ。 :
ここ数日、DXRubyを触っていて「便利だなー」と思った点が。メインループが数行で済む点は、ありがたい。おかげで、各ファイルの動作テストが書きやすいなと。
フツー、スクロールゲーム用のアレコレを書く場合は…。
DXRubyなら、敵のプログラムが書いてあるスクリプトファイルに、ちょこっと動作確認用のメインループを書いちゃえば、そのスクリプトだけで動きの確認がそこそこできるわけで。
特に Ruby の場合、以下のような書き方が可能で。
ということで、Ruby + DXRuby は、プロトタイプ作成に便利だなと再認識。
フツー、スクロールゲーム用のアレコレを書く場合は…。
- ゲーム本体のプログラムを動かして、
- 目的の場所までスクロールさせて、
- そこで出てくる敵その他の動きを確認して、
- 敵のプログラムを調整して、
DXRubyなら、敵のプログラムが書いてあるスクリプトファイルに、ちょこっと動作確認用のメインループを書いちゃえば、そのスクリプトだけで動きの確認がそこそこできるわけで。
特に Ruby の場合、以下のような書き方が可能で。
if $0 == __FILE__ # このスクリプトを単体で動かした時だけ、この部分を通る endまあ、Python なども、同じことができますけど…。何にせよ、この部分に動作確認用のソレを書いとけば、そのスクリプトファイルをそっくりそのまま、本体プログラム側で使うこともできてしまう。
ということで、Ruby + DXRuby は、プロトタイプ作成に便利だなと再認識。
[ ツッコむ ]
以上です。



