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

Project1

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

[胡扯] 【用代码说话】#2.史莱姆巢穴

[复制链接]

Lv5.捕梦者 (暗夜天使)

只有笨蛋才会看到

梦石
1
星屑
21840
在线时间
9438 小时
注册时间
2012-6-19
帖子
7120

开拓者短篇九导演组冠军

跳转到指定楼层
1
发表于 2016-7-31 00:52:42 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式

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

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

x
本帖最后由 喵呜喵5 于 2016-7-31 01:00 编辑

【前言】

前篇传送门:https://rpg.blue/forum.php?mod=viewthread&tid=394674

在上一篇教程中,我曾举了一个史莱姆的例子来说明为什么要使用面向对象。不过,略微有些遗憾的是,上一篇的知识只是面向对象思想的冰山一角,所以,这一篇可以看作是对上一篇内容的一个延续和补充。通过这一篇的内容,我希望能够让你真正开始了解面向对象为了什么、做了什么、抽象了什么。当然,我承认,即使搞不懂面向对象也不代表你就无法写出一个完整的脚本了,但是,如果你我都能从这个简短而又啰嗦的小教程中学到什么的话,我将倍感欣慰。
出于让代码易于阅读的目的,在这次的示例中,各种命名我使用的是英文(而非拼音或汉字),不过,请不用担心,相应单词的中文释义我都将通过注释的方式和代码一并给出

那么,没有意义的废话就此打住,下面,让我们一起进入史莱姆的巢穴——面向对象的世界吧

【用代码说话】#2.史莱姆巢穴



  1. class Slime
  2.   def appear; @hp = 10; end
  3.   def die; @hp = 0; end
  4.   def bark
  5.     if @hp == 0
  6.       p 'Uuuu...'
  7.     else
  8.       p 'GaO!!'
  9.     end
  10.   end
  11. end
  12. enemy1 = Slime.new
  13. enemy2 = Slime.new
  14. enemy1.appear
  15. enemy2.appear
  16. enemy1.bark
  17. enemy2.bark
  18. enemy1.die
  19. enemy1.bark
  20. enemy2.bark
  21. enemy3 = Slime.new
  22. enemy3.appear
  23. enemy3.bark
  24. # Slime - 史莱姆
  25. # appear - 出现
  26. # die - 死亡
  27. # bark - 吠叫
  28. # enemy - 敌人
复制代码

请不要借助任何代码执行工具,阅读上面的代码并思考,输出的结果是什么?



这次的教程我决定从一个传统的日式 RPG 故事作为开始:
一个新人冒险者接到了他冒险生涯中的第一个任务——讨伐三只史莱姆,从公会的接待那里领到了史莱姆的图鉴后,他兴致勃勃的踏上了旅途,没走多远,两个敌人嘎哦嘎哦的叫着从草丛中出现了……
“嘿,这就是史莱姆,他们的行为举止果然和图鉴上写的一模一样!”
一边注意着周围的环境,冒险者打倒了了其中一只史莱姆
“真是绝情啊,明明自己的同伴已经死亡了,另外这只史莱姆却仿佛什么都没发生一样的”
冒险者一边看着发出呜呜的声音瘫软在路边的史莱姆,一边开始将集中精力应对起剩下的那只史莱姆
这时候,从冒险者的身后,第三只史莱姆精力充沛的叫着嘎哦出现了!

从1967年第一种面向对象的语言 Simula67 发明到你阅读这篇教程的这个时间节点,编程语言已经发生了太多的改变,不论是内存控制亦或是垃圾收集,多亏了编程语言和硬件性能的发展,我们得以从各种乏味单调的任务中解脱从而将精力放到运行逻辑本身。鉴于继续顺着这个话题说下去估计这篇教程的主题就回不来了,我们就此打住,用代码说话
回到我们最开始的故事,冒险者公会将史莱姆写成图鉴,史莱姆的行动和图鉴一模一样,史莱姆1死了以后,史莱姆2仿佛什么都没变,史莱姆3在史莱姆1死了以后仍然精力充沛的出现,这就是面向对象的特征
下面,我们可以从头开始完整的分析一遍代码了,我们一起从上往下看一下

  1. class Slime
  2.   def appear; @hp = 10; end
  3.   def die; @hp = 0; end
  4.   def bark
  5.     if @hp == 0
  6.       p 'Uuuu...'
  7.     else
  8.       p 'GaO!!'
  9.     end
  10.   end
  11. end
