设为首页收藏本站|繁體中文

Project1

 找回密码
 注册会员
搜索
楼主: 八云紫
打印 上一主题 下一主题

[RMVX发布] 新手教程--从0开始学RGSS2(2013-09-21 修复索引地址)

  [复制链接]

Lv1.梦旅人

梦石
0
星屑
50
在线时间
28 小时
注册时间
2011-1-12
帖子
42
1
发表于 2011-3-20 20:22:25 | 显示全部楼层
本帖最后由 爱丽丝·玛格特罗依德 于 2011-3-20 20:23 编辑

Sprite 和 Spriteset 类集


Sprite_XXX
       Sprite 的本意是精灵的意思, 也可以理解成显示图片的相框. 那么 Sprite_XXX 的类也就是用来显示而定义使用的. 也就是说, VX 的全部显示画面都是由这个类集来完成.

       1. Sprite_Base
       所有 Sprite_XXX 类的父类. Sprite_Base 继承于 Sprite . 从这里可以看出 Sprite_Base 和其子类都是用来显示的.
       查看 Sprite_Base 的定义可以看到一个陌生的变量定义. @@XXX 变量.
           @@XXX
           @@ 开头的变量叫做 类变量(其实我更喜欢叫 静态变量). 这类的变量有个特点. 也就是说 类变量是这个类的实例都共同拥有以及共享的变量.
       例子:

  1. class A
  2.   attr_accessor :b
  3.   @@a = 1
  4.   def initialize(n)
  5.     @b = n
  6.   end
  7.   def a
  8.     return @@a
  9.   end
  10.   def a=(n)
  11.     @@a = n
  12.   end
  13. end

  14. a1 = A.new("a1")
  15. a2 = A.new("a2")

  16. p "a1.a = #{a1.a}"   #=> a1.a = 1
  17. p "a1.b = #{a1.b}"  #=> a1.b = a1

  18. a2.a = "a3"
  19. a2.b = "a3"

  20. p "a1.a = #{a1.a}"  #=> a1.a = a3
  21. p "a1.b = #{a1.b}" #=> a1.b = a1
复制代码
从例子可以看出 @@ 变量的特点的说. 我们从 a2 那里修改了 @@a 的值, 都是 a1 中的值也发生了改变.

      回到 Sprite_Base .  Sprite_Base 最主要的作用是 显示动画.

      2. Sprite_Character
      用于显示 行走图 的精灵类. 这里包括所有的行走图, 心情. 如果我们把某事件的行走图改成使用图块来显示的话, 显示部分也是这个类的创建的.

      3. Sprite_Battler
      显示 战斗者 图片的精灵类. 当然, 在默认的脚本的前提下, 这个类是专门给敌人的战斗图准备的. 因为我方是没有战斗图的. 这个战斗类型参考 DQ1 的模式.

      4. Sprite_Picture
      显示图片的精灵类. 这里的图片, 包含我们使用事件 "显示图片" 里使用到的所有图片. 包括地图上显示的 和 战斗中显示的.

      5. Sprite_Timer
      显示计时器效果的类. 我们使用的显示计时器, 是由这个类来管理的.

Spriteset_XXX
      Spriteset , 可以理解成用于管理 Sprite_XXX 的管理类. 只有三个.

      1. Spriteset_Weather
      管理天气的类.  默认一共有三种 雨、风、雪 .

      2. Spriteset_Map
      管理地图显示的类.  这个类是个关键的说. 这里具体说明下.
      1) 显示端口
      Spriteset_Map 内部生成了三个 显示端口(Viewport), 分别是 viewport1, viewport2, viewport3.
          ☆ viewport1: 限定 地图图块(默认z), 远景图(z = -100), 角色行走图, 以及 飞行船的地面影子
          ☆ viewport2: 限定 天气, 地图上显示的图片, 以及 计时器的显示 其 Z 值等于 50, 也就是里面的显示的内容总是比 viewport1 的显示内容靠前. 这就是为什么, 显示的图片总是可以遮挡住玩家, 但是无论你怎么修改图片的 Z 都是没有作用的.
          ☆ viewport3: 限定 显示的亮度. 用于修改整体的亮度等问题.
      2) 地图图块
      所有的 VX 的地图图块显示都是这里定义的, 这个可以查看 create_tilemap 这个方法. 如果想修改图块的话, 可以从这里入手.

      3. Spriteset_Battle
      管理战斗画面的类. 这里也有三个 Viewport:
          ☆ viewport1: 限定 战斗背景, 战场活动块(也就是背景那个黑色的椭圆部分), 敌人的战斗图, 我方角色的战斗图(这里默认的脚本没有调用, 仅仅给我们扩展使用的)
          ☆ viewport2: 限定 天气, 战斗中显示的图片, 以及 计时器的显示 其 Z 值等于 50, 也就是里面的显示的内容总是比 viewport1 的显示内容靠前. 这就是为什么, 显示的图片总是可以遮挡住玩家, 但是无论你怎么修改图片的 Z 都是没有作用的.
          ☆ viewport3: 限定 显示的亮度. 用于修改整体的亮度等问题.

