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

Project1

 找回密码
 注册会员
搜索
查看: 5701|回复: 22
打印 上一主题 下一主题

[原创发布] 日历的RGSS3入门教程 - 精灵与位图Lv3 精灵更新与状态机

[复制链接]

Lv5.捕梦者

梦石
10
星屑
39587
在线时间
1920 小时
注册时间
2010-11-14
帖子
3320

R考场第七期纪念奖

跳转到指定楼层
1
发表于 2019-5-24 17:24:20 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式

加入我们,或者,欢迎回来。

您需要 登录 才可以下载或查看,没有帐号?注册会员

x
本帖最后由 KB.Driver 于 2019-5-24 17:26 编辑

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

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

评分

参与人数 3+3 收起 理由
夏虫沉默 + 1 精品文章
miantouchi + 1 先收藏,慢慢啃
hyrious + 1 塞糖

查看全部评分

用头画头像,用脚写脚本

Lv5.捕梦者

梦石
10
星屑
39587
在线时间
1920 小时
注册时间
2010-11-14
帖子
3320

R考场第七期纪念奖

2
 楼主| 发表于 2019-5-24 17:26:11 | 只看该作者
本帖最后由 KB.Driver 于 2019-5-24 17:27 编辑

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

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

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

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

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



用头画头像,用脚写脚本
回复 支持 反对

使用道具 举报

Lv5.捕梦者

梦石
10
星屑
39587
在线时间
1920 小时
注册时间
2010-11-14
帖子
3320

R考场第七期纪念奖

3
 楼主| 发表于 2019-5-24 17:27:35 | 只看该作者
本帖最后由 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


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

用头画头像,用脚写脚本
回复 支持 反对

使用道具 举报

Lv5.捕梦者

梦石
10
星屑
39587
在线时间
1920 小时
注册时间
2010-11-14
帖子
3320

R考场第七期纪念奖

4
 楼主| 发表于 2019-5-24 17:30:26 | 只看该作者
本帖最后由 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,执行下一次循环。


点评

错误已修正  发表于 2019-6-20 08:30
2%2=2这个地方笔误了吧,应该是2%3吧  发表于 2019-6-20 07:45
用头画头像,用脚写脚本
回复 支持 反对

使用道具 举报

Lv5.捕梦者

梦石
10
星屑
39587
在线时间
1920 小时
注册时间
2010-11-14
帖子
3320

R考场第七期纪念奖

5
 楼主| 发表于 2019-5-24 17:35:10 | 只看该作者
本帖最后由 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

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


用头画头像,用脚写脚本
回复 支持 反对

使用道具 举报

Lv5.捕梦者

梦石
10
星屑
39587
在线时间
1920 小时
注册时间
2010-11-14
帖子
3320

R考场第七期纪念奖

6
 楼主| 发表于 2019-5-24 17:36:41 | 只看该作者
本帖最后由 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


运行结果:

用头画头像,用脚写脚本
回复 支持 反对

使用道具 举报

Lv5.捕梦者

梦石
10
星屑
39587
在线时间
1920 小时
注册时间
2010-11-14
帖子
3320

R考场第七期纪念奖

7
 楼主| 发表于 2019-5-24 17:38:13 | 只看该作者
本帖最后由 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


运行结果:

用头画头像,用脚写脚本
回复 支持 反对

使用道具 举报

Lv5.捕梦者

梦石
10
星屑
39587
在线时间
1920 小时
注册时间
2010-11-14
帖子
3320

R考场第七期纪念奖

8
 楼主| 发表于 2019-5-24 17:39:53 | 只看该作者
本帖最后由 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,加深对这一节的理解和应用。
用头画头像,用脚写脚本
回复 支持 反对

使用道具 举报

Lv5.捕梦者

梦石
10
星屑
39587
在线时间
1920 小时
注册时间
2010-11-14
帖子
3320

R考场第七期纪念奖

9
 楼主| 发表于 2019-5-24 17:43:46 | 只看该作者
本帖最后由 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


运行结果:

用头画头像,用脚写脚本
回复 支持 反对

使用道具 举报

Lv5.捕梦者

梦石
10
星屑
39587
在线时间
1920 小时
注册时间
2010-11-14
帖子
3320

R考场第七期纪念奖

10
 楼主| 发表于 2019-5-24 17:47:39 | 只看该作者
本帖最后由 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的对应关系。

用头画头像,用脚写脚本
回复 支持 反对

使用道具 举报

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

本版积分规则

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

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

GMT+8, 2024-11-16 19:41

Powered by Discuz! X3.1

© 2001-2013 Comsenz Inc.

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