文章

使用U2D开发类银河恶魔城原型的杂谈

在大一的高等程序语言设计的课设中,我选择了自己感兴趣的游戏开发,并以C#与Unity2D完成了所有系统的构建,此博客整合了我在开发过程中记录的部分笔记、遇到的部分问题以及思考,更细节的技巧请参阅我的项目仓库中各脚本的代码注释

使用U2D开发类银河恶魔城原型的杂谈

一、写在前面

1.1 关于做的东西

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轴

  • 实体的刚体模型要服从以下设置,保证实体不倒

FreezeZaxis.png

3.1.2 地面与墙壁的检测

  • 要把组件拖入父Sprite的脚本中赋值

GroundWallCheckSettings.png

  • 地面检测参数isGrounded依赖于whatIsGround参数,这个参数记录着什么图层代表着地面,每添加一个需要地面检测的实体,都要先对其进行whatIsGround的赋值

BeforeGroundCheck.png

  • 同样在此之前,还要对地面相关物体进行赋值,将其归类为Ground图层先

WhatIsGround.png

3.1.3 角色与地面的碰撞检测不充分的问题

  • 当人物站在悬崖边上的时候,若地面检测线恰巧在空中,这样人物处于Idle状态中,而从Idle到Move需要的条件是isGround && xInput != 0,导致人物卡在悬崖边上无法移动
  • 我想过把检测线横着放并往下移一点,也可以增加一条检测线,分别放置在人物左右两侧脚下,实际上这些都是治标不治本,除非你能保证游戏中不会出现尖尖的地面会触发检测不到的问题
  • 然后我搜了下别人都是怎么解决的,方法复杂繁多,后续有时间待我都去实现一遍

EdgeProblem.png

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勾选上,而玩家(碰撞发生的另一个对象)的碰撞箱则不需要设置

ColliderIsTrigger.png

四、关于动画逻辑

4.1 消除延迟与卡帧

消除延迟与卡帧.png

  • 每一根链接线的Has Exit TimeTransition Duration(s)应当为否和0,否则动画之间的衔接会有操作延迟
  • Any State拉出的链接线,要注意把Can Transition To Self应当为否,不然会一直从Any State触发与之相连的第一级动画,导致动画卡在其第一帧

4.2 多条件约束防止动画间冲突

  • 跳跃动画的触发条件是”isGrounded为假”,冲刺动画的触发条件是”isDashing为真”
  • 需求要求当在空中冲刺时人物应当播放冲刺动画
  • 故而应当给跳跃动画的触发条件增加一个”isDashing为假”,防止空中冲刺仍然播放跳越动画

4.3 攻击动画需要完整播放

  • 攻击动画需要设置Exit Time = 1来保证完整播放,这与别的动作不太一样

攻击动画.png

4.4 跳跃与落下使用BlendTree进行整合

  • 在Animator内右键可新建一个BlendTree及其相对应的一个参数(这里设置为yVelocity)
  • 通过Threshold的设置来控制上升和落下两种动作的相互转换,如此处yVelocity为1则调用上升动画,-1则调用下落动画

BlendTree.png

4.5 动画结束触发函数

  • 动画结束时可以触发某一绑定到Animation组件中的脚本中的函数,来达到修改某些值的效果

AnimationTrigger.png

4.6 攻击连招运用子状态

  • 右键Animator空白处创建Sub-State Machine
  • 双击进入其内,新建一个新状态Empty用于接收Entry板块延伸出来的黄色箭头(默认进入黄色箭头的状态,无法消除),再把三个攻击动画拖进去与Empty相连
  • 建立一个int的叫ComboCounter的Parameter,利用ComboCounter的数字(1,2,3)作为判断条件控制三段攻击动画

SubStateAttack.png

  • 然后到外面,把外部Entry和这个子状态连接起来,选择Empty连接模式(即这个状态不需要与外界的Exit相连,因为其内部就有Exit),这条Transition的触发条件是Attack(是否在攻击)为真

(EmptyTransition.png

  • 记得个每一段攻击动画的结束处触发那个使得退出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不起作用

KeyUp.png

  • 暂时直接删除除跳跃键以外的离开滑墙方式,可以通过S键加速下滑,待我找到解决方法再来更新这条笔记

七、关于引擎漏洞

7.1 多选对象导致对象已设置好的变量值被改变

  • 我在开发中为我的角色技能设置了多个通过碰撞解锁技能的SkillActivator,他们共同用同一个脚本,脚本中有成员变量skillType,其数据类型是我自定义的枚举组,包含比如Dash_SkillThrow_Sword_Skill等成员,而每个SkillActivator的成员变量skillType都需要在Unity编辑器的对应Inspector上被赋予对应的技能类型
  • 我在正确赋予了这些技能对应的skillType后,当我通过shift + 鼠标左键多选这些SkillActivator后,他们的skillType居然都自动被还原为枚举组中的第一个元素了??

UnityShiftClickBug.png

本文由作者按照 CC BY-NC-SA 4.0 进行授权