Ruby教程 – 晴兰的秘密社团 https://seiran.mist.so Ruby与RGSS研究 Tue, 15 Jan 2019 10:09:01 +0000 zh-CN hourly 1 https://wordpress.org/?v=4.9.5 利用set_trace_func方法来判断一个方法是否被alias过 https://seiran.mist.so/ruby/%e5%88%a9%e7%94%a8set_trace_func%e6%96%b9%e6%b3%95%e6%9d%a5%e5%88%a4%e6%96%ad%e4%b8%80%e4%b8%aa%e6%96%b9%e6%b3%95%e6%98%af%e5%90%a6%e8%a2%abalias%e8%bf%87.html https://seiran.mist.so/ruby/%e5%88%a9%e7%94%a8set_trace_func%e6%96%b9%e6%b3%95%e6%9d%a5%e5%88%a4%e6%96%ad%e4%b8%80%e4%b8%aa%e6%96%b9%e6%b3%95%e6%98%af%e5%90%a6%e8%a2%abalias%e8%bf%87.html#comments Mon, 25 Aug 2014 19:13:06 +0000 http://seiran.mist.so/?p=147 其实就是囧叔跟我说的利用irb的help相同的技巧,在set_trace_func下调用,在合适的binding中取得信息并且直接用throw…catch来跳出(实际并不执行方法体),

详细方法见代码注释:

def custom_trace(sym, &b)
  u = method(sym)                            #得到方法
  v = u.arity < 0 ? [] : [0] * u.arity       #伪造方法参数
  x = lambda{u.call(*v)}                     #x.call将要调用这个方法,但是实际上不会真的调用
  set_trace_func Proc.new
  ret = catch(:gogogo) do                         #捕捉gogogo并且返回新的方法名                          
    x.call                                  #执行上述方法
  end
  set_trace_func nil#取消跟踪
  ret
end

def aliased?(sym)
  sym != custom_trace(sym) do |*e|
    next unless e[0] == "call"       #定位是call
    throw :gogogo, e[-3]             #退出, e[-3]就是当时的方法名
  end
end

def a u 
  puts "Hello"
end
alias b a

p aliased? :b #=> true
p aliased? :a #=> false

顺便,在Ruby1.8的Method没有定义source_location的情况下取得方法定义的位置的方法,以及取得参数名的方法。

def source_location(sym)
  custom_trace(sym) do |*e|
    next unless e[0] == "call"      
    throw :gogogo, eval("[__FILE__, __LINE__]", e[-2])
  end
end

def parameters(sym)
    custom_trace(sym) do |*e|
    next unless e[0] == "call"      
    throw :gogogo, eval("local_variables", e[-2]) 
=begin
  第一行就出现的本地变量是一般就是参数
  除非有意写成def add(a,b); s = 3
  这个方法除了返回"a" "b"还会返回"s"
=end
  end
end

 

 

]]>
https://seiran.mist.so/ruby/%e5%88%a9%e7%94%a8set_trace_func%e6%96%b9%e6%b3%95%e6%9d%a5%e5%88%a4%e6%96%ad%e4%b8%80%e4%b8%aa%e6%96%b9%e6%b3%95%e6%98%af%e5%90%a6%e8%a2%abalias%e8%bf%87.html/feed 2
晴兰的反向Ruby教程-2 RMVA小程序 https://seiran.mist.so/ruby/seiran-ruby-reversed-2.html https://seiran.mist.so/ruby/seiran-ruby-reversed-2.html#respond Wed, 21 Nov 2012 21:04:22 +0000 http://seiran.9bu.org/?p=25 上一节我们学习了Ruby的消息,如何定义消息(方法,我们在这里不需要区分消息与方法,可以认为是一样的),现在当你去看F1手册时,看到的都是模块、类和方法名。下面试试来做更多的事情。
RMVA的手册在写本文时大约没有完整的中文版,但就像XP、VX、VA各版本的手册的”RGSS参考>标准库>预置函数”一节开宗明义都会说到的,像system这种函数,事实上是Kernel模块的一个方法,以消息编程来看,不指定对象,我们发消息的对象是Kernel。也可以指定对象,用”对象.方法”来调用。因此事实上是这个样子的:

def 运行 程序
   Kernel.system 程序
end

运行 "notepad"

