文章目录
- Roll A Ball
- Space Shooter
- Survival Shooter
- UFO Game
Roll A Ball
- PlayerController.cs
/*
* PlayerController.cs
*/
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
public class PlayerController : MonoBehaviour
{
public float speed;
public Text grade;
public Text win;
public Button restartBtn;
float h;
float v;
int count;
Rigidbody rb;
// Use this for initialization
void Start()
{
rb = GetComponent<Rigidbody>();
}
void FixedUpdate()
{
h = Input.GetAxis("Horizontal");
v = Input.GetAxis("Vertical");
Vector3 force = new Vector3(h, 0f, v);
rb.AddForce(force * speed * Time.deltaTime);
}
// Update is called once per frame
void Update()
{
grade.text = "Grade : " + count;
}
void OnTriggerEnter(Collider other)
{
if (other.tag == "Collection")
{
Destroy(other.gameObject);
count += 1;
if (count == 10)
{
win.gameObject.SetActive(true);
restartBtn.gameObject.SetActive(true);
}
}
}
public void restartGame()
{
SceneManager.LoadScene("Main", LoadSceneMode.Single);
}
}
- CameraController.cs
/*
* CameraController.cs
*/
using UnityEngine;
public class CameraController : MonoBehaviour
{
public Transform player;
Vector3 offset;
// Use this for initialization
void Start()
{
offset = player.position - transform.position;
}
// Update is called once per frame
void Update()
{
transform.position = player.position - offset;
}
}
- CollectionsController.cs
/*
* CollectionsController.cs
*/
using UnityEngine;
public class CollectionController : MonoBehaviour
{
// Use this for initialization
void Start()
{
}
// Update is called once per frame
void Update()
{
Vector3 rotation = new Vector3(1, 1, 1);
transform.Rotate(rotation);
}
}
Space Shooter
- PlayerController.cs
using UnityEngine;
// 定义飞行边界
[System.Serializable]
public class Boundary
{
public float xMin, xMax, zMin, zMax;
}
public class PlayerController : MonoBehaviour
{
public Boundary boundary;
public float speed;
public float tilt;
public GameObject shot;
public Transform shotSpawn;
public float fireRate;
private float nextFire;
Rigidbody rb;
// Use this for initialization
void Start()
{
}
// Update is called once per frame
void Update()
{
if (Input.GetButton("Fire1") && Time.time > nextFire)
{
nextFire = Time.time + fireRate;
Instantiate(shot, shotSpawn.position, shotSpawn.rotation);
GetComponent<AudioSource>().Play();
}
}
void FixedUpdate()
{
float moveHorizontal = Input.GetAxis("Horizontal");
float moveVertical = Input.GetAxis("Vertical");
rb = GetComponent<Rigidbody>();
Vector3 movement = new Vector3(moveHorizontal, 0, moveVertical);
movement *= speed;
rb.velocity = movement;
// 限制飞行边界
rb.position = new Vector3(
Mathf.Clamp(rb.position.x, boundary.xMin, boundary.xMax),
0f,
Mathf.Clamp(rb.position.z, boundary.zMin, boundary.zMax)
);
// 添加运动倾斜
rb.rotation = Quaternion.Euler(0f, 0f, rb.velocity.x * -tilt);
}
}
- GameController.cs
using System.Collections;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
/// <summary>
/// System.Collections
/// 协程需使用该命名空间
/// </summary>
public class GameController : MonoBehaviour
{
public GameObject[] hazards;
public Vector3 spawnValues;
public int hazardCount;
public float startWait;
public float spawnWait;
public float waveWait;
public Text scoreText;
public Text restartText;
public Text gameoverText;
private int score;
private bool bGameOver;
private bool bRestart;
// Use this for initialization
void Start()
{
bGameOver = false;
bRestart = false;
restartText.text = "";
gameoverText.text = "";
score = 0;
UpdateScore();
// 延时函数
//InvokeRepeating("GenerateHazards", 2f, 2f);
// 使用协程实现一波又一波的行星生成
StartCoroutine(SpawnWaves());
}
IEnumerator SpawnWaves()
{
while (true)
{
yield return new WaitForSeconds(startWait);
for (int i = 0; i < hazardCount; i++)
{
Vector3 spawnPosition = new Vector3(Random.Range(-spawnValues.x, spawnValues.x), spawnValues.y, spawnValues.z);
Quaternion spawnRotation = Quaternion.identity;
Instantiate(hazards[Random.Range(0, hazards.Length)], spawnPosition, spawnRotation);
yield return new WaitForSeconds(spawnWait);
}
// 稍作停顿 开始下一波灾难
yield return new WaitForSeconds(waveWait);
if (bGameOver)
{
restartText.text = "Please press 'R' for restart";
bRestart = true;
break;
}
}
}
void GenerateHazards()
{
Vector3 spawnPosition = new Vector3(Random.Range(-spawnValues.x, spawnValues.x), spawnValues.y, spawnValues.z);
Quaternion spawnRotation = Quaternion.identity;
Instantiate(hazards[Random.Range(0, hazards.Length)], spawnPosition, spawnRotation);
}
void UpdateScore()
{
scoreText.text = "Score : " + score;
}
// Update is called once per frame
void Update()
{
// 仅检测有无重新开始游戏的请求
if (bRestart)
{
if (Input.GetKeyDown(KeyCode.R))
{
// 重新加载场景
SceneManager.LoadScene("Main", LoadSceneMode.Single);
}
}
}
// interface
// 计算分数
public void AddScore(int newScoreVal)
{
score += newScoreVal;
UpdateScore();
}
// 游戏结束
public void GameOver()
{
gameoverText.text = "Game Over ! ";
bGameOver = true;
}
}
- DestroyByContact.cs
using UnityEngine;
public class DestroyByContact : MonoBehaviour
{
public GameObject asteroidExplosion;
public GameObject playerExplosion;
public int scoreVal;
// 引用外部类
private GameController gameController;
// Use this for initialization
void Start()
{
// FindWithTag找到GameObject实例
GameObject gameControllerObject = GameObject.FindWithTag("GameController");
if (gameControllerObject != null)
{
// GameObject中有多个组件,GetComponent找到其脚本组件
gameController = gameControllerObject.GetComponent<GameController>();
}
else
{
Debug.Log("Can not find 'GameController' script");
}
}
void OnTriggerEnter(Collider other)
{
if (other.tag == "Boundary")
{
return;
}
if (other.tag == "Player")
{
// 实例化玩家爆炸预制体
Instantiate(playerExplosion, other.transform.position, other.transform.rotation);
gameController.GameOver();
}
else
{
// 行星自身爆炸预制体
Instantiate(asteroidExplosion, transform.position, transform.rotation);
}
Destroy(other.gameObject);
Destroy(gameObject); // 销毁自身
gameController.AddScore(scoreVal);
// 爆炸预制体还未销毁,因其无法触及销毁边界,故添加定时销毁
// 定时销毁脚本挂载在爆炸预制体自身
}
}
- Mover.cs
using UnityEngine;
public class Mover : MonoBehaviour
{
public float speed;
Rigidbody rb;
// Use this for initialization
void Start()
{
rb = GetComponent<Rigidbody>();
rb.velocity = transform.forward * speed;
}
}
- RandomRotator.cs
using UnityEngine;
public class RandomRotator : MonoBehaviour
{
// 陨石随机旋转
public float tumble;
Rigidbody rb;
// Use this for initialization
void Start()
{
rb = GetComponent<Rigidbody>();
// 添加旋转角速度 insideUnitSphere:返回半径为1球体内的一个随机点
rb.angularVelocity = Random.insideUnitSphere * tumble;
}
}
- DestroyByBoundary.cs
using UnityEngine;
public class DestroyByBoundary : MonoBehaviour
{
void OnTriggerExit(Collider other)
{
Destroy(other.gameObject);
}
}
- DestroyByTime.cs
using UnityEngine;
using System.Collections;
public class DestroyByTime : MonoBehaviour
{
public float lifetime;
// Use this for initialization
void Start()
{
Destroy(gameObject, lifetime);
}
}
Survival Shooter
该项目脚本较多,首先来剖析player相关脚本
- PlayerHealth.cs
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
/*
* PlayerHealth
* 玩家生命管理 受到伤害处理
*/
public class PlayerHealth : MonoBehaviour
{
public int startingHealth = 100;
public int currentHealth;
public AudioClip deathClip;
Animator anim;
AudioSource playerAudio;
PlayerMovement playerMovement;
PlayerShooting playerShooting;
bool isDead;
bool damaged;
// 添加UI相关控件
public Slider healthSlider;
public Image damageImage;
public Color flashColor = new Color(1f, 0f, 0f, 0.1f); // 闪动接近透明的红色
public float flashSpeed = 5f;
void Awake()
{
anim = GetComponent<Animator>();
playerAudio = GetComponent<AudioSource>();
playerMovement = GetComponent<PlayerMovement>();
playerShooting = GetComponentInChildren<PlayerShooting>();
currentHealth = startingHealth;
}
void Update()
{
if (damaged)
{
damageImage.color = flashColor;
// 闪动一下 则还原白色纯透明
//damageImage.color = Color.Lerp(damageImage.color, Color.clear, flashSpeed * Time.deltaTime);
}
else
{
damageImage.color = Color.Lerp(damageImage.color, Color.clear, flashSpeed * Time.deltaTime);
}
damaged = false;
}
// interface
// Enemy Attack 调用
public void TakeDamage(int amount)
{
damaged = true;
currentHealth -= amount;
healthSlider.value = currentHealth;
playerAudio.Play();
if (currentHealth <= 0 && !isDead)
{
Death();
}
}
void Death()
{
isDead = true;
playerShooting.DisableEffects();
anim.SetTrigger("Die");
playerAudio.clip = deathClip;
playerAudio.Play();
playerMovement.enabled = false;
playerShooting.enabled = false;
}
// Player Death Animation Event 调用
public void RestartLevel()
{
/*
* 重新加载场景后光照可能变暗
* 因为选择的光照是realtime实时光照
* 编辑器在当前场景时候,光照是已经渲染好的
* 重新加载的时候光照没有进行渲染
* 需要预烘焙,以便保存光照贴图信息
* windows -> lighting 取消勾选Auto 再点击旁边的Build
*/
//SceneManager.LoadScene("Main", LoadSceneMode.Single); // 重新加载场景
//SceneManager.LoadScene("Main", LoadSceneMode.Additive); // 在现有场景基础上继续游戏
SceneManager.LoadScene("Main"); // Mode可省
}
}
- PlayerMovement.cs
using UnityEngine;
/*
* Survival Shooter
* PlayerMovement
*/
public class PlayerMovement : MonoBehaviour
{
public float speed = 6f;
Animator anim;
Rigidbody playerRigidbody;
Vector3 movement;
int floorMask; // 方便定义射线仅与地板相交
float camRayLength = 100f; // 射线长度
// 初始化一些引用
void Awake()
{
floorMask = LayerMask.GetMask("Floor");
anim = GetComponent<Animator>();
playerRigidbody = GetComponent<Rigidbody>();
}
void FixedUpdate()
{
// GetAxisRaw 直接返回-1 1 在FPS游戏中可实现立即转向
float h = Input.GetAxisRaw("Horizontal");
float v = Input.GetAxisRaw("Vertical");
Move(h, v);
Turning();
Animating(h, v);
}
void Move(float h, float v)
{
// Gravity On
movement.Set(h, 0f, v);
movement = movement.normalized * speed * Time.deltaTime;
playerRigidbody.MovePosition(transform.position + movement);
// Gravity Off
//movement.Set(h, 0f, v);
//movement = movement.normalized * speed * Time.deltaTime;
//playerRigidbody.velocity = movement;
}
// 玩家转向朝向射线与地板的相交点
void Turning()
{
Ray camRay = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit floorHit;
// 确保射线只能与地面相交
if (Physics.Raycast(camRay, out floorHit, camRayLength, floorMask))
{
Vector3 playerToMouse = floorHit.point - transform.position;
playerToMouse.y = 0f;
// 构建旋转四元数
//Quaternion playerRotation = Quaternion.identity;
Quaternion playerRotation = Quaternion.LookRotation(playerToMouse);
playerRigidbody.MoveRotation(playerRotation);
}
}
void Animating(float h, float v)
{
bool walking = h != 0f || v != 0f;
anim.SetBool("IsWalking", walking);
}
}
- PlayerShooting.cs
using UnityEngine;
/*PlayerShooting
* 挂载在Player -> GunBarrelEnd
* 并设置Environment Layer Shootable
* Enemy Shootable
* Light 开火高亮
* Line Render 绘制激光线
*/
public class PlayerShooting : MonoBehaviour
{
public int damagePerShot = 20;
public float timeBetweenBullets = 0.15f;
public float range = 100f; // 激光射线最长距离
float timer;
Ray shootRay;
RaycastHit shootHit;
int shootableMask;
ParticleSystem gunParticles;
Light gunLight;
LineRenderer gunLine;
AudioSource gunAudio;
float effectDisplayTime = 0.2f;
void Awake()
{
shootableMask = LayerMask.GetMask("Shootable");
gunParticles = GetComponent<ParticleSystem>();
gunLine = GetComponent<LineRenderer>();
gunAudio = GetComponent<AudioSource>();
gunLight = GetComponent<Light>();
}
// Update is called once per frame
void Update()
{
timer += Time.deltaTime;
if (Input.GetButton("Fire1") && timer >= timeBetweenBullets && Time.timeScale != 0)
{
Shoot();
}
if (timer >= timeBetweenBullets * effectDisplayTime)
{
DisableEffects();
}
}
void Shoot()
{
timer = 0f;
gunAudio.Play();
gunLine.enabled = true;
gunLight.enabled = true;
gunParticles.Stop();
gunParticles.Play();
gunLine.SetPosition(0, transform.position);
shootRay.origin = transform.position;
shootRay.direction = transform.forward;
// 与shootable物体相交测试
if (Physics.Raycast(shootRay, out shootHit, range, shootableMask))
{
// 绘制激光线
gunLine.SetPosition(1, shootHit.point);
// 击中的胶囊体碰撞器所属的对象(GameObject)得到其EnemyHealth
EnemyHealth enemyHealth = shootHit.collider.GetComponent<EnemyHealth>();
if (enemyHealth != null)
{
enemyHealth.TakeDamage(damagePerShot, shootHit.point);
}
}
else
{
gunLine.SetPosition(1, shootRay.origin + shootRay.direction * range);
}
}
public void DisableEffects()
{
gunLine.enabled = false;
gunLight.enabled = false;
}
}
再来剖析Enemy相关脚本
- EnemyAttack.cs
using UnityEngine;
/*
* EnemyAttack
*
*/
public class EnemyAttack : MonoBehaviour
{
public float timeBetweenAttacks = 0.5f;
public int attackDamage = 10;
Animator anim;
GameObject player;
PlayerHealth playerHealth;
EnemyHealth enemyHealth;
bool playerInRange;
float timer;
void Awake()
{
player = GameObject.FindGameObjectWithTag("Player");
playerHealth = player.GetComponent<PlayerHealth>();
enemyHealth = GetComponent<EnemyHealth>();
anim = GetComponent<Animator>();
}
// Sphere Collider
void OnTriggerEnter(Collider other)
{
if (other.gameObject == player) // if (other.tag == "player")
{
playerInRange = true;
}
}
void OnTriggerExit(Collider other)
{
if (other.gameObject == player)
{
playerInRange = false;
}
}
// Update is called once per frame
void Update()
{
timer += Time.deltaTime;
if (timer >= timeBetweenAttacks && playerInRange && enemyHealth.currentHealth > 0)
{
Attack();
timer = 0f;
}
if (playerHealth.currentHealth <= 0)
{
anim.SetTrigger("PlayerDead"); // Enemy切换到Idle状态
}
}
void Attack()
{
if (playerHealth.currentHealth > 0)
{
playerHealth.TakeDamage(attackDamage);
}
}
}
- EnemyHealth.cs
using UnityEngine;
/*
* EnemyHealth
* 敌人生命管理 受到伤害相关
*/
public class EnemyHealth : MonoBehaviour
{
public int startingHealth = 100;
public int currentHealth;
// Dead后会下沉消失
public float sinkSpeed = 2.5f;
public int scoreValue = 10;
public AudioClip deathClip;
Animator anim;
AudioSource enemyAudio;
ParticleSystem hitParticles;
CapsuleCollider capsuleCollider;
bool isDead;
bool isSinking;
void Awake()
{
anim = GetComponent<Animator>();
enemyAudio = GetComponent<AudioSource>();
hitParticles = GetComponentInChildren<ParticleSystem>();
capsuleCollider = GetComponent<CapsuleCollider>();
currentHealth = startingHealth;
}
// Update is called once per frame
void Update()
{
if (isSinking)
{
transform.Translate(-Vector3.up * sinkSpeed * Time.deltaTime);
}
}
// interface
// 受到攻击
public void TakeDamage(int amount, Vector3 hitPoint)
{
if (isDead)
return;
enemyAudio.Play();
currentHealth -= amount;
// 粒子特效
hitParticles.transform.position = hitPoint;
hitParticles.Play();
if (currentHealth <= 0)
{
Death();
}
}
// Enemy Death Animation Event 调用
// 死亡后下沉
public void StartSinking()
{
GetComponent<NavMeshAgent>().enabled = false;
GetComponent<Rigidbody>().isKinematic = true; // 运动不受物理力影响
isSinking = true;
ScoreManager.score += scoreValue;
Destroy(gameObject, 2f);
}
void Death()
{
isDead = true;
capsuleCollider.isTrigger = true;
anim.SetTrigger("Dead");
// 更换其声音片段为死亡音效
enemyAudio.clip = deathClip;
enemyAudio.Play();
}
}
- EnemyMovement.cs
using UnityEngine;
/*
* EnemyMovement
* Nav Mesh Agent 组件
*/
public class EnemyMovement : MonoBehaviour
{
Transform player;
EnemyHealth enemyHealth;
NavMeshAgent nav;
void Awake()
{
// InvokeRepeating 自动重复生成敌人,故使用FindGameObjectWithTag查找玩家
player = GameObject.FindGameObjectWithTag("Player").transform;
enemyHealth = GetComponent<EnemyHealth>();
// 得到导航网格
nav = GetComponent<NavMeshAgent>();
}
// Update is called once per frame
void Update()
{
if (enemyHealth.currentHealth > 0)
{
nav.SetDestination(player.position);
}
}
}
接着继续剖析Managers相关
- EnemyManager.cs
using UnityEngine;
/*
* EnemyManager
* Windows -> Aavigation 预烘焙导航网格
* Nav Mesh Agent 添加AI导航组件
* Animator Override Controller 可以重载不同敌人的动画动作
* 将该脚本在EnemyManager下重复挂载多次便于升车多类对象,enemy设置不同的数值
*/
public class EnemyManager : MonoBehaviour
{
public GameObject enemy;
public float spawnTime = 3f;
public Transform[] spawnPoints; // 在编辑器中设置生成点
// Use this for initialization
void Start()
{
// 调用重复延时函数
InvokeRepeating("Spawn", spawnTime, spawnTime);
// Invoke("Spawn", spawnTime);
}
void Spawn()
{
// 暂时先在原点生成
//Vector3 spawnPos = Vector3.zero;
//Quaternion spawnRotation = Quaternion.identity;
//Instantiate(enemy, spawnPos, spawnRotation);
int spawnPointIndex = Random.Range(0, spawnPoints.Length);
Instantiate(enemy, spawnPoints[spawnPointIndex].position, spawnPoints[spawnPointIndex].rotation);
}
}
- GameOverManager.cs
using UnityEngine;
using UnityEngine.SceneManagement;
/*
* GameOverManager
* 该脚本挂载在Canvas(画布),即最顶层UI上
*/
public class GameOverManager : MonoBehaviour
{
public PlayerHealth playerHealth;
public float restartDelay = 5f; // 在5s后重新开始游戏
Animator anim; // 将UI图形渐变制作成AC动画 (淡入淡出相关) Windows -> Animation
float restartTimer;
void Awake()
{
anim = GetComponent<Animator>();
}
// Update is called once per frame
void Update()
{
if (playerHealth.currentHealth <= 0)
{
anim.SetTrigger("GameOver");
restartTimer += Time.deltaTime;
if (restartTimer > restartDelay)
{
// 重新开始游戏,加载场景
// 需要去掉PlayerHealth RestartLeve1的调用(delete Animation Event)
SceneManager.LoadScene("Main");
}
}
}
// PlayerHealth.cs 相关相同功能代码
// Player Death Animation Event 调用
//public void RestartLevel()
//{
// /*
// * 重新加载场景后光照可能变暗
// * 因为选择的光照是realtime实时光照
// * 编辑器在当前场景时候,光照是已经渲染好的
// * 重新加载的时候光照没有进行渲染
// * 需要预烘焙,以便保存光照贴图信息
// * windows -> lighting 取消勾选Auto 再点击旁边的Build
// */
// //SceneManager.LoadScene("Main", LoadSceneMode.Single); // 重新加载场景
// //SceneManager.LoadScene("Main", LoadSceneMode.Additive); // 在现有场景基础上继续游戏
// SceneManager.LoadScene("Main"); // Mode可省
//}
}
- ScoreManager.cs
using UnityEngine;
using UnityEngine.UI;
/*
* ScoreManager
* 该脚本挂载在UI控件ScoreText上
*/
public class ScoreManager : MonoBehaviour
{
/*外部可通过该类直接访问该变量,无需实例化,在EnemyHealth脚本 Enemy Death中进行分数累加
* ScoreManager.score += scoreValue;
*/
public static int score;
Text text;
void Awake()
{
text = GetComponent<Text>();
score = 0;
}
// Update is called once per frame
void Update()
{
text.text = "Score: " + score;
}
}
最后剖析Camera跟随脚本
- CameraFollow.cs
using UnityEngine;
using System.Collections;
/*
* CameraFollow
*/
public class CameraFollow : MonoBehaviour
{
public Transform target;
public float smoothing = 5f; // 平滑插值 防止摄像机移动中抖动
Vector3 offset;
// Use this for initialization
void Start()
{
offset = target.position - transform.position;
}
void FixedUpdate()
{
// default 0.02s
// transform.position = target.position - offset;
Vector3 targetCameraPos = target.position - offset;
// 平滑插值
transform.position = Vector3.Lerp(transform.position, targetCameraPos, smoothing * Time.deltaTime); // 0.1 平滑系数
}
}
UFO Game
- PlayerController.cs
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
public class PlayerController : MonoBehaviour
{
public float speed;
private Rigidbody2D rb2d;
public Text countText;
public Text winText;
public Text restartText;
int count;
bool bRestart;
// Use this for initialization
void Start()
{
rb2d = GetComponent<Rigidbody2D>();
winText.text = "";
}
void FixedUpdate()
{
float moveHorizontal = Input.GetAxis("Horizontal");
float moveVertical = Input.GetAxis("Vertical");
Vector2 movement = new Vector2(moveHorizontal, moveVertical);
rb2d.AddForce(movement * speed * Time.deltaTime);
}
void OnTriggerEnter2D(Collider2D other)
{
//if (other.tag == "PickUp")
//{
// other.gameObject.SetActive(false);
// count += 1;
// SetCountText();
//}
if (other.gameObject.CompareTag("PickUp"))
{
other.gameObject.SetActive(false);
//Destroy(other.gameObject);
count = count + 1;
SetCountText();
}
}
void SetCountText()
{
countText.text = "Grade: " + count.ToString();
if (count >= 12)
{
// winText.gameObject.SetActive(true); // 可在编辑器中将text直接置空
winText.text = "You Win!";
// 重新开始游戏按键提示
restartText.text = "Please press 'R' for restart";
bRestart = true;
}
}
void Update()
{
if (bRestart)
{
if (Input.GetKeyDown(KeyCode.R))
{
// 重新加载游戏场景
SceneManager.LoadScene("Main");
}
}
}
}
- Rotator.cs
using UnityEngine;
public class Rotator : MonoBehaviour
{
void Update()
{
// transform.Rotate(0, 0, 1); // 2D绕Z轴旋转即可
transform.Rotate(new Vector3(0, 0, 45) * Time.deltaTime);
}
}
- CameraFollow.cs
using UnityEngine;
using System.Collections;
public class CameraFollow : MonoBehaviour
{
public GameObject player;
private Vector3 offset;
// Use this for initialization
void Start()
{
offset = player.transform.position - transform.position;
}
// Update is called once per frame
void Update()
{
transform.position = player.transform.position - offset;
}
}