地上の洞窟

どこにも行かず、液晶と「にらめっこ」し続ける人の物語。

【RGSS3】ポップテキスト

ポップテキスト
名前表示や行き先表示、獲得アイテムの表示などに!

マップ上のキャラクターに対して、指定した文字列を様々な方式で表示します。

表示方法

基本的にはスクリプトを実行して表示します。

「CraftLib」スクリプトを導入していれば、
マップ読み込み時にイベントページの一番上に配置した注釈を読み取り、
イベントを実行せずに表示することもできます。

また、表示方式は、比較的簡単に改造や追加が可能な構造になっています。

ポップテキストの使用方法・改造方法は別ページで詳しく解説しています。


更新履歴 バージョン 内容
2023/11/23 v2.0.1 マップ移動時にテキストが解放されない不具合の修正
2023/11/22 v2.0.0 テキスト表示中にセーブロードした際に、
表示中のテキストがセーブデータに保持されない不具合の修正
(これにより表示方式のsprite, bitmap周りの管理が変わり、
従来の表示方式との互換性は失われます)
2023/10/07 v1.0.1 「:eject」方式で表示中、メニュー画面等を開いてから
マップに戻るとクラッシュしてしまう不具合の修正
2023/09/18 v1.0.0 初版

→スクリプト一覧へ
→「ポップテキストの使用・改造」解説ページへ
→「CraftLib」スクリプト配布ページへ


スクリプト(ダブルクリックで全選択)↓

=begin =========================================================================
 ■ 「ポップテキスト」- PopText v2.0.1 by 地上の洞窟
================================================================================
  
  マップ上のキャラクターに対して、指定した文字列を様々な方式で表示します。
  (全ての機能を使用するには「CraftLib」スクリプトが必要です)

================================================================================
 ■ 設定項目
=end #==========================================================================

module PopText


  # UNPOP_ON_EVENT_NEW_PAGE
  # イベントの改ページ時、そのイベントに表示中のテキストを削除します。

  UNPOP_ON_EVENT_NEW_PAGE = true

  
  # DEFAULT_FONT_SIZE : 通常文字のサイズを指定します。

  DEFAULT_FONT_SIZE = 16


  # DEFAULT_FONT_BOLD : 通常文字を太字にします。

  DEFAULT_FONT_BOLD = true


  # DEFAULT_HALF_WIDTH_RATE : 半角文字幅の計算に使う倍率
  # フォントサイズに対して、半角文字の横幅はどれぐらいの倍率かを指定します。
  # 必要があれば使用しているフォントの書体に合わせて調整してください。

  DEFAULT_HALF_WIDTH_RATE = 0.6


#===============================================================================
# ■ 上級者向け設定(表示方式)
#===============================================================================

  # セットアップ
  module Setup
    def default
      # 基準サイズの設定
      @size = DEFAULT_FONT_SIZE

      # スプライト・ビットマップの作成
      create_graphic

      # フォントの設定
      font = bitmap.font
      font.size = @size
      font.bold = DEFAULT_FONT_BOLD

      # テキストの描画
      draw_text_defalut

      # 位置の設定
      sprite.ox = bitmap.width / 2.0
      sprite.oy = 32 + bitmap.height
      sprite.z = 1000

      # コントロールの設定
      @control = :control_default
    end

    def name
      default
      @control = :control_name
    end

    def eject
      default
      if @character
        @x = @character.real_x
        @y = @character.real_y
        @opacity = 255
        @phase = 0
        @count = 0
        @character = nil
      end
      @control = :control_eject
    end
  end

  # 更新処理
  module Control
    def control_default
      sprite.x = @character.screen_x
      sprite.y = @character.screen_y
    end

    def control_name
      dx = ($game_player.real_x - @character.real_x).abs
      dx = $game_map.width - dx if $game_map.loop_horizontal? && dx > $game_map.width / 2
      dy = ($game_player.real_y - @character.real_y).abs
      dy = $game_map.height - dy if $game_map.loop_vertical? && dy > $game_map.height / 2
      if Math.sqrt(dx * dx + dy * dy) < 4
        sprite.visible = true
        sprite.x = @character.screen_x
        sprite.y = @character.screen_y
      else
        sprite.visible = false
      end
    end

    def control_eject
      case @phase
      when 0
        distance = (20 * @count / 60.0)
        sprite.x = $game_map.adjust_x(@x) * 32 + 16
        sprite.y = $game_map.adjust_y(@y) * 32 + 32 - 4 - distance
        if (@count += 1) > 60
          @count = 0
          @phase += 1
        end
      when 1
        progress = @count / 30.0
        distance = 20 + 10 * progress - 6 * progress * progress
        sprite.x = $game_map.adjust_x(@x) * 32 + 16
        sprite.y = $game_map.adjust_y(@y) * 32 + 32 - 4 - distance
        sprite.opacity = (@opacity -= 8.5) # 255 / 30
        if (@count += 1) > 30
          @count = 0
          @phase += 1
        end
      else
        remove
      end
    end
  end