下面来看看更多的例子

 

手册的”RGSS参考>游戏库>Audio” “RGSS リファレンス>ゲームライブラリ>Audio”

这个页面,可以看到一堆模块方法。

例如bgm_play:

Audio.bgm_play(filename[, volume[, pitch[, pos]]])

要注意的是,Audio就是要发消息的对象,消息名是bgm_play,后面的消息参数分别是文件名、音量、音调、位置。加了方括号的表示可以省略。例如:

Audio.bgm_play "Audio/BGM/Theme1.ogg"

但这样,会播放了不到1秒就游戏结束了,因为没有让他停留在那里播放,我只是对音频模块说,播放这个BGM,然后游戏的主要脚本也就结束了。为了让他停在那里播放,可以用rgss_stop:

Audio.bgm_play "Audio/BGM/Theme1.ogg"
rgss_stop

现在为止我们只能手动点击关闭按钮关掉。

当然也可以像上一节一样弄的好看点

alias 无限刷新画面 rgss_stop
音频模块 = Audio
class << 音频模块
  alias 播放BGM bgm_play
end
某音乐 = "Audio/BGM/Theme1.ogg"
#----------------------------以上是各种设置和别名---------------------------
#----------------------------以下是要做的事情------------------------------
音频模块.播放BGM 某音乐
无限刷新画面

说是半残播放器,是因为只能播放一首曲子,不过更改下面的设置可以播放其他的曲子。从网络上取得的脚本,通常也会有些设置。

几乎所有的图形游戏里面都会有一个刷新画面的循环,这里是最简单的一种。 当然文字版的猜数字可能是不需要的。

一个游戏至少要和用户有所交互,就说刚才简单的播放BGM,可以说是音乐欣赏,但是我为了控制它的播放情况,可以读取用户的按键:

  • 上键: 增大音量
  • 下键: 减小音量
  • 右键: 提高音调
  • 左键: 降低音调
  • 回车, C: 暂停、恢复暂停
  • ESC, X: 退出游戏

下面的代码里面的@, 可以这么看,表示’我的’一个变量,也就是正在写代码的这个对象的一个变量。Ruby里面的变量有@,@@,$开头的几种,分别是对象的变量,类的变量,全局变量。还有什么开头都不写的,但不写的一般而言,大写字母开头的并不是变量而是常量
下面是两种写法,判断按键的过程既可以是程序逻辑,也可以是数据,抽象的方式不一样而已。数据的话,相当于设置按键。

class << Audio
  alias 播放BGM bgm_play
  alias BGM位置 bgm_pos
end

class << Input
  alias 按了一下? trigger? #repeat? 并不是一按下就起作用,就按一下
  alias 一直按着? repeat?  #repeat? 并不是一按下就起作用,重复按下
  alias 按着?     press?   #press?  是按下就会起作用
end

@图形模块      = Graphics
@音频模块      = Audio
@键盘模块      = Input
@上键          = :UP
@下键          = :DOWN
@左键          = :LEFT
@右键          = :RIGHT
@回车          = :C
@ESC           = :B
#--------------------------↑别名-------------------------------
@某音乐      = "Audio/BGM/Theme1.ogg"

@音量        = 100
@音调        = 100
@暂停位置    = 0

@设置是暂停  = false
@改变了设置  = false
@退出游戏    = false
@暂停        = false
#--------------------------↑数据-------------------------------
def 初始化
  @音频模块.播放BGM @某音乐
end

def 按了一下? 某键        #按了一下?
  @键盘模块.按了一下? 某键
  #问键盘模块,按了一下某键吗?Ruby的方法名最后的问号多是要返回真假,表示测试或者疑问
end

def 判断按键修改设置
  case
    when 按了一下?(@上键)
      @音量 += 5
      @改变了设置 = true
    when 按了一下?(@下键)
      @音量 -= 5
      @改变了设置 = true
    when 按了一下?(@左键)
      @音调 -= 5
      @改变了设置 = true
    when 按了一下?(@右键)
      @音调 += 5
      @改变了设置 = true
    when 按了一下?(@回车)
      @暂停 = ! @暂停 #切换暂停/不暂停
      @改变了设置 = true
      @设置是暂停 = true
    when 按了一下?(@ESC)
      @退出游戏 = true
  end
end

