RPG Maker VX地图随机气泡对话 Ver1.0

最近刚看完了Ruby的《元编程》,收获颇丰。

想趁着印象深刻的时候多写点东西练练手,于是想到了这么一个功能。

在辉光翼战记中,玩家在迷宫中移动时,角色偶尔会在屏幕下方进行一些小对话。虽然对剧情本身没有什么影响,

但是这个功能不但削弱了在迷宫中移动时的烦躁感,而且还可以加深对人物性格的刻画。

RPG Maker VX的屏幕默认只有544 * 416大小,在冒险中专门取出一块地方来进行小对话会让游戏画面显得更加狭窄,由此选择了使用漂浮气泡对话。

本脚本主要使用了RPG Maker VX的跟随和村人のつぶやき两个脚本,以这两个脚本为基础,设计了一个小型DSL,这两个脚本的内容附在文章最后。

 

使用方法如下:

story_event "First Event" do

  message [-1, 'First Message']

  message [0, 'Second Message']

  scope [1, 2, 3, 5]


end

每个小对话使用 story_event来定义,并使用一个字符串对其命名,便于区别其他的小对话

message用来声明一条信息,其中第一个参数表示说话角色,第二个参数表示信息内容。

scope用来声明作用范围,其内容为一个包含了剧情可发生地图的id数组,上例中该剧情可以在map_id为1、2、3、5的地图中发生。

 

通过提前设置好对话和作用范围,就可以实现玩家在地图移动时的随机对话了。

作为1.0版本,只实现了一个大致上的功能。还有一些设想中的功能没有制作,内容包括:

1. 添加一个run_switch, 内容是一个开关id数组,当run_switch中的所有开关打开时该剧情才可能发生。

2. 添加一个close_switch,其功能和run_switch相反,用来终止一个剧情的发生。

3. 每条message除了可以声明角色和信息内容以外,还可以指定气泡滞留时间,使得句子长的信息滞留更久,句子短的信息消失更快。

4. 可以通过给地图增加特殊声明来修改该地图中随机剧情的出现频度

大概会在后续版本中添加上去。

 

附录

地图随机剧情脚本

  #==============================================================================
  #
  #   通过扩展Kernel的功能实现的小型DSL
  #   由于RMVX的问题,class_eval内的代码是被迫实现的
  #   原版代码如下
  #   Kernel.send :define_method, :story_event do |name, &block|
  #     now_event = event_list[name] = Move_Talking::Story_Event.new(name)
  #     block.call
  #   end
  #   在RMVX中,由于|name, &block|处会报语法错误,所以变成了现在的丑陋样子。
  #   如果有人知道在RMVX中怎么通过define_method定义方法并可以传递block请告诉我。
  #
