这套课程实在太长了,而且代码一边写一边重构,全部做笔记意义不大,现将后面的部分做一个摘要目录,里面有意义的再着重整理成笔记。

子弹碰撞

Collider2DComponentIColliderMsg想要接受碰撞事件继承这个接口即可。碰撞方和接受方都会接受到对方的标签。

Collider2DComponent只能挂载在有RigidBody组件的对象上。Collider2DComponent会把对象自己和对方碰撞体身上所有实现了IColliderMsg的脚本全都触发。

在Logic文件夹中新建Collider2DMsg文件夹(在后面重构后被删除),在其中新建PlayerCollider2DMsg继承IColliderMsg,使用这个Mono脚本来处理Player的碰撞消息,用新的脚本把逻辑分离开。在PlayerView中动态挂载这个脚本和Collider2DComponent

添加BulletOwner枚举,防止自己的子弹打中自己,在IBulletModel文件里声明IBullet接口,让IBulletModel继承IBullet、然后PlayerBulletModel中添加好这个枚举。让Bullet继承IBullet并实现。

IBullet中添加GetAttack方法,获取配置中的玩家或敌人伤害。并在PlayerBulletModel中实现接口。最后在PlayerCollider2DMsg中判断

标签自动添加*

在Unity的工程文件目录——Project Settings中的TagManager.asset保存了当前工程的标签信息,如果标签莫名消失了可以通过还原这个文件的备份来恢复。

在Const文件夹中新建Tags,在Editor文件夹中新建AutoTags,InternalEditorUtility的使用、InitializeOnLoadMethodInitializeOnLoad特性的使用

除了自动添加Tag的方法,还有一个将文件夹内所有的Prefab都指定Tag的方法。

行为接口

把每个游戏对象的受伤、死亡等逻辑抽象出来,使用行为接口来实现。(一种简化的状态逻辑)

在Logic文件夹中新建Behaviour文件夹,在其中新建IBehaviour接口。

PlayerCollider2DMsg中获取接口并执行。

重构PlayerCollider2DMsg,从而让PlayerCollider2DMsg成为一个公共组件。在IBullet接口中定义Targets属性。

我们的IBullet直接传给了自己的子弹,这导致我们想要获取Player自身的IBullet提供的值只能通过发射出的子弹获取,这样不合理,所以我们将BulletMgr实现IBullet接口并实现,这样就能在PlayerCollider2DMsg中获取到自身的IBullet

对于不发射子弹直接撞击玩家的敌方飞机,它们只有挂载IBullet接口才能触发Player的死亡行为,不发射子弹但挂载IBullet不合理,所以我们使用新的接口实现这里的逻辑。在Logic文件夹中新建IAttackTarget(在后面的重构中被删除),我们让PlayerViewEnemyView(目前没有这个脚本)实现这个接口,使用这个接口判断敌我就不需要子弹了。

PlayerCollider2DMsg中完成这些判断逻辑,将这个脚本重命名为Collider2DMsgComponent,将Collider2DMsgComponent转移到Component文件夹,并删除之前的ColliderMsg文件夹。

PlayerBehaviour

在Behaviour文件夹中新建PlayerBehaviour,继承IBehaviour并实现

首先在GameModel中声明LifeMax属性并实现。然后修改UI中的LifeItem.GetLifeMin逻辑,之前直接使用Consts.LifeMax是不对的,把它删掉。

Shield护盾特效

Shield特效的添加和路径配置略。

Unity的Collider是每帧瞬移的,如果移动速度太快,可能引发不碰撞或者碰撞到不该碰撞的对象的问题。

一个父对象没有挂载Collider,但是只要它的子对象挂载了Collider,一样可以触发父对象的碰撞事件监听,所以我们在使用有Collider的特效时,不能挂载到父对象中。

PlayerView中添加对于MsgEvent.EVENT_USE_SHIELD的监听,在监听的方法中实例化特效,注意不要设置特效的父级。

我们的Shield特效预制体是带Collider的,所以需要在TagsAutoTags当中添加它的Tag

在Logic——View文件夹中新建ShieldView继承EffectView,动态挂载到Shield特效上。

