Project1

标题: 【小周实践系列第一季第一弹】教你制作状态窗口 [打印本页]

作者: 天地有正气    时间: 2014-4-4 22:30
标题: 【小周实践系列第一季第一弹】教你制作状态窗口
本帖最后由 天地有正气 于 2014-4-7 14:28 编辑

【小周实践系列第一季】教你制作状态窗口



@w (防止出错,先@了)

注意:本文适合有一定的脚本基础的人阅读!

以下是正文部分。

请先建立一个事件,用脚本“$w = Window_MapStatus.new”,

然后用独立开关切换至第二页,"$w.update"。

这就生成了实例。







打开脚本编辑器,输入以下内容。

  1. class Window_MapStatus < Window_Base
  2.   def initialize
  3.     super(0,0,200,150)
  4.   end
  5. end
复制代码
这一段中,Window_MapStatus是类名,继承父类Window_Base.
然后定义initialize 方法,super是继承父类的同名方法,也就继承了Window_Base的initialize和x,y,width,height四个参数。
super括号里的数字就这四个属性。
然后在super后面添加一句“self.contents = Bitmap.new(width - 32, height - 32)”这句脚本十分重要,不是空白的窗口都要有这个,他是生成了一张
Bitmap。就相当于在窗口里生成了一张画布,这样就可以在上面写些东西了。

我们修改一下脚本:

  1. class Window_MapStatus < Window_Base
  2.   # 开始定义类
  3.   attr_accessor :hp
  4.   # 使用attr_accessor来造实例变量,可读可写。
  5.   def initialize
  6.     # 开始定义方法
  7.     super(0,0,200,150)
  8.     # 继承父类
  9.     self.contents = Bitmap.new(width - 32, height - 32)
  10.     # 生成Bitmap
  11.     @hp = 0
  12.     # 初始化hp为零。
  13.     refresh
  14.     # 调用refresh方法,刷新
  15.   end
  16.   # 结束方法定义
  17.   def refresh
  18.     # 定义refresh方法
  19.   end
  20. end
  21. # 类定义结束
复制代码
我增加了一些注释,并且定义了一个可读可写的实例变量,目的是为了减少刷新次数。
接下来是要定义refresh方法了。
我直接完成了这个简易的血条脚本如下。

  1. class Window_MapStatus < Window_Base
  2.   # 开始定义类
  3.   attr_accessor :hp
  4.   # 使用attr_accessor来造一个方法,可读可写。
  5.   def initialize
  6.     # 开始定义方法
  7.     super(0,0,200,150)
  8.     # 继承父类
  9.     self.contents = Bitmap.new(width - 32, height - 32)
  10.     # 生成Bitmap
  11.     @hp = 0
  12.     # 初始化hp为零。
  13.     refresh
  14.     # 调用refresh方法,刷新
  15.   end
  16.   # 结束方法定义
  17.   def refresh
  18.     # 定义refresh方法
  19.     if @hp != $game_party.actors[0].hp
  20.       # 条件分歧,当@hp不等于队伍中一号角色的hp时
  21.       @hp = $game_party.actors[0].hp
  22.       # @hp赋值为队伍一号角色的hp
  23.       self.contents.clear
  24.       # 清空内容
  25.       self.contents.draw_text(0,0,60,32,"HP")
  26.       # 描绘文字(draw_text使用方法:draw_text(x坐标,y坐标,矩形宽度,矩形高度,字符串内容))
  27.       # 所谓矩形就是描绘这些文字的空间,空间不够就会缩小文字
  28.       self.contents.fill_rect(60,0,100,32,Color.new(255,255,255))
  29.       # 填充矩形
  30.       # 这句就是描绘一个白色的矩形,作为值槽的背景色。
  31.       # 属性为x,y,width,height,颜色。
  32.       # 颜色是Color类,颜色要是Color类的实例,并给出颜色属性。
  33.       # 颜色属性就是红,绿,蓝,透明度。
  34.       @w = 96 * @hp / $game_party.actors[0].maxhp
  35.       # 定义血槽的宽度
  36.       self.contents.fill_rect(62,2,@w,28,Color.new(180,0,0,180))
  37.       # 描绘血槽
  38.     end  
  39.     # if语句结束
  40.   end
  41.   # 方法定义结束
  42. end
  43. # 类定义结束