#=============================================================================
  lambda {
    
    event_list = {}   #用以储存所有小剧情事件
    now_event = nil   #用以构造小剧情事件
    
    
    #--------------------------------------------------------------------------
    # ● 定义story_event方法
    #--------------------------------------------------------------------------
    Kernel.class_eval('def story_event(name, &block)
      event_list = get_event_list
      event_list[name] = Move_Talking::Story_Event.new(name)
      now_event = event_list[name]
      set_now_event(now_event)
      block.call
    end')
    
    #--------------------------------------------------------------------------
    # ● 获取event_list
    #     story_event方法专用
    #     如果可以将story_event改回使用define_method实现,本方法可作废
    #--------------------------------------------------------------------------
    Kernel.send :define_method, :get_event_list do
      return event_list
    end
    
    #--------------------------------------------------------------------------
    # ● 设置now_event
    #     story_event方法专用
    #     如果可以将story_event改回使用define_method实现,本方法可作废
    #--------------------------------------------------------------------------
    Kernel.send :define_method, :set_now_event do |event|
      now_event = event
    end
    
    #--------------------------------------------------------------------------
    # ● 定义message方法
    #     接收一个数组作为message内容
    #--------------------------------------------------------------------------
    Kernel.send :define_method, :message do |param|
      now_event.msg_data << param
    end
    
    #--------------------------------------------------------------------------
    # ● 定义scope方法
    #     接收一个数组作为scope内容
    #--------------------------------------------------------------------------
    Kernel.send :define_method, :scope do |scope|
      now_event.scope = scope
    end
    
    #--------------------------------------------------------------------------
    # ● 定义run_switch方法
    #      接收一个数组作为run_switch内容
    #--------------------------------------------------------------------------
    Kernel.send :define_method, :run_switch do |run_switch|
      now_event.run_switch = run_switch
    end
    
    #--------------------------------------------------------------------------
    # ● 获取当前地图适用的小剧情
    #     此处只考虑map_id其他操作在scene_map中具体判别
    #--------------------------------------------------------------------------
    Kernel.send :define_method, :get_story_event do |map_id|
      result = []
      event_list.each do |key, val|
        if    val.scope.nil?            then result.push val 
        elsif val.scope.include? map_id then result.push val
        end
      end
      return result
    end
    
  }.call

  #==============================================================================
  # ■ Move_Talking
  #------------------------------------------------------------------------------
  #  移动中对话的主要模块
  #==============================================================================
  module Move_Talking
    TRIGGER_COOLDOWN = 1500
    AWP = 500
    
    #--------------------------------------------------------------------------
    # ●Story_Event
    #
    #   用来装载小对话的类
    #--------------------------------------------------------------------------
    class Story_Event
      
      attr_accessor :scope
      attr_accessor :run_switch
      attr_accessor :name
      attr_accessor :msg_data
      attr_accessor :cooldown
      
      #--------------------------------------------------------------------------
      # ● 初始化
      #--------------------------------------------------------------------------
      def initialize(name)
        @name = name
        @msg_data = []
        @scope = nil
        @run_switch = nil
        @index = 0
        @cooldown = 200
      end

      
      #--------------------------------------------------------------------------
      # ● 运行
      #    每次显示一条信息
      #--------------------------------------------------------------------------
      def run
        player = @msg_data[@index][0]
        message = @msg_data[@index][1].dup
        if player == -1
          $game_player.message = message
        else
          $game_party.characters[player].message = message
        end
        @index += 1
      end
      
      #--------------------------------------------------------------------------
      # ● 判断是否全部显示完毕
      #--------------------------------------------------------------------------
      def over?
        @index == @msg_data.size
      end
      
      #--------------------------------------------------------------------------
      # ● 重置
      #    将index复位,以便重复播放
      #--------------------------------------------------------------------------
      def reset
        @index = 0
      end
      
    end
  end
  #==============================================================================
  # ■ Scene_Map
  #------------------------------------------------------------------------------
  #  处理地图画面的类。
  #==============================================================================
  class Scene_Map < Scene_Base
    
    #--------------------------------------------------------------------------
    #    初始化
    #--------------------------------------------------------------------------
    alias story_event_initialize initialize
    def initialize
      story_event_initialize
      @story_count = 0
      @message_count = 0
      @story_event = nil
    end
    
    #--------------------------------------------------------------------------
    #    更新
    #--------------------------------------------------------------------------
    alias story_event_update update
    def update
      @story_count += 1
      if @story_count >= 1500 && @story_event.nil?
        events = get_story_event($game_map.map_id)
        @story_event = events[rand(events.size)]
        @story_event.reset
        @story_event.run
        @message_count = 0
      elsif @story_event != nil
        @message_count += 1
        if @message_count == 200
          @story_event.run
          @message_count = 0
        end
        if @story_event.over?
          @story_event = nil
          @story_count = 0
        end
      end
      story_event_update
    end
    
  end
地图随机剧情脚本

 村人のつぶやき

#==============================================================================
# ★ RGSS2_村人のつぶやき
# tomoaky (http://hikimoki.hp.infoseek.co.jp/)
#==============================================================================

#==============================================================================
# ■ 設定項目
#==============================================================================
module TMRBT
  SW_MESSAGE_STOP = 2             # 機能無効化スイッチのID
  MESSAGE_DURATION = 180          # メッセージの表示時間(フレーム)
  MESSAGE = {}
  
  # おじさんタイプのメッセージリスト
  MESSAGE["Test"] = [
    'Test message'
  ]
end

#==============================================================================
# ■ コマンド
#==============================================================================
module TMRBT
  module Commands
    module_function
    #--------------------------------------------------------------------------
    # ● 指定したIDのイベントにメッセージを強制
    #--------------------------------------------------------------------------
    def mes(id, text, count = 300)
      event = $game_map.interpreter.get_character(id)
      if event != nil
        event.message = text
        event.message_count = count
      end
    end
    #--------------------------------------------------------------------------
    # ● 指定したIDのイベントの村人タイプを変更
    #--------------------------------------------------------------------------
    def mes_type(id, type = nil)
      return if id < 0    # プレイヤーには無効
      event = $game_map.interpreter.get_character(id)
      if event != nil
        event.murabito = type
      end
    end
  end
end

class Game_Interpreter
  include TMRBT::Commands
end

#==============================================================================
# ■ Game_Character
#==============================================================================
class Game_Character
  #--------------------------------------------------------------------------
  # ● 公開インスタンス変数
  #--------------------------------------------------------------------------
  attr_accessor :message                  # フキダシメッセージ
  attr_accessor :message_count            # メッセージ待機カウント
  #--------------------------------------------------------------------------
  # ● オブジェクト初期化
  #--------------------------------------------------------------------------
  alias tmrbt_game_character_initialize initialize
  def initialize
    tmrbt_game_character_initialize
    @message = ""
    @message_count = 60 + rand(240)
  end
  #--------------------------------------------------------------------------
  # ○ メッセージを表示できる状態かどうかを返す
  #--------------------------------------------------------------------------
  def message_can_speak?
    return false if @character_name == "" and @tile_id == 0
    return false if $game_switches[TMRBT::SW_MESSAGE_STOP]
    return true
  end
end

#==============================================================================
# ■ Game_Event
#==============================================================================
class Game_Event < Game_Character
  #--------------------------------------------------------------------------
  # ● 公開インスタンス変数
  #--------------------------------------------------------------------------
  attr_accessor :murabito                 # 村人タイプ
  #--------------------------------------------------------------------------
  # ● オブジェクト初期化
  #     map_id : マップ ID
  #     event  : イベント (RPG::Event)
  #--------------------------------------------------------------------------
  alias tmrbt_game_event_initialize initialize
  def initialize(map_id, event)
    tmrbt_game_event_initialize(map_id, event)
    @murabito = event.name =~ /<村人:(\S+?)>/i ? $1 : nil  # 村人判定
  end
  #--------------------------------------------------------------------------
  # ● フレーム更新
  #--------------------------------------------------------------------------
  alias tmrbt_game_event_update update
  def update
    tmrbt_game_event_update
    if @murabito != nil
      if @message_count > 0
        @message_count -= 1
        if @message_count == 0
          if message_can_speak?
            n = TMRBT::MESSAGE[@murabito].size
            @message = TMRBT::MESSAGE[@murabito][rand(n)].clone
          end
          @message_count = 270 + rand(300)
        end
      end
    end
  end
  #--------------------------------------------------------------------------
  # ○ 指定したIDのイベントにメッセージを強制(カスタム移動用)
  #--------------------------------------------------------------------------
  def mes(id, text, count = 300)
    TMRBT::Commands.mes(id == 0 ? @id : id, text, count)
  end
  #--------------------------------------------------------------------------
  # ○ 指定したIDのイベントの村人タイプを変更(カスタム移動用)
  #--------------------------------------------------------------------------
  def mes_type(id, type = nil)
    TMRBT::Commands.mes_type(id == 0 ? @id : id, type)
  end
end

#==============================================================================
# ■ Sprite_Character
#==============================================================================
class Sprite_Character < Sprite_Base
  @@bitmap_murawin = Cache.system("murawin")
  @@windowskin = Cache.system("Window")
  #--------------------------------------------------------------------------
  # ● オブジェクト初期化
  #     viewport  : ビューポート
  #     character : キャラクター (Game_Character)
  #--------------------------------------------------------------------------
  alias tmrbt_sprite_character_initialize initialize
  def initialize(viewport, character = nil)
    @message_duration = 0
    tmrbt_sprite_character_initialize(viewport, character)
  end
  #--------------------------------------------------------------------------
  # ● 解放
  #--------------------------------------------------------------------------
  alias tmrbt_sprite_character_dispose dispose
  def dispose
    dispose_message
    tmrbt_sprite_character_dispose
  end
  #--------------------------------------------------------------------------
  # ● フレーム更新
  #--------------------------------------------------------------------------
  alias tmrbt_sprite_character_update update
  def update
    tmrbt_sprite_character_update
    update_message
    if @character.message != ""
      @message = @character.message
      start_message
      @character.message = ""
    end
  end
  #--------------------------------------------------------------------------
  # ○ 文字色取得
  #     n : 文字色番号 (0~31)
  #--------------------------------------------------------------------------
  def text_color(n)
    x = 64 + (n % 8) * 8
    y = 96 + (n / 8) * 8
    return @@windowskin.get_pixel(x, y)
  end
  #--------------------------------------------------------------------------
  # ○ 特殊文字の変換
  #--------------------------------------------------------------------------
  def convert_special_characters
    if @message == nil
      @message = ""
    end
    @message.gsub!(/\\L/)              { "\x00" }
    @message.gsub!(/\\V\[([0-9]+)\]/i) { $game_variables[$1.to_i] }
    @message.gsub!(/\\N\[([0-9]+)\]/i) { $game_actors[$1.to_i].name }
    @message.gsub!(/\\C\[([0-9]+)\]/i) { "\x01[#{$1}]" }
    @message.gsub!(/\\\\/)             { "\\" }
  end
  #--------------------------------------------------------------------------
  # ○ フキダシメッセージ表示の開始
  #--------------------------------------------------------------------------
  def start_message
    dispose_message
    convert_special_characters
    @message_duration = TMRBT::MESSAGE_DURATION
    bitmap = Bitmap.new(160, 160)
    bitmap.font.name = "Verdana"
    bitmap.font.size = 14
    w = 0
    contents_x = 4
    contents_y = 0
    loop do
      c = @message.slice!(/./m)         # 次の文字を取得
      case c
      when nil
        break                           # 描画すべき文字がなければ終了
      when "\x00"                       # 改行
        w = contents_x if w < contents_x
        contents_x = 4
        contents_y += 16
        next
      when "\x01"                       # \C[n]  (文字色変更)
        @message.sub!(/\[([0-9]+)\]/, "")
        bitmap.font.color = text_color($1.to_i)
        next
      else
        bitmap.font.color = Color.new(255, 255, 255)
        bitmap.draw_edge_text(contents_x, contents_y, 40, 16, c, 0, Color.new(94, 24, 24))
        c_width = bitmap.text_size(c).width
        contents_x += c_width
        if contents_x >= 140            # 右端にきていれば改行
          w = contents_x if w < contents_x
          contents_x = 4
          contents_y += 16
        end
      end
    end
    h = contents_y + (contents_x == 4 ? 0 : 16)
    w = (w < contents_x ? contents_x + 4 : w + 4)
    @message_sprite = ::Sprite.new(viewport)
    @message_sprite.ox = w / 2
    @message_sprite.oy = h + 4
    @message_sprite.bitmap = Bitmap.new(w, h + 8)
    rect = Rect.new(0, 0, 8, 8)
    @message_sprite.bitmap.blt(0, 0, @@bitmap_murawin, rect, 192)
    rect.x += 8
    @message_sprite.bitmap.blt(w - 8, 0, @@bitmap_murawin, rect, 192)
    rect.y += 8
    @message_sprite.bitmap.blt(w - 8, h - 8, @@bitmap_murawin, rect, 192)
    rect.x -= 8
    @message_sprite.bitmap.blt(0, h - 8, @@bitmap_murawin, rect, 192)
    rect.set(16, 0, 8, 8)
    @message_sprite.bitmap.blt(@message_sprite.ox - 4, h,
      @@bitmap_murawin, rect, 192)
    color = @@bitmap_murawin.get_pixel(8, 8)
    color.alpha = 192
    @message_sprite.bitmap.fill_rect(8, 0, w - 16, h, color)
    @message_sprite.bitmap.fill_rect(0, 8, 8, h - 16, color)
    @message_sprite.bitmap.fill_rect(w - 8, 8, 8, h - 16, color)
    @message_sprite.bitmap.blt(0, 0, bitmap, bitmap.rect)
    bitmap.dispose
    update_message
  end
  #--------------------------------------------------------------------------
  # ○ フキダシメッセージの更新
  #--------------------------------------------------------------------------
  def update_message
    if @message_duration > 0
      @message_duration -= 1
      if @message_duration == 0 or not @character.message_can_speak?
        @message_duration = 0
        dispose_message
      else
        @message_sprite.x = x
        @message_sprite.y = y - height
        @message_sprite.z = z + 200
        @message_sprite.opacity = @message_duration * 24
      end
    end
  end
  #--------------------------------------------------------------------------
  # ○ フキダシメッセージの解放
  #--------------------------------------------------------------------------
  def dispose_message
    if @message_sprite != nil
      @message_sprite.dispose
      @message_sprite = nil
    end
  end
end
村人のつぶやき

跟随脚本

http://bbs.66rpg.com/thread-111958-1-1.html

 

posted on 2013-07-21 09:51  死线之蓝  阅读(1520)  评论(0编辑  收藏  举报

导航