其余部分简要1
这套课程实在太长了,而且代码一边写一边重构,全部做笔记意义不大,现将后面的部分做一个摘要目录,里面有意义的再着重整理成笔记。
子弹碰撞
Collider2DComponent
、IColliderMsg
想要接受碰撞事件继承这个接口即可。碰撞方和接受方都会接受到对方的标签。
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
的使用、InitializeOnLoadMethod
和InitializeOnLoad
特性的使用
除了自动添加Tag的方法,还有一个将文件夹内所有的Prefab都指定Tag的方法。
行为接口
把每个游戏对象的受伤、死亡等逻辑抽象出来,使用行为接口来实现。(一种简化的状态逻辑)
在Logic文件夹中新建Behaviour文件夹,在其中新建IBehaviour
接口。
在PlayerCollider2DMsg
中获取接口并执行。
重构PlayerCollider2DMsg
,从而让PlayerCollider2DMsg
成为一个公共组件。在IBullet
接口中定义Targets属性。
我们的IBullet
直接传给了自己的子弹,这导致我们想要获取Player自身的IBullet
提供的值只能通过发射出的子弹获取,这样不合理,所以我们将BulletMgr
实现IBullet
接口并实现,这样就能在PlayerCollider2DMsg
中获取到自身的IBullet
对于不发射子弹直接撞击玩家的敌方飞机,它们只有挂载IBullet
接口才能触发Player的死亡行为,不发射子弹但挂载IBullet
不合理,所以我们使用新的接口实现这里的逻辑。在Logic文件夹中新建IAttackTarget
(在后面的重构中被删除),我们让PlayerView
和EnemyView(目前没有这个脚本)
实现这个接口,使用这个接口判断敌我就不需要子弹了。
在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的,所以需要在Tags
和AutoTags
当中添加它的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文件中新建PowerBulletModel
,PowerBulletModel
中使用了许多PlayerBulletModel
相同的实现,所以我们把它俩都设为单例,修改之前的PlayerView
、PowerView
中Model的创建,都用单例代替。PowerBulletModel
中比较特异性的地方就是这种子弹的轨迹和图片等。设定PowerBullet的轨迹,从飞机头部一共在60~120度范围内每隔5度一个直线轨迹,一共13条轨迹。
特殊子弹的图片Path配置略。Power的Prefab和路径配置略。
目前的对象池取出子弹时没有重置子弹的图片,我们需要修改一下,修改IBulletModel
的SpritePath
,我们不在Model中提供Sprite路径,而是直接在Model中缓存Sprite。把Bullet.Start
里面读取图片的逻辑直接转移到IBulletModel
中。
在
Bullet
脚本中既有Init
方法又有Start
方法,Init
方法在Start
方法之前执行,差一帧。如果子弹的速度有区分,
Bullet.Start
中的读取速度的逻辑也需要转移到Init
当中
子弹发射方向和旋转*
子弹每一帧都会调用MoveComponent.Move(Vector2.up)
,这会导致所有的子弹无视自己的初始发射方向,在Y轴的位移每一帧都相同。
解决方法也很简单,在ITrajectory
中添加GetDirection
方法,获取到当前子弹的方向向量(是一个单位向量)。具体实现直接用Cos和Sin乘偏转角度即可。
然后修改Bullet
的UpdateFun
,现在每个子弹在开始发射的瞬间都有了自己的方向向量,不需要再代入公式计算位置了,删除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
添加Pause
、SetLoop
、RePlay
方法
修改BulletMgr
的Fire
协程函数
我们在协程函数中,最开始的时候直接loop播放Model中指定的音效,然后在指定的发射间隙,将音效的loop关闭,重新发射子弹时再开启。