🟥 效果展示
首先,将PUN设为离线模式,先来进行本地测试。等发布时,再取消勾选改为联网模式。
为什么设置,可参考:传送门
🟧 机器人运动
Robot Kyle 从Assets拖到层级面板,进行如下配置:
1️⃣ 状态机及状态机控制
a、指定机器人状态机:Kyle Robot
b、挂载如下代码:
该代码负责控制机器人运动,WAD运动,跑起来后右键跳跃。
using Photon.Pun; using UnityEngine; public class PlayerAnimatorManager : MonoBehaviourPun { #region Private Fields [SerializeField] float directionDampTime = 0.25f; Animator animator; #endregion #region Mono CallBacks void Start() { animator = GetComponent<Animator>(); } void Update() { //教程参考:https://skode.blog.csdn.net/article/details/106356112 if (PhotonNetwork.IsConnected == true && photonView.IsMine == false) return; AnimatorStateInfo stateInfo = animator.GetCurrentAnimatorStateInfo(0); // 只有在我们跑步的时,按下右键才允许跳跃。 if (stateInfo.IsName("Base Layer.Run") && Input.GetButtonDown("Fire2")) animator.SetTrigger("Jump"); float h = Input.GetAxis("Horizontal"); float v = Input.GetAxis("Vertical") < 0 ? 0 : Input.GetAxis("Vertical"); animator.SetFloat("Speed", h * h + v * v); animator.SetFloat("Direction", h, directionDampTime, Time.deltaTime); } #endregion }
2️⃣ 添加角色控制器
为机器人添加 CharacterController,调整Center等,属性如下:
3️⃣ 制作预制体
创建 Resources 文件夹,将Robot Kyle拖到Resources制成预制体。
🟨 相机跟随
为机器人添加如下脚本,勾选 FollwOnStart
该脚本的FollowOnStart,为离线模式,该功能是在离线模式下,让摄像机自动找到人物跟随。在发布时的联网状态下,需取消勾选,自己写代码判断哪个人物是自己的,来跟随。
你的Camera要为 MainCamera
using UnityEngine; // Camera work. Follow a target public class Skode_CameraWork : MonoBehaviour { #region Fields [Tooltip("在局部x-z平面到目标的距离")] public float distance = 7.0f; [Tooltip("我们希望相机高于目标的高度")] public float height = 3.0f; [Tooltip("相机高度的平滑时滞")] public float heightSmoothLag = 0.3f; [Tooltip("允许相机垂直于目标,例如,提供更多的景色和较少的地面")] public Vector3 centerOffset = Vector3.zero; [Tooltip("如果预制组件被光子网络改变,则将此设置为false,并在需要时手动调用OnStartFollowing()")] public bool followOnStart = false; // cached transform of the target Transform cameraTransform; // maintain a flag internally to reconnect if target is lost or camera is switched bool isFollowing; // Represents the current velocity, this value is modified by SmoothDamp() every time you call it. float heightVelocity; // Represents the position we are trying to reach using SmoothDamp() float targetHeight = 100000.0f; #endregion #region Mono Callbacks void Start() { // Start following the target if wanted. if (followOnStart) OnStartFollowing(); } void LateUpdate() { // The transform target may not destroy on level load, // so we need to cover corner cases where the Main Camera is different everytime we load a new scene, and reconnect when that happens if (cameraTransform == null && isFollowing) OnStartFollowing(); // only follow is explicitly declared if (isFollowing) Apply(); } #endregion #region Public Methods /// <summary> /// Raises the start following event. /// Use this when you don't know at the time of editing what to follow, typically instances managed by the photon network. /// </summary> public void OnStartFollowing() { cameraTransform = Camera.main.transform; isFollowing = true; // we don't smooth anything, we go straight to the right camera shot Cut(); } #endregion #region Private Methods /// <summary> /// Follow the target smoothly /// </summary> void Apply() { Vector3 targetCenter = transform.position + centerOffset; // Calculate the current & target rotation angles float originalTargetAngle = transform.eulerAngles.y; float currentAngle = cameraTransform.eulerAngles.y; // Adjust real target angle when camera is locked float targetAngle = originalTargetAngle; currentAngle = targetAngle; targetHeight = targetCenter.y + height; // Damp the height float currentHeight = cameraTransform.position.y; currentHeight = Mathf.SmoothDamp(currentHeight, targetHeight, ref heightVelocity, heightSmoothLag); // Convert the angle into a rotation, by which we then reposition the camera Quaternion currentRotation = Quaternion.Euler(0, currentAngle, 0); // Set the position of the camera on the x-z plane to: // distance meters behind the target cameraTransform.position = targetCenter; cameraTransform.position += currentRotation * Vector3.back * distance; // Set the height of the camera cameraTransform.position = new Vector3(cameraTransform.position.x, currentHeight, cameraTransform.position.z); // Always look at the target SetUpRotation(targetCenter); } /// <summary> /// Directly position the camera to a the specified Target and center. /// </summary> void Cut() { float oldHeightSmooth = heightSmoothLag; heightSmoothLag = 0.001f; Apply(); heightSmoothLag = oldHeightSmooth; } /// <summary> /// Sets up the rotation of the camera to always be behind the target /// </summary> /// <param name="centerPos">Center position.</param> void SetUpRotation(Vector3 centerPos) { Vector3 cameraPos = cameraTransform.position; Vector3 offsetToCenter = centerPos - cameraPos; // Generate base rotation only around y-axis Quaternion yRotation = Quaternion.LookRotation(new Vector3(offsetToCenter.x, 0, offsetToCenter.z)); Vector3 relativeOffset = Vector3.forward * distance + Vector3.down * height; cameraTransform.rotation = yRotation * Quaternion.LookRotation(relativeOffset); } #endregion }
🟩 攻击系统
1️⃣ 激光射线
在机器人Head下新建如图所示两个cube作为激光射线
2️⃣ 碰撞器设置
只使用一个Collider就好,并作为触发器,避免作为碰撞器将别人碰飞
- 将右眼激光的Collider移除,左眼Collider拉大包裹住两个cube
- 勾选Collider的 isTrigger
- 将带Collider射线的tag设为 beam
3️⃣ 激光控制
机器人添加下方脚本,并将Beams赋值给它
实现:当按下鼠标左键,打开激光。松开左键,关闭激光。
在网络中多人玩,还要考虑:
我按下了鼠标左键,那场景中的各个机器人,怎么判断我是属于谁,你按下鼠标左键我要不要执行程序?
if (photonView.IsMine),便实现了此功能。判断这个机器人是不是我的。是的话,执行程序。
using UnityEngine; using Photon.Pun; public class PlayerManager : MonoBehaviour { #region Parameters public GameObject beams; //当用户开火时,为True bool IsFiring; #endregion #region Mono CallBacks void Awake() { beams.SetActive(false); } void Update() { if (PhotonNetwork.IsConnected == true && photonView.IsMine) ProcessInputs(); if (IsFiring != beams.activeSelf) beams.SetActive(IsFiring); } #endregion #region Private Methods void ProcessInputs() { //鼠标左键 if (Input.GetButtonDown("Fire1")) IsFiring = true; if (Input.GetButtonUp("Fire1")) IsFiring = false; } #endregion }
4️⃣ 预制体Apply一下
🟦 生命值系统
目标:
当射线击中时,扣0.1血,一直击中,每秒0.1。
当生命值0时,离开房间。
1、GameManager改为单例
2、PlayerManager 更新如下:
using UnityEngine; using Photon.Pun; public class PlayerManager : MonoBehaviourPunCallbacks { #region Parameters public GameObject beams; public float Health = 1f; //当用户开火时,为True bool IsFiring; #endregion #region Mono CallBacks void Awake() { beams.SetActive(false); } void Update() { if (Health <= 0f) GameManager.ins.Skode_LeaveRoom(); if (PhotonNetwork.IsConnected == true && photonView.IsMine) ProcessInputs(); if (IsFiring != beams.activeSelf) beams.SetActive(IsFiring); } #endregion #region Private Methods void ProcessInputs() { //鼠标左键 if (Input.GetButtonDown("Fire1")) IsFiring = true; if (Input.GetButtonUp("Fire1")) IsFiring = false; } void OnTriggerEnter(Collider other) { if (!photonView.IsMine && !other.CompareTag("beam")) return; Health -= 0.1f; } void OnTriggerStay(Collider other) { if (!photonView.IsMine && !other.CompareTag("beam")) return; //乘以增量时间,防止因为帧率FPS不同,扣血不同(举例:不乘,每帧执行一次扣血,卡的人比流畅的人扣血少) Health -= 0.1f * Time.deltaTime; } #endregion }