复制代码

  • 冒险者公会写了一本怪物图鉴,定义了一个叫 Slime 的类
  • Slime 有一个名叫出现的行为,当他执行这个行为的时候,他的 @hp 会变为 10
  • Slime 有一个名叫死亡的行为,当他执行这个行为的时候,他的 @hp 会变为 0
  • Slime 有一个名叫吠叫的行为,当他的 @hp 为 0 时,他的叫声是呜呜呜,当他的 @hp 为 10 时,他的叫声是嘎哦

  1. enemy1 = Slime.new
  2. enemy2 = Slime.new
  3. enemy1.appear
  4. enemy2.appear
  5. enemy1.bark
  6. enemy2.bark
  7. enemy1.die
  8. enemy1.bark
  9. enemy2.bark
  10. enemy3 = Slime.new
  11. enemy3.appear
  12. enemy3.bark
复制代码

  • 敌人1是一只史莱姆
  • 敌人2也是一只史莱姆
  • 敌人1出现了!
  • 敌人2出现了!
  • 敌人1开始吠叫
  • 敌人2开始吠叫
  • 敌人1死了
  • 敌人1开始吠叫
  • 敌人2开始吠叫
  • 敌人3是一只史莱姆
  • 敌人3出现了!
  • 敌人3开始吠叫

这段代码运行后的结果是
  1. "GaO!!"
  2. "GaO!!"
  3. "Uuuu..."
  4. "GaO!!"
  5. "GaO!!"
复制代码

在上一篇中,我们曾经说到,对于面向编程来说,每一个实例都是不同的,就好比这里的敌人1、敌人2、敌人3是三只不同的史莱姆一样,那么,这三只史莱姆明明行为都是一样的,为什么却需要让他们不同呢,这里就引出了各个实例“状态”的作用,在这次的例子里,就是三只史莱姆的HP,也就是代码中出现的 @hp 的功能。正因为像这种以 @ 开头的变量是用来表明实例状态的,所以我们称呼他为“实例变量”
实例的变量的存在让一个个实例的状态不再相同,在 class Slime 中,我们定义了史莱姆的行为,在实例 enemy1、enemy2、enemy3 中,我们储存了史莱姆的状态,从冒险者的角度来看,史莱姆在做的事情仅仅只是吠叫而已,冒险者无法关注到史莱姆吠叫时的 if else 逻辑,冒险者也没有必要去关注史莱姆吠叫时的 if else 逻辑。将不确定因素进行封装来让代码易于理解,这正是面向对象的编程所期望的
当然,虽然吹了半天面向对象,但是没有面向对象并不代表你就写不出代码了,例如,上面那段代码完全可以用下面这段面向过程的代码来等效替代
  1. def appear; return 10; end
  2. def die; return 0; end
  3. def bark(hp)
  4.   if hp == 0
  5.     p 'Uuuu...'
  6.   else
  7.     p 'GaO!!'
  8.   end
  9. end
  10. enemy1 = 'Slime'
  11. enemy2 = 'Slime'
  12. enemy1_hp = appear
  13. enemy2_hp = appear
  14. bark(enemy1_hp)
  15. bark(enemy2_hp)
  16. enemy1_hp = die
  17. bark(enemy1_hp)
  18. bark(enemy2_hp)
  19. enemy3 = 'Slime'
  20. enemy3_hp = appear
  21. bark(enemy3_hp)
复制代码
如果场上同时出现三只史莱姆时,两种编程方式只从行数上来看似乎相差也不大……
  1. enemy1 = Slime.new
  2. enemy1.appear
  3. enemy2 = Slime.new
  4. enemy2.appear
  5. enemy3 = Slime.new
  6. enemy3.appear
复制代码
  1. enemy1 = 'Slime'
  2. enemy1_hp = appear
  3. enemy2 = 'Slime'
  4. enemy2_hp = appear
  5. enemy3 = 'Slime'
  6. enemy3_hp = appear