复制代码
对不起,写到这里刚刚发现,请把事件中的$w.update换成$w.refresh,这样对于这种方法来说,效果更好!

当然,这个脚本还是有不少缺陷的,我会在后面的教程中解决。

如果有错误请大家指出。

有不懂的地方也欢迎来提问。

作业:请把这个状态栏完善。


下节课预告:制作渐变血条

作者: 天地有正气    时间: 2014-4-5 21:27
渐变值槽的绘制

(预览图主楼放出了,所以这里就不在放了。)

渐变色就是一堆相近的颜色拍在一块,然后给人一种渐变的感觉。

可以用循环达成。

首先需要观察一下颜色,寻找一些规律。



不难发现,当 红=255,蓝=0时,绿的值越大颜色就会变浅。

于是我们可以这样:

for i in 0..@w-2
  self.contents.fill_rect(64+i,4,1,24,Color.new(255,i*2,0))
end

至于for循环怎么用,这里就不多说了。

每次绘制一列像素,因为i的值在变化,所以颜色和坐标也会相应的改变。

完成的脚本:

  1. class Window_MapStatus < Window_Base
  2.   # 开始定义类
  3.   attr_accessor :hp
  4.   # 使用attr_accessor来造一个方法,可读可写。
  5.   def initialize
  6.     # 开始定义方法
  7.     super(0,0,200,150)
  8.     # 继承父类
  9.     self.contents = Bitmap.new(width - 32, height - 32)
  10.     # 生成Bitmap
  11.     @hp = 0
  12.     # 初始化hp为零。
  13.     refresh
  14.     # 调用refresh方法,刷新
  15.   end
  16.   # 结束方法定义
  17.   def refresh
  18.     # 定义refresh方法
  19.     if @hp != $game_party.actors[0].hp
  20.       # 条件分歧,当@hp不等于队伍中一号角色的hp时
  21.       @hp = $game_party.actors[0].hp
  22.       # @hp赋值为队伍一号角色的hp
  23.       self.contents.clear
  24.       # 清空内容
  25.       self.contents.draw_text(0,0,60,32,"HP")
  26.       # 描绘文字(draw_text使用方法:draw_text(x坐标,y坐标,矩形宽度,矩形高度,字符串内容))
  27.       # 所谓矩形就是描绘这些文字的空间,空间不够就会缩小文字
  28.       self.contents.fill_rect(60,0,100,32,Color.new(255,255,255))
  29.       # 填充矩形
  30.       # 这句就是描绘一个白色的矩形,作为值槽的背景色。
  31.       # 属性为x,y,width,height,颜色。
  32.       # 颜色是Color类,颜色要是Color类的实例,并给出颜色属性。
  33.       # 颜色属性就是红,绿,蓝,透明度。
  34.       [url=home.php?mod=space&uid=133944]@w[/url] = 92 * @hp / $game_party.actors[0].maxhp
  35.       # 定义血槽的宽度
  36.       #self.contents.fill_rect(64,4,@w,24,Color.new(180,0,0,180))
  37.       # 描绘血槽      
  38.       for i in 0..@w-2
  39.         # 循环开始
  40.         self.contents.fill_rect(64+i,4,1,24,Color.new(255,i*2,0))
  41.         # 描绘渐变血槽
  42.       end
  43.       #循环结束
  44.     end  
  45.     # if语句结束
  46.   end
  47.   # 方法定义结束
  48. end
  49. # 类定义结束
复制代码
作业(真的有人看完吗):自制sp槽,也是用渐变绘制。

@protosssonny  ,范例我准备写完后提供。
作者: heiwang1997    时间: 2014-4-6 13:09
交作业啦!

