废墟图书馆吧 关注:54,472贴子:745,740

[mod相关] 关于简单的来一些自设被动

只看楼主收藏回复

Hi~ o(* ̄▽ ̄*)ブ米娜桑,
当你兴致勃勃的打开编辑器,准备好一展拳脚开始自嗨之路。却发现自己那充满创造性的被动设想不存在于那一串列表中任何的文字中;又或者发现那些东缝西凑的来自都市各强者的被动名让这自嗨用的mod有着一种莫名的寒碜感吗。 那么,就让我们一起来学习如何自制一些轻自设级的被动吧。
首先我们需要一个下好的 "dnspy" 来对游戏根目录的"Assembly-CSharp.dll"进行一些前置的分析。
然后我们可以发现找到"Assembly-CSharp.dll"是一个基于"Net Framwork 4.0框架"的类库,因此为了兼容性考虑,我们在自行编写被动时会考虑采用这一有点有点臃肿的框架.
接着,我们来到"PassiveAbilityBase"这一类中。

我们可以看到这一类引用了三个名称空间"System","System.Collections.Generic","LOR_DiceSystem" .
其中"LOR_DiceSystem"并不是来自于系统自带的,因此我们可以去这一名称空间进行进一步的探索

可以看到该名称空间内定义了许多的枚举类型,而根据我们的游戏理解和一些直觉可以感知到,这些枚举类型将会和我们自定义战斗书页等将会有非常重要的作用。不过像我一样仅仅粗通皮毛的话,现在的我们还没到达这种境界,可以暂且不管。

接下来,我们来到这个类的初始化器,我们可以看到一个传入的参数,且该参数的类型为"BattleUnitModel", 通过我们游戏理解可以判断, 这两个耦合在一起的模块中这个"BattleUnitModel"的类所定义的参数与游戏中玩家的角色有密切的关联。在我们之后的被动编辑中将会多次引用变量"owner"。 同时,我们还应该阅读这一"父类"所定义的各种方法为我们接下来的自定义被动做铺垫.
那么接下来我们就可以在进行简单的被动编辑前做最后一件事,便是要记住我们一定会用到的类"PassiveAbilityBase", "BattleUnitModel", 和一个名称空间 "LOR_DiceSystem",如果有着我们想要实现的功能,我们应该先尝试从这些类里面找到是否已有对应的"方法".
举个例子, 我们想要这样一个被动"每回合开始时,将手中所有费用不为一的书页费用减一。每回合开始后,获得每一个存活的敌方单位卡组中任一一张战斗书页且将书页的费用置为零"
我们可以这样逐步的完成被动的编写
以类"PassiveAbilityBase"为父类定义一个派生类.

以关键词override 重载父类的方法 "OnRoundStart" 定义一个 "BattleDiceCardModel"的"list"泛型 通过 父类中定义的变量"owner" 访问"BattleUnitBase"定义的变量"allyCardDetail"的方法"GetAllDeck()".
之后, 通过 "foreach"语句 遍历 该泛型中的所有元素。 为每一个元素(以if else语句或switch语句)分别执行方法"AddCost()"("其中也可以通过continue"关键字完成AddCost(0)的操作)
这样,我们便完成了该被动的一半。接下来我们照猫画虎,即可完成剩下的一半(可通过控制跳出循环的语句控制加入手中的书页。)

当然,如果觉得这加卡加卡过于imba,可以通过方法"AddBuf()"为书页添加"佚亡"效果,至于编写完被动之后就是对.xml文件进行编辑了。