复制代码
但是,当每只史莱姆除了拥有HP这个状态,还拥有 MP、TP、EXP、LV、ATK、DEF 这些状态的时候……
(@atk 论坛 @ bug修正)
  1. class Slime
  2.   def appear
  3.     @hp = 10
  4.     @mp = 10
  5.     @tp = rand(5)
  6.     @exp = rand(5)
  7.     @lv = rand(5)
  8.     @atk = rand(5)
  9.     @def = rand(5)
  10.   end
  11.   def die; @hp = 0; end
  12.   def bark
  13.     if @hp == 0
  14.       p 'Uuuu...'
  15.     else
  16.       p 'GaO!!'
  17.     end
  18.   end
  19. end
  20. enemy1 = Slime.new
  21. enemy1.appear
  22. enemy2 = Slime.new
  23. enemy2.appear
  24. enemy3 = Slime.new
  25. enemy3.appear
复制代码
  1. def appear_hp; return 10; end
  2. def appear_mp; return 10; end
  3. def appear_tp; return rand(5); end
  4. def appear_exp; return rand(5); end
  5. def appear_lv; return rand(5); end
  6. def appear_atk; return rand(5); end
  7. def appear_def; return rand(5); end
  8. def die; return 0; end
  9. def bark(hp)
  10.   if hp == 0
  11.     p 'Uuuu...'
  12.   else
  13.     p 'GaO!!'
  14.   end
  15. end
  16. enemy1 = 'Slime'
  17. enemy1_hp = appear_hp
  18. enemy1_mp = appear_mp
  19. enemy1_tp = appear_tp
  20. enempy1_exp = appear_exp
  21. ………………$%$^%$@#$@
复制代码
当然,你可能会利用 hash 把你的代码写成这样
  1. def appear
  2.   return {
  3.     :hp => 10,
  4.     :mp => 10,
  5.     :tp => rand(5),
  6.     :exp => rand(5),
  7.     :lv => rand(5),
  8.     :atk => rand(5),
  9.     :def => rand(5),
  10.   }
  11. end
  12. def die; return 0; end
  13. def bark(hp)
  14.   if hp == 0
  15.     p 'Uuuu...'
  16.   else
  17.     p 'GaO!!'
  18.   end
  19. end
  20. enemy1 = 'Slime'
  21. enemy1_state = appear
  22. enemy2 = 'Slime'
  23. enemy2_state = appear
  24. enemy2 = 'Slime'
  25. enemy2_state = appear
复制代码
那么,恭喜你,你已经在很大程度上理解了面向对象编程所做的事情是什么了哦,在你的代码里 enemy1_state[:hp] 对应的就是面向编程中 enemy1 实例的实例变量 @hp 所承担的作用
如果你对 Proc 略有了解,更进一步,把你的代码改写成这样
  1. def appear
  2.   die = Proc.new{ 0 }
  3.   bark = Proc.new{|hp|
  4.     if hp == 0
  5.       p 'Uuuu...'
  6.     else
  7.       p 'GaO!!'
  8.     end
  9.   }
  10.   return {
  11.     :hp => 10,
  12.     :mp => 10,
  13.     :tp => rand(5),
  14.     :exp => rand(5),
  15.     :lv => rand(5),
  16.     :atk => rand(5),
  17.     :def => rand(5),
  18.     :die => die,
  19.     :bark => bark
  20.   }
  21. end
  22. enemy1 = 'Slime'
  23. enemy1_state = appear
  24. enemy2 = 'Slime'
  25. enemy2_state = appear
  26. enemy2 = 'Slime'
  27. enemy2_state = appear
复制代码
那么,你的代码已经越来越接近面向对象的思维了呢。实际上,对于类似 lua 或者 javascript 这样的语言,这就是他们面向对象的实现方式。当然,这里要特别说明的是,虽然现阶段面向对象在你眼中可能只是一个语法糖一样的东西,实际上,他却和面向过程存在编程思想上的区别。

