这是Unity官方案例Tanks的素材,读者可以自行取阅。
链接:https://pan.baidu.com/s/1PSAZeT5zQOQJXNxzP9qs1A
提取码:a57u
本篇文章是我在观看了官方教程后写的脚本,相较于官方的更为详细,方便你们拿来直接查看引用。而且是适用于新版本的Unity。我这一版本的Unity是Version 2019.2.9f1 Persional。
建议多阅读观看官网的文档和教程,一方面他们的代码更加的规范,另一方面游戏开发的思想要更加的好。一个很明显的区别在于,官方的代码,每个模块之间相互独立,各司其职缺一不可。但又不会在一个脚本里塞入过多的内容显得冗杂多余,整体上层次分明,架构清楚。各个类之间高内聚,低耦合。比国内所谓的一些学院教的要好得多,解释的也更加的清楚。
官方中英机翻视频教程点这里。个人建议不要开倍速一点点看完,适应一下老师的语速,而不要知其然不知其所以然。那么,废话不多说,直接上代码。英语水平有限,注释能看懂就行,还望见谅。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
public class GameManager : MonoBehaviour
{
// Properties
public int m_NumRoundsToWin = 5; // number of rounds per start
public float m_StartDelay = 3f; // time delay ecah start
public float m_EndDelay = 3f; // time delay each end
private int m_RoundNumber; // round in which you are
// Reference
public CameraContorller m_CameraContorl;
public Text m_MessageText;
public GameObject m_TankPrefab;
public TankManager[] m_Tanks;
private WaitForSeconds m_StartWait;
private WaitForSeconds m_EndWait;
private TankManager m_RoundWinner;
private TankManager m_GameWinner;
private void Start()
{
m_StartWait = new WaitForSeconds(m_StartDelay);
m_EndWait = new WaitForSeconds(m_EndDelay);
SpawnAllTanks();
SetCameraTargets();
StartCoroutine(GameLoop());
}
private void SpawnAllTanks()
{
// Spawn all the tanks for player, set their numbers from 1 to 2, and then call the function SetUp() to draw color for tanks.
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);
m_Tanks[i].m_PlayerNumber = i + 1;
m_Tanks[i].SetUp();
}
}
private void SetCameraTargets()
{
// Assign all the tank's transform to the GameraControl script.
Transform[] targets = new Transform[m_Tanks.Length];
for (int i = 0; i < targets.Length; ++i)
{
targets[i] = m_Tanks[i].m_Instance.transform;
}
m_CameraContorl.m_Targets = targets;
}
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());
}
}
private IEnumerator RoundStarting()
{
// When the game firstly starts or back to the scene(0) again, we should reset all tanks.
// And then, we disable all tanks' control so that the player couldn't contorl them.
// At the same time, we set the camera to the right position and size that calculated by the function in the CameraControl script.
// Finally, we show the text "Round" and we wait for a while so that it won't flash by.
ResetAllTanks();
DisableTankControl();
m_CameraContorl.SetStartPositionAndSize();
m_RoundNumber++; // plus 1 to the round count
m_MessageText.text = "ROUND " + m_RoundNumber;
yield return m_StartWait;
}
private IEnumerator RoundPlaying()
{
// Ok now, we need to give the palyers control of their tank, or they will be angry.
// So, we enable all tanks' control, and change the text to null.
// You don't expect a big line blocking the palyers' view.
// When two players fight out a winner, we can return the funcion.
// Of course, their is nothing we could return.
EnableTankControl();
m_MessageText.text = string.Empty;
while (!OneTankLeft())
{
yield return null;
}
}
private IEnumerator RoundEnding()
{
// A winner came out. Winner whould like to run around for celebrating, but we don't think is a wise move.
// So we disable tanks' control again, and we set winner for this round to null.
// Because it still stores the reference of the winner of the last round.
// And then, we need to find the reference of the winner of this round and we judge wether he is the game winner.
// Again we show some "text" and wait for a while, then we get into the next round or start game again.
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;
}
private bool OneTankLeft()
{
// Determine if any tanks are dead.
int numTanksLeft = 0;
for (int i = 0; i < m_Tanks.Length; ++i)
{
if (m_Tanks[i].m_Instance.activeSelf)
numTanksLeft++;
}
return numTanksLeft <= 1;
}
private TankManager GetRoundWinner()
{
// Go through all the tanks, if find a TankManager who's Instance is active, then return it, or else return null.
for (int i = 0; i < m_Tanks.Length; ++i)
{
if (m_Tanks[i].m_Instance.activeSelf)
return m_Tanks[i];
}
return null;
}
private TankManager GetGameWinner()
{
// Go through all the tanks, if find a TankManager who's m_Wins is equal to 5, then return it, or else return null.
for (int i = 0; i < m_Tanks.Length; ++i)
{
if (m_Tanks[i].m_Wins == m_NumRoundsToWin)
return m_Tanks[i];
}
return null;
}
private string EndMessage()
{
string message = "DARW!";
if (m_RoundWinner != null)
message = m_RoundWinner.m_ColoredPlayerText + " WINS THE ROUND!";
message += "\n\n\n\n";
for (int i = 0; i < m_Tanks.Length; ++i)
{
message += m_Tanks[i].m_ColoredPlayerText + ": " + m_Tanks[i].m_Wins + " WINS\n";
}
if (m_GameWinner != null)
message = m_GameWinner.m_ColoredPlayerText + " WIN THE GAME!";
return message;
}
private void ResetAllTanks()
{
for (int i = 0; i < m_Tanks.Length; ++i)
{
m_Tanks[i].Reset();
}
}
private void DisableTankControl()
{
for (int i = 0; i < m_Tanks.Length; ++i)
{
m_Tanks[i].DisableTankControl();
}
}
private void EnableTankControl()
{
for (int i = 0; i < m_Tanks.Length; ++i)
{
m_Tanks[i].EnableTankControl();
}
}
}
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[Serializable]
public class TankManager
{
// Properties
[HideInInspector] public int m_PlayerNumber;
[HideInInspector] public int m_Wins;
[HideInInspector] public string m_ColoredPlayerText;
// Reference
public Color m_PlayerColor;
public Transform m_SpawnPoint;
private TankMovement m_Movement;
private TankShooting m_Shooting;
private GameObject m_CanvasGameObject;
[HideInInspector] public GameObject m_Instance;
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 = " + ColorUtility.ToHtmlStringRGB(m_PlayerColor) + ">PLAYER " + m_PlayerNumber + " ";
MeshRenderer[] renderers = m_Instance.GetComponentsInChildren<MeshRenderer>();
for (int i = 0; i < renderers.Length; ++i)
{
renderers[i].material.color = m_PlayerColor;
}
}
public void DisableTankControl()
{
// Turn off the script and turn off the canvas.
m_Movement.enabled = false;
m_Shooting.enabled = false;
m_CanvasGameObject.SetActive(false);
}
public void EnableTankControl()
{
// Turn on the script and turn on the canvas.
m_Movement.enabled = true;
m_Shooting.enabled = true;
m_CanvasGameObject.SetActive(true);
}
public void Reset()
{
// Set the Instance back to it's spawn point.
m_Instance.transform.position = m_SpawnPoint.position;
m_Instance.transform.rotation = m_SpawnPoint.rotation;
// all of the tank's apart from the winner will be off, and we need to reset all of them
// so we need to turn everything off first befor we can turn it back on again
m_Instance.SetActive(false);
m_Instance.SetActive(true);
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TankMovement : MonoBehaviour
{
// Properties
public int m_PlayerNumber = 1;
public float m_MoveSpeed = 12f;
public float m_TurnSpeed = 180f;
public float m_PitchRange = 0.2f;
private float m_MovementInputValue;
private float m_TurnInputValue;
private float m_OriginalPitch;
private string m_MovementAxisName;
private string m_TurnAxisName;
// Reference
public AudioSource m_MovementAudio;
public AudioClip m_EngineIdling;
public AudioClip m_EngineDriving;
private Rigidbody m_RigidBody;
private void Awake()
{
m_RigidBody = GetComponent<Rigidbody>();
}
private void OnEnable()
{
// This function is called when this script is turned on.
m_RigidBody.isKinematic = false;
// Initialize inputvalue
m_MovementInputValue = 0f;
m_TurnInputValue = 0f;
}
private void OnDisable()
{
m_RigidBody.isKinematic = true;
}
private void Start()
{
// Get diffrent axisname input name for diffrent player.
m_MovementAxisName = "Vertical" + m_PlayerNumber;
m_TurnAxisName = "Horizontal" + m_PlayerNumber;
// For now the originalPith's value equal to 1
m_OriginalPitch = m_MovementAudio.pitch;
}
private void Update()
{
// Store the Player's input and make sure the audio for the engine is playing.
m_MovementInputValue = Input.GetAxis(m_MovementAxisName);
m_TurnInputValue = Input.GetAxis(m_TurnAxisName);
EngineAudio();
}
private void EngineAudio()
{
// Play the correct audio clip based on whether or not the tank is moving and what audio is currently playing.
if (Mathf.Abs(m_MovementInputValue) < 0.1f && Mathf.Abs(m_TurnInputValue) < 0.1f)
{
if (m_MovementAudio.clip == m_EngineDriving)
{
m_MovementAudio.clip = m_EngineIdling; // change the clip
float lowPitch = m_OriginalPitch - m_PitchRange;
float highPitch = m_OriginalPitch + m_PitchRange;
m_MovementAudio.pitch = Random.Range(lowPitch, highPitch); // set the pitch between 0.8 and 1.2
m_MovementAudio.Play();
}
}
else
{
if (m_MovementAudio.clip == m_EngineIdling)
{
m_MovementAudio.clip = m_EngineDriving;
float lowPitch = m_OriginalPitch - m_PitchRange;
float highPitch = m_OriginalPitch + m_PitchRange;
m_MovementAudio.pitch = Random.Range(lowPitch, highPitch); // set the pitch between 0.8 and 1.2
m_MovementAudio.Play();
}
}
}
private void FixedUpdate()
{
// MOve ang turn the tank.
Move();
Turn();
}
private void Move()
{
// Adjust the position of the tank based on the player's input.
Vector3 movement = transform.forward * m_MovementInputValue * m_MoveSpeed * Time.deltaTime;
m_RigidBody.MovePosition(m_RigidBody.position + movement);
}
private void Turn()
{
// Adjust the position of the tank based on the player's input.
float turn = m_TurnInputValue * m_TurnSpeed * Time.deltaTime;
Quaternion turnRotation = Quaternion.Euler(0f, turn, 0f);
m_RigidBody.MoveRotation(m_RigidBody.rotation * turnRotation);
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CameraContorller : MonoBehaviour
{
// Properties
public float m_DampTime = 0.2f; // time that the camera reach to the target will take
public float m_ScreenEdgeBuff = 4f;
public float m_MinSize = 6.5f;
private float m_ZoomSpeed;
// Reference
[HideInInspector] public Transform[] m_Targets;
private Camera m_Camera;
private Vector3 m_MoveVelocity;
private Vector3 m_DesiredPosition; // the position we want camera reach to
private void Awake()
{
m_Camera = GetComponentInChildren<Camera>();
}
private void FixedUpdate()
{
Move();
Zoom();
}
private void Move()
{
// Calculate the desiredposition.
FindAveragePosition();
// smooth the CamearRig's follow motion
transform.position = Vector3.SmoothDamp(transform.position, m_DesiredPosition, ref m_MoveVelocity, m_DampTime);
}
private void FindAveragePosition()
{
// Get the average position among all the tanks and the let the CameraRig reach to the position.
Vector3 averagePos = new Vector3();
int numTargets = 0;
// check if the target' gameObject is active, wo don't need to zoom in on a deactivated tank
for (int i = 0; i < m_Targets.Length; ++i)
{
if (!m_Targets[i].gameObject.activeSelf)
continue;
averagePos += m_Targets[i].position; // plus all tanks' position and count the total of active tank
++numTargets;
}
if (numTargets > 0)
averagePos /= numTargets;
averagePos.y = transform.position.y; // Keep the CameraRig's height and Camera's height at a level
m_DesiredPosition = averagePos;
}
private void Zoom()
{
// Get the size and make Camera size reach to it smoothly.
float requiredSize = FindRequiredSize();
m_Camera.orthographicSize = Mathf.SmoothDamp(m_Camera.orthographicSize, requiredSize, ref m_ZoomSpeed, m_DampTime);
}
private float FindRequiredSize()
{
// Calculate the size.
Vector3 desiredLocalPos = transform.InverseTransformPoint(m_DesiredPosition);
float size = 0f;
/* Go through all the tanks, find out all the size it could be.
Pick the largest one, then the tanks are all definitely going to be on the screen. */
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;
/* For the Camera view, when tank is at Y axix, the Camera size is equal to
absolute Y value of the Vector of the position of the tank, and itequal
to X value / Camera.aspect while tank is at X axis.
Finally we could find the furthest value. */
size = Mathf.Max(size, Mathf.Abs(desiredPosToTarget.y));
size = Mathf.Max(size, Mathf.Abs(desiredPosToTarget.x) / m_Camera.aspect);
}
size += m_ScreenEdgeBuff;
size = Mathf.Max(size, m_MinSize); // set minimum size so that it won't zoom in so much
return size;
}
public void SetStartPositionAndSize()
{
// Set the scene to the right size and right position at every round.
FindAveragePosition();
transform.position = m_DesiredPosition;
m_Camera.orthographicSize = FindRequiredSize();
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class TankShooting : MonoBehaviour
{
// Properties
public int m_PlayerNumber = 1;
public float m_MinLaunchForce = 15f;
public float m_MaxLaunchForce = 30f;
public float m_MaxChargeTime = 0.75f;
private float m_CurrentLaunchForce;
private float m_ChargeSpeed;
private bool m_Fired; // record wether the player shoot the shell or not
private string m_FireButtom;
// Reference
public Rigidbody m_Shell;
public Transform m_FireTransform;
public Slider m_AimSlider;
public AudioSource m_ShootingAudio;
public AudioClip m_ChargingClip;
public AudioClip m_FireClip;
private void OnEnable()
{
m_CurrentLaunchForce = m_MinLaunchForce;
m_AimSlider.value = m_MinLaunchForce;
}
private void Start()
{
m_FireButtom = "Fire" + m_PlayerNumber;
m_ChargeSpeed = (m_MaxLaunchForce - m_MinLaunchForce) / m_MaxChargeTime;
}
private void Update()
{
// Track the current state of the fire button and make decision based on the current launch force.
m_AimSlider.value = m_MinLaunchForce;
if (m_CurrentLaunchForce >= m_MaxLaunchForce && !m_Fired)
{
// at max charge, not yet fired
m_CurrentLaunchForce = m_MaxLaunchForce;
Fire();
}
else if (Input.GetButtonDown(m_FireButtom))
{
// once we pressed fire buttom for the first time
m_Fired = false;
m_CurrentLaunchForce = m_MinLaunchForce;
m_ShootingAudio.clip = m_ChargingClip;
m_ShootingAudio.Play();
}
else if (Input.GetButton(m_FireButtom) && !m_Fired)
{
// Holding the fire button, not yet fired
m_CurrentLaunchForce += m_ChargeSpeed * Time.deltaTime;
m_AimSlider.value = m_CurrentLaunchForce;
}
else if (Input.GetButtonUp(m_FireButtom) && !m_Fired)
{
// we relased the button, having not fired yet
Fire();
}
}
private void Fire()
{
// Instantiate and launch the shell.
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; // safety catch
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class TankHealth : MonoBehaviour
{
// Properties
public float m_StartingHealth = 100f;
private float m_CurrentHealth;
private bool m_Dead;
// Reference
public Slider m_Slider;
public Image m_FillImage;
public Color m_FullHealthColor = Color.green;
public Color m_ZeroHealthColor = Color.red;
public GameObject m_ExplosionPrefab;
private AudioSource m_ExplosionAudio;
private ParticleSystem m_ExplosionParticles;
private void Awake()
{
// We can get the reference of ParticleSystem by creating an ExplosionPrefab instance.
m_ExplosionParticles = Instantiate(m_ExplosionPrefab).GetComponent<ParticleSystem>();
m_ExplosionAudio = m_ExplosionParticles.GetComponent<AudioSource>();
/* To avoid that always keep the particle system int the memory, keep them in the scene,
we put it straight in to the game as soon as it starts, we swithc it off straight away
and we assign our references for when we need them. */
m_ExplosionParticles.gameObject.SetActive(false);
}
private void OnEnable()
{
m_CurrentHealth = m_StartingHealth;
m_Dead = false;
SetHealthUI();
}
public void TankDamage(float amount)
{
// Adjust the tank's current healt, update the UI based on the new health and check whether or not the tank is dead.
m_CurrentHealth -= amount;
SetHealthUI();
if (m_CurrentHealth <= 0f && !m_Dead)
{
OnDeath();
}
}
private void SetHealthUI()
{
// Adjust the value and colour of the slider, to make sure it looks appropriate.
m_Slider.value = m_CurrentHealth;
m_FillImage.color = Color.Lerp(m_ZeroHealthColor, m_FullHealthColor, m_CurrentHealth / m_StartingHealth);
}
private void OnDeath()
{
// Play the effect for the death of the tank and deactive it.
m_Dead = true;
// move the particle system to the place where the playe died and turn on it
m_ExplosionParticles.transform.position = transform.position;
m_ExplosionParticles.gameObject.SetActive(true);
m_ExplosionParticles.Play();
m_ExplosionAudio.Play();
gameObject.SetActive(false);
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ShellExplosion : MonoBehaviour
{
//Properties
public float m_MaxDamage = 100f;
public float m_ExplosionForce = 1000f;
public float m_MaxLifeTime = 2f;
public float m_ExplosionRadius = 5f;
// Reference
public LayerMask m_TankMask;
public ParticleSystem m_ExplosionParticles;
public AudioSource m_ExplosionAudio;
private void Start()
{
Destroy(gameObject, m_MaxLifeTime);
}
private void OnTriggerEnter(Collider other)
{
// Find all the tanks in an area around the shell and damage them.
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);
// addressing a particular instance of TankHealth script so we can talk to it and reduce health with it
TankHealth targetHealth = targetRigidbody.GetComponent<TankHealth>();
if (!targetHealth)
continue;
float damage = CalculateDamage(targetRigidbody.position);
targetHealth.TankDamage(damage);
}
/* When the shell is destoryed, its child is destoryed either, either is ParticleSystem;
So we need to unparent them, so that they could still play even if the shell is destoyed.
After that, they will explode and ducouple themsevles. */
m_ExplosionParticles.transform.parent = null;
m_ExplosionParticles.Play();
m_ExplosionAudio.Play();
Destroy(m_ExplosionParticles.gameObject, m_ExplosionParticles.main.duration);
Destroy(gameObject); // remove the shell as well
}
private float CalculateDamage(Vector3 targetPosition)
{
// Calculate the amout of damage a target should takes based on it's position.
Vector3 explosionToTarget = targetPosition - transform.position;
float explosionDistance = explosionToTarget.magnitude;
float relativeDistance = (m_ExplosionRadius - explosionDistance) / m_ExplosionRadius;
float damage = relativeDistance * m_MaxDamage;
/* The damage starting off at 1 in the center going down to 0 at the edge and then they'll be
negative beyond the edge. We don't want that, so we choose it from 0 and negative damage. */
damage = Mathf.Max(0f, damage);
return damage;
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class UIDirectionContorller : MonoBehaviour
{
// Properties
public bool m_UseRelativeRotation = true;
// Reference
private Quaternion m_RelativeRotation;
private void Start()
{
m_RelativeRotation = transform.parent.localRotation;
}
private void Update()
{
if (!m_UseRelativeRotation)
transform.rotation = m_RelativeRotation;
}
}
一定要自己写一遍哦~~~