这个游戏可能有点大,我们一步步来实现。
一、模型的导入和坦克的移动逻辑
首先给坦克模型添加Rigidbody组件,设置mass=2000;添加Box Collider组件,调整触发器的大小。
编写模型移动的脚本:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TankMove : MonoBehaviour
{
//表示坦克移动和旋转的变量
public float moveSpeed = 20.0f;
public float rotSpeed = 50.0f;
//要分配各组件的变量
private Rigidbody rbody;
private Transform tr;
//保存键盘输入值的变量
private float h, v;
// Start is called before the first frame update
void Start()
{
//初始化各组件
rbody = GetComponent<Rigidbody>();
tr = GetComponent<Transform>();
//将Rigidbody的重心设置为较低的值
rbody.centerOfMass = new Vector3(0.0f, -0.5f, 0.0f);
}
// Update is called once per frame
void Update()
{
h = Input.GetAxis("Horizontal");
v = Input.GetAxis("Vertical");
//旋转和移动处理
tr.Rotate(Vector3.up * rotSpeed * h * Time.deltaTime);
tr.Translate(Vector3.forward * v * moveSpeed * Time.deltaTime);
}
}
以上脚本的编写和普通游戏物体的移动相似,通过获取X和Y轴进行旋转和移动。
***这里要注意一点是坦克的重心相对较低,较低重心的方式为:
rbody.centerOfMass = new Vector3(0.0f, -0.5f, 0.0f);
将获取到的刚体组件进行调用centerOfMass函数进行实现。
二、实现坦克的履带动画
整体思路我们是通过改变履带纹理的偏移量实现履带旋转。
纹理的理解是如果现在有一个物体,给它增加一个材质(Matiral),其实这个材质就是一个纹理,我们可以设置其的shader方式,包括纹理贴图Diffuse、Normal map等
具体的代码:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TrackAnim : MonoBehaviour
{
//纹理旋转速度
private float scrollSpeed = 1000.0f;
private Renderer _renderer;
// Start is called before the first frame update
void Start()
{
_renderer = GetComponent<Renderer>();
}
// Update is called once per frame
void Update()
{
var offset = Time.time * scrollSpeed * Input.GetAxisRaw("Vertical");
//更改默认纹理的Y偏移量值
_renderer.material.SetTextureOffset("_MainTex", new Vector2(0, offset));
//更改常规纹理的Y偏移量值
_renderer.material.SetTextureOffset("_BumpMap", new Vector2(0, offset));
}
}
这里需要说明的是Renderer是渲染组件,如果需要对其材质改变,需要先获取渲染组件,在调用材质,再设置材质的偏移量。
这里的所有操作都是在update函数中进行,而时间的改变主要在于Time.time:其表示总共花费的时间。
这里的函数重点再说一句:
void SetTextureOffset(string propertyName, Vecter2 offset)
纹理属性名称“_MainTex”--Diffuse类型、“_BimpMap”--Normal map类型、"_Cube"--Cubemap类型。
单词:Renderer---渲染、offset---偏移量。
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
三、摄像机追随
主要用到是Unity内置的资源包SmoothFollow-------路径Standard Assets/Utility/SmoothFollow
将脚本SmoothFollow添加到Main Camera上,并且设置追随目标(Target)
四、旋转炮塔
炮塔旋转这一部分可能有点复杂,我们慢慢来看。
我们主要根据鼠标的旋转方向进行旋转,如何实现这个过程:
1、首先创建一条又摄像机到鼠标位置的一条射线
//通过主摄像机生成向鼠标光标指示位置发射的射线
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
2、接下来对射线进行处理
先通过下面函数进行判断,具体而言ray是我们生成的射线,hit是碰撞体信息,Infinity是射线距离,1<<8是过滤层
***1<<8这个参数相对较为重要,它决定了射线碰撞的对象是第8层,我们将Terrain设置为了第8层,碰撞对象就是Terrain。
Physics.Raycast(ray, out hit, Mathf.Infinity, 1 << 8)
3、将射线的位置转化为本地坐标
//将射线击中的位置转换为本地坐标
Vector3 relative = tr.InverseTransformPoint(hit.point);
4、根据本地坐标计算炮塔旋转的角度
//用反正切函数Atan2计算炮塔要旋转的角度
float angle = Mathf.Atan2(relative.x, relative.z) * Mathf.Rad2Deg;
上面是对x,z的坐标进行了反正切运算,求出一个弧度值,在乘以Mathf.Rad2Deg转化为了角度值。
5、对炮塔进行旋转
//以rotSpeed变量作为炮塔旋转角度
tr.Rotate(0, angle * Time.deltaTime * rotSpeed, 0);
整体代码:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TurretCtrl : MonoBehaviour
{
private Transform tr;
//保存光线击中地面的位置的变量
private RaycastHit hit;
//炮塔的旋转速度
public float rotSpeed = 5.0f;
// Start is called before the first frame update
void Start()
{
tr = GetComponent<Transform>();
}
// Update is called once per frame
void Update()
{
//通过主摄像机生成向鼠标光标指示位置发射的射线
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
//在场景视图中以绿色光线表示射线
Debug.DrawRay(ray.origin, ray.direction * 100.0f, Color.green);
if (Physics.Raycast(ray, out hit, Mathf.Infinity, 1 << 8))
{
//将射线击中的位置转换为本地坐标
Vector3 relative = tr.InverseTransformPoint(hit.point);
//用反正切函数Atan2计算炮塔要旋转的角度
float angle = Mathf.Atan2(relative.x, relative.z) * Mathf.Rad2Deg;
//以rotSpeed变量作为炮塔旋转角度
tr.Rotate(0, angle * Time.deltaTime * rotSpeed, 0);
}
}
}
将脚本添加到Turret上,就实现了移动鼠标光标进行炮塔旋转。
五、调整炮身角度
使用鼠标滚轮实现炮弹的发射角度,就是上下移动
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CannonCtrl : MonoBehaviour
{
private Transform tr;
public float rotSpeed = 100.0f;
// Start is called before the first frame update
void Start()
{
tr = GetComponent<Transform>();
}
// Update is called once per frame
void Update()
{
float angle = -Input.GetAxis("Mouse ScrollWheel") * Time.deltaTime * rotSpeed;
tr.Rotate(angle, 0, 0);
}
}
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
六、建立炮弹预设和发射逻辑
1、建立炮弹预设
1) 创建一个空对象,添加Capsule Collider组件和Rigidbody组件,
另外还需要添加一个Trail Renderer组件,主要实现炮弹发射后的视觉效果,其需要添加一个纹理。
2)添加脚本,实现子弹的向前发射
//炮弹速度
public float speed = 6000.0f;
GetComponent<Rigidbody>().AddForce(transform.forward * speed);
3)完成预设之后,我们将其放置在Resources文件夹下,用以脚本中调动预设资源。
2、设置炮弹的起始位置
Tank--->Turrent(炮台)--->Cannon(大炮)--->FirePos,在Cannon下创建空子对象FirePos,将其移动到合适的位置。
3、点击鼠标左键实现发射子弹
1)定义炮弹预设和炮弹初始点的变量
//炮弹预设
public GameObject cannon = null;
//炮弹发射初始点
public Transform firePos;
2)加载预设资源到变量
void Awake()
{
//加载Resources文件夹中的Cannon预设
cannon = (GameObject)Resources.Load("Cannon");
}
3)点击鼠标左键实现发射子弹
void Update()
{
//点击鼠标左键时发射逻辑
if (Input.GetMouseButtonDown(0))
{
Fire();
}
}
void Fire()
{
Instantiate(cannon, firePos.position, firePos.rotation);
}
}
克隆,首先应该克隆预设、克隆发射位置、克隆旋转。
整体代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FireCannon : MonoBehaviour
{
//炮弹预设
public GameObject cannon = null;
//炮弹发射初始点
public Transform firePos;
void Awake()
{
//加载Resources文件夹中的Cannon预设
cannon = (GameObject)Resources.Load("Cannon");
}
void Update()
{
//点击鼠标左键时发射逻辑
if (Input.GetMouseButtonDown(0))
{
Fire();
}
}
void Fire()
{
Instantiate(cannon, firePos.position, firePos.rotation);
}
}
4、子弹和物体发生碰撞时产生爆炸效果
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Cannon : MonoBehaviour
{
//炮弹速度
public float speed = 6000.0f;
//爆炸效果预设
public GameObject expEffect;
private CapsuleCollider _collider;
private Rigidbody _rigidbody;
// Start is called before the first frame update
void Start()
{
_collider = GetComponent<CapsuleCollider>();
_rigidbody = GetComponent<Rigidbody>();
GetComponent<Rigidbody>().AddForce(transform.forward * speed);
//3秒后执行自动爆炸的协程函数
StartCoroutine(this.ExplosionCannon(3.0f));
}
void OnTriggerEnter()
{
//撞击地面或坦克时立即爆炸
StartCoroutine(this.ExplosionCannon(0.0f));
}
IEnumerator ExplosionCannon(float tm)
{
yield return new WaitForSeconds(tm);
//禁用Collider组件
_collider.enabled = false;
//无需再受物理引擎影响
_rigidbody.isKinematic = true;
//动态生成爆炸预设
GameObject obj = (GameObject)Instantiate(expEffect, transform.position, Quaternion.identity);
Destroy(obj, 1.0f);
//Trail Renderer消失并等待一段时间后删除炮弹
Destroy(this.gameObject, 1.0f);
}
// Update is called once per frame
void Update()
{
}
}
七、炮弹发射音效
首先为Tank添加AudioSource组件
具体过程:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FireCannon : MonoBehaviour
{
//炮弹预设
private GameObject cannon = null;
//炮弹发射声音
private AudioClip fireSfx = null;
//AudioSource组件
private AudioSource sfx = null;
//炮弹发射初始点
public Transform firePos;
void Awake()
{
//加载Resources文件夹中的Cannon预设
cannon = (GameObject)Resources.Load("Cannon");
//从Resources文件夹中加载炮弹声音文件
fireSfx = Resources.Load<AudioClip>("CannonFire");
//声明AudioSource变量
sfx = GetComponent<AudioSource>();
}
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
//点击鼠标左键时发射逻辑
if (Input.GetMouseButtonDown(0))
{
Fire();
}
}
void Fire()
{
sfx.PlayOneShot(fireSfx, 1.0f);
Instantiate(cannon, firePos.position, firePos.rotation);
}
}
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
八、安装Photon Unity Networking插件
1、在资源商店搜索PUN---Photon Unity Networking Free,下载安装
2、需要在网站www.exitgames.com注册会员得到Application ID
3、下载好PUN后,将AppId填入,选择地区,协议选择TCP协议稳定一点
4、选择Auto-Join Lobby(自动加入大厅)
单词:Photon(光子)、Lobby(游戏大厅)
这里先介绍一下网络游戏的基本过程:
玩家--->首先连接光子云(Photon Cloud)---->连接成功后会进入到游戏大厅(Lobby)--->接着光子云创建游戏房间(room)--->玩家进入房间就可以开始游戏了
这里需要说明的是第一个创建房间的玩家会成为主客户端,如果玩家退出,其他玩家成为主客户端。
8.1连接Photon Cloud
层次视图创建空对象(PhotonInit)
主要的代码如下:
public string version = "v1.0";
void Awake()
{
//连接Photo Cloud
PhotonNetwork.ConnectUsingSettings(version);
}
PhotonNetwork.ConnectUsingSettings(),通过这个函数就可连接到光子云,参数是版本号,实现同一版本号的玩家进行游戏。
完整代码:
public string version = "v1.0";
void Awake()
{
//连接Photo Cloud
PhotonNetwork.ConnectUsingSettings(version);
}
//正常连接Photo Cloud并进入大厅后调用回调函数
void OnJoinedLobby()
{
Debug.Log("Entered Lobby!");
}
8.2随机配对
随机加入房间
//正常连接Photo Cloud并进入大厅后调用回调函数
void OnJoinedLobby()
{
Debug.Log("Entered Lobby!");
PhotonNetwork.JoinRandomRoom();
}
随机连接房间失败
//随机连接房间失败时调用回调函数
void OnPhotonRandomJoinFailed()
{
Debug.Log("No rooms!");
}
8.3制作房间
//随机连接房间失败时调用回调函数
void OnPhotonRandomJoinFailed()
{
Debug.Log("No rooms!");
//建立房间
PhotonNetwork.CreateRoom("MyRoom");
}
void OnJoinedRoom()
{
Debug.Log("Enter Room");
}
完整代码:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PhotonInit : MonoBehaviour
{
//App版本信息
public string version = "v1.0";
void Awake()
{
//连接Photo Cloud
PhotonNetwork.ConnectUsingSettings(version);
}
//正常连接Photo Cloud并进入大厅后调用回调函数
void OnJoinedLobby()
{
Debug.Log("Entered Lobby!");
PhotonNetwork.JoinRandomRoom();
}
//随机连接房间失败时调用回调函数
void OnPhotonRandomJoinFailed()
{
Debug.Log("No rooms!");
//建立房间
PhotonNetwork.CreateRoom("MyRoom");
}
void OnJoinedRoom()
{
Debug.Log("Enter Room");
}
void OnGUI()
{
//画面左上角出现连接过程日志
GUILayout.Label(PhotonNetwork.connectionStateDetailed.ToString());
}
}