首页 > 其他分享 >软件构造思想在Unity项目中的实践举例(2)

软件构造思想在Unity项目中的实践举例(2)

时间:2024-05-28 20:54:48浏览次数:14  
标签:unote UNote pos 谱面 Unity 举例 修饰 软件 speed

本文系笔者在学习软件构造课程期间所写,不保证通用性和正确性,仅供参考。

目录

  1. 前言
  2. Spec撰写与Test First
  3. 防止表示泄漏
  4. 重载与修饰
  5. 结语

一、前言

详见上一期软件构造思想在Unity项目中的实践举例(1),这是一个早早就选好题但因为懒才拖到现在的系列。我将介绍我的一个正在工作中的unity项目:一款自制音乐游戏,而本期将围绕其目前最复杂的一个过程:读取谱面文件,并据此生成关卡。在这其中,软件构造的思想和规范在很大程度上帮助了我把这个过程从想到什么写什么变得更加规范,优雅。

挖坑不填是这样的

可以通过这份游戏内截图直观感受一下这个项目大概是什么样的:

当前进展

二、Spec撰写与Test First

整个过程主要在一个类:ChartReader中完成。给定文件路径,ChartReader会读取并分析文件内容,返回一个解析好的谱面内容ChartInfo类。

在这里先提一个关于内存分配的设计:从上面的概括看出读取谱面的这个过程完全可以成为一个静态方法,然而读取过程复杂,需要划分成很多小函数,还涉及到一些本地变量,不适合把所有用到的变量都设为static。所以定义了一个public的静态方法和一个private的动态方法,静态方法实际上是声明了一个动态对象,再用该动态对象读取文件。这样的好处一方面在于内存分配在栈上,谱面读取完后就会被清理,另一方面也便于多次调用。

    public static ChartInfo Read(string levelPath, int difficulty, float speed)
    {
        ChartReaderNew chartReader = new();
        return chartReader.InClassRead(levelPath, difficulty, speed);
    }

    private ChartInfo InClassRead(string levelPath, int difficulty, float speed){...}

2.1 用Spec规范方法

回到这一块的主题。由于谱面文件中会涉及到对很多物体的描述,也就意味着类中会有很多不同的方法,这些方法堆在一个类里,回头再来看很容易就看晕了,这时候spec就很有用了,它描述了每个方法是干什么的,输入格式是什么,返回什么,很快就可以想起之前是怎么实现的。另外,写完文档注释后,在其他地方遇到这个方法,鼠标划上去就能看到描述,也十分方便。

举个例子,游戏中的音符有一种是UntrackNote(简称为UNote),即没有轨道的Note,虽然它在游戏中的表现是这样的,但实际上还是附着在一个透明的轨道上。在谱面文件里自然不需要体现它的轨道,所以一个UNote在谱面文件中表现为一行:

unote(timeJudge, width, pos, speed);
timeJudge:判定时间|正整数,width:Note的宽度|正浮点数,pos:Note的水平位置|任意浮点数,speed:下落速度|任意浮点数

主方法中,用正则表达式匹配到unote后,将得到的匹配作为参数传给处理函数HandleUNote(Match match)。怎么确定传进来的Match就是合法的匹配呢?这时候spec就派上用场了:

/// <summary>
/// 将一个由unote行匹配得到的Match转换为一个UNote对象。<br/>
/// unote(timeJudge, width, pos, speed);<br/>
/// timeJudge:判定时间|正整数,width:Note的宽度|正浮点数,pos:Note的水平位置|任意浮点数,speed:下落速度|任意浮点数<br/>
/// </summary>
/// <param name="match"> 该Match的Groups中的捕获组需要依次符合上述数据的规定。</param>
/// <returns> 转换好的UNote。如果Groups长度不够,会抛出异常。</returns>
private UNote HandleUNote(Match match){...}

因为是private的方法所以注释得没有非常非常规范正式了。但是这么标注一下后,调试起来就轻松多了。有一说一,C#使用XML风格的文档注释,倒是没有Java来得直观与方便。

事实上,除了在ChartReader类内的这些注释,在其他很多地方,我也在逐步完善spec,spec的实现对个人来说有效地避免了过几周如看天书的情况,以后如果与别人合作了也方便交流与规范。

2.2 制定测试用例

ChartReader天然地适合测试优先的思想:要做什么已经很明确了,而复杂的内部逻辑又很难保证不出bug。在实现之前,先把测试用例想好,对于实际编写也大有脾益。鉴于是unity,最好的测试方式当然是实际运行起来跑一遍了!

