注册会员 登录
Project1 返回首页

QQEat https://rpg.blue/?326184 [收藏] [复制] [分享] [RSS] 这个人很懒,什么也没留下。

日志

【RM】定时器/定时执行脚本v3.0

热度 1已有 307 次阅读2018-9-14 12:30 |个人分类:脚本

#encoding:utf-8
#==============================================================================
# ■ 定时器v3.0 By_QQEat ————2022.01.16
#
#   说明:定时执行脚本, tween 动画等。
#
#   [创建、释放等基础方法]
#
#     @timer = Timer.new      # 创建
#     @timer.update(tag=nil)  # 更新(传标签参数后, 则只更新指定标签的任务)
#     @timer.dispose          # 释放
#     @timer.disposed?        # 是否已释放?
#     @timer.clear            # 清空计时任务
#     @timer.timer            # 查看所有任务
#
#   [添加任务]
#     
#     @timer.cancel(*tags)  # 删除定时任务(支持正则表达式)
#     @timer.after(delay, action, tag=nil)
#     @timer.every(delay, action, count=nil, after=nil, tag=nil)
#     @timer.during(delay, action, after=nil, tag=nil)
#     @timer.script{|wait| ...; wait.call(n); ... }
#     @timer.tween(delay, subject, target, method, after=nil, tag=nil, *args, &update)
#     after/every/tween # [delay]参数支持整数/范围类型
#
#     @timer.progress(tag)  # 查看定时器的进度(0~1)
#     @timer.has?(tag, include=false)  # 是否有指定任务, 如果存在则返回任务, 不存在返回 nil
#                                      # tag 支持正则表达式、字符串
#                                      # include 表示是否以包含匹配(只在 tag 为字符串时有效)。
#     Timer.tween_(t, a, b, m) # 类方法, 用来查看单步值
#
#   [范例]
#
#     # 添加一60帧后执行一次的定时
#     @timer.after(60, proc { p 'after: 60帧后输出' })
#
#     # 添加一个每50帧循环执行的定时
#     @timer.every(50, proc {|n| p "every: 每50帧输出一次, 当前第#{n}次" })
#
#     # 添加一个每50帧循环执行的定时, 循环次数为 4 次, 由于第一个时间参数为负数, 表示第一次循环不需要等待
#     @timer.every(-50, proc { p 'every: 每50帧输出一次, 4次后结束' }, 4, proc{ p 'every: 执行完成' })
#
#     # 添加持续执行 2.5 秒时间的任务(持续循环执行,运行时间超出指定秒数后,则不再执行第二次, 但第一次不会中断)
#     @timer.during(2.5, proc { p '我会持续执行2.5秒' }, proc{ p 'during执行完成' })
#
#     # 将附加块传入函数的内部创建Fiber,使用wait.call(n)不影响主线程进行等待,下面的代码相当于一个简化版的after集合。
#     @timer.script{|wait|
#       puts 1
#       wait.call 60 # 等待60帧
#       puts 2
#       wait.call 60 # 等待60帧
#       puts 3
#     }
#     
#     # 上面的代码等同于下面的
#     puts 1
#     @timer.after(60, proc{
#       puts 2
#       @timer.after(60, proc{
#         puts 3
#       })
#     })
#
#
#   [Tween补间方法]
#     [说明]
#       tween 为补间(动画)方法, 用于对变量增减补间所用(绘制、显示等)
#       定时器流程为调用 subject 对象中的【实例变量/Hash#Key/方法[v/v=]】来调整直到
#       与 target 里存在的【实例变量/Hash#Key】值一致, 属性值只支持整数/小数。 
#     [渐变方式]
#       'linear'
#       'bezier'
#       'in-out-' + method
#       'out-in-' + method
#       'in-' + method
#       'out-' + method
#     [渐变方法]
#       'quad/cubic/quart/quint/sine/expo/circ/back/bounce/elastic/bezier'
#     [Bezier]
#       使用 bezier 补间方法需要在 tween 的 tag 参数后传入一个数组([x1, y1, x2, y2])
#       例:@timer.tween(10, s, t, 'bezier', nil, nil, [0,0,1,1])
#       或者:@timer.tween(10, s, t, CubicBezier.new(0,0,1,1))
#       Bezier曲线图在线查看:https://cubic-bezier.com/
#
#     # tween范例:修改哈希
#     subject = {:x => 50, :y => 10, :other => { :float => 0.0 } }
#     target = {:x => 300, :y => 100, :other => { :float => 10.0 } }
#     @timer.tween(120, subject, target, 'in-out-cubic', proc { p 'tween: 执行完成' })
#     
#     # tween范例:修改实例变量
#     class Test; attr_accessor :value; def initialize(v); @value = v; end; end
#     subject = Test.new(50)
#     @timer.tween(120, subject, {value: 100}, 'in-out-cubic', proc { p 'tween: 执行完成' })
#     
#     # tween范例:没实例变量则修改方法(需要两个方法同时存在, 如:x/x=)
#     sprite = Sprite.new; sprite.bitmap = Bitmap.new(50,50)
#     @timer.tween(120, sprite, {x: 100, y: 50, src_rect: {width: 25}}, 'linear', proc { p 'tween: 执行完成' })
#
#     # tween范例:其他对象(此操作同理也是利用修改方法, x/x=  y/y=)
#     s = Struct.new(:x, :y)[0, 0]
#     @timer.tween(120, s, {x: 100, y: 100}, 'linear', proc { p 'tween: 执行完成' })
#
#     # tween范例:Target 也可以是非 Hash 的带有实例变量的对象(会检测target中所有实例变量来处理)
#     class Test; attr_accessor :value; def initialize(v); @value = v; end; end
#     t1 = Test.new(30); t2 = Test.new(100)
#     @timer.tween(120, t1, t2, 'linear', proc { p 'tween: 执行完成' })
#
#     # tween范例:每帧更新回调
#     @timer.tween(120, Sprite.new, {x: 10, src_rect: {width: 50}}, 'linear'){|t, i, r, k, v|
#       p "#{t}, #{i}, #{r}, #{k}, #{v}"
#
#      # t 当前定时对象, t[:time] 即为当前帧
#      # i (0当前帧更新前/1对象赋值前/2对方赋值后/3当前帧更新后)
#      # r 操作的对象
#      # k 操作的属性
#      # v (要增加的值/增加后的值)
#
#     }
#
#     # tween范例:错误忽略
#     $spr = Sprite.new
#     @timer.tween(6000, $spr, {x: 10, _try_: true}, 'linear')
#       # target里声明_try_为真, 则表示在计时器执行中忽略错误,
#       # 这时在计时器执行中如果$spr执行了dispose方法,
#       # 定时器则会忽略错误不会报错导致游戏停止运行。
#
#     # tween范例(单步取值)
#     Timer.tween_(0.3, 100, 200, 'linear') # 返回 100->200 使用 linear 方法 30% 进度的值
#
#     [target参数特殊属性]
#        _cb_(Proc):作用与 tween 的附加区块功能类似。
#                    参数为(对象, 当前帧), 会在当前属性调用完毕后调用一次 _cb_ 。
#
#        _try_(Boolean):设置为true后, 定时器的任务触发错误时不会停止。
#
#        _before_(Hash):设置属性的初始值。
#
#==============================================================================