不知不觉已经写了这么多字了,是时候结束这篇的内容了,和前一篇一样,在这一篇的最后,会有一个小小的恶作剧代码,说是恶作剧代码,实际上是一个小小的内容预告,在下一篇中,我们将一起学习面向对象编程的另一个重要概念——继承
  1. class Slime
  2.   def appear; @hp = 10; end
  3.   def die; @hp = 0; end
  4.   def bark
  5.     if @hp == 0
  6.       p 'Uuuu...'
  7.     elsif @hp > 50
  8.       p 'Golden——!'
  9.     else
  10.       p 'GaO!!'
  11.     end
  12.   end
  13. end
  14. class Golden_Slime < Slime
  15.   def appear; @hp = 100; end
  16. end
  17. enemy = Golden_Slime.new
  18. enemy.appear
  19. enemy.bark
复制代码

评分

参与人数 7星屑 +247 收起 理由
欧买歌 + 30 看不懂
kklt + 10 塞糖
garfeng + 20
Vortur + 23 塞糖
威风镰鼬 + 14 塞糖
zaiy2863 + 60 喵污喵
正太君 + 90 头像好评...

查看全部评分

Lv4.逐梦者 (版主)

聪仔

梦石
0
星屑
6187
在线时间
3077 小时
注册时间
2013-12-26
帖子
3145
2
发表于 2016-7-31 08:13:07 | 只看该作者
依然是坐等火了之后再帮你移到技术区...(为什么不上后台自己移呢
聪聪全国第三帅...
他们都叫我【人赢聪】
我的RM能力雷达图:

回复 支持 反对

使用道具 举报

Lv3.寻梦者 (版主)

…あたしは天使なんかじゃないわ

梦石
0
星屑
2208
在线时间
4033 小时
注册时间
2010-10-4
帖子
10779

开拓者贵宾

3
发表于 2016-7-31 10:37:18 | 只看该作者
本帖最后由 taroxd 于 2016-7-31 12:38 编辑

RUBY 代码复制
  1. def Slime
  2.   this = {
  3.     :hp => 10,
  4.     :mp => 10,
  5.     :tp => rand(5),
  6.     :exp => rand(5),
  7.     :lv => rand(5),
  8.     :atk => rand(5),
  9.     :def => rand(5),
  10.     :die => lambda { this[:hp] = 0 },
  11.     :bark => lambda {
  12.       if this[:hp] == 0
  13.         puts 'Uuuu...'
  14.       else
  15.         puts 'GaO!!'
  16.       end
  17.     }
  18.   }
  19. end
  20.  
  21. slime1 = Slime()
  22. slime1[:bark].call
  23. slime1[:die].call
  24. slime1[:bark].call



不用 class 强行实现一下封装、继承、多态这些面向对象的特性w
RUBY 代码复制
  1. def new(constructor, *args)
  2.   props = {}
  3.   this = -> action, *a {
  4.     if action == :props
  5.       props
  6.     else
  7.       constructor[this, action, *a]
  8.     end
  9.   }
  10.   constructor[this, :initialize, *args]
  11.   this
  12. end
  13.  
  14. MyObject = -> this, action, *args {
  15.   raise NoMethodError.new("undefined method #{action}", action, args)
  16. }
  17.  
  18. Barkable = -> parent {
  19.   -> this, action, *args {
  20.     case action
  21.     when :bark
  22.       if this[:hp] == 0
  23.         puts 'Uuuu...'
  24.       else
  25.         puts 'GaO!!'
  26.       end
  27.     else
  28.       parent[this, action, *args]
  29.     end
  30.   }
  31. }
  32.  
  33. Slime = -> parent {
  34.   -> this, action, *args {
  35.     case action
  36.     when :initialize, :set_hp
  37.       this[:props][:hp], = args
  38.     when :hp
  39.       this[:props][:hp]
  40.     when :die
  41.       this[:set_hp, 0]
  42.     else
  43.       parent[this, action, *args]
  44.     end
  45.   }
  46. }[Barkable[MyObject]]
  47.  
  48. GoldenSlime = -> parent {
  49.   -> this, action, *args {
  50.     case action
  51.     when :initialize
  52.       parent[this, action, 100]
  53.     when :bark
  54.       if this[:hp] > 50
  55.         puts 'Golden!'
  56.       else
  57.         parent[this, action]
  58.       end
  59.     else
  60.       parent[this, action, *args]
  61.     end
  62.   }
  63. }[Slime]
  64.  
  65. slime1 = new GoldenSlime
  66. slime1[:bark]
  67. slime1[:die]
  68. slime1[:bark]

评分

参与人数 1星屑 +240 收起 理由
喵呜喵5 + 240 撞壁停不下来

查看全部评分

回复 支持 反对

使用道具 举报

Lv2.观梦者

梦石
0
星屑
409
在线时间
286 小时
注册时间
2015-10-4
帖子
294
4
发表于 2016-7-31 12:03:00 | 只看该作者
习得用哈希写方法ww
回复 支持 反对

使用道具 举报

Lv3.寻梦者 (暗夜天使)

梦石
1
星屑
2971
在线时间
1041 小时
注册时间
2013-8-9
帖子
2328

R考场第七期纪念奖开拓者

5
发表于 2016-7-31 13:00:28 | 只看该作者
/w\又屠杀史莱姆了
2021.8-2024.5
消失了3年的阿鑫再次出现
回复 支持 反对

使用道具 举报

Lv4.逐梦者 (版主)

漾夕☽星化残月☾

梦石
0
星屑
8596
在线时间
3857 小时
注册时间
2015-5-12
帖子
2077

剧作品鉴家

6
发表于 2016-7-31 13:11:43 | 只看该作者

ぷよぷよ?
回复 支持 反对

使用道具 举报

Lv4.逐梦者

梦石
0
星屑
9280
在线时间
2504 小时
注册时间
2011-5-20
帖子
15389

开拓者

7
发表于 2016-7-31 16:06:22 | 只看该作者
什么?让史莱姆进入我们的巢穴?
[img]http://service.t.sina.com.cn/widget/qmd/5339802982/c02e16bd/7.png
回复 支持 反对

使用道具 举报

Lv2.观梦者

梦石
0
星屑
254
在线时间
316 小时
注册时间
2015-7-2
帖子
1747

开拓者

8
发表于 2016-7-31 17:28:28 | 只看该作者
开始懵了……
尤其是提到hash那里就完全不会了……

最底下那段代码的输出应该是
  1. "Golden——!"
复制代码
?
不太懂专业说法,但是Golden_Slime这个类是Slime类的子集这样的感觉?

点评

hash之后的东西如果实在无法理解的话可以先跳过  发表于 2016-7-31 20:11
是子类 继承父类Slime  发表于 2016-7-31 18:00
测试你的东方project认知程度?那就来玩[url=https://store.steampowered.com/app/930840/TouHouAsked/]《东方百问》[/url]吧!
东方风自作曲认知企划绝赞咕咕咕中
回复 支持 反对

使用道具 举报

Lv3.寻梦者

梦石
0
星屑
4598
在线时间
1206 小时
注册时间
2016-4-7
帖子
982

开拓者

9
发表于 2016-7-31 22:23:17 手机端发表。 | 只看该作者
taroxd 发表于 2016-7-31 10:37
def Slime
  this = {
    :hp => 10,

如果黑一下hash的method_missing就更好了 233

class实际上也只是种包含了method的更复杂的数据结构罢了 lua面向对象的实现就是靠的table 而js则依托于object 然而这些和ruby的hash都是类似的东西

点评

你大概需要 OpenStruct  发表于 2016-8-1 10:28
回复 支持 反对

使用道具 举报

Lv3.寻梦者

梦石
0
星屑
2749
在线时间
2630 小时
注册时间
2013-1-16
帖子
5657

贵宾

10
发表于 2016-8-1 23:11:39 | 只看该作者
面向对象的道理我都懂,然而我就是习惯面向过程怎么办╮(╯▽╰)╭

点评

活该没有对象(x  发表于 2016-8-5 07:44
(Created by @喵kano)


施工现场:hotege.github.io
回复 支持 反对

使用道具 举报

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

本版积分规则

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

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

GMT+8, 2024-12-26 15:13

Powered by Discuz! X3.1

© 2001-2013 Comsenz Inc.

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