网络游戏制作---坦克大战(2)
一、动态生成坦克
1、首先将Tank预设放置到Resources文件夹下
2、进入房间后生成坦克,主要用到的函数为:Quaternion.identity表示无旋转
float pos = Random.Range(-100.0f, 100.0f); PhotonNetwork.Instantiate("Tank", new Vector3(pos, 20.0f, pos), Quaternion.identity, 0)
完整代码:
void OnJoinedRoom() { Debug.Log("Enter Room"); //调用生成坦克函数 CreateTank(); } //生成坦克的函数 void CreateTank() { float pos = Random.Range(-100.0f, 100.0f); PhotonNetwork.Instantiate("Tank", new Vector3(pos, 20.0f, pos), Quaternion.identity, 0); }
二、动态生成坦克后摄像机跟随会产生问题
怎样实现摄像机动态跟随?
整体的思路是通过Photon View组件的isMine属性判断生成的预设是本地的还是远程网络的
//使用SmoothFollow脚本前添加命名空间 using UnityStandardAssets.Utility; //声明PhotonView组件的变量 private PhotonView pv = null; //主摄像机追踪的CamPivot游戏对象 public Transform camPivot; //分配PhotonView组件 pv = GetComponent<PhotonView>(); //如果PhotonView为自己本地生成的坦克 if (pv.isMine) { //为主摄像机中的SmoothFollow脚本设置追踪对象 Camera.main.GetComponent<SmoothFollow>().target = camPivot; }
三、控制自己的坦克
//如果PhotonView为自己本地生成的坦克 if (pv.isMine) { //为主摄像机中的SmoothFollow脚本设置追踪对象 Camera.main.GetComponent<SmoothFollow>().target = camPivot; //将Rigidbody的重心设置为较低的值 rbody.centerOfMass = new Vector3(0.0f, -0.5f, 0.0f); } else { //如果是远程玩家的坦克,则设置使其不受物理力的影响 rbody.isKinematic = true; }
四、平滑移动和旋转处理
PhotonView组件传输的周期性导致坦克的移动不够平滑,通过OnPhotonSerializeView回调函数构建数据通信部分逻辑,
实现游戏中坦克位置和旋转信息的网络同步。
//声明坦克位置和旋转信息的变量并设置初始值 private Vector3 currPos = Vector3.zero; private Quaternion currRot = Quaternion.identity;
void Awake()
{
//设置传送数据类型
pv.synchronization = ViewSynchronization.UnreliableOnChange;
//将PhotonView组件的Observed属性设置为TankMove脚本
pv.ObservedComponents[0] = this;
//设置坦克位置和旋转值的初始值
currPos = tr.position;
currRot = tr.rotation;
}
OnPhotonSerializeView函数
void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info) { //传送本地坦克的位置和炮塔旋转信息 if (stream.isWriting) { stream.SendNext(tr.position); stream.SendNext(tr.rotation); } else //接受远程玩家坦克的位置和炮塔旋转信息 { currPos = (Vector3)stream.ReceiveNext(); currRot = (Quaternion)stream.ReceiveNext(); } }
接下来是Update函数
void Update() { //如果是自己本地的坦克则直接移动/旋转 if (pv.isMine) { h = Input.GetAxis("Horizontal"); v = Input.GetAxis("Vertical"); //旋转和移动处理 tr.Rotate(Vector3.up * rotSpeed * h * Time.deltaTime); tr.Translate(Vector3.forward * v * moveSpeed * Time.deltaTime); } else //如果是远程玩家的坦克 { //将远程玩家的坦克平滑移动到目标位置 tr.position = Vector3.Lerp(tr.position, currPos, Time.deltaTime * 3.0f); //将远程玩家的坦克炮塔平滑旋转到一定角度 tr.rotation = Quaternion.Slerp(tr.rotation, currRot, Time.deltaTime * 3.0f); } }
位置:Vector3.Lerp()函数实现了从tr.position移动到currPos的位置,最后返回的是Vector3的位置坐标信息。
旋转:Quaternion.Slerp()函数实现了从tr.rotation旋转到CurrRot的位置,最后返回的是旋转的四元组信息。
整体代码:
using System.Collections; using System.Collections.Generic; using UnityEngine; //使用SmoothFollow脚本前添加命名空间 using UnityStandardAssets.Utility; public class TankMove : MonoBehaviour { //表示坦克移动和旋转的变量 public float moveSpeed = 20.0f; public float rotSpeed = 50.0f; //要分配各组件的变量 private Rigidbody rbody; private Transform tr; //保存键盘输入值的变量 private float h, v; //声明PhotonView组件的变量 private PhotonView pv = null; //主摄像机追踪的CamPivot游戏对象 public Transform camPivot; //声明坦克位置和旋转信息的变量并设置初始值 private Vector3 currPos = Vector3.zero; private Quaternion currRot = Quaternion.identity; // Start is called before the first frame update void Awake() { //初始化各组件 rbody = GetComponent<Rigidbody>(); tr = GetComponent<Transform>(); //将Rigidbody的重心设置为较低的值 rbody.centerOfMass = new Vector3(0.0f, -0.5f, 0.0f); //分配PhotonView组件 pv = GetComponent<PhotonView>(); //设置传送数据类型 pv.synchronization = ViewSynchronization.UnreliableOnChange; //将PhotonView组件的Observed属性设置为TankMove脚本 pv.ObservedComponents[0] = this; //如果PhotonView为自己本地生成的坦克 if (pv.isMine) { //为主摄像机中的SmoothFollow脚本设置追踪对象 Camera.main.GetComponent<SmoothFollow>().target = camPivot; //将Rigidbody的重心设置为较低的值 rbody.centerOfMass = new Vector3(0.0f, -0.5f, 0.0f); } else { //如果是远程玩家的坦克,则设置使其不受物理力的影响 rbody.isKinematic = true; } //设置坦克位置和旋转值的初始值 currPos = tr.position; currRot = tr.rotation; } void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info) { //传送本地坦克的位置和炮塔旋转信息 if (stream.isWriting) { stream.SendNext(tr.position); stream.SendNext(tr.rotation); } else //接受远程玩家坦克的位置和炮塔旋转信息 { currPos = (Vector3)stream.ReceiveNext(); currRot = (Quaternion)stream.ReceiveNext(); } } // Update is called once per frame void Update() { //如果是自己本地的坦克则直接移动/旋转 if (pv.isMine) { h = Input.GetAxis("Horizontal"); v = Input.GetAxis("Vertical"); //旋转和移动处理 tr.Rotate(Vector3.up * rotSpeed * h * Time.deltaTime); tr.Translate(Vector3.forward * v * moveSpeed * Time.deltaTime); } else //如果是远程玩家的坦克 { //将远程玩家的坦克平滑移动到目标位置 tr.position = Vector3.Lerp(tr.position, currPos, Time.deltaTime * 3.0f); //将远程玩家的坦克炮塔平滑旋转到一定角度 tr.rotation = Quaternion.Slerp(tr.rotation, currRot, Time.deltaTime * 3.0f); } } }
五、同步炮塔和炮身
向Turrent和Cannon添加PhotonView组件,同时修改TurrentCtrl和CannonCtrl脚本,这个过程主要是保证玩家只能移动自己坦克的炮塔和炮身。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class TurretCtrl : MonoBehaviour { private Transform tr; //保存光线击中地面的位置的变量 private RaycastHit hit; //炮塔的旋转速度 public float rotSpeed = 5.0f; //PhotonView组件 private PhotonView pv = null; //保存远程网络坦克炮塔旋转值的变量 private Quaternion currRot = Quaternion.identity; // Start is called before the first frame update void Awake() { tr = GetComponent<Transform>(); pv = GetComponent<PhotonView>(); //将Photon View的Observed属性设置为当前脚本 pv.ObservedComponents[0] = this; //设置Photon View同步属性 pv.synchronization = ViewSynchronization.UnreliableOnChange; //初始化旋转值 currRot = tr.localRotation; } // Update is called once per frame void Update() { //如果是本地生成的坦克 if (pv.isMine) { //通过主摄像机生成向鼠标光标指示位置发射的射线 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); } } else //如果是远程网络玩家的坦克 { //从当前位置平滑移动到目标角度 tr.localRotation = Quaternion.Slerp(tr.localRotation, currRot, Time.deltaTime * 3.0f); } } //收发数据回调函数 void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info) { if (stream.isWriting) { stream.SendNext(tr.localRotation); } else { currRot = (Quaternion)stream.ReceiveNext(); } } }
using System.Collections; using System.Collections.Generic; using UnityEngine; public class CannonCtrl : MonoBehaviour { private Transform tr; public float rotSpeed = 100.0f; //PhotonView组件 private PhotonView pv = null; //保存远程网络坦克炮身旋转角度的变量 private Quaternion currRot = Quaternion.identity; // Start is called before the first frame update void Awake() { tr = GetComponent<Transform>(); pv = GetComponent<PhotonView>(); //将Photon View的Observed属性设置为当前脚本 pv.ObservedComponents[0] = this; //设置Photon View的同步属性 pv.synchronization = ViewSynchronization.UnreliableOnChange; //初始化旋转值 currRot = tr.localRotation; } // Update is called once per frame void Update() { if (pv.isMine) { float angle = -Input.GetAxis("Mouse ScrollWheel") * Time.deltaTime * rotSpeed; tr.Rotate(angle, 0, 0); } else { //从当前位置平滑旋转到目标角度 tr.localRotation = Quaternion.Slerp(tr.localRotation, currRot, Time.deltaTime * 3.0f); } } //收发数据回调函数 void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info) { if (stream.isWriting) { stream.SendNext(tr.localRotation); } else { currRot = (Quaternion)stream.ReceiveNext(); } } }