#===============================================================================
# ■ ここからソースコード
#===============================================================================

  HALF_WIDTH_REGEXP = /[。-゚[:ascii:]]/

  DEFAULT_HALF_WIDTH_RATE_CALC = [[1 - DEFAULT_HALF_WIDTH_RATE, 0].max, 1].min
  
  # テキスト描画位置の補正(微妙にズレてる気がした故の調整)
  DEFAULT_DRAW_X_FIX = 1

  COMMENT_REGEXP = /PopText\[((.*),(.*)|(.*))\]/
  COMMENT_UNPOP = "UNPOP"

  if const_defined?(:CraftLib)
    CraftLib::SetupComment.register(COMMENT_REGEXP) do |event, m|
      if m[3]
        if (s = m[3].strip) == COMMENT_UNPOP
          PopText.unpop(event)
        else
          PopText.character(event, eval(s, TOPLEVEL_BINDING))
        end
      else
        PopText.character(
          event,
          eval(m[1].strip, TOPLEVEL_BINDING),
          eval(m[2].strip, TOPLEVEL_BINDING)
        )
      end
    end
  end
  
  class << self
    attr_accessor :font
    attr_reader :viewport

    def setup
      @controller ||= []
      @queue = []
      @unpop_character = {}
      clear
    end

    def create(viewport)
      @viewport = viewport
      refresh
    end

    def refresh
      $game_map.ptxt_controller.each { |controller| controller.refresh }
    end

    def clear
      $game_map.ptxt_controller.each { |controller| controller.remove }.clear
    end

    def dispose
      $game_map.ptxt_controller.each { |controller| controller.dispose }
      @viewport = nil
    end

    def update
      unless $game_map.ptxt_unpop_character.empty?
        $game_map.ptxt_controller.delete_if do |controller|
          if $game_map.ptxt_unpop_character[controller.character]
            controller.remove
            true
          else
            controller.update
          end
        end
        $game_map.ptxt_unpop_character.clear
      else
        $game_map.ptxt_controller.delete_if { |controller| controller.update }
      end
  
      unless $game_map.ptxt_queue.empty?
        $game_map.ptxt_controller.concat(
          $game_map.ptxt_queue.each { |controller| controller.refresh }
        )
        $game_map.ptxt_queue.clear
      end  
    end

    def character(character, text, setup = :default)
      $game_map.ptxt_queue << PopTextController.new(character, text, setup)
    end
    
    def player(text, setup = :default)
      $game_map.ptxt_queue << PopTextController.new($game_player, text, setup)
    end

    def event(id, text, setup = :default)
      if (event = $game_map.events[id])
        $game_map.ptxt_queue << PopTextController.new(event, text, setup)
      end
    end

    def unpop(character)
      $game_map.ptxt_unpop_character[character] = true
    end

    def unpop_player
      $game_map.ptxt_unpop_character[$game_player] = true
    end

    def unpop_event(id)
      if (event = $game_map.events[id])
        $game_map.ptxt_unpop_character[event] = true
      end
    end
  end
  
  class PopTextController
    include Setup
    include Control
    attr_reader :character
    attr_reader :sprite

    def initialize(character, text, setup)
      @character = character
      @text = text
      @setup = setup
      @remove = false
      @graphic_holder = GraphicHolder.new
    end

    def bitmap
      @graphic_holder.bitmap
    end

    def bitmap=(bitmap)
      @graphic_holder.bitmap = bitmap
    end

    def sprite
      @graphic_holder.sprite
    end

    def sprite=(sprite)
      @graphic_holder.sprite = sprite
    end

    def need_refresh?
      @graphic_holder.need_refresh
    end

    def create_graphic
      self.sprite = Sprite.new(PopText.viewport)
      self.sprite.bitmap = self.bitmap = (
        Bitmap.new(@width ||= default_width, @height ||= default_height)
      )
    end

    def default_width
      @size * (@text.to_s.length - @text.to_s.scan(HALF_WIDTH_REGEXP).size * DEFAULT_HALF_WIDTH_RATE_CALC)
    end

    def default_height
      @size
    end

    def draw_text_defalut
      bitmap.draw_text(DEFAULT_DRAW_X_FIX, 0, @width, @height, @text, 1)
    end

    def refresh
      send(@setup)
      send(@control) if @control
      @graphic_holder.need_refresh = false
    end

    def update
      refresh if need_refresh?
      send(@control) if @control
      @remove
    end

    def dispose
      @graphic_holder.dispose
    end

    def remove
      @remove = true
      @control = nil
      @character = nil
      dispose
    end

    class GraphicHolder
      attr_accessor :sprite, :bitmap, :need_refresh
      
      def marshal_dump
        true
      end

      def marshal_load(obj)
        @need_refresh = obj
      end

      def dispose
        @bitmap.dispose if @bitmap
        @sprite.dispose if @sprite
      end
    end
  end
end

class Game_Map
  attr_reader :ptxt_controller, :ptxt_queue, :ptxt_unpop_character

  alias ptxt_setup setup
  def setup(map_id)
    if @ptxt_controller
      PopText.clear
    else
      @ptxt_controller = []
    end
    @ptxt_queue = []
    @ptxt_unpop_character = {}
    ptxt_setup(map_id)
  end
end

class Game_Event < Game_Character
  alias ptxt_setup_page setup_page
  def setup_page(new_page)
    PopText.unpop(self) if PopText::UNPOP_ON_EVENT_NEW_PAGE
    ptxt_setup_page(new_page)
  end
end

class Spriteset_Map
  alias ptxt_create_characters create_characters
  def create_characters
    ptxt_create_characters
    PopText.create(@viewport1)
  end

  alias ptxt_update_characters update_characters
  def update_characters
    ptxt_update_characters
    PopText.update
  end

  alias ptxt_dispose_characters dispose_characters
  def dispose_characters
    ptxt_dispose_characters
    PopText.dispose
  end
end