使用U2D开发类银河恶魔城原型的杂谈
在大一的高等程序语言设计的课设中,我选择了自己感兴趣的游戏开发,并以C#与Unity2D完成了所有系统的构建,此博客整合了我在开发过程中记录的部分笔记、遇到的部分问题以及思考,更细节的技巧请参阅我的项目仓库中各脚本的代码注释
一、写在前面
1.1 关于做的东西
课程答辩材料:MetroidvaniaPresentation.pdf
游戏展示视频:
- 全系统展示精简版
- 战斗系统展示
1.2 做这个的原因
因为比较喜欢这个品类的游戏,玩过比较出名的有Hollow Knight、Ori两部曲、Ender Lilies、神之亵渎等,也玩过偏类魂的盐与避难所、偏休闲RPG的不死鸟传说、类宝可梦的怪物圣所,以及偏解密和平台跳跃的Animal Well等,所以想要借着做课设的机会尝试进行该品类的游戏开发,并且游戏开发的编程属实是把面向对象的封装、多态和继承这三个特性展现得很透彻,对我这个初学者来说是很棒的上手实践题材
正好网上有关于这个的一个很棒的教程,我便依靠着仅有的一点C++基础硬跟着课程开始写C#,好在语法有很多相通的地方,并且Unity引擎把大部分复杂的功能都封装了起来,初选阶段的我仅需调用接口即可,所以我才得以上手并跟着一百多集的教程一步步做完了这个项目,并添加了自己的一些小创意,比如偷了MC的音符盒和刷怪笼贴图来分别实现一个游戏内点歌(
曲目的选取狠狠夹带私货,可以说最初就是为了夹带私货才做的这个)的系统和刷怪笼系统等
二、关于素材获取
大部分图形美术资产(Sprites,Tilesets,UI等)均从itch.io上筛选而来,由于游戏整体是像素风格,少部分Sprites由我通过Aseprite进行魔改或者简单临摹,并且由于不懂画画,如果一些角色的特殊动作在找到的SpritesSheet中没有匹配的,我就只能
灵活复用素材了,比如把攻击结束收剑的一帧作为防御的单帧循环动画(深切体会到了没有美术是多么寸步难行)其它素材资产(Font、SFX、BGM等)不是从itch.io获取就是从各种经典游戏动漫的OST中
直接攫取,或是从各种杂七杂八的素材网站中获取,这里就不一一列举了
三、关于碰撞检测
3.1 实体与环境间的碰撞检测
3.1.1 移动冻结z轴
- 实体的刚体模型要服从以下设置,保证实体不倒
3.1.2 地面与墙壁的检测
- 要把组件拖入父Sprite的脚本中赋值
- 地面检测参数isGrounded依赖于whatIsGround参数,这个参数记录着什么图层代表着地面,每添加一个需要地面检测的实体,都要先对其进行whatIsGround的赋值
- 同样在此之前,还要对地面相关物体进行赋值,将其归类为Ground图层先
3.1.3 角色与地面的碰撞检测不充分的问题
- 当人物站在悬崖边上的时候,若地面检测线恰巧在空中,这样人物处于Idle状态中,而从Idle到Move需要的条件是
isGround && xInput != 0
,导致人物卡在悬崖边上无法移动 - 我想过把检测线横着放并往下移一点,也可以增加一条检测线,分别放置在人物左右两侧脚下,实际上这些都是治标不治本,除非你能保证游戏中不会出现尖尖的地面会触发检测不到的问题
- 然后我搜了下别人都是怎么解决的,方法复杂繁多,后续有时间待我都去实现一遍
3.2 GameObject间的碰撞触发效果
- 关于判断技能飞剑的Prefab是否碰到敌人,或者判断人物是否碰到掉落物,我们用以下的函数,花括号内即Collider之间发生碰撞后,会发生什么
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private void OnTriggerEnter2D(Collider2D collision)
//判断飞剑是否碰到敌人,内容略
{
}
private void OnTriggerEnter2D(Collider2D collision)
//判断主角是否与物品发生了碰撞
{
//若主角与物品的碰撞箱碰撞,则捡起物品
if(collision.GetComponent<Player>() != null)
{
Debug.Log("Picked Up Item " + itemData.itemName);
//销毁此item
Destroy(gameObject);
}
}
- 此时,若想上述函数能正常运行(即飞剑Prefab碰到敌人后上述函数执行,或任务碰到物品后能正常拾取物品),必须把飞剑/掉落物的碰撞箱内的
Is Trigger
勾选上,而玩家(碰撞发生的另一个对象)的碰撞箱则不需要设置
四、关于动画逻辑
4.1 消除延迟与卡帧
- 每一根链接线的
Has Exit Time
和Transition Duration(s)
应当为否和0,否则动画之间的衔接会有操作延迟 - 从
Any State
拉出的链接线,要注意把Can Transition To Self
应当为否,不然会一直从Any State
触发与之相连的第一级动画,导致动画卡在其第一帧
4.2 多条件约束防止动画间冲突
- 跳跃动画的触发条件是”isGrounded为假”,冲刺动画的触发条件是”isDashing为真”
- 需求要求当在空中冲刺时人物应当播放冲刺动画
- 故而应当给跳跃动画的触发条件增加一个”isDashing为假”,防止空中冲刺仍然播放跳越动画
4.3 攻击动画需要完整播放
- 攻击动画需要设置Exit Time = 1来保证完整播放,这与别的动作不太一样
4.4 跳跃与落下使用BlendTree进行整合
- 在Animator内右键可新建一个BlendTree及其相对应的一个参数(这里设置为yVelocity)
- 通过Threshold的设置来控制上升和落下两种动作的相互转换,如此处yVelocity为1则调用上升动画,-1则调用下落动画
4.5 动画结束触发函数
- 动画结束时可以触发某一绑定到Animation组件中的脚本中的函数,来达到修改某些值的效果
4.6 攻击连招运用子状态
- 右键Animator空白处创建Sub-State Machine
- 双击进入其内,新建一个新状态Empty用于接收Entry板块延伸出来的黄色箭头(默认进入黄色箭头的状态,无法消除),再把三个攻击动画拖进去与Empty相连
- 建立一个int的叫ComboCounter的Parameter,利用ComboCounter的数字(1,2,3)作为判断条件控制三段攻击动画
- 然后到外面,把外部Entry和这个子状态连接起来,选择Empty连接模式(即这个状态不需要与外界的Exit相连,因为其内部就有Exit),这条Transition的触发条件是Attack(是否在攻击)为真
- 记得个每一段攻击动画的结束处触发那个使得退出AttackState的函数
- 记得把AttackStack内的comboCounter和Animator内的对应Parameter链接起来(
player.anim.SetInteger("ComboCounter", comboCounter);
)
4.7 BlendTree一直处于激活状态的问题
- 状态机中跳跃和坠落状态是处于一个Blend Tree中的,依靠yVelocity(Animator中的一个float类型的parameter)的正负来决定人物处于jump(上升)状态还是fall(下落状态),同时,人物还有一个登墙跳的动画(未使用Blend Tree),这个动画与跳跃动画是不同的,但是在登墙跳状态的时候,上面那个Blend Tree的yVelocity的检测仍然在继续,导致会调用jump的动画,导致和wallJumpState的动画相重叠,导致很奇怪,目前通过调整这两段动画相同来防止画面出现奇怪的复合,但是这不是长久之计
- 待我找到问题所在再来更新这条笔记
五、关于瓦片地图
5.1 TileMap砖块大小的恰当设置
- 我最开始导入Tilesets的时候,遇到过瓦片在Palette中大小正常,但是一放到Grid网格中的时候就会出现瓦片Sprite被缩小到格子中间,其余格子都是空白的情况,搜了半天发现是参数设置的问题,参考此处
六、关于用户交互
6.1 跳开墙壁的交互问题
- 人物脱离滑墙状态的时候(比如是左边的墙),按D能让人物脱离滑墙状态;按空格能让人物跳开墙。一般人想要跳开墙是想按D然后再按空格的,但这样会先触发脱离墙壁的状态转换,导致还没来得及按空格就掉下去了
- 尝试过通过以下方式解决,但是KeyUp不起作用
- 暂时直接删除除跳跃键以外的离开滑墙方式,可以通过S键加速下滑,待我找到解决方法再来更新这条笔记
七、关于引擎漏洞
7.1 多选对象导致对象已设置好的变量值被改变
- 我在开发中为我的角色技能设置了多个通过碰撞解锁技能的
SkillActivator
,他们共同用同一个脚本,脚本中有成员变量skillType
,其数据类型是我自定义的枚举组,包含比如Dash_Skill
、Throw_Sword_Skill
等成员,而每个SkillActivator
的成员变量skillType
都需要在Unity编辑器的对应Inspector上被赋予对应的技能类型 - 我在正确赋予了这些技能对应的
skillType
后,当我通过shift + 鼠标左键
多选这些SkillActivator
后,他们的skillType
居然都自动被还原为枚举组中的第一个元素了??