RGSSティプス

標準スクリプトに含まれる不具合とその修正

条件分岐で4個目の防具が無視される

イベント,およびコモン・イベントのコマンド「条件分岐」で,4個目の防具の所持を判別できないという不具合。これを修正するにはセクションInterpreter 3のメソッドcommand_111に記述されている:

        when 4  # 防具
          result = (actor.armor1_id == @parameters[3] or
                    actor.armor2_id == @parameters[3] or
                    actor.armor3_id == @parameters[3])

この部分を,次のように書き換えればよい:

        when 4  # 防具
          result = (actor.armor1_id == @parameters[3] or
                    actor.armor2_id == @parameters[3] or
                    actor.armor3_id == @parameters[3] or
                    actor.armor4_id == @parameters[3])

1行目にfalseが返るスクリプトを記述するとフリーズする

イベント,およびコモン・イベントのコマンド「スクリプト」で,1行目にfalseが返るスクリプトを記述するとフリーズするという不具合。これを修正するにはセクションInterpreter 7のメソッドcommand_355に記述されている以下のコードを取り除けばよい:

    # 戻り値が false の場合
    if result == false
      # 終了
      return false
    end

ただし,スクリプトからfalseを返しても以降の処理が常に継続されてしまう。

無駄な処理の削除

セクションScene_Battle 1にあるScene_Battleクラスのupdateメソッドを見ると,やたらと条件判定が多い。これは明らかにパフォーマンスに影響しているので,不要な部分は削ったほうが良いだろう。特に273行目辺りに記述されている次の箇所が最も処理に時間がかかるようだ:

    # エフェクト表示中の場合
    if @spriteset.effect?
      return
    end

また,バトル・イベントで「文章表示」や「アニメーション表示」を行うと重くなってしまうようだ。この辺のコードも見直したほうが良いだろう。

キャラクタ・グラフィックの仕様拡張

RPGツクールXPでのキャラクタ・グラフィックは,キャラクタ1体につき1枚のグラフィックを用意することになっている。デフォルトでは上下左右の4方向のグラフィックを縦方向(上から順に下,左,右,上)に,それぞれ4枚のアニメーションを横方向に並べて表現する。つまり,縦横4マスの計16パターンという仕様である。キャラクタ1体分の大きさは画像の大きさを縦横それぞれ4で割って自動的に割り出すため,大きさは4の倍数であれば何でも良い。この仕様を拡張して,斜め方向を加えた8方向に対応させたい(つまり横4マス,縦8マスとなる)。

RPGツクールXPで新規プロジェクトを作成し,スクリプトのSprite_Characterのセクションを見ると,まずサイズの割り出しは次のようになっている:

self.bitmap = RPG::Cache.character(@character.character_name, @character.character_hue)
@cw = bitmap.width / 4
@ch = bitmap.height / 4
self.ox = @cw / 2
self.oy = @ch

これを見ると@cwはキャラクタの横幅,@cyはキャラクタの縦幅,self.oxは横方向の中心座標,self.oyは縦方向の開始座標が代入されているようだ。

次に,キャラクタの矩形の割り出しは:

sx = @character.pattern * @cw
sy = (@character.direction - 2) / 2 * @ch
self.src_rect.set(sx, sy, @cw, @ch)

となっている。@character.patternは現在のパターンの位置,@character.directionはキャラクタの向いている方向を指している。@character.directionの値は,下の場合は2,左の場合は4,右の場合は6,上の場合は8となっている。

つまり,8方向に対応させたい場合,先の@ch = bitmap.height / 4の部分を@ch = bitmap.height / 8に変更し,@character.directionには上下左右方向で使われていない適当な数値を代入してやれば良い。ここでは左下を10,右下を12,左上を14,左下を16とする。キャラクタのグラフィックは上から順に下,左,右,上,左下,右下,左上,右上と並べる(横方向のパターンについては通常の場合と同様)。

次に,Game_Characterクラスに,斜め方向に移動したり向きの変更を行う関数を定義する:

def move_lower_left
  if turn_enabled
    turn_lower_left
  end
  if (passable?(@x, @y, 2) and passable?(@x, @y + 1, 4)) or
     (passable?(@x, @y, 4) and passable?(@x - 1, @y, 2))
    @x -= 1
    @y += 1
    increase_steps
  end
end
def move_lower_right
  if turn_enabled
    turn_lower_right
  end
  if (passable?(@x, @y, 2) and passable?(@x, @y + 1, 6)) or
     (passable?(@x, @y, 6) and passable?(@x + 1, @y, 2))
    @x += 1
    @y += 1
    increase_steps
  end
end
def move_upper_left
  if turn_enabled
    turn_upper_left
  end
  if (passable?(@x, @y, 8) and passable?(@x, @y - 1, 4)) or
     (passable?(@x, @y, 4) and passable?(@x - 1, @y, 8))
    @x -= 1
    @y -= 1
    increase_steps
  end
end
def move_upper_right
  if turn_enabled
    turn_upper_right
  end
  if (passable?(@x, @y, 8) and passable?(@x, @y - 1, 6)) or
     (passable?(@x, @y, 6) and passable?(@x + 1, @y, 8))
    @x += 1
    @y -= 1
    increase_steps
  end
end
def turn_lower_left
  unless @direction_fix
    @direction = 10
    @stop_count = 0
  end
end
def turn_lower_right
  unless @direction_fix
    @direction = 12
    @stop_count = 0
  end
end
def turn_upper_left
  unless @direction_fix
    @direction = 14
    @stop_count = 0
  end
end
def turn_upper_right
  unless @direction_fix
    @direction = 16
    @stop_count = 0
  end
end

あとは,キー入力時などの方向周りの処理を行う部分で,これらの関数を呼び出すように変更を加える:

