007 External
这期教程,我们来讲解如何使用外部变量。
在上期教程中,我简化了一下网站上的涅槃特性,并且#掉了两行代码——
那么,为什么原代码需要这两行代码呢?
实际上,如果你真的照着我的教程来做的话,你就会发现,现在这个涅槃特性会无限次数的发动,而不是仅仅发动一次,我们想要的是它仅仅发动一次。
那这个时候我们该怎么做呢?
这个时候我们就需要引入额外的外部变量来帮助我们。
我们需要一个变量,或者说一个东西,来额外存储这个特性是否已经发动过了。
比如说有一个变量trigger,当trigger是false时,就代表这个特性还没有发动,当trigger是true时,就代表这个特性已经发动过了,所以不能再次发动。
我们还需要思考,这个trigger是哪一个层级的变量,是基于battle的变量,还是基于battler的变量。
battle就是指这场战斗,battler就是指场上的某一只精灵。
显然,我们这个特性是一场战斗只会发动一次,所以这个trigger应该是battle层级的变量。
关于一个变量到底应该是基于battle的变量,还是基于battler的变量,你就想一下这个变量在精灵换下场之后是否应该重置刷新,如果不重置,那么就是要基于battle的变量,如果需要重置,那么就是基于battler的变量。
思路有了,那么该怎么做呢?
说实话,这里确实是有一点难了,你需要对战斗系统和代码运行的逻辑有较深的认识你才能完全理解。
首先,打开\Pokemon Essentials v21.1 2023-07-30\Data\Scripts\011_Battle\001_Battle里的001_Battle.rb。
接着,往下拉,找到这里——
在后面添加上abilityTrigger,我们就使用这个变量来记录特性是否已经发动过了。
接着,找到下面的initialize方法,这个叫做初始化方法,我们在initialize里面初始化一下abilityTrigger。
初始化的意思就是默认设置,就是abilityTrigger默认状态下是什么样的,这里的话我们希望默认应该是false,即特性还没发动过,发动过之后再变成true。
往下拉到这个方法的最下面——
加上abilityTrigger——
这样就好了。
所以,当你想加一个基于battle的变量的时候,就在001_Battle.rb里加,照着格式来,第一步,增加attr_accessor :xxx;第二步,初始化。
这里解释一下为什么abilityTrigger的初始化是@abilityTrigger = [Array.new(@party1.length, false), Array.new(@party2.length, false)]这样的,而不能跟@moldBreaker = false一样。
这是因为abilityTrigger需要记录,我方和对方的,队伍里的,全部的,精灵的,特性发动状态;
而moldBreaker,这个是破格,它只需要记录当前场上是否被破格,它们是完全不一样的。
我们假设我方和对方都是6只精灵,那么实际上abilityTrigger就是这样的@abilityTrigger = [[false, false, false, false, false, false], [false, false, false, false, false, false]]。
也就是说,@abilityTrigger本身是一个包含两个数组的数组。
数组是什么呢?
数组就是Array,你可以简单的理解为一系列数据的集合,我们用[],方括号来表示数组。
如果仅仅只是这样[],那这就是一个空的数组,里面没有任何数据;
如果是这样[1, 2, 3],那这个数组里就包含有3个数据,或者说3个元素,Element,这3个元素分别是1、2和3。
所以,对于@abilityTrigger来说,它是[[false, false, false, false, false, false], [false, false, false, false, false, false]],它本身是一个数组,它里面含有两个元素。
而这两个元素也都是数组,并且是一模一样的,都是[false, false, false, false, false, false]。
这两个元素的每一个元素里面又包含了6个元素,6个false。
其实就是两个元素,第一个元素代表我方,第二个元素代表对方。
接着元素里面又包含了6个false,就是分别用来记录每一只精灵的特性发动状态的,默认是false,就是特性还没有发动。
接着我们来详细看一下@abilityTrigger = [Array.new(@party1.length, false), Array.new(@party2.length, false)],它为什么要这样写——
前面的@,你不要去管它,这里它就是要加一个@,并没有过多的花样,你不需要去深入了解;
Array.new(@party1.length, false),这个的意思是新建一个数组,如果仅仅只是新建数组的话,跟你@abilityTrigger = [[], []]直接这样写没有什么区别。
为什么不能直接这样写,是因为它不仅仅是新建了数组,还通过new括号后面的参数在数组里添加了默认的元素;
@party1.length, false,这是new括号里面的参数,其实就是添加了一定数量的false,那么这个一定数量是多少数量呢?
这个数量由@party1.length决定,就是你队伍里有多少只精灵,就添加多少个false。
因为每一场战斗敌我双方的队伍数量都是不一定的,所以只能这么写。
另外,因为一只精灵只会有一个特性,而每只精灵有一个自己对应的位置来记录false或者true,所以你并不需要为每一个一场战斗只会发动一次的一次性特性都增加一个基于battle的变量,你一辈子只需要添加这么一次就够了。
接着,在下面添加上这一段代码——
这里的话是新加了一个方法battle_abilityTrigger?(battler),使用这个方法来标记特性是否已经发动过了。
我们在初始化时,让abilityTrigger记录了全部的false,那么在特性发动之后,abilityTrigger里面记录的某一个false就要变成true,怎么才能变成true呢,就是通过现在这个新加的方法。
这个方法分为两个部分,首先是检查当前精灵的特性是否已经发动过了,如果已经发动过了,就返回true,就不会再次发动了;
如果没有发动过,那么就会先标记特性已经发动过了,再返回false,接着特性发动;
等到第二次运行这个方法的时候,因为特性已经发动过了,就会直接返回true,不再发动特性。
在这个方法中,方法最后会return,或者说返回,会返回false或者true,实际上就是这个方法本身battle_abilityTrigger?(battler) == false或者battle_abilityTrigger?(battler) == true。
这里的@abilityTrigger[battler.index & 1][battler.pokemonIndex],就是用来确定@abilityTrigger里面的某一个元素的。
[battler.index & 1]会返回0或者1,就是我方,或者对方。
[battler.pokemonIndex]会返回当前精灵在队伍中的编号。
所以合起来就是,先确定是我方还是对方,再确定是队伍里的哪只精灵的特性是否已经发动过了。
也就是说,@abilityTrigger[battler.index & 1]这个部分,会先确定是@abilityTrigger里面的第一个数组,还是第二个数组;
再加上后面这个部分,@abilityTrigger[battler.index & 1][battler.pokemonIndex],会确定是数组里的哪一个元素。
这里面的话,其实是涉及到了如何获取一个数组里面的元素,比如说有这样一个数组[1, 2, 3],我们就可以直接这样写[1, 2, 3][1],那么[1, 2, 3][1]是多少呢?
答案是2。
如果你看不明白的话,我们可以这样写,a = [1, 2, 3],所以a[1]就是2。
我们通过数组里面元素的序号来获取元素,并且,这个序号是从0开始的,也就是说第一个元素是0,第二个是1,而a[1],就是指a这个数组里面的第二个元素,也就是2。
现在,假如说a = [[1, 2, 3], [4, 5, 6], [7, 8, 9]],就是a本身是个数组,而a里面的3个元素也是数组,那这个时候 a[1]是什么呢?
答案是[4, 5, 6]。
那么a[1][2]呢?
a[1][2]其实就是[4, 5, 6][2],所以a[1][2]就是6。
如果还是不能理解,那么就多看几遍教程吧。
最后,也是最重要的,就算你理解不了,你也至少一定要会使用battle_abilityTrigger?(battler)这个方法,直接复制粘贴过去使用就好了。
不要求你完全懂,但要求你要会用,这是做所有一次性特性的基础。
那么,这个方法该怎么用呢?
很简单,在你所有想要一场战斗只会发动一次的特性里,都加上检查这个方法就好了。
那么我们就把这个方法加到涅槃里——
原本#掉的两行就可以删掉了,现在这样的写法更简洁,只需要一行就可以达到我们的要求。
这一行next if battle.battle_abilityTrigger?(battler)里面,if就是如果怎么怎么样,就是看if后面的检查,或者说条件,是false还是true,而我们前面也说了,battle.battle_abilityTrigger?(battler)要么是false,要么是true;
这一行里面的next,就是下一个的意思,意思就是说,如果if成立,或者说if后面的检查是true,那么就直接下一个,而不会执行next后面的代码。
在这里的意思就是说,如果特性已经发动过了,就直接跳过。
另外就是,因为battle_abilityTrigger?(battler)这个方法是写在Battle里的,所以你要对battle调用这个方法。
这样的话,现在这个涅槃就只会发动一次了。
另外,关于这个next,如果你转不过弯来的话,你也可以这样写,if !battle.battle_abilityTrigger?(battler),就是不写next,但是要在检查的内容前面加个!,注意,这个是英文的叹号。
这样写的意思是,当battle.battle_abilityTrigger?(battler)是false的话,才会执行if里面的内容,具体写出来就是这样的——
注意,这样写的话后面不要忘了end,因为你现在是初学,所以你就记住就好了,有if,就要有end,if和end是成对出现的。
只有一种情况下会有if,但是没有end,那就是if里面的代码只有一行的时候,我们来看一个特性——
这个是冲浪之尾,它这样的写法就是有if,但是没有end,同样的,你也可以把它改成这样——
实际上,我还是建议你用if和end对,甚至连next都不要用,转来转去脑子绕来绕去还可能搞错了,还不如统一就用if和end对,逻辑上最简单直白,也不会错。
你现在也不需要去考虑性能啊效率啊什么的,先能实现效果再说,这些以后再去考虑。
接着,我们再来看一个——
这个是我们之前写的活力焕发,那么我们想让这个特性只发动一次的话呢,就可以直接这样做——
请自行测试涅槃和活力焕发是否现在真的只会发动一次了。
视频预览(0702)——
浏览附件2024-07-02 14-41-59.mp4
浏览附件2024-07-02 14-43-49.mp4
那么这期教程就到此为止,下期教程我们将会讲解如何使用基于battler的变量。
感谢阅读。
这期教程,我们来讲解如何使用外部变量。
在上期教程中,我简化了一下网站上的涅槃特性,并且#掉了两行代码——
那么,为什么原代码需要这两行代码呢?
实际上,如果你真的照着我的教程来做的话,你就会发现,现在这个涅槃特性会无限次数的发动,而不是仅仅发动一次,我们想要的是它仅仅发动一次。
那这个时候我们该怎么做呢?
这个时候我们就需要引入额外的外部变量来帮助我们。
我们需要一个变量,或者说一个东西,来额外存储这个特性是否已经发动过了。
比如说有一个变量trigger,当trigger是false时,就代表这个特性还没有发动,当trigger是true时,就代表这个特性已经发动过了,所以不能再次发动。
我们还需要思考,这个trigger是哪一个层级的变量,是基于battle的变量,还是基于battler的变量。
battle就是指这场战斗,battler就是指场上的某一只精灵。
显然,我们这个特性是一场战斗只会发动一次,所以这个trigger应该是battle层级的变量。
关于一个变量到底应该是基于battle的变量,还是基于battler的变量,你就想一下这个变量在精灵换下场之后是否应该重置刷新,如果不重置,那么就是要基于battle的变量,如果需要重置,那么就是基于battler的变量。
思路有了,那么该怎么做呢?
说实话,这里确实是有一点难了,你需要对战斗系统和代码运行的逻辑有较深的认识你才能完全理解。
首先,打开\Pokemon Essentials v21.1 2023-07-30\Data\Scripts\011_Battle\001_Battle里的001_Battle.rb。
接着,往下拉,找到这里——
在后面添加上abilityTrigger,我们就使用这个变量来记录特性是否已经发动过了。
接着,找到下面的initialize方法,这个叫做初始化方法,我们在initialize里面初始化一下abilityTrigger。
初始化的意思就是默认设置,就是abilityTrigger默认状态下是什么样的,这里的话我们希望默认应该是false,即特性还没发动过,发动过之后再变成true。
往下拉到这个方法的最下面——
加上abilityTrigger——
这样就好了。
所以,当你想加一个基于battle的变量的时候,就在001_Battle.rb里加,照着格式来,第一步,增加attr_accessor :xxx;第二步,初始化。
这里解释一下为什么abilityTrigger的初始化是@abilityTrigger = [Array.new(@party1.length, false), Array.new(@party2.length, false)]这样的,而不能跟@moldBreaker = false一样。
这是因为abilityTrigger需要记录,我方和对方的,队伍里的,全部的,精灵的,特性发动状态;
而moldBreaker,这个是破格,它只需要记录当前场上是否被破格,它们是完全不一样的。
我们假设我方和对方都是6只精灵,那么实际上abilityTrigger就是这样的@abilityTrigger = [[false, false, false, false, false, false], [false, false, false, false, false, false]]。
也就是说,@abilityTrigger本身是一个包含两个数组的数组。
数组是什么呢?
数组就是Array,你可以简单的理解为一系列数据的集合,我们用[],方括号来表示数组。
如果仅仅只是这样[],那这就是一个空的数组,里面没有任何数据;
如果是这样[1, 2, 3],那这个数组里就包含有3个数据,或者说3个元素,Element,这3个元素分别是1、2和3。
所以,对于@abilityTrigger来说,它是[[false, false, false, false, false, false], [false, false, false, false, false, false]],它本身是一个数组,它里面含有两个元素。
而这两个元素也都是数组,并且是一模一样的,都是[false, false, false, false, false, false]。
这两个元素的每一个元素里面又包含了6个元素,6个false。
其实就是两个元素,第一个元素代表我方,第二个元素代表对方。
接着元素里面又包含了6个false,就是分别用来记录每一只精灵的特性发动状态的,默认是false,就是特性还没有发动。
接着我们来详细看一下@abilityTrigger = [Array.new(@party1.length, false), Array.new(@party2.length, false)],它为什么要这样写——
前面的@,你不要去管它,这里它就是要加一个@,并没有过多的花样,你不需要去深入了解;
Array.new(@party1.length, false),这个的意思是新建一个数组,如果仅仅只是新建数组的话,跟你@abilityTrigger = [[], []]直接这样写没有什么区别。
为什么不能直接这样写,是因为它不仅仅是新建了数组,还通过new括号后面的参数在数组里添加了默认的元素;
@party1.length, false,这是new括号里面的参数,其实就是添加了一定数量的false,那么这个一定数量是多少数量呢?
这个数量由@party1.length决定,就是你队伍里有多少只精灵,就添加多少个false。
因为每一场战斗敌我双方的队伍数量都是不一定的,所以只能这么写。
另外,因为一只精灵只会有一个特性,而每只精灵有一个自己对应的位置来记录false或者true,所以你并不需要为每一个一场战斗只会发动一次的一次性特性都增加一个基于battle的变量,你一辈子只需要添加这么一次就够了。
接着,在下面添加上这一段代码——
这里的话是新加了一个方法battle_abilityTrigger?(battler),使用这个方法来标记特性是否已经发动过了。
我们在初始化时,让abilityTrigger记录了全部的false,那么在特性发动之后,abilityTrigger里面记录的某一个false就要变成true,怎么才能变成true呢,就是通过现在这个新加的方法。
这个方法分为两个部分,首先是检查当前精灵的特性是否已经发动过了,如果已经发动过了,就返回true,就不会再次发动了;
如果没有发动过,那么就会先标记特性已经发动过了,再返回false,接着特性发动;
等到第二次运行这个方法的时候,因为特性已经发动过了,就会直接返回true,不再发动特性。
在这个方法中,方法最后会return,或者说返回,会返回false或者true,实际上就是这个方法本身battle_abilityTrigger?(battler) == false或者battle_abilityTrigger?(battler) == true。
这里的@abilityTrigger[battler.index & 1][battler.pokemonIndex],就是用来确定@abilityTrigger里面的某一个元素的。
[battler.index & 1]会返回0或者1,就是我方,或者对方。
[battler.pokemonIndex]会返回当前精灵在队伍中的编号。
所以合起来就是,先确定是我方还是对方,再确定是队伍里的哪只精灵的特性是否已经发动过了。
也就是说,@abilityTrigger[battler.index & 1]这个部分,会先确定是@abilityTrigger里面的第一个数组,还是第二个数组;
再加上后面这个部分,@abilityTrigger[battler.index & 1][battler.pokemonIndex],会确定是数组里的哪一个元素。
这里面的话,其实是涉及到了如何获取一个数组里面的元素,比如说有这样一个数组[1, 2, 3],我们就可以直接这样写[1, 2, 3][1],那么[1, 2, 3][1]是多少呢?
答案是2。
如果你看不明白的话,我们可以这样写,a = [1, 2, 3],所以a[1]就是2。
我们通过数组里面元素的序号来获取元素,并且,这个序号是从0开始的,也就是说第一个元素是0,第二个是1,而a[1],就是指a这个数组里面的第二个元素,也就是2。
现在,假如说a = [[1, 2, 3], [4, 5, 6], [7, 8, 9]],就是a本身是个数组,而a里面的3个元素也是数组,那这个时候 a[1]是什么呢?
答案是[4, 5, 6]。
那么a[1][2]呢?
a[1][2]其实就是[4, 5, 6][2],所以a[1][2]就是6。
如果还是不能理解,那么就多看几遍教程吧。
最后,也是最重要的,就算你理解不了,你也至少一定要会使用battle_abilityTrigger?(battler)这个方法,直接复制粘贴过去使用就好了。
不要求你完全懂,但要求你要会用,这是做所有一次性特性的基础。
那么,这个方法该怎么用呢?
很简单,在你所有想要一场战斗只会发动一次的特性里,都加上检查这个方法就好了。
那么我们就把这个方法加到涅槃里——
原本#掉的两行就可以删掉了,现在这样的写法更简洁,只需要一行就可以达到我们的要求。
这一行next if battle.battle_abilityTrigger?(battler)里面,if就是如果怎么怎么样,就是看if后面的检查,或者说条件,是false还是true,而我们前面也说了,battle.battle_abilityTrigger?(battler)要么是false,要么是true;
这一行里面的next,就是下一个的意思,意思就是说,如果if成立,或者说if后面的检查是true,那么就直接下一个,而不会执行next后面的代码。
在这里的意思就是说,如果特性已经发动过了,就直接跳过。
另外就是,因为battle_abilityTrigger?(battler)这个方法是写在Battle里的,所以你要对battle调用这个方法。
这样的话,现在这个涅槃就只会发动一次了。
另外,关于这个next,如果你转不过弯来的话,你也可以这样写,if !battle.battle_abilityTrigger?(battler),就是不写next,但是要在检查的内容前面加个!,注意,这个是英文的叹号。
这样写的意思是,当battle.battle_abilityTrigger?(battler)是false的话,才会执行if里面的内容,具体写出来就是这样的——
注意,这样写的话后面不要忘了end,因为你现在是初学,所以你就记住就好了,有if,就要有end,if和end是成对出现的。
只有一种情况下会有if,但是没有end,那就是if里面的代码只有一行的时候,我们来看一个特性——
这个是冲浪之尾,它这样的写法就是有if,但是没有end,同样的,你也可以把它改成这样——
实际上,我还是建议你用if和end对,甚至连next都不要用,转来转去脑子绕来绕去还可能搞错了,还不如统一就用if和end对,逻辑上最简单直白,也不会错。
你现在也不需要去考虑性能啊效率啊什么的,先能实现效果再说,这些以后再去考虑。
接着,我们再来看一个——
这个是我们之前写的活力焕发,那么我们想让这个特性只发动一次的话呢,就可以直接这样做——
请自行测试涅槃和活力焕发是否现在真的只会发动一次了。
视频预览(0702)——
浏览附件2024-07-02 14-41-59.mp4
浏览附件2024-07-02 14-43-49.mp4
那么这期教程就到此为止,下期教程我们将会讲解如何使用基于battler的变量。
感谢阅读。