剩下的剩下, 大家自己去看看吧.(其实是咱想偷懒)
回复 支持 反对

使用道具 举报

Lv1.梦旅人

梦石
0
星屑
50
在线时间
28 小时
注册时间
2011-1-12
帖子
42
2
发表于 2011-3-23 23:13:14 | 显示全部楼层
本帖最后由 爱丽丝·玛格特罗依德 于 2011-3-24 20:12 编辑

自定义脚本


       如果把 VX 原来自带的脚本称作默认脚本的话, 那么游戏制作者(不知道能不能叫做 RMer) 为了实现默认脚本没有实现的功能而写了一个自定义的脚本, 这个脚本个人命名为自定义脚本 or 外带脚本 .

基本设计原则
      由于 Ruby 本身就是 OO(面向对象, Object Oriented) 编程语言, 理所应当的由 Ruby 引申而来的 RGSS 与 RGSS2 也同样是面向对象的. 所以, 按照软件工程的设计理念来说, 基本的设计原则有以下几个需要注意:
      ★ 开关原则(The Open-Closed Principle, OCP)
      "模块应该对外延具有开放性, 对修改具有封闭性".
      也就是说, 设计者如果需要设计一个遵守 OCP原则 的构件的话, 就需要采用一种无需对构件自身内部做修改就可以进行扩展的方法来说明构件.
      举个例子: Array 类有一个遍历的方法 each . each 在设计的时候, 没有使用具体的方法构件. 因为 each 所需要的功能是不可预见的. 我们可以使用 each 来显示, 也可以来增加数组内的数值.  
     其实使用 Proc 来实现也有一点 OCP 的味道:
  1. def show(num, proc)
  2.   proc.call(num)
  3. end

  4. show(1, Proc.new{|n| p "n = #{n}"})
  5. show(1, Proc.new{|a| p "a = #{a + 2}"})
复制代码
这里没有修改 show 内部的代码(虽然很简单), 但是从参数上实现了外部的修改.

      ★ Liskov原则(Liskov Substitution Principle, LSP)
      "子类可以替换它们的基类".
      也就是说, 在重要功能的实现上, 子类需要按照基类的设计原则来实现.
      例如我们写一个 场景类(Scene), 除了需要继承与 Scene_Base 这个基类以外, 我们还需要保证里面重要的三个方法, start, update, terminate 或多或少的需要实现. 而不是自己自定义一个 myStart 来替换 start 方法.

      ★ 接口分离原则(Interface Segregation Principle, ISP)
      "多个用户在用接口比一个通用接口要好".
      自定义脚本的在接口的设计上需要考虑到不同的使用者. 游戏制作使用者 或者 玩家. 两者提供的接口应该是一样的. 至少在脚本数据的操作权限什么的处理上是不一样的.
      例如默认的脚本提供了一个 Debug 的功能. 这个功能就是专门给制作者的(被破解后就另当别论了), 玩家有玩发布后版本的游戏是不能使用 F9 来调用 Debug 功能.

      ★ 共同封装原则(Common Closure Principle, CCP)
      "一同变更的类应该合在一起".
      类本身就是表示对现实生活中一类具有共同特征的事物的抽象. 比如人 这个类. 你可以扩充一些方法到 人这个类 里去, 比如 吃饭, 睡觉, 上6R. 但是不能将 飞翔 这个能力附加给人, 因为现实里人是不会飞的.
      就是说, Game 类集 是用来处理 游戏数据的, 但是最好不要在 Game 类集里添加 Sprite 类集 的内容. 因为它本身在设计的时候是不处理这些的(当然有特殊原因的除外).

      ★ 发布复用等价原则(Release Reuse Equivalency Principle, REP)
      "复用粒度就是发布粒度".
      对于脚本使用者来说, 能看到一个号的脚本乃至系统持续性的更新是很高兴的. 但是他们又希望脚本作者在更新新版本的脚本的时候, 又不会对使用旧版本的他们产生影响. 也就是说, 旧版本的使用方法在新版本里是不会改变的.
     于是乎, 这个原则就要求脚本作者在更新新版本的脚本的时候, 能够兼容就版本. 让使用旧版本的使用者不改变使用方法二能直接使用新版本的脚本(相对于旧版本的功能).
     从软件工程的角度来说, 发布的时候, 脚本里的所有类, 模块等部分都是要求可以复用的, 也就是在后续的版本都会使用到.

      ★ 依赖倒置原则(Dependency Inversion Principle, DIP)
      "依赖于抽象, 而非具体实现".
      这个原则其实包换了两个含义:
          高层模块不应该依赖于低层模块,二者都应该依赖于抽象
          抽象不应该依赖于细节,细节应该依赖于抽象
      如果高层模块越依赖低层模块的话, 那么就意味着高层模块的复用性就越低. 这也是为什么 ASM(汇编) 编写的程序的代码可移植性很低. 因为它依赖于底层硬件设备.
      最好的解决方法就是在这两者之间添加一个中间抽象层. 这种做法的一个典型的例子就是 微软的 DirectX. DirectX 中间就有一个硬件抽象层(HAL) 利用这个抽象层兼容硬件和软件模块. 这样, 如果硬件模块需要修改, 那么只要让修改后的硬件模块兼容 HAL 就可以了, 这样就可以完全不需要修改软件模块.

      ★ 共同复用原则(Common Reuse Principle, CRP)
      "不能一起复用的类不能被分到一起".
      这个其实很好理解. 实现某个功能的脚本不能将和这个功能无关的类, 构件封装到这个脚本里去. 因为这个无关的部分可能在其他地方被其他脚本所使用到. 如果这部分被修改的话, 那么就很容易出现问题.
      这个也可以理解成, 脚本冲突.
      产生的原因, 大部分是 名字冲突. 包括:
      ◇ 变量名冲突 : 两脚本使用了一个很通常化的变量名字, RGSS2 理解成两个变量时一样的. 从而导致冲突.
      ◇ 方法名冲突 : 这个可以分成两类, 一个是方法名冲突: 两脚本修改或者使用了相同的方法名, 但是两个方法的功能是不一样的, 那么, 后定义的方法将覆盖掉之前的方法, 从而导致冲突. 另一个是使用 alias 定义方法别名的时候, 使用了相同的旧方法名, 这个就会出现很典型的 堆载过深(SystemStackError) 错误.

