Project1

标题: 日历的RGSS3入门教程 - 精灵与位图Lv3 精灵更新与状态机 [打印本页]

作者: KB.Driver    时间: 2019-5-24 17:24
标题: 日历的RGSS3入门教程 - 精灵与位图Lv3 精灵更新与状态机
本帖最后由 KB.Driver 于 2019-5-24 17:26 编辑

上一课: 日历的RGSS3入门教程 - 精灵与位图Lv2 初识位图与矩形

精灵与位图Lv3 精灵更新与状态机
课程难度:★★★☆☆ 战士
涉及的RGSS知识:
类:Sprite,Bitmap,Rect
涉及的Ruby知识:数组,常量,状态机


作者: KB.Driver    时间: 2019-5-24 17:26
本帖最后由 KB.Driver 于 2019-5-24 17:27 编辑

3.1 动画原理
在之前的教程中,我们所学的都是在游戏画面上显示静态精灵。
从关于精灵的脚本编写完成开始,游戏画面就不会再有任何变化。
这一次,我们来学习怎样让精灵产生动画的效果。
在学习写这块脚本之前,我先啰嗦一下,好让大家更容易理解我们后面所做的工作。

要想让精灵动起来,就不得不说一下动画的原理。
人类发明摄影技术后,动画是这样运作的。
在工作台上将动画的一幅幅画面拍下来,再通过放映机将动画胶卷上一连串的画面依次投影到荧屏上。

当然,最简单的动画可以是在小册子的每一页画一个相近的图案,快速翻动小册子就能在眼前呈现出一副连贯的动画。
这时候,小册子就构成了胶卷,而我们翻页的手充当了放映机,我们的眼睛就是“荧屏”。
为了制作动画,我们必须要准备小册子上每一页的内容。
小册子上每一页的画面,我们称之为“一帧的画面”或简称一帧。
动画的制作,就是在动画的持续时长里,决定好每帧的画面和这些帧要如何放映。

于是,我们可以简单地抽象一下:动画需要胶卷、放映机和荧屏,而我们能控制的只是前两个。
先说说胶卷吧,胶卷就是一幅幅完整的静态画面。
没有胶卷,放映机就没有东西可放。你将空白的小册子正着翻、反着翻,都不会呈现任何内容。
再说说放映机。放映机要做的事情只有一个,就是决定胶卷上的每一帧要如何投影到荧屏上形成最终画面。
简单来说,放映机决定胶卷的行为。

那么,动画的原理要怎么用在我们编程里呢?
游戏的画面就是动画里的荧屏,但是这里与现实中的动画不同的地方在于,我们并没有现成的放映机与动画胶卷。
于是,我们需要通过编程来实现这两个功能。




作者: KB.Driver    时间: 2019-5-24 17:27
本帖最后由 KB.Driver 于 2019-5-24 17:30 编辑

3.2 切换位图的动画

我们先来做一个简单的画面切换的动画,结合代码与演示效果来讲解这其中的胶卷与放映机。

# 背景精灵
back_sprite = Sprite.new
# 用于存储所用位图的数组
bitmaps = []
# 将1上至3代入循环中读取位图
# 因为3个位图的文件名只是最后的数字不同
1.upto(3) do |i|
  bitmaps.push(Bitmap.new("Graphics/Parallaxes/Mountains#{i}"))
end

index = 0 # 初始化数组索引

# 循环更新画面
loop do
  # 将数组对应索引的位图设置为精灵的位图
  back_sprite.bitmap = bitmaps[index]
  # 改变索引,以便在下一次设置时改变位图
  index = (index + 1) % bitmaps.size
  # 画面的更新
  Graphics.update
end


先输入以上代码,然后让我们运行工程查看结果:
有点伤眼睛,折叠一下


作者: KB.Driver    时间: 2019-5-24 17:30
本帖最后由 KB.Driver 于 2019-6-20 08:29 编辑

相比前两课,这次实现了一个动态的画面。下面我们来解析一下这段代码。

bitmaps = []

这一行中,我们将一个空的数组(Array)赋值给了bitmaps。我们可以把数组(Array)想象成一个大箩筐,里面可以放很多东西。
在这里通过使用bitmaps这个数组,我们就省去了为每一张位图创建一个位图对象的功夫。
因为数组在编程中使用频繁,我们不需要像之前一样通过Array.new来创建数组对象,而是用一对方括号作为创建数组的快捷方式。