def 更新设置
  exit if @退出游戏 #当退出游戏为真时退出
  if @改变了设置   #如果改变了设置
    unless @设置是暂停      #除非改变的设置是暂停 相当于if not 设置是暂停
      @当前位置 = @音频模块.BGM位置
      #当前位置 = Audio.bgm_pos
      @音频模块.播放BGM @某音乐, @音量, @音调, @当前位置
    else
      if @暂停
        @暂停位置 = Audio.bgm_pos
        Audio.bgm_stop
      else
        @音频模块.播放BGM @某音乐, @音量, @音调, @暂停位置
      end
    end
  end
  #复位设置
  @改变了设置 = false
  @设置是暂停 = false
end

#--------------------------↓运行-------------------------------
初始化
loop do
  Graphics.update
  Input.update
  #↑这两句就不用别名了,是整个RGSS系列都很重要的用法
  判断按键修改设置
  更新设置
end

对比一下下面的数据设置,不过下面这种用法不需要太追究:

class << Audio
  alias 播放BGM bgm_play
  alias BGM位置 bgm_pos
end

class << Input
  alias 按了一下? trigger? #repeat? 并不是一按下就起作用,就按一下
  alias 一直按着? repeat?  #repeat? 并不是一按下就起作用,重复按下
  alias 按着?     press?   #press?  是按下就会起作用
end

@图形模块      = Graphics
@音频模块      = Audio
@键盘模块      = Input
@上键          = :UP
@下键          = :DOWN
@左键          = :LEFT
@右键          = :RIGHT
@回车          = :C
@ESC           = :B
#--------------------------↑别名-------------------------------
@某音乐      = "Audio/BGM/Theme1.ogg"

@音量        = 100
@音调        = 100
@暂停位置    = 0

@设置是暂停  = false
@改变了设置  = false
@退出游戏    = false
@暂停        = false

@按键设置 = {
   @上键  =>      ->{@音量 += 5} ,#这里有逗号
   @下键  =>      ->{@音量 -= 5} ,
   @左键  =>      ->{@音调 += 5} ,
   @右键  =>      ->{@音调 -= 5} ,
   @回车 =>       ->{
      @暂停 = ! @暂停 #切换暂停/不暂停
      @设置是暂停 = true
   },#可以写几行
   @ESC => ->{@退出游戏 = true}, 
}
#--------------------------↑数据-------------------------------
def 初始化
  @音频模块.播放BGM @某音乐
end

def 判断按键修改设置
  if @按键设置.any?{|键, 要做的事情|   #对于按键设置表而言,有任何为真的情况:
    if @键盘模块.按了一下? 某键        #↓
      要做的事情.call                 
      true #按下了某键                
    else                             
      false
    end
  }                                  #↓
     @改变了设置 = true               #有任何为真的情况,改变了设置
  end
end

def 更新设置
  exit if @退出游戏 #当退出游戏为真时退出
  if @改变了设置   #如果改变了设置
    unless @设置是暂停      #除非改变的设置是暂停 相当于if not 设置是暂停
      @当前位置 = @音频模块.BGM位置
      @音频模块.播放BGM @某音乐, @音量, @音调, @当前位置
    else
      if @暂停
        @暂停位置 = Audio.bgm_pos
        Audio.bgm_stop
      else
        @音频模块.播放BGM @某音乐, @音量, @音调, @暂停位置
      end
    end
  end
  #复位设置
  @改变了设置 = false
  @设置是暂停 = false
end

#--------------------------↓运行-------------------------------
初始化
loop do
  Graphics.update
  Input.update
  判断按键修改设置
  更新设置
end

平面 = Plane
图片 = Bitmap

class Plane
  alias 图片 bitmap
  alias 图片= bitmap=
end

@壁纸 = 平面.new #新建一个平面
@壁纸.图片 = 图片.new "Graphics/Titles1/Night" #新建(事实上是载入图片)

rgss_stop #还记得吗 无限刷新画面

 

注意,这里”@壁纸.图片=”, 事实上是给@壁纸这个对象,发送了”图片=”这个消息,消息名是”图片=”,而!并!不!是!在”@壁纸”这个对象的范围里面做了: @壁纸.instance_eval do @图片 = … end 以后会提到这个instance_eval 事实上我们之前所做的一切事情都相当于 self.instance_eval do 平面 = Plane … end 而self是当前对象