还是以UNote为例子,接下来引入谱面文件中的另一个概念:修饰行。修饰行修饰其前面的元素,但本身并不能作为一个实际的物体存在。如下例,单独的unote只会垂直下落,而pos修饰行使其拥有了水平移动的能力:

unote(1000, 1.0, -1.5, 1);

pos(0, 3.0, i, 500, 0, o);

则pos行蕴含了这么一个信息:0ms时,unote在水平3.0的位置上,通过"i"曲线移动,直到500ms移动到水平0的位置上,再通过"o"曲线移动,直到1000ms (timeJudge)移动到水平-1.5的位置上。

这时就有很多可能了:unote的速度为负会怎样?unote的时间超出了音乐长度会怎样?单独一条pos行会怎样?pos行中的时间序列非递增会怎样?pos行中的时间超出了unote本身的时间会怎样?...在制作测试用例时明确了这些问题,实现的时候思路也会清晰很多。

像这样的修饰行还有很多,这也是导致这个工作复杂的主要因素之一。

三、防止表示泄漏

对于一个unity项目来说,到最后总归是要把项目整个打包起来的,内部某个方法是否有表示泄漏,似乎并不太重要。然而,在文件读取的过程中,表示泄漏的问题也有了十分的现实意义。

这还得说到上文介绍的修饰行,例如对unote而言,还有另外一个修饰行speed,容易想见它是用来精细控制音符的下落速度的。

对应的,在UNote类中,有一个域叫speedList,存储着这个UNote在各个时间段的运动速度。

一开始,我的思路甚是简单粗暴:直接把这个域设为public,读谱的时候直接给这个域赋值就好了。学习软件构造后,我认识到这种方法的大问题所在:太表示泄漏了!虽然没有什么不怀好意的人攻击我,但是由于还有其他类型的修饰行,还需要经常对UNote进行操作,很难保证在其他方法中不会一不小心又把speedList改了,两次操作叠加,最后产生bug。于是,改进后的UNote改为用一个方法来为speedList赋值,在方法中进行了必要的转化和防御性拷贝。

反过来想,这样做不仅是防止了表示泄漏,也是对相关操作的规范化。当遇到速度相关的修饰行时,便不再是想到哪步就随意地改一改speedList,而是要等到整个步骤完成后用完整的结果进行赋值。显然,这样的规范化对代码的可维护性也大有好处。

四、重载与修饰

在一开始构思谱面文件语法时,我希望它是一个尽可能自由的语法,因此提供了一些语句的“重载”。比如上文提到,unote行的格式为

unote(timeJudge, width, pos, speed);

事实上,语法中还设计了一种省略了speed参数的unote行写法:

unote(timeJudge, width, pos);

在一开始的设计中,我要求如果采用了省略的写法,则该unote行必须有一个speed修饰行来提供速度。这看起来好像挺合理的,是吧?可是实际实现起来,就会发觉这其中遇到了许多困难,尤其重要的是不便于维护:如果还可以省略pos或者width呢?自由度变得更高了之后,对缺省的处理和对必要修饰行的检查就会变得繁琐且困难。

软件构造中讲到的重载以及decorator设计模式给了我启发,于是我更改一开始的语法规则:

现在,每个表示某个游戏物体的语句本身必须可以在没有修饰行的情况下单独表示一个行为完整的游戏物体;所有的修饰行,是在一个完整的游戏物体的基础上覆盖它原有的属性,或者为它装饰上新的属性。

这个改变令我豁然开朗:原来的修饰行既可以游离又可能不可缺失,而现在的修饰行就相当于一个个插件,插在游戏物体上,改变它的行为。这种改变,无论是对客户端(玩家、制谱师),还是对代码实现,逻辑都清晰很多。基于这个思想,还可以扩展出双行绑定的修饰行、嵌套的修饰行......开发顿时变得十分顺利。

我仍然保留原来的省略语法:unote可以没有speed参数,但是现在它可以不需要speed修饰行,而是像方法的重载一样,设置了speed的默认参数:令其为1。

五、结语

由于本文主要是介绍在项目实践中体现的软件构造思想,因此并没有详细地解释整个ChartReader的实现原理,甚至在一部分中对实际实现的叙述有一定的修改。相信对有音乐游戏游玩经验的读者来说这篇文章应该会挺好理解的;如果没有接触过音乐游戏的话,也希望我的叙述足够直观吧。

