using UnityEngine; namespace Complete { public class CameraControl : MonoBehaviour { /// <summary> /// 相机重新聚焦的时间 /// </summary> public float m_DampTime = 0.2f; /// <summary> /// /// </summary> public float m_ScreenEdgeBuffer = 4f; /// <summary> /// 正交模式吓的最小视口大小 /// </summary> public float m_MinSize = 6.5f; /// <summary> /// 相机需要保卫的目标数组 /// </summary> [HideInInspector] public Transform[] m_Targets; /// <summary> /// 相机 /// </summary> private Camera m_Camera; /// <summary> /// 变焦速度 /// </summary> private float m_ZoomSpeed; /// <summary> /// 移动速度 /// </summary> private Vector3 m_MoveVelocity; /// <summary> /// 目标位置 /// </summary> private Vector3 m_DesiredPosition; private void Awake () { m_Camera = GetComponentInChildren<Camera> (); } private void FixedUpdate () { Move (); Zoom (); } /// <summary> /// 移动 /// </summary> private void Move () { FindAveragePosition (); transform.position = Vector3.SmoothDamp(transform.position, m_DesiredPosition, ref m_MoveVelocity, m_DampTime); } /// <summary> /// 查找所有目标的中间位置 /// </summary> 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; m_DesiredPosition = averagePos; } /// <summary> /// 变焦 /// </summary> private void Zoom () { float requiredSize = FindRequiredSize(); m_Camera.orthographicSize = Mathf.SmoothDamp (m_Camera.orthographicSize, requiredSize, ref m_ZoomSpeed, m_DampTime); } /// <summary> /// 查找需要的视口大小 /// </summary> /// <returns></returns> private float FindRequiredSize () { Vector3 desiredLocalPos = transform.InverseTransformPoint(m_DesiredPosition); float size = 0f; for (int i = 0; i < m_Targets.Length; i++) { if (!m_Targets[i].gameObject.activeSelf) continue; Vector3 targetLocalPos = transform.InverseTransformPoint(m_Targets[i].position); Vector3 desiredPosToTarget = targetLocalPos - desiredLocalPos; size = Mathf.Max(size, Mathf.Abs(desiredPosToTarget.y)); size = Mathf.Max(size, Mathf.Abs(desiredPosToTarget.x) / m_Camera.aspect); } size += m_ScreenEdgeBuffer; size = Mathf.Max (size, m_MinSize); return size; } /// <summary> /// 设置相机的起始位置和大小 /// </summary> public void SetStartPositionAndSize () { FindAveragePosition (); transform.position = m_DesiredPosition; m_Camera.orthographicSize = FindRequiredSize (); } } }
using System.Collections; using UnityEngine; using UnityEngine.SceneManagement; using UnityEngine.UI; namespace Complete { public class GameManager : MonoBehaviour { /// <summary> /// 要赢得游戏的局数 /// </summary> public int m_NumRoundsToWin = 5; /// <summary> /// 从回合准备转换到回合开始的时间 /// </summary> public float m_StartDelay = 3f; /// <summary> /// 从回合开始转换到回合结束的时间 /// </summary> public float m_EndDelay = 3f; /// <summary> /// 摄像机控制 /// </summary> public CameraControl m_CameraControl; /// <summary> /// 消息文本 /// </summary> public Text m_MessageText; /// <summary> /// 坦克预设 /// </summary> public GameObject m_TankPrefab; /// <summary> /// 坦克管理数组 /// </summary> public TankManager[] m_Tanks; /// <summary> /// 当前回合数 /// </summary> private int m_RoundNumber; /// <summary> /// 回合开始后的等待时间 /// </summary> private WaitForSeconds m_StartWait; /// <summary> /// 回合结束后的等待时间 /// </summary> private WaitForSeconds m_EndWait; /// <summary> /// 回合获胜者 /// </summary> private TankManager m_RoundWinner; /// <summary> /// 游戏获胜者 /// </summary> private TankManager m_GameWinner; private void Start() { m_StartWait = new WaitForSeconds (m_StartDelay); m_EndWait = new WaitForSeconds (m_EndDelay); SpawnAllTanks(); SetCameraTargets(); //开启游戏主循环协程 StartCoroutine (GameLoop ()); } /// <summary> /// 孵化所有坦克 /// </summary> private void SpawnAllTanks() { for (int i = 0; i < m_Tanks.Length; i++) { m_Tanks[i].m_Instance = Instantiate(m_TankPrefab, m_Tanks[i].m_SpawnPoint.position, m_Tanks[i].m_SpawnPoint.rotation) as GameObject; m_Tanks[i].m_PlayerNumber = i + 1; m_Tanks[i].Setup(); } } /// <summary> /// 设置相机目标 /// </summary> private void SetCameraTargets() { Transform[] targets = new Transform[m_Tanks.Length]; for (int i = 0; i < targets.Length; i++) { targets[i] = m_Tanks[i].m_Instance.transform; } m_CameraControl.m_Targets = targets; } /// <summary> /// 游戏循环协程 /// </summary> /// <returns></returns> private IEnumerator GameLoop () { yield return StartCoroutine (RoundStarting ()); yield return StartCoroutine (RoundPlaying()); yield return StartCoroutine (RoundEnding()); if (m_GameWinner != null) { SceneManager.LoadScene (0); } else { StartCoroutine (GameLoop ()); } } /// <summary> /// 回合准备协程 /// </summary> /// <returns></returns> private IEnumerator RoundStarting () { ResetAllTanks (); DisableTankControl (); m_CameraControl.SetStartPositionAndSize (); m_RoundNumber++; m_MessageText.text = "ROUND " + m_RoundNumber; yield return m_StartWait; } /// <summary> /// 回合开始协程 /// </summary> /// <returns></returns> private IEnumerator RoundPlaying () { EnableTankControl (); m_MessageText.text = string.Empty; while (!OneTankLeft()) { yield return null; } } /// <summary> /// 游戏结束协程 /// </summary> /// <returns></returns> private IEnumerator RoundEnding () { DisableTankControl (); m_RoundWinner = null; m_RoundWinner = GetRoundWinner (); if (m_RoundWinner != null) m_RoundWinner.m_Wins++; m_GameWinner = GetGameWinner (); string message = EndMessage (); m_MessageText.text = message; yield return m_EndWait; } /// <summary> /// 检查激活的坦克数量是否小于等于1,用来设置这局游戏是否结束 /// </summary> /// <returns></returns> private bool OneTankLeft() { int numTanksLeft = 0; for (int i = 0; i < m_Tanks.Length; i++) { if (m_Tanks[i].m_Instance.activeSelf) numTanksLeft++; } return numTanksLeft <= 1; } /// <summary> /// 获取每局的胜者 /// </summary> /// <returns></returns> private TankManager GetRoundWinner() { for (int i = 0; i < m_Tanks.Length; i++) { if (m_Tanks[i].m_Instance.activeSelf) return m_Tanks[i]; } return null; } /// <summary> /// 获取游戏的胜者 /// </summary> /// <returns></returns> private TankManager GetGameWinner() { for (int i = 0; i < m_Tanks.Length; i++) { if (m_Tanks[i].m_Wins == m_NumRoundsToWin) return m_Tanks[i]; } return null; } /// <summary> /// 每回合结束或游戏结束的消息 /// </summary> /// <returns></returns> private string EndMessage() { //默认信息为平局 string message = "DRAW!"; //有回合赢家,显示这回合谁赢了 if(m_RoundWinner != null) message = m_RoundWinner.m_ColoredPlayerText + " WINS THE ROUND!"; message += " "; for (int i = 0; i < m_Tanks.Length; i++) { message += m_Tanks[i].m_ColoredPlayerText + ": " + m_Tanks[i].m_Wins + " WINS "; } if (m_GameWinner != null) message = m_GameWinner.m_ColoredPlayerText + " WINS THE GAME!"; return message; } /// <summary> /// 重置所有坦克 /// </summary> private void ResetAllTanks() { for (int i = 0; i < m_Tanks.Length; i++) { m_Tanks[i].Reset(); } } /// <summary> /// 开启所有坦克的控制 /// </summary> private void EnableTankControl() { for (int i = 0; i < m_Tanks.Length; i++) { m_Tanks[i].EnableControl(); } } /// <summary> /// 关闭所有坦克的控制 /// </summary> private void DisableTankControl() { for (int i = 0; i < m_Tanks.Length; i++) { m_Tanks[i].DisableControl(); } } } }
using System; using UnityEngine; namespace Complete { [Serializable] public class TankManager { /// <summary> /// 坦克颜色 /// </summary> public Color m_PlayerColor; /// <summary> /// 坦克孵化点 /// </summary> public Transform m_SpawnPoint; /// <summary> /// 玩家编号 /// </summary> [HideInInspector] public int m_PlayerNumber; /// <summary> /// 包含颜色的代表玩家的字符串 /// </summary> [HideInInspector] public string m_ColoredPlayerText; /// <summary> /// 坦克实例的引用 /// </summary> [HideInInspector] public GameObject m_Instance; /// <summary> /// 玩家已赢得的局数 /// </summary> [HideInInspector] public int m_Wins; /// <summary> /// 坦克移动 /// </summary> private TankMovement m_Movement; /// <summary> /// 坦克射击 /// </summary> private TankShooting m_Shooting; /// <summary> /// 坦克游戏物体上的canvas /// </summary> private GameObject m_CanvasGameObject; /// <summary> /// 设置坦克 /// </summary> public void Setup () { m_Movement = m_Instance.GetComponent<TankMovement> (); m_Shooting = m_Instance.GetComponent<TankShooting> (); m_CanvasGameObject = m_Instance.GetComponentInChildren<Canvas> ().gameObject; m_Movement.m_PlayerNumber = m_PlayerNumber; m_Shooting.m_PlayerNumber = m_PlayerNumber; //设置带颜色的玩家名 m_ColoredPlayerText = "<color=#" + ColorUtility.ToHtmlStringRGB(m_PlayerColor) + ">PLAYER " + m_PlayerNumber + "</color>"; //获取并设置坦克及子物体的颜色 MeshRenderer[] renderers = m_Instance.GetComponentsInChildren<MeshRenderer> (); for (int i = 0; i < renderers.Length; i++) { renderers[i].material.color = m_PlayerColor; } } /// <summary> /// 关闭控制 /// </summary> public void DisableControl () { m_Movement.enabled = false; m_Shooting.enabled = false; m_CanvasGameObject.SetActive (false); } /// <summary> /// 开启控制 /// </summary> public void EnableControl () { m_Movement.enabled = true; m_Shooting.enabled = true; m_CanvasGameObject.SetActive (true); } /// <summary> /// 重置坦克状态 /// </summary> public void Reset () { m_Instance.transform.position = m_SpawnPoint.position; m_Instance.transform.rotation = m_SpawnPoint.rotation; m_Instance.SetActive (false); m_Instance.SetActive (true); } } }
using UnityEngine; using UnityEngine.UI; namespace Complete { /// <summary> /// 坦克生命 /// </summary> public class TankHealth : MonoBehaviour { /// <summary> /// 初始坦克生命 /// </summary> public float m_StartingHealth = 100f; /// <summary> /// 血量滑动条 /// </summary> public Slider m_Slider; /// <summary> /// 血量滑动条的图片 /// </summary> public Image m_FillImage; /// <summary> /// 坦克满血的滑动条颜色 /// </summary> public Color m_FullHealthColor = Color.green; /// <summary> /// 坦克没血的滑动条颜色 /// </summary> public Color m_ZeroHealthColor = Color.red; /// <summary> /// 坦克爆炸的预设 /// </summary> public GameObject m_ExplosionPrefab; /// <summary> /// 坦克爆炸的音效 /// </summary> private AudioSource m_ExplosionAudio; /// <summary> /// 坦克爆炸的粒子系统 /// </summary> private ParticleSystem m_ExplosionParticles; /// <summary> /// 坦克当前的血量 /// </summary> private float m_CurrentHealth; /// <summary> /// 坦克是否死亡 /// </summary> private bool m_Dead; private void Awake () { //实例化爆炸预设并获取爆炸粒子系统的引用 m_ExplosionParticles = Instantiate (m_ExplosionPrefab).GetComponent<ParticleSystem> (); //获取爆炸的音效 m_ExplosionAudio = m_ExplosionParticles.GetComponent<AudioSource> (); //禁用爆炸粒子系统 m_ExplosionParticles.gameObject.SetActive (false); } private void OnEnable() { //设置当前血量为起始血量 m_CurrentHealth = m_StartingHealth; //设置死亡标志位为false m_Dead = false; //修改血量UI SetHealthUI(); } /// <summary> /// 坦克受到伤害 /// </summary> /// <param name="amount"></param> public void TakeDamage (float amount) { //修改当前血量 m_CurrentHealth -= amount; //修改血量UI SetHealthUI (); //如果死亡,当前血量<=0 if (m_CurrentHealth <= 0f && !m_Dead) { OnDeath (); } } /// <summary> /// 设置坦克血量UI /// </summary> private void SetHealthUI () { //设置滑动条的值为当前血量 m_Slider.value = m_CurrentHealth; //根据当前血量和起始血量的比值设置坦克的滑动条血量的颜色 m_FillImage.color = Color.Lerp (m_ZeroHealthColor, m_FullHealthColor, m_CurrentHealth / m_StartingHealth); } /// <summary> /// 坦克死亡 /// </summary> private void OnDeath () { //设置死亡标志位 true m_Dead = true; //将爆炸粒子系统的位置移动到坦克的位置 m_ExplosionParticles.transform.position = transform.position; m_ExplosionParticles.gameObject.SetActive (true); //播放爆炸粒子系统 m_ExplosionParticles.Play (); //播放爆炸音效 m_ExplosionAudio.Play(); //禁用坦克 gameObject.SetActive (false); } } }
using UnityEngine; namespace Complete { /// <summary> /// 坦克移动 /// </summary> public class TankMovement : MonoBehaviour { /// <summary> /// 玩家编号 /// </summary> public int m_PlayerNumber = 1; /// <summary> /// 坦克的移动速度 /// </summary> public float m_Speed = 12f; /// <summary> /// 坦克的转向速度 /// </summary> public float m_TurnSpeed = 180f; /// <summary> /// 移动声源 /// </summary> public AudioSource m_MovementAudio; /// <summary> /// 空闲时的声音 /// </summary> public AudioClip m_EngineIdling; /// <summary> /// 移动时的声音 /// </summary> public AudioClip m_EngineDriving; /// <summary> /// 音高范围 /// </summary> public float m_PitchRange = 0.2f; /// <summary> /// 控制前后移动的轴名 /// </summary> private string m_MovementAxisName; /// <summary> /// 控制 /// </summary> private string m_TurnAxisName; /// <summary> /// 刚体 /// </summary> private Rigidbody m_Rigidbody; /// <summary> /// 移动输入 /// </summary> private float m_MovementInputValue; /// <summary> /// 转向输入 /// </summary> private float m_TurnInputValue; /// <summary> /// 原始音高 /// </summary> 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; 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 (); } /// <summary> /// 处理引擎的声音 /// </summary> 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 (); } /// <summary> /// 移动 /// </summary> private void Move () { //创建一个坦克的速度向量 Vector3 movement = transform.forward * m_MovementInputValue * m_Speed * Time.deltaTime; //刚体移动 m_Rigidbody.MovePosition(m_Rigidbody.position + movement); } /// <summary> /// 旋转 /// </summary> private void Turn () { //根据输入,旋转速度以及每帧的时间计算旋转的值 float turn = m_TurnInputValue * m_TurnSpeed * Time.deltaTime; //创建y轴的四元数旋转 Quaternion turnRotation = Quaternion.Euler (0f, turn, 0f); //刚体旋转 m_Rigidbody.MoveRotation (m_Rigidbody.rotation * turnRotation); } } }
using UnityEngine; using UnityEngine.UI; namespace Complete { /// <summary> /// 坦克射击 /// </summary> public class TankShooting : MonoBehaviour { /// <summary> /// 玩家编号 /// </summary> public int m_PlayerNumber = 1; /// <summary> /// 炮弹刚体 /// </summary> public Rigidbody m_Shell; /// <summary> /// 射击的位置 /// </summary> public Transform m_FireTransform; /// <summary> /// 射击的箭头滑动条 /// </summary> public Slider m_AimSlider; /// <summary> /// 发射声音 /// </summary> public AudioSource m_ShootingAudio; /// <summary> /// 充能声音 /// </summary> public AudioClip m_ChargingClip; /// <summary> /// 开火声音 /// </summary> public AudioClip m_FireClip; /// <summary> /// 发射炮弹最小的力 /// </summary> public float m_MinLaunchForce = 15f; /// <summary> /// 发射炮弹最大的力 /// </summary> public float m_MaxLaunchForce = 30f; /// <summary> /// 发射炮弹的最大充能时间 /// </summary> public float m_MaxChargeTime = 0.75f; /// <summary> /// 控制发射的轴名 /// </summary> private string m_FireButton; /// <summary> /// 当前的发射力 /// </summary> private float m_CurrentLaunchForce; /// <summary> /// 充能速度 /// </summary> private float m_ChargeSpeed; /// <summary> /// 是否已发射 /// </summary> private bool m_Fired; private void OnEnable() { //重置当前发射力和箭头滑动条的值 m_CurrentLaunchForce = m_MinLaunchForce; m_AimSlider.value = m_MinLaunchForce; } private void Start () { //根据玩家编号设置 发射的轴名 m_FireButton = "Fire" + m_PlayerNumber; //计算发射充能 m_ChargeSpeed = (m_MaxLaunchForce - m_MinLaunchForce) / m_MaxChargeTime; } private void Update () { //设置箭头滑动条的值 m_AimSlider.value = m_MinLaunchForce; //如果当前的发射力大于最大发射力,并且没有发射 if (m_CurrentLaunchForce >= m_MaxLaunchForce && !m_Fired) { //用最大发射力发射 m_CurrentLaunchForce = m_MaxLaunchForce; Fire (); } //如果按下了发射按钮 else if (Input.GetButtonDown (m_FireButton)) { //设置发射标志位为false m_Fired = false; //设置当前发射力为最小力 m_CurrentLaunchForce = m_MinLaunchForce; //播放充能声音 m_ShootingAudio.clip = m_ChargingClip; m_ShootingAudio.Play (); } //如果发射按钮被按住且炮弹并没有发射 else if (Input.GetButton (m_FireButton) && !m_Fired) { //更新当前发射力 m_CurrentLaunchForce += m_ChargeSpeed * Time.deltaTime; //更新箭头滑动条的值 m_AimSlider.value = m_CurrentLaunchForce; } //如果发射键松开且炮弹没有发射 else if (Input.GetButtonUp (m_FireButton) && !m_Fired) { //发射炮弹 Fire (); } } /// <summary> /// 发射 /// </summary> private void Fire () { //设置发射标志位 m_Fired = true; //获取炮弹刚体的引用 Rigidbody shellInstance = Instantiate (m_Shell, m_FireTransform.position, m_FireTransform.rotation) as Rigidbody; //设置刚体的速度 shellInstance.velocity = m_CurrentLaunchForce * m_FireTransform.forward; //播放发射声音 m_ShootingAudio.clip = m_FireClip; m_ShootingAudio.Play (); //重置当前发射力为最小发射力 m_CurrentLaunchForce = m_MinLaunchForce; } } }
using UnityEngine; namespace Complete { /// <summary> /// 炮弹爆炸 /// </summary> public class ShellExplosion : MonoBehaviour { /// <summary> /// 坦克层 /// </summary> public LayerMask m_TankMask; public ParticleSystem m_ExplosionParticles; /// <summary> /// 爆炸声源 /// </summary> public AudioSource m_ExplosionAudio; /// <summary> /// 最大伤害 /// </summary> public float m_MaxDamage = 100f; /// <summary> /// 爆炸力 /// </summary> public float m_ExplosionForce = 1000f; /// <summary> /// 炮弹的生命周期 /// </summary> public float m_MaxLifeTime = 2f; /// <summary> /// 爆炸半径 /// </summary> public float m_ExplosionRadius = 5f; private void Start () { //如果炸弹没有被销毁,经过指定时间后自动销毁 Destroy (gameObject, m_MaxLifeTime); } private void OnTriggerEnter (Collider other) { //以炮弹为中心,爆炸半径为半径发射相交球 Collider[] colliders = Physics.OverlapSphere (transform.position, m_ExplosionRadius, m_TankMask); //循环遍历碰撞体 for (int i = 0; i < colliders.Length; i++) { //获取目标刚体 Rigidbody targetRigidbody = colliders[i].GetComponent<Rigidbody> (); //如果没有刚体,则继续检测下一个 if (!targetRigidbody) continue; //目标刚体添加爆炸力 targetRigidbody.AddExplosionForce (m_ExplosionForce, transform.position, m_ExplosionRadius); //获取目标的血量 TankHealth targetHealth = targetRigidbody.GetComponent<TankHealth> (); //如果没有,检测下一个 if (!targetHealth) continue; //根据目标位置计算伤害 float damage = CalculateDamage (targetRigidbody.position); //目标受到伤害 targetHealth.TakeDamage (damage); } m_ExplosionParticles.transform.parent = null; //播放爆炸粒子效果 m_ExplosionParticles.Play(); //播放爆炸声音 m_ExplosionAudio.Play(); //定时销毁粒子系统 Destroy (m_ExplosionParticles.gameObject, m_ExplosionParticles.duration); //销毁炮弹 Destroy (gameObject); } /// <summary> /// 计算伤害 /// </summary> /// <param name="targetPosition">目标位置</param> /// <returns></returns> private float CalculateDamage (Vector3 targetPosition) { //创建一个从自身到目标的向量 Vector3 explosionToTarget = targetPosition - transform.position; //计算这个向量的模 float explosionDistance = explosionToTarget.magnitude; //计算相对距离 float relativeDistance = (m_ExplosionRadius - explosionDistance) / m_ExplosionRadius; //根据距离比例和最大伤害计算伤害 float damage = relativeDistance * m_MaxDamage; //确保最小伤害为0 damage = Mathf.Max (0f, damage); return damage; } } }
using UnityEngine; namespace Complete { /// <summary> /// UI朝向控制 /// </summary> public class UIDirectionControl : MonoBehaviour { /// <summary> /// 是否使用相对旋转 /// </summary> public bool m_UseRelativeRotation = true; /// <summary> /// 局部旋转 /// </summary> private Quaternion m_RelativeRotation; private void Start () { //局部旋转为父对象的局部旋转 m_RelativeRotation = transform.parent.localRotation; } private void Update () { if (m_UseRelativeRotation) transform.rotation = m_RelativeRotation; } } }
视频:https://pan.baidu.com/s/1eRFbQYA
项目:https://pan.baidu.com/s/1midmmuc