case Input.dir8
when 1
  move_lower_left
when 2
  move_down
when 3
  move_lower_right
when 4
  move_left
when 6
  move_right
when 7
  move_upper_left
when 8
  move_up
when 9
  move_upper_right
end

向きだけ変えたいときは,turn_で始まる名前の関数を直接呼び出せば良い。

ちなみにInput.dir8では入力されたキーをテンキーに対応した数値,つまり斜め方向は奇数,それ以外は偶数で返すようになっている。これに反して方向を表す数値に10から16までの偶数を採ったのは,キャラクタの矩形を求める式がsy = (@character.direction - 2) / 2 * @chであったため。本格的に仕様を拡張したいのであれば,この式にも変更を加えて,テンキーに対応した数値で扱ったほうが何かと便利だろう。

俯瞰マップ(クォーター・ビュー)

ウィンドウのフルスクリーン化

#==============================================================================
# Win32
#==============================================================================
module Win32
  #--------------------------------------------------------------------------
  # A few constants used throughout the program
  #--------------------------------------------------------------------------
  BUF_SIZE = 256
  WS_VISIBLE = 0x10000000
  GWL_STYLE = -16
  HWND_TOPMOST = -1
  SWP_SHOWWINDOW = 64
  DM_PELSWIDTH = 0x80000
  DM_PELSHEIGHT = 0x100000
  DM_DISPLAYFREQUENCY = 0x400000
  CDS_FULLSCREEN = 4
  DISP_CHANGE_SUCCESSFUL = 0
  HORZRES = 8
  VERTRES = 10
  VREFRESH = 116
  # windows version
  buf = [148].pack('L') + '\0' * 144
  b = Win32API.new('kernel32', 'GetVersionExA', 'P', 'I').call(buf)
  if b != 0
    (size, major, minor, build, platform, version) = buf.unpack('LLLLLA128')
    case platform
    when 0
      WINPLATFORM = 'Win32s'
    when 1
      WINPLATFORM = 'Win95'
    when 2
      WINPLATFORM = 'WinNT'
    else
      WINPLATFORM = 'Unknown'
    end
  else
    WINPLATFORM = 'Unknown'
  end
  #--------------------------------------------------------------------------
  # Local function prototypes
  #--------------------------------------------------------------------------
  GetPrivateProfileString = Win32API.new('kernel32', 'GetPrivateProfileStringA', 'PPPPLP', 'L')
  FindWindow = Win32API.new('user32', 'FindWindowA', 'PP', 'L')
  SetWindowLong = Win32API.new('user32', 'SetWindowLongA', 'LIL', 'L')
  SetWindowPos = Win32API.new('user32', 'SetWindowPos', 'LLIIIII', 'I')
  GetDC = Win32API.new('user32', 'GetDC', 'L', 'L')
  ReleaseDC = Win32API.new('user32', 'ReleaseDC', 'LL', 'I')
  GetDeviceCaps = Win32API.new('gdi32', 'GetDeviceCaps', 'LI', 'I')
  ChangeDisplaySettings = Win32API.new('user32', 'ChangeDisplaySettingsA', 'PL', 'L')
  #--------------------------------------------------------------------------
  # Get window handle
  #--------------------------------------------------------------------------
  def get_window
    text = '\0' * BUF_SIZE
    GetPrivateProfileString.call('Game', 'Title', '', text, BUF_SIZE - 1, '.\\Game.ini')
    text.delete!('\0')
    FindWindow.call('RGSS Player', text)
  end
  module_function :get_window
  #--------------------------------------------------------------------------
  # Fullscreen
  #--------------------------------------------------------------------------
  attr_reader :devmode
  def fullscreen(mode = 1)
    if mode > 0
      hDC = GetDC.call(0)
      dmPelsWidth = GetDeviceCaps.call(hDC, HORZRES)
      dmPelsHeight = GetDeviceCaps.call(hDC, VERTRES)
      dmFields = DM_PELSWIDTH | DM_PELSHEIGHT
      dmDisplayFrequency = 0
      if WINPLATFORM == 'WinNT'
        dmFields |= DM_DISPLAYFREQUENCY
        dmDisplayFrequency = GetDeviceCaps.call(hDC, VREFRESH)
      end
      @devmode = [0, 0, 0, BUF_SIZE * 4, 0, dmFields, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                  0, 0, 0, 0, dmPelsWidth, dmPelsHeight, 0, dmDisplayFrequency,
                  0, 0, 0, 0, 0, 0].pack('IIIIILIIIIIIIIIIIIIIILLLLLLLLLLL')
      ReleaseDC.call(0, hDC)
      # main
      hWnd = get_window
      if SetWindowLong.call(hWnd, GWL_STYLE, WS_VISIBLE) == 0
        return false
      end
      devmode = [0, 0, 0, BUF_SIZE * 4, 0, DM_PELSWIDTH | DM_PELSHEIGHT, 0, 0, 0, 0,
                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 640, 480, 0, 0, 0, 0,
                 0, 0, 0, 0].pack('IIIIILIIIIIIIIIIIIIIILLLLLLLLLLL')
      if ChangeDisplaySettings.call(devmode, CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL
        return false
      end
      if SetWindowPos.call(hWnd, HWND_TOPMOST, 0, 0, 640, 480, SWP_SHOWWINDOW) == 0
        return false
      end
      return true
    else
      ChangeDisplaySettings.call(@devmode, 0)
    end
  end
  module_function :fullscreen
end

BUF_SIZE * 4としているのはDEVMODE構造体のサイズが環境によって可変なので,適当な大きさのバッファを確保するため(これはまずいかも)。適宜,Cによるウィンドウのフルスクリーン化も参照してほしい。