【2020.1.12】
概述
本案例来自unity官方中级游戏教程Tanks(单机双人坦克大战)
项目同时用于Unity机器学习内容的学习,预定计划为训练可规避障碍物,欲图消灭玩家的坦克ai
第一部分:场景设置
Lighting setting中的部分调整
1.关闭实时烘焙
2.一些未找到的设置:关闭【Backed GI】,使用【Precomputed Realtime GI】并调整实时分辨率由2至0.5
3.调整环境照明为纯色
摄像机相关设置
1.调整摄像机坐标到适宜位置,并旋转到合适的角度
2.投影模式设置为正投影
3.设置背景色,使得背景暴露在镜头中时其视觉效果符合游戏色调
第二部分:坦克的创建和控制
模型导入
对Tank设置相应的Layer属性
刚体组件
为Tank添加Rigbody组件
基本设置不变,冻结Tank的y轴位移和x、z轴旋转
碰撞器组件
为Tank添加Box Collider组件
将碰撞体中心和大小调整到适宜数值
声音资源组件
为Tank添加第一个Audio Source组件(EngineIdel)
选择声音片段EngineIdel,并设置播放属性Loop
为Tank添加第二个Audio Source组件(空置)
此声音资源组件用于Tank开火音效,在这个部分不进行具体实现
添加特效
将DustTrail预制体设置为Tank的子物体并复制,分别命名为RightDustTrail和LeftDustTrail,分别作为Tank的左右轨道移动痕迹特效
将坦克保存为预制体
将构造好的Tank保存为Prefabs
脚本
TankMovement
此脚本用于控制坦克的移动,其具体功能为:
1.获取玩家输入
2.通过代码控制音频、
3.控制坦克前后移动
4.控制坦克水平转向
具体的编程实现如下:
public int m_PlayerNumber = 1;//玩家编号(?)
public float m_Speed = 12f;//移动速度
public float m_TurnSpeed = 180f;//转向速度
public AudioSource m_MovementAudio;//移动音效
public AudioClip m_EngineIdling;//静止声音片段
public AudioClip m_EngineDriving;//行驶声音片段
public float m_PitchRange = 0.2f;//音高变化范围
private string m_MovementAxisName;//移动控制轴名称(基于PlayerNumber)
private string m_TurnAxisName;//转向控制轴名称(基于PlayerNumber)
private Rigidbody m_Rigidbody;//刚体组件
private float m_MovementInputValue;//移动控制输入
private float m_TurnInputValue;//转向控制输入
private float m_OriginalPitch;//原始音高(?)
private void Awake()
{
m_Rigidbody = GetComponent<Rigidbody>();
}
private void OnEnable()
{
m_Rigidbody.isKinematic = false;//接受动力学模拟
//重置坦克的输入值,以重新开始输入
m_MovementInputValue = 0f;
m_TurnInputValue = 0f;
}
private void OnDisable()
{
m_Rigidbody.isKinematic = true;
}
private void Start()
{
m_MovementAxisName = "Vertical" + m_PlayerNumber;
//对于Player1,其移动输入的轴名称为Vertical1,以此类推
m_TurnAxisName = "Horizontal" + m_PlayerNumber;
m_OriginalPitch = m_MovementAudio.pitch;//存储移动音效的原始音高
}
private void Update()
{
//获取轴输入
m_MovementInputValue = Input.GetAxis(m_MovementAxisName);
m_TurnInputValue = Input.GetAxis(m_TurnAxisName);
//
EngineAudio();
}
private void EngineAudio()//控制坦克音效并调整音高
{
if (Mathf.Abs(m_MovementInputValue) < 0.1f && Mathf.Abs(m_TurnInputValue) < 0.1f)//坦克未移动
{
if (m_MovementAudio.clip == m_EngineDriving)//当前音效为移动音效
{
m_MovementAudio.clip = m_EngineIdling;//将音效切换为静止音效
m_MovementAudio.pitch = Random.Range(m_OriginalPitch - m_PitchRange, m_OriginalPitch + m_PitchRange);
//使音效控制在以原始音高为基础的适宜范围
m_MovementAudio.Play();//重新播放音效
}
}
else
{
if (m_MovementAudio.clip == m_EngineIdling)//当前音效为静止音效
{
m_MovementAudio.clip = m_EngineDriving;//将音效切换为移动音效
m_MovementAudio.pitch = Random.Range(m_OriginalPitch - m_PitchRange, m_OriginalPitch + m_PitchRange);
//使音效控制在以原始音高为基础的适宜范围
m_MovementAudio.Play();//重新播放音效
}
}
}
private void FixedUpdate()
{
Move();
Turn();
}
private void Move()//移动方法
{
Vector3 movement = transform.forward * m_MovementInputValue * m_Speed * Time.deltaTime;//移动矢量
m_Rigidbody.MovePosition(m_Rigidbody.position + movement);//使坦克移动到指定的绝对位置
}
private void Turn()//转向方法
{
float turn = m_TurnInputValue * m_TurnSpeed * Time.deltaTime;//旋转浮点数
Quaternion turnRotation = Quaternion.Euler(0f, turn, 0f);//?
m_Rigidbody.MoveRotation(m_Rigidbody.rotation * turnRotation);//?
}
完成效果图
存在的问题:
1.坦克旋转控制相关方法(向量、欧拉角、四元数)的有关知识
2.关于代码中变量的命名前缀"m_"
这是C#中的一种变量命名规范,m意为member,表示变量是该类成员变量
第三部分:摄像机控制
创建空物体CameraRig
1.创建空物体CameraRig,用于摄像机的控制,调整其到合适的角度
2.将Camera设置为CameraRig的子物体(此时Camera的位置信息将以CameraRig为中心),调整其坐标到适宜位置
脚本
CameraControl
此脚本用于控制摄像机的移动,注意,脚本附着于CameraRig
此脚本所控制的摄像机能够适应场景中存在多个玩家的情况
具体编程实现如下:
public float m_DampTime = 0.2f;//相机移动凝滞时间
public float m_ScreenEdgeBuffer = 4f;//?屏幕边缘缓冲
public float m_MinSize = 6.5f;//最小尺寸
/*[HideInInspector]*/ public Transform[] m_Targets;//跟随目标
private Camera m_Camera;//摄像机组件引用
private float m_ZoomSpeed;//变焦速度
private Vector3 m_MoveVelocity;//摄像机当前移动速度
private Vector3 m_DesiredPosition;//期望位置(这里指摄像机在跟随多辆坦克时其中心应位于的平均位置)
private void Awake()
{
m_Camera = GetComponentInChildren<Camera>();
}
private void FixedUpdate()
{
Move();
Zoom();
}
private void Move()
{
FindAveragePosition();
transform.position = Vector3.SmoothDamp(transform.position, m_DesiredPosition, ref m_MoveVelocity, m_DampTime);
//平滑阻尼方法
//参数释义:当前物体位置,目标物体位置,参数按引用传递的当前移动速度(方法每次调用时会修改原本数值),到达目标时间
}
private void FindAveragePosition()//获取平均位置
{
Vector3 averagePos = new Vector3();//有效目标平均位置向量
int numTargets = 0;//有效目标数量
for (int i = 0; i < m_Targets.Length; i++)
{
if (!m_Targets[i].gameObject.activeSelf)//目标游戏对象不处于活跃状态(如在一轮中死亡)
continue;//跳过此轮循环
averagePos += m_Targets[i].position;//累加位置
numTargets++;//累加数量
}
if (numTargets > 0)
averagePos /= numTargets;//获得平均位置
averagePos.y = transform.position.y;//确保摄像机的y轴位置不变,即便坦克在后续修改中有y轴上的移动可能
m_DesiredPosition = averagePos;
}
private void Zoom()//变焦
{
float requierdSize = FindRequierdSize();
m_Camera.orthographicSize = Mathf.SmoothDamp(m_Camera.orthographicSize, requierdSize, ref m_ZoomSpeed, m_DampTime);
//该语句欲图调整正交摄像机视窗大小
//参数释义:正交摄像机当前视窗大小,所需的视窗大小,当前变焦速度,完成变焦时间
}
private float FindRequierdSize()
{
Vector3 desiredLocalPos = transform.InverseTransformPoint(m_DesiredPosition);
//A.InverseTransformPoint(B)
//该方法返回B以A为世界坐标原点时的坐标向量,即B相对A的坐标
//再这里通过此方法获取期望位置相对CameraRig的坐标
float size = 0f;
for (int i = 0; i