class Timer
  #--------------------------------------------------------------------------
  # ● config
  #--------------------------------------------------------------------------
  TWEEN_METHOD_ASYNCHRONOUS = false # 异步, 默认关(开启后多个tween操作同一对象的方法时, 所取得的属性是否独立/共用(默认))
  #--------------------------------------------------------------------------
  # ● attr
  #--------------------------------------------------------------------------
  attr_reader :timer
  #--------------------------------------------------------------------------
  # ● initialize
  #--------------------------------------------------------------------------
  def initialize
    @timer = {}
    @_attr = Hash.new{|h,k| h[k] = Hash.new{|h2,k2| h2[k2] = {} } } # attr->tag->obj->key
  end
  #--------------------------------------------------------------------------
  # ● dispose / disposed?
  #--------------------------------------------------------------------------
  def dispose; return if disposed?; self.clear; @timer = @_attr = nil; end
  def disposed?; self.timer.nil?; end
  #--------------------------------------------------------------------------
  # ● clear
  #--------------------------------------------------------------------------
  def clear
    @_attr.clear if @_attr
    self.timer.clear
  end
  #--------------------------------------------------------------------------
  # ● progress
  #--------------------------------------------------------------------------
  def progress(tag)
    return nil unless t = has?(tag)
    return t[:time] / t[:delay].to_f
  end
  #--------------------------------------------------------------------------
  # ● has?
  #--------------------------------------------------------------------------
  def has?(tag, include=false)
    if tag.is_a?(String) then
      if include then
        self.timer.find{|k, v| k.include?(tag) }
      else
        self.timer[tag]
      end
    elsif tag.is_a?(Regexp) then
      self.timer.find{|k, v| k =~ tag }
    end
  end
  #--------------------------------------------------------------------------
  # ● update
  #--------------------------------------------------------------------------
  def update(tags=nil)
    tags = tags ? [tags].flatten : self.timer.keys
    for tag in tags do
      timer = has?(tag)
      next unless timer
      timer[:time] += 1
      case timer[:type]
      when :after
        if timer[:time] >= timer[:delay] then
          timer[:action].call
          self.cancel(tag)
        end
      when :every
        if timer[:time] >= timer[:delay] then
          timer[:action].call timer[:counter], timer[:delay]
          timer[:time] -= timer[:delay]
          timer[:delay] = _getResolvedDelay(timer[:any_delay], true)
          timer[:counter] += 1
          if timer[:count] > 0 then
            if timer[:counter] >= timer[:count] then
              timer[:after].call if timer[:after] != nil
              self.cancel(tag)
            end
          end
        end
      when :during
        if Graphics.frame_count.to_f / Graphics.frame_rate - (timer[:time] -= 1) <= timer[:delay] then
          timer[:action].call
        else
          timer[:after].call if timer[:after] != nil
          self.cancel(tag)
        end
      when :tween
        ps = timer[:time]/timer[:delay].to_f
        s = _tween(timer[:method], ps > 1 ? 1 : ps, *timer[:args])
        ds = s - timer[:last_s]
        timer[:last_s] = s
        timer[:update].call(timer, 0) if timer[:update]
        for _,info in timer[:payload] do
          ref,key,delta,initial,type,param = info
          cb,try,before = param
          add = delta*ds
          timer[:update].call(timer, 1, ref, key, add) if timer[:update]
          begin
            _v = timer[:time] == 1 ? before[key] : nil
            case type
            when -1  # hash
              _v = ref[key] unless _v
              _v += add
              # Accuracy
              if timer[:time] >= timer[:delay] then
                _v = _v.round(initial.class == Float ? initial.to_s.split('.')[1].size : 0)
              end
              ref[key] = _v
            when 0    # variable
              key = "@#{key}" unless key.to_s.include?('@')
              _v = ref.instance_variable_get(key) unless _v
              _v += add
              # Accuracy
              if timer[:time] >= timer[:delay] then
                _v = _v.round(initial.class == Float ? initial.to_s.split('.')[1].size : 0)
              end
              ref.instance_variable_set(key, _v)
            when 1    # method
              ref_id = ref.__id__
              data = @_attr[tag]
              @_attr.values.find{|v| data[ref_id] = v[ref_id] } unless TWEEN_METHOD_ASYNCHRONOUS
              data[ref_id][key] = _v if _v
              _v = data[ref_id][key] ||= ref.__send__(key)
              _v += add
              # Accuracy
              if timer[:time] >= timer[:delay] then
                _v = _v.round(initial.class == Float ? initial.to_s.split('.')[1].size : 0)
              end
              ref.__send__("#{key}=", data[ref_id][key] = _v)  # Fine-tuning
            end
          rescue Exception => e
            raise e unless try
          end
          cb.call(ref, timer[:time]-1) if cb
          timer[:update].call(timer, 2, ref, key, _v) if timer[:update]
        end
        timer[:update].call(timer, 3) if timer[:update]
        if timer[:time] >= timer[:delay] then
          timer[:after].call if timer[:after] != nil
          self.cancel(tag)
        end
      end
    end
  end
  #--------------------------------------------------------------------------
  # ● cancel
  #--------------------------------------------------------------------------
  def cancel(*tags)
    tags.each do |t|
      if t.is_a?(Regexp) then
        cancel(*self.timer.keys.select{|k| k =~ t })
      else
        @_attr.delete(t)
        self.timer.delete(t)
      end
    end
  end
  #--------------------------------------------------------------------------
  # ● after
  #--------------------------------------------------------------------------
  def after(delay, action, tag=nil)
    tag ||= UUID()
    self.cancel(tag)
    self.timer[tag] = {
      :tag=>tag,
      :type=>:after, 
      :time=>0, 
      :delay=>_getResolvedDelay(delay), 
      :action=>action,
    }
    return tag
  end
  #--------------------------------------------------------------------------
  # ● every
  #--------------------------------------------------------------------------
  def every(delay, action, count=nil, after=nil, tag=nil)
    if count.is_a?(String) then
      tag, count = count, 0
    elsif count.is_a?(Fixnum) and after.is_a?(String) then
      tag, after = after, nil
    end
    tag ||= UUID()
    self.cancel(tag)
    t = _getResolvedDelay(delay)
    t = t.abs if first = t < 0 # The first cycle does not need to wait
    self.timer[tag] = {
      :tag=>tag,
      :type=>:every, 
      :time=>first ? t : 0, 
      :any_delay=>delay, 
      :delay=>t, 
      :action=>action, 
      :counter=>0, 
      :count=>count || 0, 
      :after=>after, 
    }
    return tag
  end
  #--------------------------------------------------------------------------
  # ● during
  #--------------------------------------------------------------------------
  def during(delay, action, after=nil, tag=nil)
    if after.is_a?(String) then
      tag, after = after, nil
    end
    tag ||= UUID()
    self.cancel(tag)
    self.timer[tag] = {
      :tag=>tag,
      :type=>:during, 
      :time=>Graphics.frame_count.to_f / Graphics.frame_rate, 
      :delay=>delay, 
      :action=>action, 
      :after=>after,
    }
    return tag
  end
  #--------------------------------------------------------------------------
  # ● script
  #--------------------------------------------------------------------------
  def script
    return unless block_given?
    f = Fiber.new{|wait| yield(wait) }
    f.resume(proc{|t| self.after(t, proc{ f.resume }); Fiber.yield })
  end
  #--------------------------------------------------------------------------
  # ● tween
  #--------------------------------------------------------------------------
  def tween(delay, subject, target, method, after=nil, tag=nil, *args, &update)
    if after.is_a?(String) then
      after, tag = nil, after
    end
    tag ||= UUID()
    self.cancel(tag)
    self.timer[tag] = {
      :tag=>tag,
      :type=>:tween, 
      :time=>0, 
      :delay=>_getResolvedDelay(delay), 
      :subject=>subject, 
      :target=>target, 
      :method=>method, 
      :update=>update,
      :after=>after,
      :args=>args,
      :last_s=>0,
      :payload=>tweenCollectPayload(subject, target, {}), 
    }
    return tag
  end
  #--------------------------------------------------------------------------
  # ● _getResolvedDelay
  #--------------------------------------------------------------------------
  def _getResolvedDelay(delay, abs=false)
    case delay
    when Numeric
      return abs ? delay.abs : delay
    when Range
      a, b = delay.first, delay.last
      return a+rand(b-a+(delay.exclude_end? ? 0 : 1))
    end
  end
  #--------------------------------------------------------------------------
  # ● tweenCollectPayload
  #--------------------------------------------------------------------------
  def tweenCollectPayload(subject, target, out)
    unless target.is_a?(Hash) # object convert to hash.
      hash = {}
      target.instance_variables.each do |var|
        hash[var.to_sym] = target.instance_variable_get(var)
      end
      target = hash
    end
    keys = target.select{|k, v| v.is_a?(Numeric) }.keys
    before = target[:_before_] || {}
    for k, v in target do
      next if [:_cb_, :_try_, :_before_].include?(k) # callback ignore
      # with instance attribute
      ref = (subject.is_a?(Hash) ? subject[k] : subject.instance_variable_get('@' << k.to_s))
      # with method(method/method=)
      ref_ = nil
      if ref.nil? then
        ref = ref_ = begin
          subject.__send__(k)
        rescue Exception => e
          puts e.message
          raise RGSSError.new("#{self.to_s.insert(-2, '#tween')}: error from method `#{k}' in subject(#{subject}).\nThere are method(#{k}) in target, maybe not in subject ?")
        end
      end
      if v.is_a?(Numeric) then
        raise TypeError.new("#{self.to_s.insert(-2, '#tween')}: wrong class type `#{k}' in subject of (#{ref.class} for Numeric)") unless ref.is_a?(Numeric)
        delta = v - (before[k] || ref)
        out[out.size+1] = [
          subject, k, delta, v, 
          (ref_.nil? ? (subject.is_a?(Hash) ? -1 : 0) : 1),
          [
            k == keys[-1] ? target[:_cb_] : nil,
            target[:_try_],
            before,
          ],
        ]
      else  # Hash or Object , Continue recursion
        tweenCollectPayload(ref, v, out)
      end
    end
    return out
  end
  #--------------------------------------------------------------------------
  # ● tween step
  #--------------------------------------------------------------------------
  def self.tween_(t, a, b, m)
    @@timer ||= self.new
    t >= 1 ? b : @@timer.instance_eval{ _tween(m, t) } * (b-a) + a
  end
  #--------------------------------------------------------------------------
  # ● _tween
  #--------------------------------------------------------------------------
  def _tween(method, *args)
    t = {
      :out => proc{|f| proc {|s,*a| 1 - f.call(1-s, *a) } },
      :chain => proc{|f1,f2| proc {|s,*a| (s < 0.5 ? f1.call(2*s, *a) : 1 + f2.call(2*s-1, *a))*0.5 } },
      :linear => proc{|s| s },
      :quad => proc{|s| s ** 2 },
      :cubic => proc{|s| s ** 3 },
      :quart => proc{|s| s ** 4 },
      :quint => proc{|s| s ** 5 },
      :sine => proc{|s| 1-Math.cos(s*Math::PI/2) },
      :expo => proc{|s| 2 ** (10*(s-1)) },
      :circ => proc{|s| 1-Math.sqrt(1-s*s) },
      :back => proc{|s, bounciness| bounciness ||= 1.70158; s*s*((bounciness+1)*s - bounciness) },
      :bounce => proc{|s| a, b = 7.5625, 1/2.75; [a*s**2, a*(s-1.5*b)**2 + 0.75, a*(s-2.25*b)**2 + 0.9375, a*(s-2.625*b)**2 + 0.984375].min },
      :elastic => proc{|s, amp, period| amp, period = (amp ? [1, amp].max : 1), (period ? period : 0.3); (-amp*Math.sin(2*Math::PI/period*(s-1) - Math.asin(1/amp)))*2**(10*(s-1)) },
      :bezier => proc{|s, arr| CubicBezier.new(*arr).solve(s) },
    }
    if method.is_a?(CubicBezier)
      return method.solve(args[0])
    elsif method == 'linear' then
      return t[:linear].call(*args)
    elsif method == 'bezier' then
      return t[:bezier].call(*args)
    elsif method[0...7] == 'in-out-' then
      met = t[method[7..-1].to_sym]
      f1 = met
      f2 = t[:out].call(met)
      return t[:chain].call(f1, f2).call(*args)
    elsif method[0...7] == 'out-in-' then
      met = t[method[7..-1].to_sym]
      f1 = t[:out].call(met)
      f2 = met
      return t[:chain].call(f1, f2).call(*args)
    elsif method[0...4] == 'out-' then
      met = t[method[4..-1].to_sym]
      return t[:out].call(met).call(*args)
    elsif method[0...3] == 'in-' then
      met = t[method[3..-1].to_sym]
      return met.call(*args)
    end
  end
  #--------------------------------------------------------------------------
  # ● UUID
  #--------------------------------------------------------------------------
  KEY = ('A'..'Z').to_a + (0..9).to_a
  def UUID()
    i = KEY.shuffle.first(10).join
    return (self.timer[i].nil? ? i : UUID())
  end
  #--------------------------------------------------------------------------
  # ● detection disposed?
  #--------------------------------------------------------------------------