点评

那个抽象层 我想到了JVM  发表于 2011-5-2 08:03
再来膜拜下= =  发表于 2011-5-2 07:59
好吧, 打字太快没注意到. 昨天太晚更新了, 就只更新了一半去睡觉了~~~ > <  发表于 2011-3-24 19:24
The open-closed Principle 这里有 typo >v< 接下来是打算把 SOLID 补完吧?O、L、I 都有了,还有 SRP(单一责任原则)、DIP(依赖倒置原则)……  发表于 2011-3-24 05:02

评分

参与人数 1星屑 +176 收起 理由
DeathKing + 176 那几个原则是软件工程?很有启发的说。.

查看全部评分

回复 支持 反对

使用道具 举报

Lv1.梦旅人

梦石
0
星屑
50
在线时间
28 小时
注册时间
2011-1-12
帖子
42
3
发表于 2011-5-12 15:19:00 | 显示全部楼层
本帖最后由 爱丽丝·玛格特罗依德 于 2011-5-12 15:24 编辑

API and Win32API


1. 什么是 API
       刚刚接触到 API 的人都会问这个问题, "什么是 API? 有什么用?"
       API 的全名是 "Application Programming Interface" , 也就是 "应用程序编程接口" . 这么说, 其实很笼统. 具体点的, 就是说, API 是预先就定义好的一组函数,  我们只要知道这个函数的参数, 就可以来调用某个 API 来实现某些功能. 至于 API 函数是怎么实现的? 我们不管它, 我们只需要知道 这个函数 做什么用的就可以了. 要注意的是: API 的函数都是存在于 dll 中的.

       API 有什么用? 我们知道, RGSS2 其实就是 Ruby 语言, 但是 RGSS2 由于 EB 在编写的时候, 无意或者故意性的屏蔽了某些方法, 关键字等, 使得我们想扩展 VX 的功能有点障碍. 但是, 有了 API 这个接口, 也就不怕了. 举个例子, 网络游戏通信的核心就是 C++ 的 Socket 库, 它本身就是一个 Window API . 并且, 默认的 VX 没有提供这个功能, 那么我们就只能使用这个 API 函数库来扩展.

      API 的分类. 我个人喜欢把 API 分成两类, 这个仅仅只是我个人的观点. 一类是 Window API . 它有微软提供能我们的. 用来为编程者的程序实现或者添加某些功能. 还有一类就是 自定义的 API . API 的实现由程序员编写测试, 然后封装到 dll 里, 给其他的程序使用.

