一、循环的本质与边界
循环是处理重复性工作的利器。在游戏引擎中,update函数就是一个主循环,每帧被调用一次,维持着游戏的持续运转。编写的循环,用于处理有规律的批量操作,比如创建网格状的卡片阵列,或是遍历节点的子节点集合。
for循环的三个部分各司其职:初始化只在进入时执行一次,条件判断决定是否继续迭代,迭代操作则确保循环能够推进。这三个部分用分号分隔,初始化部分甚至可以同时定义多个变量。值得注意的是,迭代操作不一定要放在括号内,写在循环体末尾同样有效,只是规范性稍差。
当处理大量数据时,循环的性能细节值得关注。遍历数组时,将长度计算提取到循环外部,采用倒序遍历的方式,可以避免每次迭代都重新获取数组长度,这在处理数百个对象时能显著减少卡顿。
死循环是初学者最常踩的坑。条件始终满足、缺少迭代操作、或者迭代方向错误,都会导致循环无法退出。在编辑器预览模式下,死循环会直接卡死整个引擎,未保存的工作可能丢失。养成在模拟器中测试循环代码的习惯,是避免灾难性后果的有效方法。
二、遍历数组的多种姿势
TypeScript提供了几种遍历数组的方式,各有适用场景。传统的索引循环适合需要同时操作索引和元素的情况;for-in循环拿到的是索引,但类型是字符串而非数字,若要进行数学运算需要先转换类型,在字符串前加加号是最简洁的转换方式;for-of循环直接拿到元素值,代码更简洁,但无法获取索引信息。
遍历节点子节点时,for-of尤为方便,无需关心数组长度,直接迭代每个子节点对象即可。选择哪种方式取决于具体需求:需要索引用for-in或直接索引循环,只需要元素值用for-of。
三、流程控制的关键字
break、continue、return三个关键字都能中断执行流,但作用范围截然不同。break用于switch语句中结束匹配,也用于循环中终止整个循环;continue仅跳过当前这一轮循环,进入下一次迭代;return则直接结束整个函数,不管当前处于多少层循环嵌套中。
在嵌套循环中,break只能跳出当前所在的循环层级,无法一次性跳出多层。如果需要从深层循环中直接返回,return往往是更直接的选择,但这意味着整个函数的执行就此终止。
四、函数封装的艺术
函数是将代码模块化、可复用的基本单元。TypeScript的函数写法比C或Java更简洁,返回类型可以省略,引擎会根据return语句自动推断。但这不意味着可以随意省略——当你希望函数必须有返回值时,显式声明返回类型能让编译器帮你把关,漏写return时会及时报错。
函数命名遵循小写开头的惯例,用有意义的动词描述其功能,比如move、rotate。参数列表中的参数称为形参,调用时传入的实际值称为实参。参数之间用逗号分隔,数量没有严格限制,但过多的参数会影响可读性,此时可以考虑将相关参数封装成对象传递。
默认参数是个实用的特性。比如播放背景音乐时,默认循环播放,特殊情况下才播放一次。默认参数必须放在参数列表末尾,否则调用时省略参数会产生歧义。可选参数则通过问号标记,表示该参数可传可不传,函数内部需要判断其是否存在再决定逻辑分支。
五、参数传递的深层机制
理解值传递与引用传递的区别,是写出正确逻辑的前提。数字、布尔等简单类型采用值传递,函数内对参数的修改不会影响外部变量;数组、对象等复合类型采用引用传递,函数内修改的是同一块内存地址的数据,外部变量会同步变化。
这个差异在数组操作中尤为明显。将数组传入函数,在函数内修改数组元素,外部的数组确实会发生变化,因为传递的是数组的引用而非拷贝。而传入单个数字,在函数内自增,外部的数字保持不变,因为只是拷贝值的修改。
剩余参数让函数能够接受任意数量的参数。通过在参数名前加三个点,传入的多个参数会被收集成一个数组。这在实现累加器、日志打印等需要可变参数的场景中非常方便,一个函数就能处理两个数相加或十个数相加的情况。
六、匿名函数与箭头函数
没有函数名的函数称为匿名函数,通常赋值给变量使用。匿名函数可以作为参数传递给其他函数,这种用法在回调场景中极为常见,比如资源加载完成后执行某个操作、动画播放完毕后切换场景。
匿名函数与箭头函数看似相似,实则在上下文绑定上有本质区别。匿名函数在运行时动态查找this指向,容易因上下文丢失而报错;箭头函数在定义时静态绑定this,继承外层作用域的上下文,行为更可预测。在事件回调中,如果回调函数需要访问当前类的成员变量,使用箭头函数可以避免未定义的错误。
将函数作为参数传递时,传递的是函数引用而非执行结果,因此不要加括号。如果回调函数需要参数,但调用方不提供参数传递,可以通过包装一层匿名函数来解决——外层函数无参数,符合调用方的要求,内部再调用实际需要参数的函数。
七、递归的逻辑与陷阱
递归是函数调用自身的编程技巧,与循环类似但思维方式不同。递归必须有一个明确的退出条件,否则会变成无限递归,最终导致栈溢出崩溃。计算斐波那契数列、求阶乘、遍历树形结构都是递归的经典应用场景。
递归的优雅之处在于将复杂问题分解为相似的子问题。求第N项斐波那契数,只需要知道第N-1项和第N-2项,而这两项又以同样的方式求解,直到抵达已知的初始条件。这种自顶向下的分解思路,在处理层级结构数据时尤为有效。
但递归也有代价:每次函数调用都会消耗栈空间,过深的递归会导致栈溢出。在实际项目中,循环调用配合定时器是更稳妥的做法,比如向日葵生产太阳的动画序列,通过延时回调实现循环而非直接递归。
八、触摸事件系统的响应机制
游戏离不开输入,触摸是最常见的移动设备输入方式。事件系统采用观察者模式:注册一个事件监听器,指定事件类型和回调函数,当事件发生时引擎自动调用回调。
触摸事件分为三个阶段:手指接触屏幕时触发start,在屏幕上滑动时持续触发move,离开屏幕时触发end。还有一个cancel事件,用于处理非正常离开的情况,比如来电打断、手指滑出屏幕边界。理解这四个事件的触发时机,是实现精准触控的基础。
全局触摸与节点触摸是两种监听方式。全局触摸响应屏幕任意位置的输入,适合虚拟摇杆等需要在空白区域滑动的操作;节点触摸只在特定节点范围内响应,点击节点外部不会触发,适合按钮、可交互物体等需要精确点击目标的场景。
九、坐标系的转换思维
触摸事件返回的是屏幕坐标,以屏幕左下角为原点。而游戏物体的坐标通常是本地坐标或世界坐标,以画布中心或世界原点为参照。直接混用不同坐标系会导致物体位置错乱,必须进行坐标转换。
将屏幕坐标赋值给物体时,应使用设置世界坐标的方法,而非直接修改本地坐标属性。因为屏幕坐标与世界坐标在画布未偏移时是重合的,而本地坐标是相对于父节点的,参考系不同。
实现拖拽移动的常用技巧是记录差值。在触摸开始时记录初始位置,滑动时计算当前位置与初始位置的偏移量,将偏移量应用到物体坐标上,然后更新初始位置为当前位置,为下一次滑动做准备。这个"记录-计算-应用-更新"的流程,是平滑拖拽的核心逻辑。
十、回调函数与异步思维
回调函数是游戏开发中无处不在的模式。资源加载、动画播放、网络请求这些耗时操作都不会立即完成,而是通过回调函数在操作结束后通知调用方。这种异步编程思维与传统同步代码不同,需要适应"发起操作-等待完成-执行回调"的流程。
回调函数可以是预定义的具名函数,也可以是临时编写的匿名函数。代码量较少时,直接在参数位置写箭头函数更为简洁;逻辑复杂时,拆分成独立的函数更清晰。关键是理解回调的执行时机——不是在定义时,而是在事件触发或操作完成时。
上下文绑定是回调函数中最容易出错的地方。当回调函数中需要访问this指向当前对象时,务必确认this的指向是否正确。箭头函数能自动绑定定义时的this,而普通匿名函数需要在注册时显式传入this参数来指定上下文。
十一、拖尾效果的实现原理
拖尾效果通过运动轨迹组件实现,让移动的物体留下渐隐的痕迹。其原理是在物体移动路径上连续生成半透明片段,随时间逐渐淡出消失。组件提供了丰富的参数控制:拖尾持续时间决定痕迹留存多久,片段间距控制密度,宽度调整粗细,快速模式优化性能。
拖尾组件需要依附于实际运动的节点才能自动产生效果。如果运动源没有可视化节点,可以创建一个空节点作为载体,通过代码将其位置同步到触摸点,从而实现手指滑动时的拖尾效果。
理解特效与实体的分离很重要。水果被切开的瞬间,水果本体销毁,同时在原位置创建一个切开动画。玩家看到的是连贯的切割效果,实际上是两个独立物体的接力演出。这种"实体消失+特效生成"的模式,是游戏中处理销毁反馈的标准做法。