还有脚本:
先@w
RUBY 代码复制
  1. class Window_MapStatus < Window_Base
  2.   def initialize
  3.     super(0,0,200,120)
  4.     self.contents = Bitmap.new(width - 32, height - 32)
  5.     @hp = 0
  6.     @sp = 0
  7.     refresh
  8.   end
  9.   def refresh
  10.     if @hp != $game_party.actors[0].hp or @sp != $game_party.actors[0].sp
  11.       @hp = $game_party.actors[0].hp
  12.       @sp = $game_party.actors[0].sp
  13.       self.contents.clear
  14.       self.contents.draw_text(0,0,60,32,"HP")
  15.       self.contents.draw_text(0,52,60,32,"SP")
  16.       self.contents.fill_rect(60,0,100,32,Color.new(255,255,255))
  17.       self.contents.fill_rect(60,52,100,32,Color.new(255,255,255))
  18.       [url=home.php?mod=space&uid=133944]@w[/url] = 96 * @hp / $game_party.actors[0].maxhp  
  19.       for i in 0..@w-5
  20.         self.contents.fill_rect(64+i,4,1,24,Color.new(255,i*2,0))
  21.       end
  22.       @w = 96 * @sp / $game_party.actors[0].maxsp  
  23.       for i in 0..@w-5
  24.         self.contents.fill_rect(64+i,56,1,24,Color.new(0,i*2,255))
  25.       end
  26.     end  
  27.   end
  28.   def update
  29.     super
  30.     refresh
  31.   end
  32. end
  
作者: 天地有正气    时间: 2014-4-7 14:46
本帖最后由 天地有正气 于 2014-4-7 20:00 编辑

现在我们写的血条脚本,只能通过地图上的事件来显示。而且血条会把菜单什么的都遮住,很不爽。

我将告诉大家怎么来把血条的生成,刷新都写在脚本里。

首先介绍alias的用法。

alias的用处是把脚本降低冲突率,完全“插件”化。

比如说,小周定义了一个类:

class XiaoZhou
  def damage
      $hp -= 100
  end
end

而P叔(@protosssonny )也定义了一个类,是

class XiaoZhou
  def damage
    p "你受伤了!"
  end
end

但是两个定义冲突了,P叔把小周的定义的方法damage重定义了一遍,于是小周就不会-hp了。

聪明的P叔想了一个办法,他用alias避免了冲突:

class XiaoZhou
  alias xz_damage damage
  def damage
    p "你受伤了!"
    xz_damage
  end
end

当使用alias后,xz_damage 相当于完全复制了damage,然后P叔重新定义damage,之后在调用xz_damage,这样就避免了冲突。

把窗口的生成、释放、刷新写在Scene_Map里,如果不用alias,就要重新复制一遍main、update这两个方法,麻烦不说,还有很大的冲突率。

于是我们要用alias。
  1. class Scene_Map
  2.   alias old_main main
  3.   def main
  4.     @map_status = Window_MapStatus.new
  5.     old_main
  6.     @map_status.dispose
  7.   end
  8.   alias old_update update
  9.   def update
  10.     old_update
  11.     @map_status.refresh
  12.   end  
  13. end  
复制代码
此时就可以删掉那个事件了!
作者: RyanBern    时间: 2014-4-8 14:26
提供几个建议:

1.没有必要将hp设置为attr_accessor,因为在这里你不希望外界用圆点运算符"."对实例变量进行操作,而且整个过程中也没有用到$w.hp这样的语句,应该删去。
2.没有必要将宽度w设置为实例变量,在这里设置一个局部临时变量w就可以了。
3.建议刷新的内容全部放到update中去,refresh只是单纯的描绘作用,因为面向对象编程讲究看标识符知其含义,因此在地图用update不变,重写MapStatus的update方法,这是写脚本的一个好习惯。

另外提醒一下3L的朋友,Hp和Sp有所不同,最大Sp允许取0值,因此计算宽度的时候,如果遇到无Sp的角色会发生除0错误,在这里改一下以增强代码安全性。
作者: fux2    时间: 2014-4-14 08:15
本帖最后由 fux2 于 2014-4-14 08:19 编辑

如果刷新放在事件里面过了地图这个窗口不是泄露了么,win7下停止工作什么的。
另外每帧refresh你在差点的机器上就能发现很明显的卡顿,正确做法是记录需要描绘的数值,发生变动才描绘。
当然你这里的refresh相当于把update和refresh写到一起了,
refresh的作用如字面一样是刷新,重新描绘,update是用于检查更新。
以下给出一个范例。

