mieki256's diary



2014/01/23(木) [n年前の日記]

#1 [dxruby] 砲台はなんとかなってきた

とりあえず、砲台を動かすために書いた処理を、別ファイルにまとめたり。

_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 まで指定可能。

動作確認その1

動作確認その2

動作確認その3

動作確認その4

以下のページが参考になりました。ありがたや。

_[ Azure Contrail ] アズールコントレイル - Tips
_Flashゲーム講座&ASサンプル集【狙撃の計算方法について】

ちなみに。DXRuby開発版なら、外積とか内積とかそのへん求めるメソッドもあるのですが…。開発版は、常時最新版しか公開されてなくて、入手にちょっと難があるので、そのあたり、DXRuby 1.4.1 に取り込まれることを期待。

DXRubyの便利なところ。 :

ここ数日、DXRubyを触っていて「便利だなー」と思った点が。メインループが数行で済む点は、ありがたい。おかげで、各ファイルの動作テストが書きやすいなと。

フツー、スクロールゲーム用のアレコレを書く場合は…。
  1. ゲーム本体のプログラムを動かして、
  2. 目的の場所までスクロールさせて、
  3. そこで出てくる敵その他の動きを確認して、
  4. 敵のプログラムを調整して、
みたいな流れになるのですが。

DXRubyなら、敵のプログラムが書いてあるスクリプトファイルに、ちょこっと動作確認用のメインループを書いちゃえば、そのスクリプトだけで動きの確認がそこそこできるわけで。

特に Ruby の場合、以下のような書き方が可能で。
if $0 == __FILE__

  # このスクリプトを単体で動かした時だけ、この部分を通る

end
まあ、Python なども、同じことができますけど…。何にせよ、この部分に動作確認用のソレを書いとけば、そのスクリプトファイルをそっくりそのまま、本体プログラム側で使うこともできてしまう。

ということで、Ruby + DXRuby は、プロトタイプ作成に便利だなと再認識。

以上です。

過去ログ表示

Prev - 2014/01 - 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