Game Dev Log
Game Dev Log
2021-4-23
关键词:物理引擎,碰撞
y轴抖动问题
现场还原:当Player落在cube平面上时,会出现y轴方向的速度不断波动的问题
可能原因:cube的厚度过小(0.2),两个物体的碰撞判断造成
解决方法:增加cube的厚度物体碰撞产生速度分量
现场还原:当Player在方块的边缘下落时,它会产生一个水平方向的速度分量,并且会一直运动下去
可能原因:Player在碰撞到方块的左(右)上角时,方块会因为牛顿第三定律而给Player施加一个水平方向的力,导致Player获得一个水平方向的速度
解决方法:1. 给Player的下表面和方块的上表面添加摩擦材质 2. 在碰撞后的帧中,获得瞬时的刚体速度(velocity = rigidbody.velocity),然后通过代码来控制该速度的水平分量,处理完后再将它重新赋值给刚体Player运动颠簸
现场还原:当Player在多个连续的方块上移动时,如果速度比较大,可能会上下颠簸
可能原因:方块在接触点上并不平滑(相当于有个坎儿),Player撞到坎儿上导致颠簸
解决方法:暂时未知物体卡在方块的侧面或者边角
现场还原:
可能原因:Player和方块表面不光滑
解决办法:设置光滑材质物体穿模
现场还原:Player在前进时,如果速度过大,可能直接从方块上传过去
可能原因:Player的移动速度过快,前后两帧之间的距离过大,导致碰撞判定不及时,物体已经穿过模型
解决办法:1. 降低Player的速度 2. 将碰撞判定改成连续,但是会降低性能(不推荐对低速物体使用)
2021-6-3
- 角色和地面之间有间隔
现场还原:角色使用Character Controller后,和Plane之间有较大空隙
可能原因:Character Controller设置问题
解决办法:查文档
2021-6-17
双人对战demo2D开发总结
总体问题
1.1 玩法缺乏创意
1.2 代码整体结构不清晰,封装差,导致可读性不高,不容易扩展
1.3 变量访问控制混乱
1.4 类中成员变量的设置太多(虽然这样能够提高开发效率)其他细节上的问题及总结
2.1 组件模式下组件之间的消息传递问题
解决办法:组件之间任何需要共享的数据都放在容器中
直接互相应用(最常用)
例如:Unity中调用GetComponent()获取游戏对象下某个类型的组件 通过传递信息的方式进行广播,这种方式一般要保留所有组件的引用
2.2 资源、素材、游戏对象、组件命名的问题
解决办法:
把每一种需要命名的对象看作一个实例,把这个实例中的属性按照重要性顺序组合起来构成名字
例如:
玩家腿部的预制件:Player_Leg_Prefab
炸弹爆炸的动画:Bomb_Explosion_Animation
冰面的砖块:Ice_Tile
2.3 关于自动机的控制
描述:
自动机可以被使用在角色控制、敌人AI上,但是如果状态和转移条件控制的不合理,则会使得代码的结构变得十分复杂,
这种情况下反而不如使用if语句,逻辑上更加直观
问题:
如何设计好状态机的状态和转移条件
解决方法:暂无
2.4 关于持续作用效果的问题
描述:
当某个条件触发时,物体的状态发生改变,并在一段时间后恢复原来的状态
例如:
当子弹和人物发生碰撞时,会刷新人物受到伤害的动画,直到动画播放完毕人物恢复到原来的状态
问题:
如果没有动画如何控制好时间?
例如:
当子弹与箱子发生碰撞的时候,箱子的颜色发生改变,直到一段时间后箱子的颜色恢复到原来的颜色
解决办法:
本质问题还是状态的改变,因此设置一个bool变量表示当前状态,当子弹与箱子发生碰撞时,改变箱子的颜色,然后记录下此时的时间,
在Update循环中检查当前时间距离上次记录的受击时间是否到达一定的间隔,如果到达设定的时间间隔,代表当前状态执行完成,
需要将状态还原为初始状态
伪代码如下:
// Variables
bool isHit = false;
float lastGetHitTime = 0f;
float intervalTime = 0.3f;
// OnTrigger
void OnTriggerCollider2D(Collider other):
if other is bullet:
isHit = true;
lastGetHitTime = currentTime;
color = hitColor;
// Update
void Update():
if isHit:
if currentTime - lastGetHitTime > intervalTime:
isHit = false;
color = originColor;
2.5 Update函数中应该如何编写结构合理的代码
解决办法:
如果采用bool变量代表状态:
代码结构采用
if()func1();
elif()func2();
elif()func3();
…
else funcn();如果采用状态机:
代码结构:
state.Update();
2021-6-17
描述:
学习项目:Character Controller With Animations -Walk, Run, Jump & Attack
地址:https://www.youtube.com/watch?v=qc0xU2Ph86Q&list=WL&index=1
状态的保持与传递
描述:在进行跳跃时,有两种选择:人物在空中依然能够操纵角色的速度大小和方向:直接添加跳跃动作即可
人物在空中的速度大小和方向由跳跃之前的状态决定
现场还原:当人物处于Idle状态下按跳跃,人物会竖直起跳;当人物移动时跳跃,人物的移动方向保持不变
解决办法:设置isGrounded状态下才接收状态量的改变分层设计
描述:在Update函数中,数据分层进行处理,这样可以方便添加一些特殊效果输入:接收来自键盘和鼠标的输入
如果屏蔽输入,那么角色将暂时不受玩家控制设置速度:设置角色的速度
在角色位置移动之前,可以通过输入和环境来共同影响速度设置位置: 设置角色的位置
动画操纵
描述:本质是通过变量的值来操纵动画
例如:混合树通过一个或者多个float类型变量的值来代表当前动画,我们在改变这些变量时就是在改变动画的状态
2020-6-18
关键词:状态机
描述:将昨天人物移动的代码以状态机的方式重写
变量的访问控制
解决办法:将大部分变量设置为私有,然后通过访问器来访问相关变量状态的复用
描述:将State基类和StateMachine类设置为泛型,以此来声明状态所属的物体状态的转换问题
描述:相对于使用if-else句型,状态机的状态在转换时只关注到2个状态,因此有利于找出和修改bug,
但是构建状态机需要画出比较严谨的状态转换图,而且状态拆分过多也可能造成许多不必要的转换
解决办法:对于状态过多导致的重复转换问题,使用层次状态机(给每个状态设置父状态,可以使用继承或者链表的形式)状态机的核心:只要控制了物体的一切状态,就可以控制物体的一切行为
如何区分不同的状态?
解决办法:根据标准而定把状态量在某一个范围的集合作为一种状态,例如速度在某个区间,人物在某个平面内移动,
某些状态还具有持续时间,如果到达持续时间,则状态发生改变,例如喝水的状态,睡觉的状态一个状态也可以划分为许多子状态,子状态是父状态集合下的一个新的划分
例如:把人物站在地面上作为一个状态,那么子状态可以是站立、移动、快跑等等一般来说,状态所包含的状态量越少(例如在地面上的状态只包含一个状态量,而行走包含站在地面上和移动速度两个状态量),则状态处理起来越简单
自然界通过产生力来产生物体的运动,我们看到的只是物体位置的改变,而物体本质的状态是受力、加速度
启发:状态只需要控制一些父状态量(加速度,速度),就能够通过既定的规则来生成子状态量(位置)状态改变的条件不一定包含在某一个状态中,需要在外部生成该条件
每个状态维护者一个状态量的范围,当超出界限时,状态量发生改变,这个临界值可以作为状态改变的条件
例如:
从地面到空中,状态isGrounded发生该改变
从站立到行走,输入发生改变
从行走到冲刺,输入发生改变
从冲刺到站立,冲刺时间、耐力值发生改变
从站立到攻击,输入发生改变
从攻击到站立,攻击持续时间发生改变思考:游戏中的取消后摇是怎么回事?
运动状态的改变影响到了攻击状态的该变(停止)状态的叠加:几个分状态合成物体的实际状态,例如物体作为质点的平移状态、物体作为轴的旋转状态和物体的攻击状态叠加形成实际看到的状态
实际开发中,不用思考以上问题,怎么简单、直观怎么来,最后都能够把问题补上的 :)
2021-7-16
UGDAP游戏开发实训经验总结
- 一款成功的游戏需要好的玩法,每个游戏都要为自己设计核心系统
核心玩法(系统)的设计:
游戏目的-可操作行为-障碍
游戏目的:
玩家游玩游戏需要追寻怎样的引导,游戏策划需要仔细的规划好游戏中的每一个目标点,如解开谜题、敌人的难度、收集道具
可操作行为:
玩家的行动能力,一般ACT游戏中,玩家能够行走、奔跑、跳跃,卡牌游戏中,玩家抽牌、出牌都是可操作行为
玩家的行为决定了他具有怎样的能力
障碍:
基于玩家的行为进行设计,可以考虑以下几方面的因素:
反应速度、智力、耐力(一般不会)、记忆力、策略…
- 倾听:
团队-投资人-游戏-玩家-自己
—- 开发方面 —-
3. 开发前需要思考的事情*
3.1 游戏需要实现怎样的功能?
最需要思考的事情,这决定了你的代码多大概率会写成屎山:)。
游戏系统:
背包系统、射击系统、玩家移动系统、敌人AI、卡牌系统、物理系统、场景交互系统…提前规划好系统后,可以把这些系统
作为游戏模块设计,降低代码之间的耦合
敌人、地形种类:
哪些敌人、地形具有相似的功能,这决定了你的代码是否使用继承,以及预制件是否使用继承
提供测试接口:
为了减少编译次数,你的游戏在测试阶段应尽可能提供多的测试接口,几乎所有能够通过调整数值进行测试的效果都可以
提供测试接口,以方便快速测试。
可以调整的数值有:
位移、速度、加速度、发力时长、收招时长、攻击范围、受击范围、硬直时间、击退距离、击倒、击飞、击飞轨迹、特效
位置、特效大小、特效持续时间、摄像机的震动幅度与时长、高亮持续时间、定帧时长…
2021-7-23
静态类和单例模式的区别:
单例模式能够延迟加载
单例模式能够继承类、接口、重写方法
一般开发工具的时候使用静态类,因为静态类是提前编译好的(如Math类),其他情况一般使用单例
2021-7-31
腾讯next idea高校游戏创意大赛总结:
- 在思考某个设计的解决方案时,通常都会有多种办法,如果它们各有利弊,则可以先记录下来,当后续的需求不会对方法
产生较大影响时,任选其一即可,切忌选择恐惧
- 项目组织和设计
设计得比较合适的地方:
@ 敌人的脚本使用状态机控制,将各种状态行为分离开来,即使后来需要改变部分敌人的初始状态也能够较快的进行设置
@ 运动体的移动方式都是通过控制 速度来得到位移,尽量不要直接对运动物体的位置进行修改
@
设计得不太合理的地方:
@ 主角状态的设置不太合理,当主角获得buff之后,仅仅添加了一个bool值来代表当前状态发生改变,导致其他所有组件
都要拿到该状态后再添加相应动作,代码耦合度非常高
@ Notebook的GameObject设计不合理,使用了太多的非子物体引用,导致场景转换十分麻烦,比较合适的做法是,
为Notebook设计一个唯一的父物体,为该物体添加Notebook Manager单例,其他物体都挂载或者间接挂载在该物体
下方,这种方式在移动物体时不会造成引用丢失。根据这里的原理,我们可以将场景中的所有物体都归类到相应系统中,
当需要其他系统的引用时,直接通过对应系统的单例获得
中规中矩的地方:
- 与他人合作
2.1 与其他程序合作
尽量不要修改同一个模块的功能,在场景中添加物体时,尽量做到整体添加
2.2 与策划合作
文本:如何将txt格式的文字转化为对话框
各种数值:如何将excel表格中的数据转化成数据对象(结构体)?
- 遇到的小问题
3.1 对于大部分成员变量添加一层封装效果(C#为访问器),可以实现部分变量的延迟加载,特别是涉及到一些变量的
同步问题时,可以在缺少该变量时及时获取
3.2 对于持久化的数据,可以存储在Scriptable Object对象中,大大降低程序对内存的使用量
3.3 通常情况下,Debug的最好方式就是输出语句,在写完一段逻辑后,应该输出相应的信息来验证是否符合自己的预期
,可以在OnGUI函数中创建Button、Label来帮助Debug
3.4 动画、音乐播放时存在同步的问题,即当前Update中设置了动画播放后,需要等待至少一帧的时间,被设置的动画
才真正开始播放,此时获取的动画状态才是正确的,可以使用协程来进行同步
3.5 敌人死亡应该和死亡动画的播放分割开来,即死亡特效新建一个游戏对象来制作
2021-9-23
腾讯next idea高校游戏创意大赛总结2:
动画和游戏逻辑的同步:
最初,我的设计理念是,能够在代码中设置好的逻辑尽量不放在编辑器中。比如,游戏对象的查找,敌人、道具的生成,组件的初始化等等,这样做的优点在于,不容易和场景中的其它对象产生耦合,同时有利于debug和做功能上的微调。
但是,在动画播放和事件触发的逻辑层面,如果纯粹使用代码来控制流程,则会比较复杂。
首先,事件的触发点难以调整。在编辑器中,能够直接在相应的帧处添加事件,开发者能够很方便地进行可视化调整。如果放在代码中添加,则难以将动画帧和事件对应起来
另一个是代码逻辑的问题。如果不添加动画帧事件,即根据normalizedTime判断事件触发的时机,则会滥用Coroutine,同时,如果多个动画中间又掺杂了多个事件,则会造成协程的层层嵌套,极大地增加了代码编写难度。
目前想到地较好解决办法是,对于小型项目,可以直接在编辑器中添加动画帧事件,但缺点是,难以找到这些动画帧事件的引用,增加了debug的难度
另一种方式是,依然在代码中为动画帧添加事件,但是,对于具体触发的帧数,还是在编辑器中设置成可调节的,尽管动画效果调整时会麻烦一些,debug却轻松一些了
游戏测试的问题:
游戏测试需要今早做,因为模块和模块,功能和功能结合的时候也可能产生很多Bug,需要多个程序之间沟通交流,通过熟悉对方的逻辑和流程找出产生bug的原因和bug的位置
对于Unity来说,在编辑器中正常运行的功能,在build出的成品中依然可能出现很严重的bug。目前已知的有,部分视频在编辑器中能够正常播放,而在最终项目中完全无法播放。对于一些代码中的和编辑器有关的类、属性和函数,在最终项目中也可能被编译器忽略掉了。
因此,build项目也需要一个测试版本,只有该版本也通过所有测试之后,才能够发布最终版本
功能设计时模块化的重要性:
在与其它程序合作的过程中,不同功能肯定是分开设计的,因此需要进行模块化设计,比如人物的行为控制模块化,地图资源加载模块化,Boss战的功能模块化等等。这就要求模块之间的耦合度尽可能低,不能或者尽量少地依赖于其它模块,这样才能在多个场景中独立地调试该游戏功能或内容。
对于可能产生依赖的部分,要区分必要依赖和非必要依赖。例如,对于一个需要追踪的敌人,追踪目标是必要的,但我们在调试的时候,可以设置一个默认追踪目标,防止因为报错而无法测试该功能,但又需要给出警告,便于后续debug
Boss战设计:
设计该内容时,策划给出了许多基本需求,这也算是首次做一个比较复杂而且精炼的内容。设计要点如下:
- Boss基本状态控制
- Boss技能效果、释放动画、顺序等
- Boss对场景的控制
- Boss战各个阶段的表现和阶段之间的转换
因此,在编写代码之前,必须先写出较为详细的设计文档,以确定整体的框架结构和各个功能实现过程中可能出现的交叉和难点。
同时,如何存储Boss战每个阶段的数据也需要精心规划,既要让数据易修改、易保存,又要尽可能将数据可视化,方便策划进行效果调整和程序员debug工作。
例如,在本次Boss战的设计中,我为每个技能、阶段设计一个类,而每个阶段又包含了该阶段Boss所拥有的技能;每个技能类、阶段类有对应的ScriptableObject,用于存储持久化数据,在进入相应阶段时,程序从这些SO中读取数据到类的成员变量中,实现Boss的阶段初始化。
在实践中主要遇到以下难点:
- Boss战中有一个在地图指定位置生成小怪的功能,而由于小怪具有比较复杂的AI,因此为其专门设计了参数的保存和读取方式,可以通过在场景中设置好召唤生物的各个状态,再将状态按照相应规则保存到字符串中,这样,每次召唤生物时,就可以通过解析字符串将召唤物初始化
- Boss战不同的阶段还涉及到地形的变化,由于地形不便于序列化存储,且同一阶段中不会发生太大的改变,因此直接作为Boss战场景的子结点,通过设置激活状态实现阶段转移
代码重构实践:
需要根据实际情况具体应用,对于需要长期运营的项目或者需求经常改动的项目,代码重构是十分有必要的,这不仅能使得代码更加清晰易懂,而且好的重构能够缩小需要改动的代码范围
但是,如果代码不用经常改动,则尽量在最初设计的时候就考虑到每个类的变量和函数分布,减少重构代码耗费的时间
游戏数据的存储:
便于序列化的数据尽量使用ScriptableObject、json等方式存储,便于与策划合作,调整效果
对于难以序列化的数据,则采用AssetBundle或者预制件的方式存储,如果没有方便的工具,尽量还是由程序员自己调整效果
2021-10-10
腾讯游戏比赛中遇到的一个寻路问题:
一个处在悬浮平台上方的敌人,如何最终处于地面上的玩家?
这个问题看似简单,对于策划来说,你让敌人从平台上掉下来不就行了吗,但是,如果仅仅这样设计,就会出现非常多的漏洞
- 敌人不知道平台的几何信息。加入玩家处在一个类似于洞穴的地形内部,敌人如果一直朝着玩家目标的位置移动,则永远无法到达,因为洞穴的出口在反方向,敌人只有折回去才能找到玩家
- 玩家处于平台下方时,敌人不知道选择从哪个方向掉下平台,如果随机选择一个方向,则会回到上一个问题
- 一般的寻路算法在这里不好使。对于常见的AStar算法而言,其寻路时建立的图结点是没有考虑重力影响的,如果让敌人直接沿着结点寻路,则会出现敌人在空中漂移的情况,这是不行的
我观察过一些游戏的敌人AI,在一般的3d游戏里,如果道路是连接在一起的,则敌人能够到达指定位置,但是,如果??
在造梦西游里出现过一样的情形,敌人同样也会在平台上方转悠而无法掉下来。
关于Unity中人物骨骼控制的问题:
最初,我对Unity中人物骨骼控制不太了解,因为我认为那是动画师的事情,所以我跟着油管上的一个TPS教程学习,它完全使用了Animation Rigging这个插件来进行人物动画制作,即仅仅控制武器的变换,然后通过IK来控制人物骨骼的变换
但是这样会产生一个问题,即循环约束,例如,令玩家的骨骼被武器约束以模拟玩家握住武器的姿势,但是,我又希望武器被玩家身体的某个骨骼约束(例如肩膀),这样便形成了循环约束,这样最开始设置的约束就会产生偏移,同时,武器本身也不能自由移动了。除了这个,Animation Rigging也还有其它的问题,比如我希望通过动画来调整权重,这又不得不在Rig对象下建立一个动画机,但可能由于一些执行顺序的问题,上层的动画机会覆盖下层动画机的设置,导致IK权重无法发生变化。后来经过我观察发现,该博主最后也不再经常使用这个插件了。
后来我是用Unity的Avatar+Humanoid来完成的骨骼动画控制,虽然最终达成了目标,但是依然受到了很多限制,调试了很长时间才做出一个比较合适的动作
最主要的问题还是在于Humanoid模式,我主要是为了方便人物动画重定向才这么做的,但是使用之后,发现角色的骨骼旋转都被Avatar控制了,只有少部分的骨骼能够通过调用API的方式进行旋转(这个API我也是找了很久才找到,之前一直在测试为什么不能通过代码控制骨骼旋转的问题),如果我希望对人物的其它骨骼旋转进行微调,则没有相应的办法(不过也可能是我没有找到)
最后调试出来的逻辑是这样的:
非瞄准模式下:
鼠标的xy输入控制人物身上的一个空对象进行旋转,让摄像机Follow这个空对象进行变换
人物的旋转:受两个因素的影响,即move的输入加上相机当前的角度,当然,角色的旋转是平滑过渡的
人物的移动:人物的旋转四元数乘上人物的前向向量得到旋转后的目标方向,之后角色朝着这个方向移动
在瞄准模式下:
人物的旋转分为两部分:
在水平方向上,人物在y轴上的旋转保持和相机的旋转角度相同。这里会产生一个同步的问题,即如果人物的旋转是在Update中计算得到的,而虚拟相机的旋转是在LateUpdate中执行的(关于为什么要在Late Update中执行,可以参考这篇文章:Update和LateUpdate的区别 - 赵青青 - 博客园 (cnblogs.com),那么人物的旋转会慢相机旋转一帧,由于相机在每一帧中的旋转角度是不均匀的,从相对论的角度来说,人物相对于相机的角度在每帧也大概率是不同的,因此会出现人物旋转的延迟问题。解决办法是,提前计算好LateUpdate中相机跟随的空对象的角度,存储到一个变量中(不过得确保空对象只在这里被计算过了,否则相机旋转放在Late Update中执行也没有意义),然后利用这个角度更新人物的旋转,最后在LateUpdate中更新跟随空对象的旋转,经过测试,不再出现旋转延迟的问题。
在垂直方向上,只有人物的胸腔部分随着摄像机的移动旋转,这里让胸腔的旋转跟随空对象沿x轴的旋转,同样需要进行差值过渡和范围裁剪
之前尝试过几种其它的旋转方式,发现效果都不太理想。例如,让武器对准相机前方一定距离的目标,然后使用IK对右臂进行约束。但是调用Avatar的API后发现,使用SetIKRotation只能作用于手腕的旋转变换,不能很好地带动肘部和肩部的旋转;对于Avatar的LookAtTarget函数,使用后发现只能操纵头部的旋转,依然不能带动人物手臂还有武器的旋转
总结:在遇到逻辑上的问题时,最好先参考别人的代码逻辑实现,然后再自己针对每一个细节进行调试,这样能避免踩到很多坑