这个游戏可能有点大,我们一步步来实现。
一、模型的导入和坦克的移动逻辑
首先给坦克模型添加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()); } }