#~   old = {}
#~   %w(cancel after every during script tween progress clear has? update).each do |name|
#~     sym  = name.to_sym
#~     old[sym] = instance_method(sym)
#~     define_method sym do |*args|
#~       raise RGSSError.new "disposed #{self.class} from :#{sym}" if disposed?
#~       old[sym].bind(self).call(*args)
#~     end
#~   end
  private :_getResolvedDelay, :tweenCollectPayload, :_tween, :UUID
end

#==============================================================================
# ■ CubicBezier
#==============================================================================
class CubicBezier
  private
  def initialize(p1x, p1y, p2x, p2y)
    @cx = 3.0 * p1x
    @bx = 3.0 * (p2x - p1x) - @cx
    @ax = 1.0 - @cx - @bx

    @cy = 3.0 * p1y
    @by = 3.0 * (p2y - p1y) - @cy
    @ay = 1.0 - @cy - @by
  end
  def sampleCurveX(t)
    ((@ax * t + @bx) * t + @cx) * t
  end
  def sampleCurveY(t)
    ((@ay * t + @by) * t + @cy) * t
  end
  def sampleCurveDerivativeX(t)
    (3.0 * @ax * t + 2.0 * @bx) * t + @cx
  end
  ZERO_LIMIT = 1e-6
  def solveCurveX(x)
    t2 = x
    x2 = 0
    derivative = 0
    8.times do |i|
      x2 = sampleCurveX(t2) - x
      return t2 if x2.abs < ZERO_LIMIT
      derivative = sampleCurveDerivativeX(t2)
      break if derivative.abs < ZERO_LIMIT
      t2 -= x2 / derivative
    end

    t1 = 1
    t0 = 0
    t2 = x
    while t1 > t0
      x2 = sampleCurveX(t2) - x
      return t2 if x2.abs < ZERO_LIMIT
      if x2 > 0
        t1 = t2
      else
        t0 = t2
      end
      t2 = (t1 + t0) / 2
    end
    return t2
  end
  public
  def solve(x)
    sampleCurveY(solveCurveX(x))
  end
end

#~ # bezier = CubicBezier.new(0,0,1,1)
#~ bezier = CubicBezier.new(0.25,0.1,0.25,1)
#~ s = Sprite.new
#~ b = s.bitmap = Bitmap.new(300,300)
#~ b.fill_rect(b.rect, Color.new(255,0,0))
#~ b.width.times do |i|
#~   arr = [i, (b.height-bezier.solve(i/b.width.to_f)*b.height).to_i]
#~   b.set_pixel(*arr, Color.new(255,255,255))
#~ end

#~ loop do
#~   Graphics.update
#~   Input.update
#~ end

鸡蛋
1

鲜花

刚表态过的朋友 (1 人)

评论 (0 个评论)

facelist doodle 涂鸦笔

您需要登录后才可以评论 登录 | 注册会员

拿上你的纸笔,建造一个属于你的梦想世界,加入吧。
 注册会员
找回密码

站长信箱:[email protected]|手机版|小黑屋|无图版|Project1游戏制作

GMT+8, 2024-3-28 18:03

Powered by Discuz! X3.1

© 2001-2013 Comsenz Inc.

返回顶部