Document 中文
什么是伪代码?什么是伪代码系统?
广义上, 伪代码是假的代码, 但它往往用来描述了某项工作的工作流程, 所以往往伪代码是面向过程的. 当然每个人写的伪代码习惯/能力不一样, 可能也会面向对象.
这里的伪代码系统是面向过程的, 语法非常简单.
PseudoCode 是为了解决游戏配表灵活性而诞生的, 可在任何可填入字符串的地方填入代码, 语法简单易用。支持鼠标编程,原生异步执行, 与多线程完美融合,兼容同步执行。
例如一个角色的技能释放过程是这样的: 在角色的前方释放一个火球并飞向敌人, 为敌人造成M点伤害, 并为敌人施加一个负面效果-----燃烧N秒, 每秒造成 P 点伤害.
那么策划配置的伪代码可以这样在Excel表格中填写:
“角色释放飞弹类法术(创建火球(), 获取当前敌人(), 计算技能伤害(), 创建燃烧负面效果(N, P))”
其中:
角色释放飞弹类法术 / 创建火球 / 获取当前敌人 / 计算技能伤害 / 创建燃烧负面效果 这些都是伪代码中的方法, 简称: 伪方法.
N, P 可能是常数, 所以直接填入数字.
M 则不是常数, 而是根据释放技能的角色属性和技能本身决定的所以需要计算, 伪代码中调用: 计算技能伤害() 方法来获得计算值.
创建火球() 类似的方法可以有: 创建冰晶() / 创建水球() / 创建能量球() 等
获取当前敌人() 类似的方法可以有: 随机周围一个敌人() / 随机一名友方() / 自己() 等
创建燃烧负面效果() 类似的方法也可以有很多: 创建诅咒负面效果() / 创建回血正面效果() 等
类似的方法越多, 组合就越丰富, 灵活性越强!
那么写出这样的流程代码出来后, 怎么让此代码能够正常工作呢? 怎么让伪代码成为真正的代码呢?
答案是: 需要完成所有”伪”方法对应的”真”方法, 这样的话伪代码才能被运行.
伪方法: 角色释放飞弹类法术(法术飞弹实体对象, 被攻击对象, 伤害数值, 负面效果) --- 在C# 中实现对应方法名的函数, 此函数接收参数为: 法术飞弹实体对象, 被攻击对象, 伤害数值, 负面效果. 此函数的作用是调用接收到的参数来完成: [法术飞弹实体对象]飞向[被攻击对象]的动画, 当飞弹撞击到被攻击对象时, 给[被攻击对象]造成[伤害数值]点的伤害, 并且施加[负面效果].
伪方法: 创建火球() --- 程序员在C# 中实现对应方法名的函数, 此函数用来完成: 调用资源在游戏中生成火球对象.
为什么不直接在C#中来实现所有的技能呢? 当技能数量很多时, 比如超过一百个技能时, 使用伪代码的优势就体现出来了. 程序员不需要按照这一百多个技能的要求一个接一个地去实现出来, 而是技能策划按照程序员提供的接口(也就是伪方法)去灵活设计. 技能策划类似于玩拼图游戏去完成一个个不同的技能.
中大型工作室推荐的工作流程: 策划先出一些需求, 程序员根据这些需求去拆分成一个个伪方法并在C# 中实现, 再将伪方法和伪方法文档提供给策划, 策划再使用这些伪方法按照伪代码语法编写组合成一个个新的流程出来, 期间可能需要程序员不断补充新的伪方法, 随着伪方法越来越多, 伪代码能够发挥的空间越来越大, 能编写出的效果也越来越灵活. 程序员在实现伪方法如果按照PseudeCode规范去写的话, 伪方法的使用文档能够自动生成, 可在只打开Unity的情况下查看文档.
PseudoCode 特别适合个人游戏开发者或者小工作室, 往往一人兼顾多种工作的情况. 比如写代码的兄弟除了需要写代码本身的工作外可能还要承担游戏玩法设计的工作, 这时候使用PseudoCode 可以不用过多地与人交流新增的伪方法的用途和使用方法. 当需要新增人手时, 使用PseudoCode规范编写的伪方法也能在Unity内GUI文档中方便地呈现伪方法的用途和使用方法.
伪代码不仅仅只是技能系统中可以使用, 对于RPG游戏来说, 游戏的剧情和任务几乎是必不可少的玩法, 而剧情和任务中, 尤其是任务会有大量需要配置的条件判断和执行过程, 条件判断和执行过程皆可使用此伪代码系统来实现.
如果为 PseudoCode 实现一个 Runtime 中的用户编辑界面的话,甚至可以让玩家在游戏中实时编程,或为游戏制作MOD,制作创意工坊作品。
游戏开发中只要是需要灵活配置条件判断和执行过程的地方都可以使用伪代码系统来实现.
伪代码语法:
语句: 用’;’隔开.每个语句由一个作为”根”的伪方法构成.
伪方法: “方法名(方法参数0, 方法参数1, ...)”, 方法名前后或参数前后的任何空格或换行符或制表符不会对伪方法执行有任何影响.
方法参数: 给方法传入其运行所需的参数, 方法才能被正确执行. 每个方法参数都可以由一个伪方法运行后返回.
基本类型: 整数/浮点数/布尔数/字符串, 对应着c# 中的int / float / bool / string 类型. 伪代码中的整数会被伪代码解析器解析为int类型, 小数会被解析为float类型.
空对象: [NULL], 用来表示一个 object 类型是 null 对象.
代码块方法: NewAction(伪代码语句) 和 NewFunc(一句有返回值的伪代码语句) , 这两个特殊方法的参数是伪代码语句, 它们封装一个伪代码语句为一个代码块提供给其他需要代码块参数的伪方法作为参数. 比如使用到 IF() 或 While() 等逻辑控制或循环控制的伪方法中.
伪代码编辑器:
伪代码可以是以字符串形式填入, 也可以是伪代码对象( PseudoCode对象)的形式存在. PseudoCode对象更加方便在UnityEditor中编辑. 如果是要在Excel表配置, 则只能是填入字符串形式的伪代码.
什么是PseudoCode对象?
当在MonoBehaviour 或 ScriptableObject 子类中定义一个类型是PseudoCode的变量, 那么这个变量就是PseudoCode对象.
PseudoCode对象可以直接在UnityEditor的Inspector中编辑, 配合伪代码的方法文档窗口更加方便编写:
另外PseudoCode对象可以在伪代码编辑器窗口编辑, 这种方式编写比较推荐, 有时候需要两种方式相互配合来编写.
点击 + 号, 或点击右侧的齿轮按钮弹出菜单, 弹出菜单中选择[替换为(Change To)...]项后, 将打开类似下面中所示的界面, 点击界面中的伪方法按钮可以在伪代码中加入或替换为一个伪方法.
伪代码方法文档窗口:
实现伪方法:
伪方法要能够被执行, 还需要程序员在C# 中去实现其执行过程. 例如:
所有的伪方法实现都是这种格式:
[伪方法相关特性] public static void 伪方法名称(PseudoCode pseudoCode, string funcName, Action<object> callback, object[] parameters, object[] commonParameters) {
伪方法获取参数
伪方法过程代码
伪方法返回
}
伪方法获取参数 :
伪方法通常有两种参数来源, 一种是伪代码中指定是基本类型的值还是由其他伪代码返回值; 另一种则是由伪代码外调用处传入参数, 这类参数叫做公共域参数, 因为同一个伪代码中所有伪方法共用相同的公共域参数. 注意”公共域参数”这个概念很重要, 公共域参数要求不同的两个伪方法往往不能共存于一个伪代码中, 用户使用不对的话, 往往会出现意想不到的运行错误!!!
一个伪方法中, 传入的公共域参数是同一系列, 但由于不同的伪方法使用公共域参数的方式可能不同: 有些伪方法不需要任何的公共域参数, 而有的只用到了传入的一部分公共参数, 那么意味着只要能从公共域参数中不获取或正确获取到自己所需要的参数的伪方法都是可以共存于同一个伪代码中的. 所以为了提高伪方法的通用性, 往往在实现伪方法前就应该明确规定公共域参数中可以获取到哪些值.
伪方法参数由 object[] parameters 传入, 具体传入什么参数就看伪代码怎么写了; 公共域参数则是由 object[] commonParameters 传入, 公共域参数的内容由调用伪代码处传入.
PseudoCode pseudoCode, string funcName 这两个参数绝大多数伪方法不会用到, 可以暂时忽略.
伪方法过程代码
过程代码即伪方法有何作用的体现.
伪方法返回
所有的伪方法都需要在自己的事情处理完后通过调用 callback 返回回去, 无论此伪方法是否具有返回值都必须调用 callback 返回.
伪方法相关特性
PFClass (同: PseudocodeFuncClass) 被标记的类说明其类中的静态方法可添加到 Pseudocode 系统中去,提供伪方法.
PFC_Ignore (同:PseudocodeFuncClass_Ignore ) 被标记的方法会被Pseudocode 系统忽略, 不提供伪方法.
PFC_FindingPath 为伪方法或类指定在选择方法名窗口中的寻找路径, 若伪方法和其所在的类都标记了 PFC_FindingPath 则优先使用伪方法标记的特性.
PFC_CommonParameterField 为伪方法或类指定公共域参数字符串, 若伪方法和其所在的类都标记了 PFC_CommonParameterField 则优先使用伪方法标记的特性.
PFC_HelpInfo 为伪方法指定帮助信息: 伪方法的注释, 返回信息, 各个参数的信息.
注意: 上述所有特性都是可以不使用的, 但如果用得好的话, 可以有效地帮助到伪代码编辑人员.
伪代码在C# 中的调用方式:
设计伪代码不是为了取代C#, 而是对C# 进行补充. 要让它运行需要在C# 中进行调用.
按照使用方式分类可以分为两种, 一种是获得或创建 PseudoCode 对象后, 使用 PseudoCode 对象的方法’.RunXXX’来调用; 另一种是通过使用 PseudoCodeHelper静态类的静态方法’.RunXXX’来调用. 后者本质上是在内部调用了前者, 为了方便后续的维护, 可能以后会不再对 PseudoCodeHelper 的调用方法进行维护, 甚至被标记为过时的.
调用方式分为四类: 回调方式调用, 协程方式调用, 异步(多线程)方式调用, 同步调用. (可参考: PseudocodeDemo.cs),
在下面的介绍中, PseudoCode 首字母大写时表示伪代码类或伪代码这个插件, 而首字母小写时代表是PseudoCode 类的实例对象.
回调方式
回调方式调用时, 使用 ‘pseudoCode.Run(...Action<object> callback...) ‘, 此种方式开始运行时不会进入其他线程, 调用处于主线程, 它就在主线程执行; 调用处于子线程, 则它就在子线程中执行. 此种调用方式可以在回调中获得伪代码运行后最终返回的值. 如果运行的伪代码不存在延迟处理过程, 则最后返回的值即是同步执行返回的值.
协程方式
PseudoCode支持在协程中调用执行伪代码并等待它执行完毕. 调用语句如下: ‘yield return pseudoCode;’ 或 ‘yield return pseudoCode.FindOrCreateChild();’ , 这两句语句的区别在于当需要同时运行同一个 pseudoCode 时,为了让它们不发生冲突, 需要创建子伪代码对象, 使用它的 FindOrCreateChild 方法能够找到或创建一个空闲的子伪代码对象. 当需要在协程中获取伪代码的返回值时, 需要先将子伪代码对象暂存为一个变量, 在等待它执行后,使用子伪代码对象的 GetRunResult 方法来获取. 如果游戏中此伪代码永远不可能同时运行多个实例时, 可以直接使用 ‘yield return pseudoCode;’ 语句.
多线程方式
这种方式使用语句 ‘await pseudoCode.RunTask<...>(...)’来调用, 这种方式调用伪代码时, 会立刻开启一个Task 来运行伪代码, 至于是不是新开了一个线程则是由系统类Task 自身决定的, 绝大多数测试过程中是开启了新的线程的, 它往往不会在忙碌的主线程中执行Task任务. 这种方式可以在如同步方式一样获取伪代码运行结果: ‘var result = await pseudoCode.RunTask ...’
同步方式
这种方式使用语句 ‘var result = pseudoCode.RunSync ...’ 来获取运行结果. 这种方式不需要等待, 所以它也无法获取到需要等待才能获取到的结果. 因此这种方式通常用来处理全是无需等待的伪方法写成的伪代码, 若使用了需要等待的伪代码, 那么往往得不到想要的运行结果.
等待方式
这种方式用法和多线程方式一样, 都可用使用 await 来等待伪代码的执行, 使用语句 ‘await pseudoCode.RunAwait<...>(...)’来调用. 这种方式内部执行和回调方式一样, 不会主动开启新的Task.
PseudocodeHelper 的用法:
PseudocodeHelper.Init 对伪代码进行初始化操作.
PseudocodeHelper.AddPseudocodeFuncs 通常使用PFClass 特性为伪代码系统添加伪代码类, 被PFClass 标记过的伪代码类中的伪方法在运行时会被伪代码系统自动添加. 如果不使用PFClass 也可以通过此方法为伪代码系统手动添加伪方法类.
PseudocodeHelper.SetPseudocodeFuncs 也可以使用此方法一个一个地添加伪方法到伪代码系统中去.
PseudocodeHelper.Run 伪代码有两种存在形式, 一种是字符串形式存在, 一种是以 PseudoCode 对象的形式存在. 要运行字符串形式的伪代码就需要使用到此方法来运行伪代码. 以 PseudoCode 对象的形式存在的伪代码可以直接调用 PseudoCode.Run 方法来运行.
PseudocodeHelper.RunCode 如果伪代码是异步的, 可以在协程中等待其执行, 可以使用此方法: yield return PseudocodeHelper.RunCode(...)
PseudocodeHelper.RunAsync 开启新的Task来运行伪代码.
注意: 从版本1.3 开始, 需要对 PseudocodeHelper 静态类进行初始化, 这是因为为了提高伪代码的运行效率, 会在它初始化过程中生成伪代码的委托, 这个过程相对比较耗时所以统一放在初始化过程中去处理是比较推荐的做法. PseudocodeHelper的初始化方法Init() 是一个异步方法, 如果需要等待它, 可以使用 await 关键字. 第一次使用 PseudocodeHelper 类也会触发初始化, 但如果触发后立刻运行伪代码的话, 通常会报错. 另外如果希望尽可能减少初始化耗时, 可以在初始化之前先改变伪代码的运行模式 runMode = RunMode.Reflection 或 RunMode.Expression_Realtime. 这样的话意味着使用反射方式运行伪方法或也使用表达式方式运行,但它会在第一次使用到某个伪方法时,对它生成对应的委托.
打包设置:
伪代码实现代码往往不会在c#中有引用, 它的调用都是通过伪代码运行机通过反射来调用运行的. Unity 的 Player 优化设置项 Managed Stripping Level 应该尽量低, 尽可能不要在打包时对代码进行裁剪.
调试:
当PseudoCode类型的伪代码运行过后, 会保存最后一次运行使用的公共参数域, 此时的PseudoCode类型的伪代码会多出一个 [调试运行 (Debug Run)] 按钮, 点击此按钮后能够使用最后一次运行使用的公共参数域再运行一次, 并且在Console中打印出运行结果, 如果报错, 也会在Console 中显示出来.
注意: 只有在伪代码运行后才会出现 [调试运行 (Debug Run)] 按钮.
Last updated