这也是为什么上面取了两次别名,事实上是两个不同的方法(消息)

 

 

这里进一步地,对图像进行操作,可以改改程序看结果

平面         = Plane
图片         = Bitmap

class Plane
  alias 图片  bitmap
  alias 图片= bitmap=
end

class Bitmap
  {
    "dispose"              => "释放",
    "disposed?"            => "释放了吗?",
    "width"                => "宽度",
    "height"               => "高度",
    "rect"                 => "矩形区域", # =>相当于 Rect.new(0, 0, width, height)
    "blt"                  => "描绘"   ,
    "stretch_blt"          => "缩放描绘",
    "fill_rect"            => "填充矩形",
    "gradient_fill_rect"   => "渐变填充",
    "clear"                => "清除",
    "clear_rect"           => "清除矩形",
    "get_pixel"            => "取点",
    "set_pixel"            => "画像素",
    "hue_change"           => "改变色相",
    "blur"                 => "模糊化",
    "radial_blur"          => "径向模糊",
    "draw_text"            => "描绘文字",
    "text_size"            => "文字大小",
    "font"                 => "字体",
    "font="                => "字体=",
  }.each{|英文名, 中文名| alias_method 中文名, 英文名}
end

@壁纸        = 平面.new #新建一个平面
@壁纸.图片   = 图片.new "Graphics/Titles1/Night" #新建(事实上是载入图片)
@壁纸.图片.径向模糊 6, 20#角度,份数

rgss_stop #还记得吗 无限刷新画面

试试对图片进行其他的操作
alias不是消息/方法,对应的方法是alias_method
{}.each{|a,b|}的话,在后面的括号里面,a就会取配对的第一个,b取第二个,例如最后一组 英文名=”font=”, 中文名=”字体=”,这个{}是个哈希/字典/配对/映射,Ruby中的类型是Hash。
each的作用是对那一堆配对的每一个,都做后面大括号的事情
从这个来看Ruby的代码也可以写成好理解的形式。不要试图手动生成上面那张表,有更好的办法以后会提到。

也可以使用系统API,就像其他程序那样:

=begin C声明,注意对比
int __stdcall MessageBox(HWND, LPSTR, LPSTR, INT);
=end
MessageBox = Win32API.new "user32", "MessageBox", "ippi", "i"
MessageBox.call  0, "Hello", "Title", 16

Win32API.new的参数: DLL名, 函数名, 参数类型表, 返回值类型

但是通常情况下,类型可以自动推断出来,返回值也可以忽略,或者通常是整数兼容的类型。

这里用method_missing来匹配MessageBox这个不存在的方法,并生成”ippi”这样的参数表:

class API
  def initialize(dll)
    @dll = dll
  end
  def method_missing sym, *a
    Win32API.new(@dll, sym.to_s, a.map{|x|x.is_a?(Integer) ? ?i: ?p}, ?i).(*a)
  end
end

user32 = API.new "user32"
user32.MessageBox 0, "Hello", "Title", 16

 

]]>
https://seiran.mist.so/ruby/seiran-ruby-reversed-2.html/feed 0
晴兰的反向Ruby教程-1 为了读F1教程和其他人脚本的入门 https://seiran.mist.so/ruby/seiran-ruby-reversed-1.html https://seiran.mist.so/ruby/seiran-ruby-reversed-1.html#respond Wed, 21 Nov 2012 19:14:52 +0000 http://seiran.9bu.org/?p=20 这个教程是面向RM初学者的,要理解的东西不会很多,教程中的例子,希望能够手动一步步实现。我们是以RPG Maker VX ACE这个版本的RGSS来讲解的。

如果有不明白的地方,多运行和改改就行了。每个例子都是可以运行的。

本来,要写RGSS脚本,要先理解脚本语言也就是Ruby。反向呢,是区别于正向而言的。 在这里Ruby必须理解你,也就是一个脚本编写者的意图。不仅仅是这样,RPGMaker这个工具也要充分理解你作为一个游戏制作者的意图。不像很多其他语言,如果想以自己想要的方式去写程序,可能会变得很麻烦。但Ruby从一开始发明就不是这样的,更多的是人在编程,而不是程序语言必须限制人的思维。(不过这并不是说Ruby是一个不严谨的语言,人性化与编程原则并不冲突。)