标签:unote,UNote,pos,谱面,Unity,举例,修饰,软件,speed
From: https://www.cnblogs.com/Senolytics/p/18150977

相关文章

  • 切勿安装这五款流氓软件,你中招了没
    流氓软件,又称为恶意软件,是一类设计用来损害用户设备、窃取信息或干扰正常使用的程序。以下是五款臭名昭著的流氓软件介绍,提醒切勿安装,只能说一个比一个毒,你中招了没可以去去虚拟机试试谁的毒更强一些,最后打开的浏览器是谁的呢?结尾为你揭晓一、2345浏览器导航二、金山毒霸......
  • Vim安装与配置教程(解决软件包Vim没有安装可候选)
    一、Vim检测是否安装1-输入vi查看是否安装;2-按Tab键,显示以下字符为未安装;3-显示以下字符为已安装(可以看到有Vim)二、Vim安装过程1.打开终端,输入  sudoaptinstallvim;2.输入Y/y,回车确定,显示安装包无法下载;3.输入下载指令sudoapt-getinstallgnome-shell,显......
  • 数字信号处理实验三:IIR数字滤波器设计及软件实现
    一、实验目的1.掌握MATLAB中进行IIR模拟滤波器的设计的相关函数的应用;2.掌握MATLAB的工具箱中提供的常用IIR数字滤波器的设计函数的应用;3.掌握MATLAB的工具箱中提供的模拟滤波器转数字滤波器的相关的设计函数的应用。二、实验内容本实验为综合性实验项目,要求通过利用MAT......
  • 二次元ai绘画图片软件哪个好?试试这些图片创作利器
    画画一直是许多人的爱好。可对于没有学过的人来说,将想象中的画面呈现出来并不容易。长时间的绘画练习让很多人望而却步,直到ai绘画功能的出现,让人们有了新的途径。即使是没有基础的人,也可以轻松地将自己的想象转化为美丽的画作,这激发了更多人对于绘画的热情。想知道这种ai绘画......
  • 想要拥有漫画脸滤镜软件?快来试试这些
    六一就快到了,在这个特别的日子,拍照成了记录快乐瞬间的必备环节。但如何让这些照片变得更加生动、更加难忘呢?借助照片变漫画功能,可以把孩子的照片变为漫画形象,为孩子在这个六一留下深刻印象。等将来家长与孩子一起重温照片时,能更添一份感动。接下来就为你讲讲几款照片变漫画......
  • BookxNote Pro 宝藏 PDF 笔记软件
    一、简介1、BookxNotePro是一款专为电子书阅读和学习笔记设计的软件,支持多种电子书格式,如PDF和EPUB,能够帮助用户高效地管理和阅读电子书籍,同时具备强大的笔记功能,允许用户对书籍内容进行标注、摘录和思维导图绘制等操作。它还支持将笔记导出为多种格式,如Anki、Xmind、PDF等......
  • Captura完全免费的电脑录屏软件
    一、简介1、Captura是一款免费开源的电脑录屏软件,允许用户捕捉电脑屏幕上的任意区域、窗口、甚至是全屏画面,并将这些画面录制为视频文件。这款软件具有多种功能,例如可以设置是否显示鼠标、记录鼠标点击、键盘按键、计时器以及声音等。此外,Captura还能从网络摄像头捕获视频......
  • 举例说明你对尾递归的理解,有哪些应用场景
    一、递归递归(英语:Recursion)在数学与计算机科学中,是指在函数的定义中使用函数自身的方法在函数内部,可以调用其他函数。如果一个函数在内部调用自身本身,这个函数就是递归函数其核心思想是把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解一般来说,递归需......
  • 软件测试|面试常见十个题目(附答案),收藏好!
    金三银四的求职季如期而至,如何在这场求职大战中脱颖而出,斩获心仪的职位,前提是要做好充足的准备!接下来跟大家分享学员在面试中经常被问到的十大问题,希望对大家有启发和帮助。需要更多题库资料,简历优化辅导的话亦可联系上老师:flyhappy1111、请介绍一下你最近测试的项目举例最......
  • 最新整理|软件测试常见项目测试点&面试问题分析
    大家好!我是川石教育的老黄,最近更新了一门课程:软件测试常见项目测试点&面试问题分析。之所以录制这门课程,是因为发现大家面试的过程中,被问及实际项目的时候回答不好,说不清楚项目的数据流、业务流、测试点等等系列问题,最终导致面试失败。这门课程也是我在辅导过5000多名学员就业......