前言
背景
学习unity以及其他相关内容(游戏服务器,lua,shader等)已经有一年的时间了,并且c++算法水平也在这一年
里有所提升,决定是时候耐心的做一款合格的游戏来总结和提炼自己了,因为只有课余时间并且目前只有我一个人,
所以项目的开发预计会持续至少4个月以上,目标就是能把游戏发到steam上(毕竟以前做的游戏都只有发给朋友玩)
这个项目的所有代码,shader甚至部分美术我都会尽可能的从头做,不会引用一些以前的老代码或者现成的插件,
并且会尽可能的保证代码的规范性和清晰的注释,这个日志大概三天一更吧(主要是看有没有新东西),会在这里记
录我在开发过程中所有遇到的困难和学到的知识。
关于游戏
选择沙盒类型的原因主要是沙盒是单机(因为并不确定会开发服务器)类型中可玩性比较高又投入比较小(相比剧
情之类的游戏)的一种,而且相对fps等也对美术等的要求较低,还有就是最近一直在玩饥荒给了我一些基础灵感。
除此以外几乎没有更深一步的想法,我也会随时请朋友们试玩并聆听意见,毕竟没有策划经验也只能走一步看一步了。
所以日志中也会包含对大量已有代码的修改等(这不是一个教程!)
日志
首先是一个第三人称的人物控制器,我打算把所有玩家脚本都以controller命名,然后将各种模块尽可能的分离以
便利后续开发,所以我分别写了LocomotionController和ViewController来控制人物的移动和视角,并将它们暂且
放在Player namespace中
LocomotionController暂时没写什么,是最基本的移动,人物模型也还没确定,没怎么涉及动画。主要是在视角ViewController上花了比较大的功夫,因为视角的舒适程度我觉得很重要。
实现一个比较顺滑的视角跟随延迟效果
在最开始的时候我就想要实现一个比较顺滑的视角跟随延迟效果,随着人物移动视角后拉,静止时又逐渐恢复;因为不能使用virtualcamera,所以这部分是本次最主要的内容。
camera作为人物子物体
一开始我想要将camera脱离人物的子物体,因为如果子物体的话总会受到人物本身的各种影响。但我后来发现虽然这样可以高度定制摄像机的移动,但这样很容易出现bug,摄像机必须监听人物的所有移动转向等,当后期各种各样的物品和怪物加入后人物还会出现被迫的移动和难以预测的移动,所以最终还是决定将摄像机设置为角色的子物体
这也就意味着,我们的所有操作都要在父物体的坐标系下进行,使用localPos等。
public float followDistance;//标准跟随距离
public float maxFollowDistance;//最大跟随距离
public float followSpeed;//移动时相机后拉的速度
public float resumeSpeed;//静止时相机恢复的速度
void UpdateCamPos()
{
//模拟相机延迟跟随的效果
//注意这里使用的都是相对位置,这样不会致使在人物收到外力移动时产生bug,所以followDistance和maxDistance也都应该设置为相对位置
//followSpeed和resumeSpeed决定了在奔跑时和静止时相机推进的速度
//从当前位置到最大/最小位置进行插值
float currentDistance = Vector3.Magnitude(thirdPersonCam.transform.localPosition);
float distance;
if(Input.GetKey(KeyCode.W))
{
distance = Mathf.Lerp(currentDistance, maxFollowDistance, Time.deltaTime * followSpeed);
}
else
{
distance = Mathf.Lerp(currentDistance, followDistance, Time.deltaTime * resumeSpeed);
}
thirdPersonCam.transform.localPosition = thirdPersonBackCamDir.normalized * distance;
}
既然想要平滑的过渡,就肯定是使用lerp插值了,因为貌似不能直接对vector3插值找点,于是我就确定方向后对距离(模长)插值,注意要使用当前位置(currentDistance)插值而不能直接在followDistance和max之间插值,不然在未达到最远距离时停下就会出问题。
视角旋转
视角的旋转我目前打算选择有一定限制的鼠标控制自由视角,获取鼠标输入进行旋转并使用clamp进行限制
public float rotateSpeed;//视角转向速度
public float lookDownThreold;//向下看的最大角度
public float lookUpThreshold;//向上看的最大角度
public float lookAroundRange;//向左右看的最大角度
void UpdateCamRot()
{
//左右旋转视角
float rotateX = -Input.GetAxis("Mouse Y");
float rotateY = Input.GetAxis("Mouse X");
//使用直接设置localEulerAngles或者先自转x再公转y的方式避免欧拉旋转带来的绕z轴旋转问题
//注意获取到的角的范围是在0-360,想实现中心对称clamp需要进行平移
float currentX, currentY;
if(thirdPersonCam.transform.localEulerAngles.x >= 180) currentX = thirdPersonCam.transform.localEulerAngles.x - 360;
else currentX = thirdPersonCam.transform.localEulerAngles.x;
if(thirdPersonCam.transform.localEulerAngles.y >= 180) currentY = thirdPersonCam.transform.localEulerAngles.y - 360;
else currentY = thirdPersonCam.transform.localEulerAngles.y;
float angX = Mathf.Clamp(currentX + rotateX * rotateSpeed * Time.deltaTime, lookDownThreold, lookUpThreshold);
float angY = Mathf.Clamp(currentY + rotateY * rotateSpeed * Time.deltaTime, -lookAroundRange, lookAroundRange);
thirdPersonCam.transform.localEulerAngles = new Vector3(angX, angY, 0);
//thirdPersonCam.transform.Rotate(x, 0, 0, Space.Self);
//thirdPersonCam.transform.Rotate(0, y, 0, Space.World);
}
这里主要遇到两个问题,第一个就是角度的值获取到的范围是0-360,没办法把我的左右或上下连贯起来(由负到正),clamp出一个很奇怪的效果,必须要先做处理
第二个是我早就知道但写的时候又忘了的欧拉旋转问题,如果将旋转写作Rotate(x, y, 0)的话就会出现意外的绕z轴旋转,主要是因为欧拉旋转顺序是zxy,第一次旋转的时候将坐标系旋转了,导致第二次旋转(也就是绕y旋转)会不是绕“我们想要的y轴”而是第一次旋转后的y轴旋转,最后z坐标也发生了变化。正确的写法就是代码和注释中的两种
视角切换
这样的话有个问题就是没法看到人物的正面,所以我就再做了个检视角度(类似gta),把摄像机放在人物前面
public Vector3 thirdPersonBackCamDir;//第三人称默认相机的方向(相对人物)
public Vector3 thirdPersonFrontCamPos;//第三人称前置相机的位置
public Transform lookAt;//注视位置
void ChangeViewAspect()
{
switch(viewtype)
{
case ViewType.ThirdPersonBack:
viewtype = ViewType.ThirdPersonFront;
break;
case ViewType.ThirdPersonFront:
viewtype = ViewType.ThirdPersonBack;
break;
}
SetCamTransform(viewtype);
}
void SetCamTransform(ViewType vt)
{
switch(viewtype)
{
case ViewType.ThirdPersonBack:
thirdPersonCam.transform.localPosition = thirdPersonBackCamDir.normalized * followDistance;
break;
case ViewType.ThirdPersonFront:
thirdPersonCam.transform.localPosition = thirdPersonFrontCamPos;
break;
}
thirdPersonCam.transform.LookAt(lookAt);
}
暂时没打算加入第一人称。
同时前置检视会禁止视角旋转和相机缩放(只有硬跟随)
void FixedUpdate()
{
if(viewtype == ViewType.ThirdPersonBack)
{
UpdateCamPos();
UpdateCamRot();
}
if(Input.GetKeyDown(KeyCode.V)) ChangeViewAspect();
}
人物移动和相机移动都放在了fixedupdate中
最终效果