在Windows系统下,双击记事本图标,也就能打开记事本,这是一个程序,也可以说是系统的一个功能。

在Windows命令提示符下,也可以通过直接输入notepad回车来打开记事本。

 

这一节的重点是,怎么定义一个功能(也就是怎么看别人定义了哪些功能),怎么使用定义的功能。

打开RMVA,新建一个工程。

打开工程文件夹:选择测试(倒数第二个菜单),然后选择打开游戏文件夹(最后一项)。将会看见游戏的文件夹。

复制脚本:进入其中的Data目录将Scripts.rvdata2复制一份,改名为MyScripts.rvdata2就行了。 如果看到的是没有后缀名.rvdata2的Scripts,改为MyScripts就行了。

删掉默认脚本:回到RMVA,打开脚本编辑器,在左侧把所有脚本都删掉。 以后就在这个工程的基础上来讲解。

 

从一个空的工程开始可以避免很多干扰

一行脚本能做到的事情有很多,首先我们来运行一个程序:

system "notepad.exe"

保存运行游戏,应该会打开一个记事本。 游戏会在关闭了记事本之后退出。

system的含义是给系统发送一个命令,让他启动程序,要启动的内容是”notepad.exe”。如果拿手机通信来比喻,可以发短信给系统,发短信这个方式是system,而内容是”notepad.exe”。

也可以发彩信: 启动程序 “notepad.exe”。 当然’启动程序’现在是没有的,下面就要说到。

也可以把notepad.exe换成别的名字,比方说”mspaint.exe”(画图),或者”winmine.exe” “mshearts.exe” “freecell.exe” “sol.exe”(依次是扫雷 红心大战 空当接龙 纸牌并不是所有机器上都有)

也可以把system换成别的预定义消息(方法),如常见的msgbox或者msgbox_p

事实上也可以约定说‘启动程序’的消息名字就是’启动程序‘而不是system,内容是记事本,于是改成下面的样子:

def 启动程序 x
  msgbox "将要启动 #{x}"
  system x
end
记事本 = "notepad.exe"

启动程序 记事本

这个从数学函数来理解就相当于:

f(x) = 提示”将要启动”x的信息 然后运行x

y = “notepad.exe”

f(y)的结果? 当然是提示 “将要启动notepad.exe” 然后运行”notepad.exe”

 

 

def 消息名字 消息内容
  ...
end

也可以:

def 加法 a,b
  a+b
end

msgbox 加法 3,5 #显示8

 

def…end是其他人脚本里面很常见的一个结构

3. 在Ruby里面,”定义消息“也可以是一个消息。

(名字是define_singleton_method。略长。含义是定义单个对象方法,对象就是消息的接受者,为这个接受者定义一个自定义消息。消息处理是一个函数,在Ruby里面,这种函数按照面向对象设计的惯例叫做方法。)

def 定义消息 消息名, 收到消息要做的事
  define_singleton_method 消息名, 收到消息要做的事
end

定义消息 "启动程序", ->程序{ system 程序 }

=begin 对比
def 启动程序 程序
  system 程序
end
=end
#井号后面的哪一行
#以及=begin =end之间的部分不会被运行

记事本 = "notepad.exe"

启动程序 记事本

注意在要做的事里面用到了一个->{}结构,他只是一个块,而不是正式的消息,写法是

->消息内容1, 消息内容2,...,消息内容N {要做的事}

同时虽然前面没写出来,def … end也可以接受多于1个内容,也叫做参数,用逗号隔开:

def 消息名 消息内容1, 消息内容2,...,消息内容N
   要做的事
end

Ruby内置define_singleton_method这类方法表明,可以很方便地为Ruby添加语法。原本是关键字的def,也可以降格成一行程序
定义消息 "启动程序", ->程序{ system 程序 }
def 运行 程序
   system 程序
end

记事本 = "notepad.exe"
画图 = "mspaint.exe"

运行 记事本
运行 画图
alias 运行 system   #定义消息的新名字: alias 新名字, 旧名字
运行 "notepad.exe"
alias也是其他人脚本里面一个很常见的用法

 

 

]]>
https://seiran.mist.so/ruby/seiran-ruby-reversed-1.html/feed 0