文章

关于课设中实现的类银河恶魔城游戏原型

在大一的高等程序语言设计的课设中,我选择了感兴趣的游戏开发,最终使用Unity完成了所有玩法系统的构建,此博客整合了我在开发过程中记录的部分笔记、遇到的部分问题以及思考,更细节的实现内容可参考对应代码仓库中的工程文件内的场景布置以及脚本实现

关于课设中实现的类银河恶魔城游戏原型

一、写在前面

1.1 关于做的东西

1.2 做这个的原因

  • 因为比较喜欢这个品类的游戏,玩过比较出名的有Hollow Knight、Ori两部曲、Ender Lilies、神之亵渎等,也玩过偏类魂的盐与避难所、偏休闲RPG的不死鸟传说、类宝可梦的怪物圣所,以及偏解密和平台跳跃的Animal Well等,所以想要借着做课设的机会尝试进行该品类的游戏开发,并且游戏开发的编程属实是把面向对象的封装、多态和继承这三个特性展现得很透彻,对我这个初学者来说是很棒的上手实践题材

  • 正好网上有关于这个的一个很棒的教程,我便依靠着仅有的一点C++基础硬跟着课程开始写C#,好在编程思路上有很多相通的地方,并且Unity引擎把大部分复杂的功能都封装了起来,初学阶段的我仅需调用API函数即可,所以我才得以上手并跟着一百多集的教程一步步做完了这个项目,并添加了自己的一些小创意,比如偷了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 游戏对象间的碰撞Trigger效果

  • 关于判断技能飞剑的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

3.1.4 玩家与地面的碰撞检测不充分的问题

  • 当人物站在悬崖边上的时候,若地面检测线恰巧在空中,这样人物处于Idle状态中,而从Idle到Move需要的条件是isGround && xInput != 0,导致人物卡在悬崖边上无法移动
  • 最简单的方式就是把检测线横着放并往下移一点,或者增加多几条检测线(前提是能保证游戏中不会出现尖尖的地面会触发检测不到的问题),还有更精确但复杂的解决方案此处不多说

EdgeProblem.png

3.2 关于动画逻辑

3.2.1 消除延迟与卡帧

消除延迟与卡帧.png

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

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

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

3.2.3 攻击动画需要完整播放

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

攻击动画.png

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

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

BlendTree.png

3.2.5 动画结束触发函数

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

AnimationTrigger.png

3.2.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);

3.2.7 (待解决)BlendTree一直处于激活状态的问题

  • 状态机中跳跃和坠落状态是处于一个Blend Tree中的,依靠yVelocity(Animator中的一个float类型的parameter)的正负来决定人物处于jump(上升)状态还是fall(下落状态),同时,人物还有一个登墙跳的动画(未使用Blend Tree),这个动画与跳跃动画是不同的,但是在登墙跳状态的时候,上面那个Blend Tree的yVelocity的检测仍然在继续,导致会调用jump的动画,导致和wallJumpState的动画相重叠,导致很奇怪,目前通过调整这两段动画相同来防止画面出现奇怪的复合,但是这不是长久之计

3.3 关于其它杂项

3.3.1 瓦片地图砖块大小的恰当设置

  • 我最开始导入Tilesets的时候,遇到过瓦片在Palette中大小正常,但是一放到Grid网格中的时候就会出现瓦片Sprite被缩小到格子中间,其余格子都是空白的情况,搜了半天发现是参数设置的问题,参考此处

3.3.2 (待解决)多选对象导致变量值重置

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

UnityShiftClickBug.png

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