IP属地:四川1楼2022-12-04 22:43回复
    在废墟图书馆中学习java


    IP属地:江西来自Android客户端2楼2022-12-04 23:11
    回复
      是自制dll教学吗?如果是相对简单的照猫画虎/缝合的话我已经会了,但还是支持
      然而再细看楼主的描述有这么多专业名词,没怎么看懂
      可能是我个人是原因……
      如果还有后续的话蹲一手,还是尽力学一学


      IP属地:广东来自Android客户端3楼2022-12-04 23:13
      收起回复


        IP属地:江苏4楼2022-12-04 23:48
        回复
          好事,也没系统研究过这个。。


          IP属地:辽宁5楼2022-12-04 23:50
          回复


            IP属地:江苏来自Android客户端6楼2022-12-05 00:07
            回复
              em..我学过一点点java,感觉很多名词都是能对上的?先收藏了


              IP属地:加拿大来自iPhone客户端7楼2022-12-05 01:55
              回复
                Hi~ o(* ̄▽ ̄*)ブ, 现在是 ——番外时间。
                在对我之前提到的那些杂七杂八的方法与类进行分析之前,我想要先来一点基础的引入 ——"C#中 名称空间,类,方法。"
                在c# 中 名称空间是使用关键字"namespace"声明的用于区分一个程序"内"和"外"的组织系统。 如果不使用"顶级语句",那么这就是我们进行编译的基础。(一些IDE在开始时便会给你创建一个新的名称空间和一个新的类)。
                而一个名称空间由两个部分组成"正文" 和 "空间名称"。 就好像一个商品和他的条形码一样。 我们可以通过using指令实现引用某一名称空间。
                对于名称空间我们常使用指令 "using"和 "alias" 进行操作。 当然,由于我目前并不太熟悉c# ,因此我目前就不细讲这两个操作指令误人子弟了。
                接下来就是"名称空间"的正文部分。 就好像现在的一袋饼干,拆开最外层的包装(进入名称空间),还有各具特色的小包装。并不能直接在这个名称空间里面声明方法,值,变量。 我们只能在名称空间里定义"接口", "类", "结构体", "枚举类型", 和"委托",这几个"小包装"
                而在这篇小小的番外中我们就小小的探索一下"类"这个小包装 。
                "类" 在c# 中是通过 关键字"class" 声明的时候有一点不一样。 "namespace"这个大包装是隐式添加了修饰符"public"并且命名空间的声明不能包含任何访问修饰符, 而我们在声明"class"时虽然默认是"private"但我们还可以根据实际需求,面向对象,进行修改。
                那么这简短的番外扯皮到此结束,接下来是万里长征第一步——对类"PassiveAbilityBase"这一个类的某一方法进行分析.


                IP属地:四川8楼2022-12-05 12:21
                回复

                  我们首先来到这第一个用关键词"virtual"修饰的方法。(virtual 关键词常用于类的继承和多态上,在子类中可以通过关键词"override"进行重构)。
                  可以看到,调用这个方法会返回一个布尔值,且调用该方法需要传入一个"BattleUnitMode"类的变量.
                  接着我们通过我们的游戏理解,可以大致判断这个方法可以决定 这个被动的持有者是否可以被选为目标。为了验证我们的猜测,我们使用dnspy自带的分析来到调用了该方法的地方

                  接着我们可以看到这个方法的调用逻辑:在方法"IsTargetable(BattleUnitModel attacker)"这一方法中。月计程序员通过foreach语句遍历了这个角色的所有被动(而类似的循环套用还有很多, 内存和cpu:月计!你为什么要这么做! 你想要钱码)。 如果某一被动中满足条件 "isActivated " 和 "Is" "!IsTargetable(BattleUnitModel attacker)" 为真则终止循环,返回一个布尔值"false"。("!"为取反操作就相当于真假互换) 。
                  我们继续进行分析(月计代码的可读性,只能用悲剧来形容)。
                  嵌套并没有结束,这个套娃只是开始罢了(悲), 可以看到,我们的猜测很正确,这个方法确实和这个角色是否能被选为目标很有关系。 我们继续来进行对这个方法进行分析
                  这又是一个返回值为布尔类型的方法。 传入了一个BattleUnitModel 类的参数 而方法由以下的参与决定
                  一. 改类中定义的布尔变量_isKnockout(取非)与类中定义的方法IsExinction(), 两个来自有关于定义了buff的方法"NotTargetRemoved()",和一个来自定义了被动的类中的方法。
                  二. 根据"条件或"的特性可知如果角色NotTargetRemoved()返回值为真,则不会执行之后的语句。
                  三.我们可以通过被动或buff给角色赋予不可选为目标的效果。
                  同时,对于变量"_isKnockout"和 类"BattleUnitModel"所定义的方法"IsExinction"的分析我会以番外的形式唠嗑吧


                  IP属地:四川9楼2022-12-05 13:07
                  回复
                    楼主能教一下置入书页和把书页置入ego吗,想改一下红姐的ego但是不知道怎么拿自制书页的ID和mod的ID


                    IP属地:河北来自Android客户端10楼2022-12-14 09:43
                    收起回复


                      IP属地:四川15楼2023-02-01 18:34
                      回复
                        牛逼,设精了。


                        IP属地:浙江来自Android客户端16楼2023-02-02 02:20
                        回复
                          因为等了好久也没人来提问相关问题,我就继续随便讲讲一些Harmonypatch的东西吧.
                          首先需要把相关的dll添加到引用,然后看个人喜好的进行一个起手式,比如我就喜欢这样

                          然后就可以开始编写未来的HarmonyPatch了,如果没有什么和别人一起搓代码,那么可以给自己写一些手摇轮椅,比如下面这种

                          主要还是让自己用的舒服,少出错。如果出现了现在做的手摇轮椅解决不了的事情就单独写就好。
                          之后便是正常的开始,我这里简单的来一个示范。
                          比如现在我想让烟气没有上限,我就该对BattleUnitBuf_smoke类的方法OnAddBuf(int addedStack)进行修改。
                          因此我就这么做了。

                          然后就是游戏实践可以看到确实有效


                          IP属地:四川17楼2023-02-14 21:08
                          回复
                            今天,我们来编一点高级点的harmonyPatch —— 给我们的关卡排一个位置;
                            首先我们先要编写一个正常的关卡,
                            跟用编辑器写出来的关卡,我们主要补充以下两行

                            其中<StoryType></StoryType> 一行是我们用来定向检索关卡的标签。
                            接着就是编写代码了。
                            首先我们可以先明确我们的需求, 需要Patch那些类的那些方法。 需要定义那些变量, 需不需要为特定的功能通过接口实现。不过像我这样的小白自然还是选择的边写边定义了。
                            首先是文件操作部分。 我们需要读取文件夹的图像文件,并转换为Unity中的Sprite类。因此可以得到如下代码
                            文件操作的方法上我们首先需要引用 System.IO。 同时考虑到文件操作路径,我们可以将当前类库所在的路径设定为根目录,因此可以用Assembly.GetExecutingAssembly().CodeBase 获取当前程序集的最初位置,将之转换为UriBuilder类后通过属性 Path 获得由Uri引用的资源路径。 通过 Uri.UnescapeDataString(string stringToUnescape)
                            方法将之转换为字符的非转义表现形式输出。通过Path.GetDirectoryName(string path)获得路径信息的字符串。
                            类似的样板如下

                            文件操作可通过以下代码获取文件信息

                            综上所述, 读取图像文件并转换为Sprite类最终可以如下代码实现

                            接下来,就是对游戏程序集的相关类的相关方法编写HarmonyPatch了;以下图片中的实现并非必要,可自由发挥


                            主要需要考虑,且最容易出现严重bug的方法为对 UIStoryProgressPanel类的SetStoryLine()的方法的HarmonyPatch
                            首先为了节省体力,我们可以直接获取一个已有的图标进行修改,因此可以考虑使用 list<T>.Find(Predicate<T> match)的方法定向查找。
                            查找后 我们通过反射挖掘UIStoryProgressIconSlot类的 iconList字段, 并通过Unity.Object.Instantiate<T>(T original, Transform parent) where T : Object 方法复制实体。
                            将复制后的实体调用对应方法进行加载。
                            之后我们需要做的就只是调整坐标和他的字段 connectLineList 的相关位置了。以下给出一个可能的实现过程(参数我乱填的,不要在意)建议考虑根据实际情况和遇到的bug进行修改。。



                            IP属地:四川18楼2023-02-20 10:31
                            回复
                              大大用dnspy修改被动的时候上面的token要怎么处理


                              IP属地:福建来自Android客户端19楼2023-06-23 14:53
                              回复