2. Win32API
      幸运的是, EB 没有屏蔽一个关键的类 Win32API . 这个类就是负责 dll 文件的加载和 API 函数的调用. 来看下基本语法:

       变量 = Win32API.new("dll文件名", "API函数名", "API函数的参数类型", "API函数的返回类型")

      ★ dll文件名: 指明某个包含后续需要调用的 API 的 dll 文件名字, 比如常用的 user32.dll 等;
      ★ API函数名: 指明这个 dll 里 导出的 API 函数的名字;
      ★ API函数的参数类型: 查看手册可以查到 API 的参数类型, 选择有下面4个:
             ☆ "p"          指针
             ☆ "n" or "l" long
             ☆ "i"           int
             ☆ "v"         void
      ★ API函数的返回类型: 查看手册可以查到 API 的返回类型, 选择和上面的一样.

3. 如何使用 APi
      我们来举一个简单的例子, API 函数是  MessageBox .
      这个函数的功能是 显示一个 消息提示框 . 参数和返回值可以查找 APi手册或者是 百度百科. 引用下 百度百科:

函数原型 : int MessageBox(HWND hWnd,LPCTSTR lpText,LPCTSTR lpCaption,UINT UType)
函数速查: Windows:3.1及以上版本:Windows:95及以上版本;Windows:1.0及以上版本;头文件:Winuser.h;库文件:USer32.lib;URicode:在Windows NT上实现为Unicode和ANSI两种版本。


      从以上的信息, 我们可以得到:
      ★ dll文件名: user32.dll , 这个可以看到 库文件:USer32.lib , 这个 lib 的名字就可以对应一个 dll .
      ★ API函数名: MessgaeBox , 就是我们需要的, 注意大小写.
      ★ API函数的参数类型: 这个需要一点点 C++ 知识. 没有的话, 可以参考 VB 的 API 手册, 都是一样的.
可以知道 MessageBox 的参数有三个,
         HWND hWnd : 类型是 HWND, 可以知道, 不是 v 不是 i 也不是 l (这里其实就是 l . ), 那么就只能是 p ;
         LPCTSTR lpText,LPCTSTR lpCaption; 这两个类似的判断和之前的一样, 只能是 p , 表示字符串.
         UINT UType: 看不出来是啥, 于是 继续百度 define UINT , 于是可以查看 #define uint unsigned int , 于是 知道是 i (出现了 int), 至于为什么这个查找, 参考 C++ 的 #define .
      ★ API函数的返回类型: 就是第一个单词, int 于是就是 i .
      注意一下: 如果某个 API 没有参数的, 参数3 可以简写成 nil 或者 "v". 返回值是 void 的话, 参数4 也可以简写成 nil , 或者 "v"


    然后来创建一个 WIn32API 类的实例:
    messagebox = Win32API.new("user32.dll", "MessageBox", "pppi", "i")
    这里注意的是, 参数3 有多个参数的话, 可以写成 字符串 或者  %w( ), 其实也是字符串

    创建之后就可以来使用了, 使用的方法是 call 方法. call 方法的参数, 参考我们之前创建的 参数3 或者函数原型.

    messagebox.call(
        0,                    # 第一个参数是 HWND, 窗口句柄, 可以不去理会他, 填写 0 即可.
        "Hello World",  # 第二个参数是一个字符串, 就是 提示框需要显示的内容
        "Message",      # 第三个参数还是一个字符串, 表示提示框的标题
        0)                   # 最后一个参数是 提示框的类型, 不常用的, 可以直接写  0.

    这样就可以实现调用一个 API 了
      
回复 支持 反对

使用道具 举报

Lv1.梦旅人

梦石
0
星屑
50
在线时间
28 小时
注册时间
2011-1-12
帖子
42
4
发表于 2011-5-12 22:10:25 | 显示全部楼层
回复 苏小脉 的帖子

網絡協議 也算是 API 这个真没想到, 不过算是接口的话, 可以理解.

教程对象是 Ruby , 所以就使用了 狭隘的定义 .  > <
回复 支持 反对

使用道具 举报

Lv1.梦旅人

梦石
0
星屑
50
在线时间
28 小时
注册时间
2011-1-12
帖子
42
5
发表于 2011-5-13 12:37:04 | 显示全部楼层
回复 link006007 的帖子

坏处啥的, 真还没有说. 抱歉.

对于 1), 咱是使用 C 来写 dll 的, 所以对 其他语言 不太熟悉. 本身使用了 API , 就会对系统产生依赖性是对的. 那个无能为力.

2) 托管啥的, 真心了解不多 = =

3) WIndow API 可以使用 GetLastError 来获取错误. Window API 本身出现 内存冲突的几率比自己写的 dll 要少的多. 如果是自己写 dll 的话, 这些错误想改还不是难事. 至少咱是经常出现这堆错误.

4) 事物都有两面性.
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册会员

本版积分规则

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

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

GMT+8, 2024-5-21 00:46

Powered by Discuz! X3.1

© 2001-2013 Comsenz Inc.

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