EffectView会让Shield特效生成在Effect层级下,这样Shield可能不会与子弹碰撞,这里后期可能要修改。

Shield特效目前没有跟随Player移动,这里后期可能要修改

在Logic——Component文件夹中新建AutoDestroyComponent,护盾特效自动销毁组件,具体的时间直接在Consts中配置一个即可。

我们的护盾只挂载Collider2DComponent,不挂载Collider2DMsgComponent,这就表示我们需要让Bullet添加Collider2DMsgComponent,让子弹自己触发Msg。但是目前Collider2DMsgComponent中的条件无法触发子弹的Msg,我们需要修改它。

教程中在这里重构了IBullet,让它整合了IAttackTarget.GetTargets()的功能,并删除了IAttackTarget接口,从而实现触发子弹自己的Msg,如果敌方飞机没有挂载IBullet也没事,Player的BulletRoot对象是挂载了BulletMgr的,而BulletMgr提供了IBullet接口,这样Player也能提供自己的子弹信息。

在Logic——Behaviour文件夹内新建BulletBehaviour并让子弹挂载,管理子弹销毁逻辑。

Power特殊子弹

Power技能是发射强力子弹。

在Logic——View文件夹中新建PowerView继承PlaneView

修改GameViewBase,添加虚函数InitComponent

在BulletModel.cs文件中新建PowerBulletModelPowerBulletModel中使用了许多PlayerBulletModel相同的实现,所以我们把它俩都设为单例,修改之前的PlayerViewPowerView中Model的创建,都用单例代替。PowerBulletModel中比较特异性的地方就是这种子弹的轨迹和图片等。设定PowerBullet的轨迹,从飞机头部一共在60~120度范围内每隔5度一个直线轨迹,一共13条轨迹。

特殊子弹的图片Path配置略。Power的Prefab和路径配置略。

目前的对象池取出子弹时没有重置子弹的图片,我们需要修改一下,修改IBulletModelSpritePath,我们不在Model中提供Sprite路径,而是直接在Model中缓存Sprite。把Bullet.Start里面读取图片的逻辑直接转移到IBulletModel中。

Bullet脚本中既有Init方法又有Start方法,Init方法在Start方法之前执行,差一帧。

如果子弹的速度有区分,Bullet.Start中的读取速度的逻辑也需要转移到Init当中

子弹发射方向和旋转*

子弹每一帧都会调用MoveComponent.Move(Vector2.up),这会导致所有的子弹无视自己的初始发射方向,在Y轴的位移每一帧都相同。

解决方法也很简单,在ITrajectory中添加GetDirection方法,获取到当前子弹的方向向量(是一个单位向量)。具体实现直接用Cos和Sin乘偏转角度即可。

然后修改BulletUpdateFun,现在每个子弹在开始发射的瞬间都有了自己的方向向量,不需要再代入公式计算位置了,删除Bullet.ResetTrajectoryPos方法即可

让子弹图片沿着发射方向倾斜,需要旋转z轴,但是要注意,Unity中Rotation的z值变大是向逆时针转的。在第一象限和第二象限计算一下就会知道:如果我们的直线子弹轨迹的偏转角度是θ,那么该子弹需要旋转θ-90°

ITrajectory中添加GetZRotate方法,然后再在Bullet中添加SetAngle方法并在Init调用即可。

子弹声音

子弹的发射频率太高了,如果每发射一颗子弹就播放一段声音的话就会很奇怪,所以我们使用一个音频文件loop的方式。当某种子弹处于发射阶段,就loop它的发射音效。

音频文件放在Resources——Audio——Battle内,Path配置略,还要在Enums添加音效的枚举,添加完后,不要忘了选中Audio文件夹然后右键——Create Json

IBullelModel中添加AudioName属性,给出当前子弹需要播放的音频。

AudioMgr添加PauseSetLoopRePlay方法

修改BulletMgrFire协程函数

我们在协程函数中,最开始的时候直接loop播放Model中指定的音效,然后在指定的发射间隙,将音效的loop关闭,重新发射子弹时再开启。