地图血条.rar (187.65 KB, 下载次数: 59)
  1. # 加入刷新部分,可以自己换位置
  2. class Scene_Map
  3.   alias _fux_main main
  4.   def main
  5.     @state_window = Window_MapState.new
  6.     _fux_main
  7.     @state_window.dispose
  8.   end
  9.   alias _fux_update update
  10.   def update
  11.     @state_window.update
  12.     _fux_update
  13.   end
  14. end
  15. #渐变描绘
  16. class Bitmap
  17.   def gradient_fill_rect(x,y,w,h,color_pack)
  18.     color1,color2 = *color_pack
  19.     tempcolor = [ color2.red-color1.red,
  20.                   color2.green-color1.green,
  21.                   color2.blue-color1.blue,
  22.                   color2.alpha-color1.alpha ]
  23.     h.times do |i|
  24.       this_color = Color.new(color1.red+tempcolor[0]*i/(h-1),
  25.                              color1.green+tempcolor[1]*i/(h-1),
  26.                              color1.blue+tempcolor[2]*i/(h-1),
  27.                              color1.alpha+tempcolor[3]*i/(h-1))
  28.       self.fill_rect(x,y+i,w,1,this_color)
  29.     end
  30.   end
  31. end

  32. # 正片
  33. class Window_MapState < Window_Base
  34.   # 初始化
  35.   def initialize
  36.     super(0,0,224,72)
  37.     self.contents = Bitmap.new(self.width-32,self.height-32)
  38.     self.contents.font.size = 14
  39.     self.opacity = 180
  40.     @remmer_symbol = [:hp,:maxhp,:sp,:maxsp]
  41.     @remmer_value =  [-1]*@remmer_symbol.size
  42.     @color_list = [ [[Color.new(0xff,0x33,0x33),Color.new(0x66,0,0)],
  43.                       Color.new(0,0,0),Color.new(255,255,255)],
  44.                     [[Color.new(0x33,0x99,0xff),Color.new(0x00,0x00,0x33)],
  45.                       Color.new(0,0,0),Color.new(255,255,255)] ]
  46.     update
  47.   end
  48.   def refresh(hp,maxhp,sp,maxsp)
  49.     self.contents.clear
  50.     x,y,w,h = 0,1,40,14
  51.     self.contents.draw_text(x,y,w,h,"HP:")
  52.     w,h = 142,14
  53.     self.contents.fill_rect(x+39,y-1,w+2,h+2,@color_list[0][2])
  54.     self.contents.fill_rect(x+40,y,w,h,@color_list[0][1])
  55.     rw = (w-2)*hp/maxhp
  56.     self.contents.gradient_fill_rect(x+41,y+1,rw,h-2,@color_list[0][0])
  57.     y,w,h = 25,40,14
  58.     self.contents.draw_text(x,y,w,h,"SP:")
  59.     w,h = 142,14
  60.     self.contents.fill_rect(x+39,y-1,w+2,h+2,@color_list[1][2])
  61.     self.contents.fill_rect(x+40,y,w,h,@color_list[1][1])
  62.     rw = maxsp > 0 ? (w-2)*sp/maxsp : 0
  63.     self.contents.gradient_fill_rect(x+41,y+1,rw,h-2,@color_list[1][0])
  64.   end
  65.   def update
  66.     pointer = $game_party.actors[0]
  67.     return unless pointer
  68.     need_refresh = false
  69.     @remmer_symbol.each_with_index do |s,i|
  70.       now_value = pointer.method(s).call
  71.       if now_value != @remmer_value[i]
  72.         @remmer_value[i] = now_value
  73.         need_refresh = true
  74.       end
  75.     end
  76.     refresh(*@remmer_value) if need_refresh
  77.   end
  78.   def dispose
  79.     @color_list = nil
  80.     @remmer_symbol = nil
  81.     @remmer_value = nil
  82.     super
  83.   end
  84. end
复制代码

作者: 化螺耕    时间: 2014-4-14 13:47
好东西啊,果断顶,对于我们新手来说真是学习的好帖子,支持楼主
作者: myownroc    时间: 2014-4-18 01:00
窗口的生成与刷新个人认为应该在Scene_Map中完成,而不是通过事件
作者: cxyhn    时间: 2014-5-3 13:29
刷新事件肯定是要放到脚本里的没错,用事件会太麻烦。
不过LZ小周教的还是挺好的,感觉学到不少。
有空的话继续更新不要放弃啊。




欢迎光临 Project1 (https://rpg.blue/) Powered by Discuz! X3.1