关于数组,我们暂时不进行深入的讲解,只在用到时进行相应的解释。
附带一提,由于数组可以承载多个对象,在为数组命名时建议使用复数形式。
比如承载位图(Bitmap)的数组用bitmap的复数形式bitmaps命名,这样方便阅读和使用。



1.upto(3) do |i|
  bitmaps.push(Bitmap.new("Graphics/Parallaxes/Mountains#{i}"))
end


这一段相比前两课可能有一点复杂。
用简单但不那么准确的话来讲,这里是一个循环语句,1.upto(3)代表从1往上数到3,也就是1,2,3三个数。

do |i|是说分别将1,2,3这三个数看作变量i来执行下面直到end之间的语句即编程中所说的循环体。

中间的语句的意思,我们先看Bitmap.new(“Graphics/Parallaxes/Mountains#{i}”),根据前两课所学,这里通过文件路径生成了一个新的位图对象。
但有一点不同的是,这一次在””这一对双引号中间有一个#{i}。
在字符串中,一个井号加一对大括号的#{}是一个求值运算符,它会把大括号的内容当做一条语句来计算结果,然后把这个结果嵌入到这个字符串中。

还记得前面说分别将1,2,3三个数看作变量i吗?
如果我们不使用#{}运算符将i包裹起来,这里就会三次去寻找名叫”Moutainsi”的文件。
我们真正需要的是三个分别叫Moutains1,Mountains2,Mountains3的文件,所以要将i的值代入到字符串里,而不是让i变成字符”i”。

看懂这一半后,我们来看看另一半:bitmaps.push(…),
这里在我们刚刚生成的bitmaps这个数组对象上调用了push方法,这个方法接收了一个参数,也就是我们前面所生成的位图。

创建了一个空的数组就像拿了一个空的箩筐,我们需要为它添加内容。
在编程中,我们将数组的内容称为元素(element),或者数组的项。
比如,我们说数组的第一个元素或者数组的第一项,指的就是最早加入的内容。
push方法的作用就是往数组添加后面括号里的内容,添加的位置是在数组的末尾。
也就是说,第一次push的内容会成为数组的第一项,而第二次push的就是数组第二项。

结合以上,这一整段的意思就是分3次往bitmaps这个数组里添加3个位图,这些位图分别是Moutains1,2,3。


index = 0 # 初始化数组索引

# 循环更新画面
loop do
  # 将数组对应索引的位图设置为精灵的位图
  back_sprite.bitmap = bitmaps[index]
  # 改变索引,以便在下一次设置时改变位图
  index = (index + 1) % bitmaps.size
  # 画面的更新
  Graphics.update
end


最后,我们要将这一大块合起来说。
还记得第一课我们说rgss_stop相当于loop {Grphics.update}吗?
这里我们不使用rgss_stop,就是因为这次的循环中我们要做的事情不止Graphics.update。

loop do也是循环语句的标志,它会无条件的反复执行后面直到end之间的所有语句。
你可能觉得奇怪,为什么之前是loop{}这样loop后面跟着一个大括号,现在是loop do 然后到end。

loop循环的写法有两种,一种是loop{ 循环体 },另一种是
loop do
  循环体
end

两种都是可以的。

一般情况下,当循环体只占一行时我们用大括号,而循环体不止一行时我们用第二种。

不要忘记,缺了Graphics.update就不会进行画面的更新。
因此在循环的最后,我们写上了这一句作为不写rgss_stop的代替。

循环体外的index = 0将一个整数0赋值给了index变量,index是索引,这里我们用它来代表数组的第几项这个编号。
为什么一开始要用0呢?
计算机是通过二进制也就是0和1来实现的,在早期硬件还不发达的时候,把数字0用来表达数字第一项可以节省内存,于是当时的编程语言比如C语言就是这样设计的。
如今,大部分编程语言继承了这一点,都用0来表示数组的第一项。(但也有例外,比如MATLAB,这里就不详细展开了)



明白index的含义后,让我们来看循环体:

  # 将数组对应索引的位图设置为精灵的位图
  back_sprite.bitmap = bitmaps[index]
  # 改变索引,以便在下一次设置时改变位图
  index = (index + 1) % bitmaps.size


还记得bitmaps是一个数组吗?
在数组后用一对中括号就可以访问数组的元素。bitmaps[0]就是bitmaps的第一项,同理bitmaps[1],bitmaps[2]分别是第二项、第三项。
这里index的初始值为0,因此第一次执行时back_sprite的位图被设置成了bitmaps里众多位图的第一张。

让我们看看下一句。为了让位图产生变化,我们需要在每次执行循环时都让索引index变化。
一种简单的方法就是index = index + 1,这样每次执行循环后index就会加1,对应从bitmaps中取出的位图就会变化。
但是当index变为3时,由于bitmaps里没有下标为3的元素(最后一项的下标为2),程序会出现数组下标出界的错误。

为了防止这种情况出现,我们需要在index超过2的时候让它回到开始的0。
在这里,%是取模运算符,用通俗的话来讲就是求余数。
比如0%3就是0除以3的余数,自然还是0。同理,1%3=1,2%3=2,而3%3由于刚好除尽没有余数,结果再次回到0。
数组bitmaps的size方法可以返回数组元素的个数,这个数字在这里就是3。

因此,这整句可以理解为:先将index增加1,然后将它对3取余数,这样可以避免index的大小超过2。
最后用计算后的index代替原来的index,执行下一次循环。



作者: KB.Driver    时间: 2019-5-24 17:35
本帖最后由 KB.Driver 于 2019-5-24 17:37 编辑

3.3 切换位置的动画
在第一节课里,我们已经认识了Sprite精灵对象的x与y属性,它们共同决定精灵在画面上显示的位置。
如果我们在图像更新的循环中改变精灵的x与y,就能形成切换位置的动画。

根据这个思路,我们需要重新编写一段代码。
为了消除原来代码的影响,让我们先选中原来的代码,右键选择”批量注释”或用快捷键Ctrl+Q来将原来的代码注释掉。
之后,在左侧插入一栏新的脚本,输入以下代码:

# 背景精灵
back_sprite = Sprite.new
back_bitmap =Bitmap.new("Graphics/Parallaxes/Mountains1")
back_sprite.bitmap = back_bitmap

# 妖精精灵
fairy_sprite = Sprite.new
fairy_bitmap =Bitmap.new("Graphics/Battlers/Fairy")
fairy_sprite.bitmap = fairy_bitmap

# 循环更新画面
loop do
  # 每次循环都让妖精精灵的x坐标增加1
  fairy_sprite.x +=1
  # 画面的更新
  Graphics.update
end

然后运行一下程序看看结果:



作者: KB.Driver    时间: 2019-5-24 17:36
本帖最后由 KB.Driver 于 2019-5-24 17:40 编辑

循环体之前的代码在第一课已经作了解释,我们主要看loop do到后面end之间的内容。

# 循环更新画面
loop do
  # 每次循环都让妖精精灵的x坐标增加1
  fairy_sprite.x += 1
  # 画面的更新
  Graphics.update
end


中间循环的部分去掉Graphics.update,就是fairy_sprite.x+= 1,也就是让妖精精灵的x坐标在原来基础上加1。
从运行结果可以看出,在不断执行加1的过程中,妖精就在画面上从左往右移动了。

然而,当妖精飞出画面右端后,就再也看不到它回来了。
能不能增加一个让妖精飞到画面右侧就自动折返往左飞的动画?

为了达到这一效果,我们想到了条件判断语句。首先让我们修改循环体的内容:

...
loop do
  # 如果妖精精灵未到达画面右端
  if fairy_sprite.x < Graphics.width - fairy_bitmap.width
    fairy_sprite.x += 1 # 则x坐标增加1
  else # 否则
    fairy_sprite.x -= 1 # x坐标减少1
  end

  # 画面的更新
  Graphics.update
end


运行结果:


作者: KB.Driver    时间: 2019-5-24 17:38
本帖最后由 KB.Driver 于 2019-5-24 17:43 编辑

结果是不是和我们想要的不太一样?
仔细观察会发现,在妖精到达画面最右端以后,会往左移动1像素,但是马上又会往右移动1像素,陷入循环往复之中。

为什么会这样呢?让我们看看代码来找原因。

#如果妖精精灵未到达画面右端
  if fairy_sprite.x < Graphics.width -fairy_bitmap.width
    fairy_sprite.x += 1 # 则x坐标增加1
  else # 否则
    fairy_sprite.x -= 1 # x坐标减少1
  end


if…else…end语句是典型的条件判断语句。
它的规则是这样的:如果if后面的表达式的结果为true即真,那么执行if下面的语句;
否则,执行else下面的语句。

这里稍微啰嗦一下,逻辑真的意思大致相当于数学里讨论一个命题是否成立。
比如1 < 2是成立的,那么结果就是true也就是真,而3 < 2不成立,结果就是false也就是假。

这样我们就知道,如果妖精未到达画面右端,那么x坐标增加1。
在动画的一开始,确实是这样没错。但是当妖精到达最右端后,它就进入了else后的语句,x坐标减少了1。
问题在于,当x坐标减少了1,下一次一循环到判断语句时,妖精的x坐标又满足了if中的”未到达画面右端”,因此不会继续执行else中的x坐标减1。

这个问题究竟怎么解决呢?

这里引入我们这一课中非常重要的一个编程知识:状态机。
状态机并不是什么高大上、难理解的东西,要弄清楚状态机我们就先要了解什么是状态。

在前面的例子中,之所以我们无法达到”妖精触碰到右边就一直往左”的效果,
就是因为if条件判断中无法对妖精是”正在从左往右走”还是”正在从右往左走”做出判断,从而使得妖精刚刚往左走了一步,就又满足了向右走的条件。
因此解决问题的关键,就在于引入“妖精的方向”这一状态,并根据这一状态来决定妖精的移动方向而不是像原来一样依赖于坐标。
像这样根据状态间的变化来执行不同代码的结构就叫状态机。

说了这么多,让我们来实际操作一下吧。输入以下代码:

# 规定代表方向的常量
RIGHT = 0
LEFT  = 1
# 规定初始运行方向
direction = RIGHT

# 背景精灵
...
# 循环更新画面
loop do
if direction == RIGHT # 如果方向为右
    fairy_sprite.x+= 1 # 则x坐标增加1
    if fairy_sprite.x >= Graphics.width - fairy_bitmap.width # 到右端
      direction =LEFT  # 方向变为左
    end
  else # 否则 如果方向为左
    fairy_sprite.x-= 1 # x坐标减少1
    if fairy_sprite.x <= 0 # 如果到达左端
      direction =RIGHT  # 方向变为右
    end
  end

  # 画面的更新
  Graphics.update
end


运行结果:


作者: KB.Driver    时间: 2019-5-24 17:39
本帖最后由 KB.Driver 于 2019-5-24 17:46 编辑

这一次,妖精学会了到达边缘就反向,我们所要的效果达到了。
让我们简单分析一下这里的代码吧。

#规定代表方向的常量
RIGHT= 0
LEFT  = 1
#规定初始运行方向
direction= RIGHT


所有的状态机在组成上都需要一个代表状态的变量和一个初始状态。
在这里,我们用direction作为代表状态的变量,而以向右RIGHT作为初始状态。

这里还引入了常量的概念,这里稍作解释。
在ruby中,所有以大写字母开头的都将被视为常量。
与变量相对,常量的值在运行过程中一般不会修改,因此适合用于像本例一样作为状态信息。
使用常量作为状态信息相比于直接使用0或者1的好处在于两点,
一是增加可读性,二是万一常量所代表的值发生变化,可以减少需要修改的代码量。

然后是循环体中的关键部分:

if direction == RIGHT # 如果方向为右
    fairy_sprite.x += 1 # 则x坐标增加1
    if fairy_sprite.x >= Graphics.width -fairy_bitmap.width # 到右端
      direction = LEFT  # 方向变为左
    end
  else # 否则 如果方向为左
    fairy_sprite.x -= 1 # x坐标减少1
    if fairy_sprite.x <= 0 # 如果到达左端
      direction = RIGHT  # 方向变为右
    end
  end


这一段代码相比之前的要长了不少,我们要了解一下缩进的概念。
在这里缩进(Indent)特指代码距离左侧的距离,距离相同的代码处于同一缩进之中。
和注释一样,在ruby中,缩进并不影响程序的执行结果,但良好的缩进有助于提高可读性。
让我们从缩进上来分析这段代码。

处在最外侧的部分是if…else…end,因此总体上这是一个条件判断语句。
让我们看最上面if的条件,==是相等操作符,用于判断其左右两侧的值是否相等。
这里通过判断direction的值与常量RIGHT是否相同,分别执行两种结果。

相同即方向向右时,执行if下方直到else之间的语句:先让妖精向右移动,如果妖精已到达画面右端则改变方向direction。
方向向左时执行else下面直到end的语句:先让妖精向左移动,如果妖精已到达画面左端则改变方向direction。
如何判断精灵在画面位置请参考第一、第二课,这里不赘述。

以上就是本节的内容,有能力的同学可以完成课后练习1,加深对这一节的理解和应用。
作者: KB.Driver    时间: 2019-5-24 17:43
本帖最后由 KB.Driver 于 2019-5-24 17:50 编辑

3.4 切换源矩形的动画
在第二课中,我们了解了矩形的相关知识,并在位图Bitmap类的blt方法中了解了源矩形src_rect的含义。
这一次,让我们做一个切换源矩形的动画。

在实际制作之前,让我们看一张图片。


这是RMVA里的一张行走图素材,我们可以看到每个小人出现了12次,分别是4个方向、每方向三张。
在我们运行游戏时,地图上显示的并不是12个小人的整体,而是同一时间内只显示12张的其中一张。
在朝一个方向前进时,小人只会在该方向的3张位图之间进行切换,从而达到行走的效果。

我们以左上角的白发少年艾里克面朝向右的三张小人为例。
整个三张小人就是动画的完整胶卷,而我们要做的就是把它切成三片,每次只显示其中的一个小人,并且不断地变化。
分析完后,让我们实际操作一下,输入以下代码:

# 规定代表方向的常量
FORWARD = 0
BACK  = 1

# 规定初始运行方向
direction = FORWARD

# 背景精灵
back_sprite = Sprite.new
back_bitmap = Bitmap.new("Graphics/Parallaxes/Mountains1")
back_sprite.bitmap = back_bitmap

# 人物精灵
char_sprite = Sprite.new
char_bitmap = Bitmap.new("Graphics/Characters/Actor4")
char_sprite.bitmap = char_bitmap
char_sprite.y = Graphics.height - 50

# 用于设置源矩阵的参数
x = 0
y = char_bitmap.height / 8 * 2
w = char_bitmap.width / 12
h = char_bitmap.height / 8

# 用于控制源矩阵的取像位置
index = 0

# 循环更新画面
loop do
  # 更新源矩阵
  char_sprite.src_rect.set(x, y, w, h)  
  
  # 改变取像位置
  if direction == FORWARD # 正在向前走
    index += 1
    direction = BACK if index == 2
  else
    index -= 1
    direction = FORWARD if index == 0
  end
  x = index * w
  
  # 改变x坐标
  char_sprite.x += 1
  
  # 画面的更新
  Graphics.update
end


运行结果:


作者: KB.Driver    时间: 2019-5-24 17:47
本帖最后由 KB.Driver 于 2019-5-24 17:52 编辑

让我们分析一下核心代码的作用。先看循环外的设置部分:

# 用于设置源矩阵的参数
x = 0
y = char_bitmap.height / 8 * 2
w = char_bitmap.width / 12
h = char_bitmap.height / 8

# 用于控制源矩阵的取像位置
index = 0


在第二课中我们知道矩形由x,y,width,height四个属性组成,因此这里的x,y,w,h就是用于下面设置源矩阵的四个参数。
我们只要打开RMVA编辑器就会知道,我们要用到的小人就是第三行的第一列至第三列。
以第一帧所需要的源矩形为例,让我们看看下面的图。
整个图片一共有八行,因此第三行的左上点的纵坐标就是位图的高度除以8乘以2(第二行的底部就是第三行的顶部),而横坐标为0。
整张图一共有8行12列,因此源矩形的宽度为位图宽度除以12,源矩形高度为位图高度除以8。



这样是不是可以看懂上面的x,y,w,h的用意了呢?
因为动画中需要让源矩形的x坐标发生变化,从而显示出人物的行走动态,我们用一个变量index记录” 帧的编号”。
下面让我们结合循环体内的代码理解index的作用。

...
  # 更新源矩阵
  char_sprite.src_rect.set(x, y, w, h)  
  
  # 改变取像位置
  if direction == FORWARD
    index += 1
    direction = BACK if index == 2
  else
    index -= 1
    direction = FORWARD if index == 0
  end
x = index * w
...


前面已经讲解了xywh的作用,因此这里调用了Rect类的set方法一次性为角色精灵的源矩阵的所有属性赋值。
值得一提的是下面的条件语句部分,还记得本课第一节所讲的状态机吗?
人物行走图的动画并不是从左到右然后再次回到最左,而是左中右再到中、左这样的变化。
因此,我们需要一个状态来说明index此时应该增加还是减少。

当index为0也就是对应行走图的左时,index应该增加,而增加到index为2也就是行走图的右边那张时,index就应该转为减少了。
补充说明一句,在ruby中,if语句可以像这里一样用类似英语中“倒装句”的格式来书写,
比如direction = BACK if index == 2这句和
if index == 2
  direction = BACK
end

是完全等效的,但是通过这种倒装的写法能够让代码更加简洁。

最后,不要忘记我们操作index的目的是要改变源矩形的x。
因为不同源矩阵之间的区别仅仅是x坐标相差一个人物的宽w, 因此x与index有x = index * w的对应关系。


作者: KB.Driver    时间: 2019-5-24 17:49
本帖最后由 KB.Driver 于 2019-5-24 17:55 编辑

直到这里为止,代码的运行是没有问题的。
但是我们看着画面,就会觉得人物的行走不太自然。
实际上,这是行走图更新速度过快导致的。
我们每1帧就要改变一次人物的行走图,而人眼无法捕捉这么快的变化。

那么,只要让行走图的更新不要那么快就可以了。让我们修改代码:

# 用于控制源矩阵的取像位置
index = 0

# 缓冲,用于防止更新过快
buffer = 10


# 循环更新画面
loop do
if buffer < 10 # 每10帧改变一次行走图
    buffer += 1
  else
    # 更新源矩阵
    char_sprite.src_rect.set(x, y, w, h)  
   
    # 改变取像位置
    if direction == FORWARD
      index += 1
      direction = BACK if index == 2
    else
      index -= 1
      direction = FORWARD if index == 0
    end
    x = index * w
   
    # 清除缓冲
    buffer = 0
  end

  
  # 改变x坐标
  char_sprite.x += 1
  
  # 画面的更新
  Graphics.update
end


运行结果:


在这里,我们引入了一个缓冲变量buffer。
每次更新行走图后,buffer的值会变为0。
而当buffer的值小于10的时候,条件语句会让buffer增加1,而不执行下面的行走图改变。
这样就可以达到每过10帧才更新一次行走图的效果,避免了更新速度太快而肉眼无法跟上的问题。

有能力的同学可以尝试完成课后练习2。注意,本练习有一定的难度和代码工作量。


作者: KB.Driver    时间: 2019-5-24 17:50
本帖最后由 KB.Driver 于 2019-5-24 17:59 编辑

3.5 其他精灵属性的动画演示

除了前面所演示的几种典型的动画,还有大量精灵Sprite类的属性可以加入动画中。
由于这些属性在第一课中已经有所介绍,下面直接以范例的形式给出,不再花大篇幅讲解。
在体会的过程中,可以改变代码中的部分参数来观察动画的变化。
有余力的同学可以完成课后练习3。

1、可见属性变化(闪烁)

back_sprite = Sprite.new
back_bitmap = Bitmap.new("Graphics/Parallaxes/Mountains1")
back_sprite.bitmap = back_bitmap

sprite1 = Sprite.new
sprite1.bitmap = Bitmap.new("Graphics/Battlers/Fanatic")

sprite1_desc = Sprite.new
sprite1_desc.bitmap = Bitmap.new(400, 26)
sprite1_desc.bitmap.draw_text(0,0,400,26,"sprite.visible = !sprite.visible")

# 循环更新画面
loop do
  sprite1.visible = !sprite1.visible
  # 画面的更新
  Graphics.update
end




作者: KB.Driver    时间: 2019-5-24 17:53
本帖最后由 KB.Driver 于 2019-5-24 17:58 编辑

2、放大倍率的变化

back_sprite = Sprite.new
back_bitmap = Bitmap.new("Graphics/Parallaxes/Mountains1")
back_sprite.bitmap = back_bitmap

sprite1 = Sprite.new
sprite1.bitmap = Bitmap.new("Graphics/Battlers/Slime")

sprite1_desc = Sprite.new
sprite1_desc.bitmap = Bitmap.new(300, 26)
sprite1_desc.bitmap.draw_text(0,0,300,26,"sprite1.zoom_x += 0.02")

sprite2 = Sprite.new
sprite2.bitmap = Bitmap.new("Graphics/Battlers/Slime")
sprite2.x = 280

sprite2_desc = Sprite.new
sprite2_desc.bitmap = Bitmap.new(300, 26)
sprite2_desc.bitmap.draw_text(0,0,300,26,"sprite2.zoom_y += 0.02")
sprite2_desc.x = 280

frames = 0
speed = 0.02
rate = 1

# 循环更新画面
loop do
  if frames > 29
    rate *= -1
    frames = 0
  end
  
  sprite1.zoom_x += speed * rate
  sprite2.zoom_y += speed * rate
  frames += 1
  
  # 画面的更新
  Graphics.update
end



作者: KB.Driver    时间: 2019-5-24 17:55
本帖最后由 KB.Driver 于 2019-5-24 17:58 编辑

3、角度的变化(旋转)

back_sprite = Sprite.new
back_bitmap = Bitmap.new("Graphics/Parallaxes/Mountains1")
back_sprite.bitmap = back_bitmap

sprite1 = Sprite.new
sprite1.bitmap = Bitmap.new("Graphics/Battlers/Mimic")
sprite1.x = 150
sprite1.y = 150

sprite1_desc = Sprite.new
sprite1_desc.bitmap = Bitmap.new(300, 26)
sprite1_desc.bitmap.draw_text(0,0,300,26,"原点在左上")

sprite2 = Sprite.new
sprite2.bitmap = Bitmap.new("Graphics/Battlers/Mimic")
sprite2.x = 400
sprite2.y = 100
sprite2.ox = sprite2.bitmap.width / 2
sprite2.oy = sprite2.bitmap.height / 2

sprite2_desc = Sprite.new
sprite2_desc.bitmap = Bitmap.new(300, 26)
sprite2_desc.bitmap.draw_text(0,0,300,26,"原点在中心")
sprite2_desc.x = 280

# 循环更新画面
loop do
  sprite1.angle += 3
  sprite2.angle += 3
  
  # 画面的更新
  Graphics.update
end



作者: KB.Driver    时间: 2019-5-24 17:56
本帖最后由 KB.Driver 于 2019-5-24 18:00 编辑

4、不透明度的变化(隐现)

back_sprite = Sprite.new
back_bitmap = Bitmap.new("Graphics/Parallaxes/Mountains1")
back_sprite.bitmap = back_bitmap

sprite1 = Sprite.new
sprite1.bitmap = Bitmap.new("Graphics/Battlers/Vampire")
sprite1.x = 20
sprite1.opacity = 0

sprite2 = Sprite.new
sprite2.bitmap = Bitmap.new("Graphics/Battlers/Vampire")
sprite2.x = 290
sprite2.mirror = true

frames = 0
speed = 5
rate = 1

# 循环更新画面
loop do
  if frames > 51
    rate *= -1
    frames = 0
  end
  
  sprite1.opacity += speed * rate
  sprite2.opacity -= speed * rate
  frames += 1
  
  # 画面的更新
  Graphics.update
end




作者: KB.Driver    时间: 2019-5-24 17:59
本帖最后由 KB.Driver 于 2019-5-24 18:01 编辑

5、颜色叠加的变化

back_sprite = Sprite.new
back_bitmap = Bitmap.new("Graphics/Parallaxes/Mountains1")
back_sprite.bitmap = back_bitmap

sprite1 = Sprite.new
sprite1.bitmap = Bitmap.new("Graphics/Battlers/Windspirit")

frames = 0
speed = 3
rate = 1
alpha = 0

# 循环更新画面
loop do
  if frames > 85
    rate *= -1
    frames = 0
  end
  
  sprite1.color.set(rand(255), rand(255), rand(255), alpha)
  alpha += speed * rate
  frames += 1
  
  # 画面的更新
  Graphics.update
end




作者: KB.Driver    时间: 2019-5-24 18:00
本帖最后由 KB.Driver 于 2019-5-24 18:05 编辑

6、色调的变化

back_sprite = Sprite.new
back_bitmap = Bitmap.new("Graphics/Parallaxes/Mountains1")
back_sprite.bitmap = back_bitmap

bitmap = Bitmap.new("Graphics/Battlers/Priest")
sprites = Array.new(3){Sprite.new}
sprites.size.times do |i|
  sprites[ i ].x = i * 170 - 45
  sprites[ i ].y = -20
  sprites[ i ].z = 1
  sprites[ i ].bitmap = bitmap
end

frames = 0
speed = 5
rate = 1
value = 0

# 循环更新画面
loop do
  if frames > 51
    rate *= -1
    frames = 0
  end
  
  sprites[0].tone.set(value, 0, 0)
  sprites[1].tone.set(0, value, 0)
  sprites[2].tone.set(0, 0, value)
  
  value += speed * rate
  frames += 1
  
  # 画面的更新
  Graphics.update
end





(不要问我为什么中括号里要加空格,论坛傻乎乎地把数组的第i项[ i ]当做斜体标志看待了……)
作者: KB.Driver    时间: 2019-5-24 18:01
施工中&
作者: KB.Driver    时间: 2019-5-24 18:07
课后练习:
练习1 加速度

利用3.2节所学知识,结合F1文档的Sprite内容,制作以下动画。
妖精从画面左侧出发,方向向右,初速度为1,加速度为0.25。
触碰画面右端后,妖精改为面朝左侧,以初速度为1、加速度为0.25向左出发。
在触碰画面左端后,再次改为面朝右侧,回到初始状态,循环往复。



使用素材(RTP):
"Graphics/ Parallaxes/ Mountains1"
"Graphics/Battlers/ Fairy"

提示:
1、        精灵朝向的改变参考Sprite的mirror属性
2、        要避免精灵加速后跑出画面,可以用条件判断,也可以用取最大最小值的方式加以限制。
例如:x = [ x + 1, 100].min 可以让x变为x+1和100之间的最小值,从而避免让x超出100。
同理,x = [x – 1, 0].max可以让x在变小的过程中不小于0。

作者: KB.Driver    时间: 2019-5-24 18:08
练习2 状态与动画(有一定难度)

利用3.3和3.4节所学知识,制作以下动画。
人物初始位置(50,300),向右移动,速度为4,前进25帧。
随后人物暂停,敌人不透明度减半,并在敌人位置显示斩击动画。
动画显示完后,敌人不透明度恢复,人物向左移动,速度为2,前进50帧。之后循环往复进行。



使用素材(RTP):
" Graphics/Battlebacks1/Grassland"
" Graphics/Battlebacks2/Grassland"
"Graphics/Characters/Actor1"
"Graphics/Battlers/Slime"
"Graphics/Animations/Sword1"

提示:
1、        人物的行走需要一个状态,人物的朝向需要一个状态,人物在行走还是在静止需要一个状态。需要先设计好条件的结构。
2、        Animation动画的演示与行走图的src_rect改变原理相同,需要运用所学知识进行推广。
3、        为了确认动画在敌人身上演示,建议将敌人精灵和动画精灵的ox与oy设置为位图中心,随后将动画精灵的x与y设置为与敌人精灵相同的数值。

作者: KB.Driver    时间: 2019-5-24 18:09
练习3 伪•标题画面

利用本课所学知识,制作以下动画。
初始状态下,塔的远景不可见,塔的近景在y方向上拉长为2倍,不透明度为0。
随后塔的近景以每帧4像素的速度向上移动,不透明度以每帧5点的速度增加。
当塔的近景不透明度达到最大值255后,塔的远景变为可见,塔的近景继续以每帧4像素向上移动,不透明度以每帧5点的速度减少。

塔的近景完全不可见后,标题文字“通天塔”以从左向右遮罩的方式出现,显示速度为4像素每帧。
标题字体微软雅黑,字号72,位置约(175,80)。

待标题遮罩完成,下方三个选项依次淡入,三个选项字体微软雅黑,字号32,位置约为(90,320),(240,320),(390,320)。
选项全部淡入完成后,人物精灵变为可见,以3.3节所学方式面朝向下行走,同时“新游戏”选项的横、纵向缩放比例变为1.2。



使用素材(RTP):
"Graphics/Titles1/Tower1"
"Graphics/Titles1/Tower2"
"Graphics/Characters/Actor4"

提示:
1、        预先规划好状态机如何设置。
2、        标题精灵的遮罩进入可以用src_rect达成。初始状态src_rect.width = 0,而后令其循环增加。





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