好久不见。
今天来汇报一下进度,等这次任务完成所有的这些随手记都会被一个完整的博客代替。
由于时间有点久了,我就不按顺序来了。
首先要说的是我放弃了Cinemachine插件,到油管上学了一下Sebastian Lague大佬2016年发布的一个系列视频
Character Creation (E08: third person camera)
这个可以说是相当简单的第三人称摄像机脚本了。我自己拿到手添加了用滚轮拉近拉远的功能。大佬不愧是大佬,代码十分简洁,相比之下我写的简直就是垃圾。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ThirdPersonCamera : MonoBehaviour
{
// 鼠标灵敏度
public float mouseSensitivity = 10f;
// 滚轮灵敏度
public float mouseScrollSensitivity = 5f;
// 第三人称目标
public Transform target;
// 摄像机离目标的位置
public float distanceFromTarget = 2f;
// y方向的限制
public Vector2 pitchMinMax = new Vector2(-40f, 85f);
// 旋转运动平滑时间
public float rotationSmoothTime = 0.12f;
// 是否锁定鼠标
public bool lockCursor;
// x方向
private float yaw;
// y方向
private float pitch;
// 滚轮拉近拉远摄像机
private float distance;
// 旋转运动平滑速度
Vector3 rotationSmoothVelocity;
// 当前的旋转
Vector3 currentRotation;
void Start()
{
if(lockCursor)
{
Cursor.lockState = CursorLockMode.Locked;
Cursor.visible = false;
}
distance = distanceFromTarget;
}
// 使用LateUpdate在target.position设置好以后设置摄像机的位置
void LateUpdate()
{
yaw += Input.GetAxis("Mouse X") * mouseSensitivity;
pitch -= Input.GetAxis("Mouse Y") * mouseSensitivity;
distance -= Input.GetAxis("Mouse ScrollWheel") * mouseScrollSensitivity;
pitch = Mathf.Clamp(pitch, pitchMinMax.x, pitchMinMax.y);
distance = Mathf.Clamp(distance, 3f, 16f);
currentRotation = Vector3.SmoothDamp(currentRotation, new Vector3(pitch, yaw, 0f), ref rotationSmoothVelocity, rotationSmoothTime);
Vector3 targetRotation = currentRotation;
transform.eulerAngles = targetRotation;
distanceFromTarget = distance;
// 摄像机的位置设置在目标位置减去自身z轴方向上的特定距离
transform.position = target.position - transform.forward * distanceFromTarget;
}
}
至于为什么不用Cinemachine呢,一是到时候展示的时候怕老师的电脑没有Unity2019,二是确实用不上那么完善的套件。
然后就是还做了一个第一人称的摄像机,方便瞄准远距离的目标,可以按左shift键来切换第三人称摄像机和第一人称摄像机,第一人称摄像机可以通过滚轮来实现开镜的那种效果,原理就是改fov,同时还有一个小细节就是视野拉近以后鼠标灵敏度也要降低才行。以下是第一人称相机代码。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FPCamera : MonoBehaviour
{
public Transform cameraTransform;
public float mouseSentivity;
public Vector2 maxMinAngle;
public float mouseScrollSpeed = 5f;
private Vector3 m_mouseInputValue;
private void Start()
{
m_mouseInputValue = new Vector3();
}
void Update()
{
float mouseX = Input.GetAxis("Mouse X");
float mouseY = Input.GetAxis("Mouse Y");
float mouseScrollWheel = -Input.GetAxis("Mouse ScrollWheel");
m_mouseInputValue.y += mouseX * mouseSentivity;
m_mouseInputValue.x -= mouseY * mouseSentivity;
// 限制垂直旋转的角度(以符合现实情况)
m_mouseInputValue.x = Mathf.Clamp(m_mouseInputValue.x, maxMinAngle.x, maxMinAngle.y);
// 水平旋转整个Controller
this.transform.localRotation = Quaternion.Euler(0, m_mouseInputValue.y, 0);
// 垂直只旋转摄像机
cameraTransform.localRotation = Quaternion.Euler(m_mouseInputValue.x, 0, 0);
// 摄像机缩放
float fov = cameraTransform.GetComponent<Camera>().fieldOfView;
mouseSentivity = 0.5f + (fov / 10);
fov += mouseScrollWheel * mouseScrollSpeed;
fov = Mathf.Clamp(fov, 5f, 40f);
cameraTransform.GetComponent<Camera>().fieldOfView = fov;
}
}
有一个小问题就是第一人称和第三人称摄像机各管各的,相互不联动,导致第一人称摄像机看着一个方向,切换回第三人称看的就是第三人称切换前的方向,这样会让人感觉很奇怪,我想什么时候给他整合一下。
然后就是再往前一段时间实现的殉爆和击毁功能。
办法简单粗暴,直接替换掉游戏对象为素材里面的击毁坦克的预制体。所谓殉爆就是制作一个车身的游戏对象,一个炮塔的游戏对象,将要殉爆的对象替换为这两个对象到正确的位置,然后给炮塔一个力,我试着用随机数让每一次殉爆的效果略有不同。
提到了击毁就不得不聊一下我构思的毁伤机制,其实很简单,打中了以后在
挂载到那个敌人身上的脚本里面用随机数来决定是击毁,成员昏迷/配件损坏,还是没有伤害。为啥不是计算装甲厚度然后吧啦吧啦吧啦呢?因为装甲啥的咱也不会做啊。
private DamageType FirePowerTest()
{
DamageType damage = DamageType.NoEffect;
float firePower = Random.Range(2f, 7f);
if(firePower < armor)
{
damage = DamageType.NoEffect;
}
else
{
if(firePower < armor + 1)
{
damage = DamageType.BailedOut;
}
else
{
if(firePower <= armor + 3)
{
damage = DamageType.Destoryed;
}
else
{
if(firePower > armor + 3)
damage = DamageType.AmmoDetonation;
}
}
}
return damage;
}
这个函数名叫火力测试,来自桌游战火FOW的游戏规则,这款二战题材兵棋桌游是我的想法来源,在这款游戏中,不仅战车毁坏与否是用摇色子,是否打中也是摇色子。我使用随机数也就是为了还原这一点。当然我觉得我做的太过分了,起码要能分前后装甲吧。我之前尝试过使用组合碰撞器来实现不同模块(履带,炮塔,前装甲,后装甲),但是失败了,碰撞要能识别不同的碰撞体需要不同标签的带有刚体的游戏物体,我的敌人只能有一个刚体,那咋办嘛?我的最终方案是,实在不行的话,就按照炮弹发射的角度和碰撞到碰撞盒子的角度来间接推断。或者干脆就这样吧。
还有就是再远一点的时候完成的炮管的瞄准指示环
玩过坦克世界和战争雷霆的朋友肯定知道,炮塔转动是有一定速度的,游戏厂商为了让视角移动不要和炮塔转动同步以至于移动缓慢,将视角和炮塔旋转分开来。其中准心是视角要瞄准的目标方向,而炮塔会按自己的速度转到这个方向,所以我们需要一个指示器来示意炮管转到哪里了。
原理要说也不难,就是在炮管往前100个长度的位置放一个UI,然后调用WorldToScreenPoint。但是写的时候踩了蛮多坑,到现在实现的已经是最好效果了,依然不准确。代码就不放出来了。丢脸。。。
高速物体的碰撞问题,由于炮弹使用了Unity自带刚体的物流系统,所以直接用了Continuous Dynamic这个碰撞检测模式,性能差但是方便啊。呵呵。没有使用射线。
炮弹的刚体
到目前为止,这个项目的不足有哪些?我觉得最大的不足应该是我的技术不足,但那是没有办法的事情。但是对于我的老本行,写脚本这件事来说,我觉得我这次写的脚本很失败,当初写的时候并没有将坦克作为一个可继承的大类写出来,之后如果要加入新的坦克类型就麻烦了。现在要改就挺麻烦的。我现在相当于写了坦克有的功能,但是没有做类型区分。
但是还是要将就这样把这个项目做到一个可玩的水平,接下来要做的就是我觉得最难做的敌人AI了。
写这段话的时候项目已经完成,由于赶着期末时间比较紧,所以后续没有再写关于这个坦克项目的随手记。
简单说明一下,这个项目最后命名为《燃烧的地平线》,关于后续的AI及项目完善,详情请看:一个Unity3D制作的坦克游戏——《燃烧的地平线》