设为首页收藏本站同能贴吧 切换语言 繁体中文
开启辅助访问 切换到窄版
返回列表
+ 发新帖
查看: 94|回复: 0

[转载发布] 射击系统脚本New

[复制链接]
累计送礼:
0 个
累计收礼:
1 个
  • TA的每日心情
    开心
    3 小时前
  • 签到天数: 220 天

    连续签到: 8 天

    [LV.7]常住居民III

    9099

    主题

    882

    回帖

    7万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    VIP
    7
    卡币
    64785
    OK点
    16
    推广点
    0
    同能卷
    50
    积分
    74796

    灌水之王

    发表于 前天 01:32 | 显示全部楼层 |阅读模式
    [Ruby] 纯文本查看 复制代码
    #==============================================================================
    # ▼ 射击系统脚本 (Shooting System) 
    # 作者:Ruigi
    # -- 放在 Main 之前
    #==============================================================================
    # 标签(写在技能/物品的「备注」栏):
    #   <gun>                  标记为射击类
    #   <guntime:数字>         射击模式持续时间(帧),默认 300
    #   <bullet:物品编号>      每次射击消耗的物品ID
    #   <kind:动画编号>        持枪状态动画(持续循环播放)
    #   <shoot:动画编号>       射击命中敌人时对目标播放的动画
    #   <Impact:动画编号>      准星位置播放的弹孔/火花(无论命中与否都播放)
    #   <Precision:数字>       准星平滑度(1 = 不滑,越大越滑越难瞄)
    #   <Automatic:秒数>       自动挡模式(支持小数)
    #   <Xrecoil:幅度>         水平后坐力(像素)
    #   <Yrecoil:幅度>         垂直后坐力(像素)
    #
    # ★ 射击技能的「使用效果」要设置伤害公式(如 a.atk * 4 - b.def * 2)
    #==============================================================================
    
    module Shooting_Config
      # ---- 基础 ----
      CROSSHAIR_IMAGE       = "crosshair"
      CROSSHAIR_SIZE        = [48, 48]
      DEFAULT_GUNTIME       = 300
      SHOOT_KEY             = :C
      HIT_SOUND             = "gun1"
      EXIT_KEYS             = [:B]
      USE_MOUSE_LEFT        = true
      SHOT_COOLDOWN         = 8
      KEYBOARD_SPEED        = 6
      CHECK_BULLET_ON_ENTER = true
      HIT_RECT_PADDING      = 0
    
      # ---- 爆头机制 ----
      HEADSHOT_ENABLED    = true
      HEADSHOT_RATIO      = 0.35
      HEADSHOT_MULTIPLIER = 2.0
      HEADSHOT_SOUND      = "Critical"
      HEADSHOT_TEXT       = "爆头!"
    
      # ---- 准星平滑 ----
      DEFAULT_PRECISION = 3
    
      # ---- 后坐力 ----
      RECOIL_DECAY = 0.82
    
      # ---- 弹孔 / 空枪反馈 ----
      IMPACT_OFFSET_X = 0
      IMPACT_OFFSET_Y = 0
      IMPACT_Z        = 8000
    
      # ★ 每次射击(命中或未命中)都重播一次 <kind:> 动画
      #   true  = 每次开火都重播(更有"枪口抖动"感)
      #   false = 只在未命中时重播(默认,避免命中时视觉过乱)
      REPLAY_KIND_ON_HIT = false
    
      # ---- 子弹 HUD ----
      HUD_X             = 0
      HUD_Y             = 0
      HUD_WIDTH         = 200
      HUD_Z             = 9000
      HUD_SHOW_NAME     = true
      HUD_LOW_COLOR     = true
      HUD_LOW_THRESHOLD = 3
    
      # ---- 自动挡 ----
      AUTO_HUD_LABEL = "[自动]"
    
      # ---- 调试 ----
      DEBUG_AUTO = false
    end
    
    #==============================================================================
    # ■ Spriteset_Battle - 开放精灵数组读取
    #==============================================================================
    class Spriteset_Battle
      attr_reader :actor_sprites, :enemy_sprites
    end
    
    #==============================================================================
    # ■ NoteNum - 宽容的标签数字解析
    #==============================================================================
    module NoteNum
      FULLWIDTH_DIGITS = {
        "0"=>"0","1"=>"1","2"=>"2","3"=>"3","4"=>"4",
        "5"=>"5","6"=>"6","7"=>"7","8"=>"8","9"=>"9"
      }
    
      def self.extract(note, tag)
        m = note.match(/<#{tag}\s*:\s*([^>\r\n]*)/i)
        return nil unless m
        raw = m[1].strip
        num = raw[/[0-90-9..,,]+/]
        return nil unless num
        s = num.each_char.map { |c| FULLWIDTH_DIGITS[c] || c }.join
        s = s.tr(".,,", ".")
        s.to_f
      end
    end
    
    #==============================================================================
    # ■ RPG::UsableItem - 标签解析
    #==============================================================================
    class RPG::UsableItem < RPG::BaseItem
      def shooting?
        return @shooting unless @shooting.nil?
        @shooting = !note[/<gun>/i].nil?
      end
    
      def gun_time
        return @gun_time unless @gun_time.nil?
        v = NoteNum.extract(note, "guntime")
        @gun_time = v ? v.to_i : Shooting_Config::DEFAULT_GUNTIME
      end
    
      def bullet_id
        return @bullet_id unless @bullet_id.nil?
        v = NoteNum.extract(note, "bullet")
        @bullet_id = v ? v.to_i : 0
      end
    
      def kind_animation_id
        return @kind_anim unless @kind_anim.nil?
        v = NoteNum.extract(note, "kind")
        @kind_anim = v ? v.to_i : 0
      end
    
      def shoot_animation_id
        return @shoot_anim unless @shoot_anim.nil?
        v = NoteNum.extract(note, "shoot")
        @shoot_anim = v ? v.to_i : 0
      end
    
      def impact_animation_id
        return @impact_anim unless @impact_anim.nil?
        v = NoteNum.extract(note, "Impact")
        @impact_anim = v ? v.to_i : 0
      end
    
      def precision
        return @precision unless @precision.nil?
        v = NoteNum.extract(note, "Precision")
        @precision = v ? v.to_i : Shooting_Config::DEFAULT_PRECISION
      end
    
      def automatic_interval
        return @auto_interval unless @auto_interval.nil?
        v = NoteNum.extract(note, "Automatic")
        @auto_interval = v && v > 0 ? v : 0.0
      end
    
      def automatic_interval_frames
        return 0 if automatic_interval <= 0
        [(automatic_interval * 60).round, 1].max
      end
    
      def automatic?
        automatic_interval > 0
      end
    
      def x_recoil
        return @x_recoil unless @x_recoil.nil?
        v = NoteNum.extract(note, "Xrecoil")
        @x_recoil = v && v > 0 ? v : 0.0
      end
    
      def y_recoil
        return @y_recoil unless @y_recoil.nil?
        v = NoteNum.extract(note, "Yrecoil")
        @y_recoil = v && v > 0 ? v : 0.0
      end
    end
    
    #==============================================================================
    # ■ Mouse - 鼠标辅助
    #==============================================================================
    module Mouse
      WIN32_AVAILABLE = defined?(::Win32API) ? true : false
    
      if WIN32_AVAILABLE
        @get_cursor_pos   = ::Win32API.new("user32", "GetCursorPos",    "P",  "I")
        @screen_to_client = ::Win32API.new("user32", "ScreenToClient",  "LP", "I")
        @find_window      = ::Win32API.new("user32", "FindWindow",      "PP", "L")
        @get_async_key    = ::Win32API.new("user32", "GetAsyncKeyState", "i", "i")
      end
    
      @cached_pos = [320, 240]
      @prev_left  = false
    
      def self.available?
        WIN32_AVAILABLE
      end
    
      def self.find_hwnd
        return 0 unless WIN32_AVAILABLE
        ["RGSS Player", "RGSS3 Player", "RPG Maker VX Ace", "Game"].each do |t|
          hwnd = @find_window.call(nil, t)
          return hwnd if hwnd != 0
        end
        0
      end
    
      def self.position
        return @cached_pos unless WIN32_AVAILABLE
        hwnd = find_hwnd
        return @cached_pos if hwnd == 0
        pos = [0, 0].pack("ll")
        @get_cursor_pos.call(pos)
        @screen_to_client.call(hwnd, pos)
        @cached_pos = pos.unpack("ll")
      end
    
      def self.left_held?
        return false unless WIN32_AVAILABLE
        (@get_async_key.call(0x01) & 0x8000) != 0
      end
    
      def self.left_click?
        now = left_held?
        result = now && !@prev_left
        @prev_left = now
        result
      end
    end
    
    #==============================================================================
    # ■ Sprite_Crosshair - 准星精灵
    #==============================================================================
    class Sprite_Crosshair < Sprite
      attr_accessor :precision
    
      def initialize(viewport = nil)
        super(viewport)
        @active    = false
        @precision = Shooting_Config::DEFAULT_PRECISION
        @target_x  = 320
        @target_y  = 240
        @current_x = 320
        @current_y = 240
        @recoil_x  = 0.0
        @recoil_y  = 0.0
    
        begin
          self.bitmap = Cache.picture(Shooting_Config::CROSSHAIR_IMAGE)
        rescue
          self.bitmap = nil
        end
    
        if self.bitmap
          self.zoom_x = Shooting_Config::CROSSHAIR_SIZE[0].to_f / self.bitmap.width
          self.zoom_y = Shooting_Config::CROSSHAIR_SIZE[1].to_f / self.bitmap.height
        else
          self.bitmap = Bitmap.new(32, 32)
          c = Color.new(255, 0, 0)
          self.bitmap.draw_line(16, 0,  16, 12, c)
          self.bitmap.draw_line(16, 20, 16, 32, c)
          self.bitmap.draw_line(0,  16, 12, 16, c)
          self.bitmap.draw_line(20, 16, 32, 16, c)
          self.bitmap.draw_line(6,  6,  12, 12, c)
          self.bitmap.draw_line(26, 6,  20, 12, c)
          self.bitmap.draw_line(6,  26, 12, 20, c)
          self.bitmap.draw_line(26, 26, 20, 20, c)
        end
    
        self.visible = false
        self.z = 9999
      end
    
      def activate
        @active = true
        self.visible = true
        if Mouse.available?
          @target_x, @target_y = Mouse.position
        else
          @target_x = 320
          @target_y = 240
        end
        @current_x = @target_x
        @current_y = @target_y
        @recoil_x  = 0.0
        @recoil_y  = 0.0
        update_position
      end
    
      def deactivate
        @active = false
        self.visible = false
      end
    
      def active?
        @active
      end
    
      def screen_pos
        [@current_x + @recoil_x, @current_y + @recoil_y]
      end
    
      def add_recoil(x_amp, y_amp)
        @recoil_x += (rand * 2.0 - 1.0) * x_amp if x_amp && x_amp > 0
        @recoil_y += (rand * 2.0 - 1.0) * y_amp if y_amp && y_amp > 0
      end
    
      def update_position
        return unless @active
    
        if Mouse.available?
          @target_x, @target_y = Mouse.position
        else
          @target_x -= Shooting_Config::KEYBOARD_SPEED if Input.press?(:LEFT)
          @target_x += Shooting_Config::KEYBOARD_SPEED if Input.press?(:RIGHT)
          @target_y -= Shooting_Config::KEYBOARD_SPEED if Input.press?(:UP)
          @target_y += Shooting_Config::KEYBOARD_SPEED if Input.press?(:DOWN)
          @target_x = [[@target_x, 0].max, 640].min
          @target_y = [[@target_y, 0].max, 480].min
        end
    
        factor = 1.0 / [@precision, 1].max
        @current_x += (@target_x - @current_x) * factor
        @current_y += (@target_y - @current_y) * factor
        @current_x = @target_x if (@target_x - @current_x).abs < 0.5
        @current_y = @target_y if (@target_y - @current_y).abs < 0.5
    
        @recoil_x *= Shooting_Config::RECOIL_DECAY
        @recoil_y *= Shooting_Config::RECOIL_DECAY
        @recoil_x = 0.0 if @recoil_x.abs < 0.1
        @recoil_y = 0.0 if @recoil_y.abs < 0.1
    
        draw_x = @current_x + @recoil_x
        draw_y = @current_y + @recoil_y
    
        w = self.bitmap ? self.bitmap.width  * zoom_x : 0
        h = self.bitmap ? self.bitmap.height * zoom_y : 0
        self.x = draw_x - w / 2
        self.y = draw_y - h / 2
      end
    end
    
    #==============================================================================
    # ■ Sprite_Impact - 弹孔动画精灵
    #==============================================================================
    class Sprite_Impact < Sprite_Base
      def initialize(viewport = nil)
        super(viewport)
        self.visible = false
        self.z = Shooting_Config::IMPACT_Z
        @playing = false
      end
    
      def play(x, y, animation_id)
        return if animation_id.nil? || animation_id <= 0
        animation = $data_animations[animation_id]
        return unless animation
        self.x = x + Shooting_Config::IMPACT_OFFSET_X
        self.y = y + Shooting_Config::IMPACT_OFFSET_Y
        @playing = true
        self.visible = true
        start_animation(animation)
      end
    
      def update
        super
        if @playing && !animation?
          @playing = false
          self.visible = false
        end
      end
    end
    
    #==============================================================================
    # ■ Window_BulletCounter - 左上角子弹数量 HUD
    #==============================================================================
    class Window_BulletCounter < Window_Base
      def initialize
        super(Shooting_Config::HUD_X, Shooting_Config::HUD_Y,
              Shooting_Config::HUD_WIDTH, fitting_height(1))
        self.z = Shooting_Config::HUD_Z
        self.visible = false
        self.openness = 255
        [url=home.php?mod=space&uid=363652]@Bullet[/url]    = nil
        @count     = 0
        @automatic = false
      end
    
      def show_bullet(item, automatic = false)
        return unless item
        @bullet = item
        @automatic = automatic
        refresh
        self.visible = true
      end
    
      def hide_bullet
        self.visible = false
        @bullet = nil
      end
    
      def update_count
        return unless @bullet
        new_count = $game_party.item_number(@bullet)
        return if new_count == @count
        @count = new_count
        refresh
      end
    
      def refresh
        contents.clear
        return unless @bullet
    
        @count = $game_party.item_number(@bullet)
        draw_icon(@bullet.icon_index, 0, 0)
    
        offset = 28
        if @automatic && Shooting_Config::AUTO_HUD_LABEL && !Shooting_Config::AUTO_HUD_LABEL.empty?
          change_color(Color.new(120, 200, 255))
          draw_text(offset, 0, 48, line_height, Shooting_Config::AUTO_HUD_LABEL)
          change_color(normal_color)
          offset += 48
        end
    
        if Shooting_Config::HUD_SHOW_NAME
          change_color(normal_color)
          draw_text(offset, 0, 120 - (offset - 28), line_height, @bullet.name)
          count_x = 148
          count_w = contents.width - count_x
        else
          count_x = offset
          count_w = contents.width - count_x
        end
    
        if Shooting_Config::HUD_LOW_COLOR && @count <= Shooting_Config::HUD_LOW_THRESHOLD
          change_color(Color.new(255, 80, 80))
        else
          change_color(system_color)
        end
        draw_text(count_x, 0, count_w, line_height, "x#{@count}", 2)
        change_color(normal_color)
      end
    end
    
    #==============================================================================
    # ■ Game_Battler - 射击状态管理 + 爆头伤害加成
    #==============================================================================
    class Game_Battler < Game_BattlerBase
      attr_accessor :gun_active
      attr_accessor :gun_time_remaining
      attr_accessor :gun_item
      attr_accessor :shooting_headshot
    
      alias shooting_init_game_battler initialize
      def initialize
        shooting_init_game_battler
        @gun_active = false
        @gun_time_remaining = 0
        @gun_item = nil
        @shooting_headshot = false
      end
    
      def start_gun(item)
        @gun_active = true
        @gun_time_remaining = item.gun_time
        @gun_item = item
      end
    
      def stop_gun
        @gun_active = false
        @gun_time_remaining = 0
        @gun_item = nil
      end
    
      def update_gun
        return unless @gun_active
        @gun_time_remaining -= 1
        stop_gun if @gun_time_remaining <= 0
      end
    
      def gun_active?
        @gun_active
      end
    
      alias shooting_make_damage_value make_damage_value
      def make_damage_value(user, item)
        shooting_make_damage_value(user, item)
        if @shooting_headshot && Shooting_Config::HEADSHOT_ENABLED && @result.hp_damage > 0
          multiplier = Shooting_Config::HEADSHOT_MULTIPLIER
          @result.hp_damage = (@result.hp_damage * multiplier).to_i
        end
      end
    end
    
    #==============================================================================
    # ■ Game_Enemy - 命中判定后备矩形
    #==============================================================================
    # 注意:不覆盖 screen_x / screen_y / screen_z —— 保留引擎原版定义,
    #       让 $data_troops 里手动摆放的敌人坐标继续生效。
    #       这里只新增一个 screen_rect 后备方法,仅在找不到精灵时使用。
    class Game_Enemy < Game_Battler
      def screen_rect
        # 引擎原版的 screen_x / screen_y 是精灵的绘制坐标(左上角)
        # 真实尺寸由精灵决定,找不到精灵时给一个宽松的默认矩形
        Rect.new(screen_x, screen_y, 64, 64)
      end
    end
    
    #==============================================================================
    # ■ Scene_Battle - 射击模式集成
    #==============================================================================
    class Scene_Battle < Scene_Base
    
      alias shooting_start start
      def start
        shooting_start
        @shooting_mode = false
        @shooting_item = nil
        @crosshair     = Sprite_Crosshair.new
        @impact        = Sprite_Impact.new
        @shot_cooldown = 0
        @bullet_window = Window_BulletCounter.new
        @dbg_counter   = 0
      end
    
      alias shooting_terminate terminate
      def terminate
        shooting_terminate
        @bullet_window.dispose if @bullet_window
        @impact.dispose        if @impact
      end
    
      alias shooting_update update
      def update
        if @shooting_mode
          update_basic
          @spriteset.update if @spriteset
          @impact.update    if @impact
          update_shooting
        else
          shooting_update
          @impact.update    if @impact
          update_shooting
        end
      end
    
      #--------------------------------------------------------------------------
      # ● 每帧射击相关更新
      #--------------------------------------------------------------------------
      def update_shooting
        @crosshair.update_position if @crosshair.active?
        @shot_cooldown -= 1 if @shot_cooldown > 0
    
        $game_party.members.each { |m| m.update_gun }
        $game_troop.members.each { |m| m.update_gun }
    
        @bullet_window.update_count if @shooting_mode
    
        return unless @shooting_mode
    
        update_gun_animation
    
        if Shooting_Config::DEBUG_AUTO
          @dbg_counter += 1
          if @dbg_counter % 30 == 0
            p "[AUTO DBG] automatic?=#{auto_mode?} interval=#{@shooting_item.automatic_interval rescue 'nil'} " +
              "frames=#{@shooting_item.automatic_interval_frames rescue 'nil'} " +
              "cooldown=#{@shot_cooldown} " +
              "kbd_press=#{Input.press?(Shooting_Config::SHOOT_KEY)} " +
              "mouse_held=#{Shooting_Config::USE_MOUSE_LEFT && Mouse.available? ? Mouse.left_held? : false}"
          end
        end
    
        if @shooting_item && $game_party.members.none? { |m| m.gun_active? }
          exit_shooting_mode
          return
        end
    
        if @shot_cooldown <= 0
          if auto_mode?
            perform_shot if shoot_key_held?
          else
            perform_shot if shoot_key_triggered?
          end
        end
    
        Shooting_Config::EXIT_KEYS.each do |key|
          if Input.trigger?(key)
            exit_shooting_mode
            return
          end
        end
      end
    
      def auto_mode?
        @shooting_item && @shooting_item.automatic?
      end
    
      def shoot_key_held?
        return true if Input.press?(Shooting_Config::SHOOT_KEY)
        if Shooting_Config::USE_MOUSE_LEFT && Mouse.available?
          return true if Mouse.left_held?
        end
        false
      end
    
      def shoot_key_triggered?
        return true if Input.trigger?(Shooting_Config::SHOOT_KEY)
        if Shooting_Config::USE_MOUSE_LEFT && Mouse.available?
          return true if Mouse.left_click?
        end
        false
      end
    
      def shot_interval
        return Shooting_Config::SHOT_COOLDOWN unless @shooting_item
        frames = @shooting_item.automatic_interval_frames
        frames > 0 ? frames : Shooting_Config::SHOT_COOLDOWN
      end
    
      #--------------------------------------------------------------------------
      # ● 拦截 process_action
      #--------------------------------------------------------------------------
      alias shooting_process_action process_action
      def process_action
        return if scene_changing?
        if !@subject || !@subject.current_action
          @subject = BattleManager.next_subject
        end
        return turn_end unless @subject
    
        if @subject.current_action
          @subject.current_action.prepare
          if @subject.current_action.valid?
            @status_window.open
            item = @subject.current_action.item
    
            if item && item.shooting? && @subject.is_a?(Game_Actor)
              if Shooting_Config::CHECK_BULLET_ON_ENTER && !has_bullet?(item)
                Sound.play_buzzer
                @log_window.add_text(sprintf("%s 的弹药不足!", @subject.name)) if @log_window
                @subject.remove_current_action
                @status_window.close rescue nil
                return
              end
    
              @subject.use_item(item)
              refresh_status
              @subject.remove_current_action
              enter_shooting_mode(item)
              return
            end
    
            execute_action
          end
          @subject.remove_current_action
        end
        process_action_end unless @subject.current_action
      end
    
      def has_bullet?(item)
        return true if item.bullet_id <= 0
        bullet = $data_items[item.bullet_id]
        return false if bullet.nil?
        $game_party.item_number(bullet) > 0
      end
    
      #--------------------------------------------------------------------------
      # ● 进入 / 退出射击模式
      #--------------------------------------------------------------------------
      def enter_shooting_mode(item)
        @shooting_mode = true
        @shooting_item = item
        @shot_cooldown = 0
        @crosshair.precision = item.precision
        @crosshair.activate
        $game_party.members.each { |m| m.start_gun(item) }
        play_actor_animation(item.kind_animation_id)
    
        if item.bullet_id > 0
          bullet = $data_items[item.bullet_id]
          @bullet_window.show_bullet(bullet, item.automatic?) if bullet
        end
    
        if Shooting_Config::DEBUG_AUTO
          p "=== 进入射击模式:#{item.name} ==="
          p "  shooting?     = #{item.shooting?}"
          p "  bullet_id     = #{item.bullet_id}"
          p "  precision     = #{item.precision}"
          p "  automatic?    = #{item.automatic?}"
          p "  auto_interval = #{item.automatic_interval}"
          p "  auto_frames   = #{item.automatic_interval_frames}"
          p "  impact_anim   = #{item.impact_animation_id}"
          p "  kind_anim     = #{item.kind_animation_id}"
          p "  x_recoil      = #{item.x_recoil}"
          p "  y_recoil      = #{item.y_recoil}"
          p "  note          = #{item.note.inspect}"
        end
      end
    
      def exit_shooting_mode
        @shooting_mode = false
        @shooting_item = nil
        @crosshair.deactivate
        $game_party.members.each { |m| m.stop_gun }
        @bullet_window.hide_bullet
      end
    
      #--------------------------------------------------------------------------
      # ● 执行射击
      #--------------------------------------------------------------------------
      def perform_shot
        return unless @shooting_item
    
        bullet_id = @shooting_item.bullet_id
        if bullet_id > 0
          bullet_item = $data_items[bullet_id]
          if bullet_item.nil? || $game_party.item_number(bullet_item) <= 0
            Sound.play_buzzer
            @log_window.add_text("弹药耗尽!") if @log_window
            exit_shooting_mode
            return
          end
        end
    
        # 拍快照:本次射击用当前准星位置判定(含已有后坐力)
        shot_x, shot_y = @crosshair.screen_pos
    
        # 扣弹药
        if bullet_id > 0
          $game_party.lose_item($data_items[bullet_id], 1)
        end
    
        # 准星位置播放弹孔/火花动画(无论命中与否)
        impact_id = @shooting_item.impact_animation_id
        if impact_id > 0 && @impact
          @impact.play(shot_x, shot_y, impact_id)
        end
    
        # 施加后坐力(视觉立即偏移,影响下一发)
        @crosshair.add_recoil(@shooting_item.x_recoil, @shooting_item.y_recoil)
    
        # 用快照位置判定目标
        target = find_enemy_at(shot_x, shot_y)
    
        # =========================================================
        # 空枪:播放开火音效 + 重播 kind 动画(开火动作反馈)
        # =========================================================
        unless target
          Sound.play_use_skill rescue nil
    
          # ★ 未命中时重新播放角色身上的持枪动画
          play_actor_animation(@shooting_item.kind_animation_id)
    
          @log_window.add_text("未命中...") if @log_window
          @shot_cooldown = shot_interval
          if bullet_id > 0 && $game_party.item_number($data_items[bullet_id]) <= 0
            exit_shooting_mode
          end
          return
        end
    
        # =========================================================
        # 命中:敌人播 <shoot:>,可选重播 kind
        # =========================================================
        headshot = headshot_hit_at?(target, shot_x, shot_y)
    
        Sound.play_use_skill rescue nil
    
        # 可选:命中时也重播 kind 动画(由配置控制)
        if Shooting_Config::REPLAY_KIND_ON_HIT
          play_actor_animation(@shooting_item.kind_animation_id)
        end
    
        play_enemy_animation(target, @shooting_item.shoot_animation_id)
    
        if Shooting_Config::HIT_SOUND && !Shooting_Config::HIT_SOUND.empty?
          Audio.se_play("Audio/SE/" + Shooting_Config::HIT_SOUND, 80, 100) rescue nil
        end
    
        if headshot
          if Shooting_Config::HEADSHOT_SOUND && !Shooting_Config::HEADSHOT_SOUND.empty?
            Audio.se_play("Audio/SE/" + Shooting_Config::HEADSHOT_SOUND, 90, 100) rescue nil
          end
          if @log_window && Shooting_Config::HEADSHOT_TEXT && !Shooting_Config::HEADSHOT_TEXT.empty?
            @log_window.add_text(Shooting_Config::HEADSHOT_TEXT) rescue nil
          end
        end
    
        target.shooting_headshot = headshot
        apply_shot_effect(target, @shooting_item)
        target.shooting_headshot = false
    
        @shot_cooldown = shot_interval
    
        if bullet_id > 0 && $game_party.item_number($data_items[bullet_id]) <= 0
          exit_shooting_mode
        end
      end
    
      #--------------------------------------------------------------------------
      # ● 命中检测 / 爆头判定
      #--------------------------------------------------------------------------
      def find_enemy_at(mx, my)
        $game_troop.alive_members.each do |enemy|
          sprite = find_enemy_sprite(enemy)
          rect   = enemy_hit_rect(enemy, sprite)
          return enemy if mx >= rect.x && mx <= rect.x + rect.width &&
                          my >= rect.y && my <= rect.y + rect.height
        end
        nil
      end
    
      def enemy_hit_rect(enemy, sprite)
        pad = Shooting_Config::HIT_RECT_PADDING
        if sprite && sprite.bitmap
          w = sprite.bitmap.width  * sprite.zoom_x
          h = sprite.bitmap.height * sprite.zoom_y
          x = sprite.x - sprite.ox * sprite.zoom_x - pad
          y = sprite.y - sprite.oy * sprite.zoom_y - pad
          Rect.new(x, y, w + pad * 2, h + pad * 2)
        else
          r = enemy.screen_rect
          Rect.new(r.x - pad, r.y - pad, r.width + pad * 2, r.height + pad * 2)
        end
      end
    
      def headshot_hit_at?(enemy, mx, my)
        return false unless Shooting_Config::HEADSHOT_ENABLED
        sprite = find_enemy_sprite(enemy)
        rect   = enemy_hit_rect(enemy, sprite)
        return false unless mx >= rect.x && mx <= rect.x + rect.width
        return false unless my >= rect.y && my <= rect.y + rect.height
        head_bottom = rect.y + rect.height * Shooting_Config::HEADSHOT_RATIO
        my <= head_bottom
      end
    
      def apply_shot_effect(target, item)
        user = $game_party.members[0]
        return unless user
        target.item_apply(user, item)
        if @log_window
          @log_window.display_action_results(target, item) rescue nil
        end
      end
    
      def play_actor_animation(anim_id)
        return if anim_id <= 0 || !@spriteset
        animation = $data_animations[anim_id]
        return unless animation
        $game_party.members.each do |actor|
          sprite = find_actor_sprite(actor)
          sprite.start_animation(animation) if sprite
        end
      end
    
      def play_enemy_animation(target, anim_id)
        return if anim_id <= 0 || !@spriteset
        animation = $data_animations[anim_id]
        return unless animation
        sprite = find_enemy_sprite(target)
        sprite.start_animation(animation) if sprite
      end
    
      def update_gun_animation
        return unless @shooting_item
        anim_id = @shooting_item.kind_animation_id
        return if anim_id <= 0 || !@spriteset
        animation = $data_animations[anim_id]
        return unless animation
    
        $game_party.members.each do |actor|
          sprite = find_actor_sprite(actor)
          next unless sprite
          next if sprite.animation?
          sprite.start_animation(animation)
        end
      end
    
      def find_actor_sprite(actor)
        return nil unless @spriteset
        return nil unless @spriteset.respond_to?(:actor_sprites)
        @spriteset.actor_sprites.find { |s| s.battler == actor }
      end
    
      def find_enemy_sprite(target)
        return nil unless @spriteset
        return nil unless @spriteset.respond_to?(:enemy_sprites)
        @spriteset.enemy_sprites.find { |s| s.battler == target }
      end
    end
    
    #==============================================================================
    # ■ 战斗窗口中射击技能/物品的图标标记
    #==============================================================================
    class Window_BattleSkill < Window_SkillList
      alias shooting_draw_item draw_item
      def draw_item(index)
        shooting_draw_item(index)
        skill = @data[index]
        return unless skill
        if skill.shooting?
          rect = item_rect(index)
          draw_icon(132, rect.x + rect.width - 24, rect.y, true) rescue nil
        end
      end
    end
    
    class Window_BattleItem < Window_ItemList
      alias shooting_draw_item draw_item
      def draw_item(index)
        shooting_draw_item(index)
        item = @data[index]
        return unless item
        if item.shooting?
          rect = item_rect(index)
          draw_icon(132, rect.x + rect.width - 24, rect.y, true) rescue nil
        end
      end
    end









    本帖子中包含更多资源

    您需要 登录 才可以下载或查看,没有账号?立即注册

    x

    帖子地址: 

    天天去同能,天天有童年!
    回复 送礼论坛版权

    使用道具 举报

    文明发言,和谐互动
    文明发言,和谐互动
    高级模式
    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    简体中文
    繁體中文
    English(英语)
    日本語(日语)
    Deutsch(德语)
    Русский язык(俄语)
    بالعربية(阿拉伯语)
    Türkçe(土耳其语)
    Português(葡萄牙语)
    ภาษาไทย(泰国语)
    한어(朝鲜语/韩语)
    Français(法语)
    关闭

    幸运抽奖

    社区每日抽奖来袭,快来试试你是欧皇还是非酋~

    立即查看

    聊天机器人
    Loading...

    QQ|Archiver|手机版|小黑屋|同能RPG制作大师 ( 沪ICP备12027754号-3 )

    GMT+8, 2026-10-14 04:21 , Processed in 0.156994 second(s), 59 queries .

    Powered by Discuz! X3.4

    Copyright © 2001-2020, Tencent Cloud.

    快速回复 返回顶部 返回列表