Space shooter遊戲
Revised on March 28, 2019
⼀款俯視⾓太空射擊遊戲,學習場景光
照設定、戰機移動控制、射擊控制、隕
石群生成、敵動迴避移動、爆炸效果、
背景音樂
 滙入時取消勾選ProjectSettings項目
Space Shooter tutorial套件
2
 https://unity3d.com/learn/tutorials/projects/space-shooter-
tutorial
線上教學影片
3
 新增3D專案
 選單命令File> New Project…
 專案名稱Space shooter
 儲存預設場景
 Main.unity
 滙入Space Shooter tutorial.unitypackage
 選單命令Assets> Import Package> Custom Package…
專案設定 1/2
4
 設定場景大小
 選單命令File> Build Settings…
 點擊Player Settings...按鈕
 Fullscreen Mode = Windowed
 Default Screen Width: 600
 Default Screen Height: 900
專案設定 2/2
5
 拖曳Modelsvehicle_playerShip到Hiearchy面板
 重新命名:Player
 重置Transform參數值
 設定Player戰機為剛體物件
 選單命令Component> Physics> Rigidbody
 取消勾選Use Gravity以符合外太空無重力環境
建立玩家戰機物件 1/2
6
 在Player戰機上加上網格碰撞器
 選單命令Component> Physics> Mesh Collider
 勾選Convex
 勾選Is Trigger
 簡化網格,拖曳Modelsplayer_ship_collider到Mesh欄
 設定Player戰機引擎噴射效果
 拖曳PrefabsVFXEnginesengines_player預製物件成為Player子物件
建立玩家戰機物件 2/2
7
 重置Main Camera之Transform參數值
 使Main Camera朝下
 Rotation(X, Y, Z) = (90, 0, 0)
 使Main Camera移動到Player戰機上方
 Position(X, Y, Z) = (0, 10, 0)
 將Main Camera投影方式設定為正交投影
 Projection = Orthographic
 Size = 10
攝影機設定 1/2
8
 調整Main Camera之Z軸位置來變更Player戰機初始顯示位置
 Position(X, Y, Z) = (0, 10, 5)
 設定Main Camera鏡頭底色
 Clear Flags = Solid Color
 Background = black
攝影機設定 2/2
9
 刪除Directional Light
 取消環境光
 選單命令Window> Rendering> Lighting Settings開啟Lighting面板
 Skybox Material = none
 Ambient Color = black
光照設定 1/3
10
 建立主光源
 選單命令GameObject> Light> Directional Light,更名為Main
Light
 重置Transform
 調整光線⾓度,Rotation(X, Y, Z) = (20, -115, 0)
 建立補光
 選取Main Light,Ctrl-D複製,更名為Fill Light
 重置Transform
 調整補光強度,Intensity = 0.5
 調整補光⾓度,Rotation(X, Y, Z) = (5, 125, 0)
 調整補光顏色,Color(R, G, B) = (128, 192, 192)
光照設定 2/3
11
 建立邊緣反射光
 複製Fill Light,更名為Rim Light
 重置Transform
 調整邊緣反射光顏色,Color = white
 調整邊緣反射光⾓度,Rotation(X, Y, Z) = (-15, 65, 0)
 調整邊緣反射光強度,Intensity = 0.25
 管理光源物件
 選單命令GameObject> Create Empty,更名為Lighting
 重置Transform
 Position(X, Y, Z) = (0, 100, 0)
 將Main Light、Fill Light及Rim Light拖曳到Lighting下做為子物件
光照設定 3/3
12
 選單命令GameObject> 3D Object> Quad,新增Quad做為背景物件,
更名為Background
 重置Transform屬性值
 使Background物件面向Main Camera
 Rotation(X, Y, Z) = (90, 0, 0)
 調整Background物件大小,使其可以填滿整個遊戲畫面
 Scale(X, Y, Z) = (15, 30, 0)
 設定背景材質
 拖曳Texturestile_nebula_green_diff到Background上
設定遊戲背景 1/2
13
 移除Mesh Collider
 將材質Shader欄變更為Unlit/Texture
 調整Background物件位置到Player戰機下方
 Position(X, Y, Z) = (0, -10, 0)
設定遊戲背景 2/2
14
 在Player物件加上PlayerController程式腳本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerController : MonoBehaviour {
void FixedUpdate () {
Rigidbody rigidBody = GetComponent<Rigidbody> ();
float moveHorizontal = Input.GetAxis ("Horizontal");
float moveVertical = Input.GetAxis ("Vertical");
Vector3 movement = new Vector3 (moveHorizontal, 0.0f, moveVertical);
rigidBody.velocity = movement;
}
}
玩家戰機移動控制 1/5
15
 執行測試,可使用方向鍵移動Player戰機,但是…
 移動速度很慢
 在PlayerController程式腳本加入速度參數
public class PlayerController : MonoBehaviour {
public float speed;
void FixedUpdate () {
Rigidbody rigidBody = GetComponent<Rigidbody> ();
float moveHorizontal = Input.GetAxis ("Horizontal");
float moveVertical = Input.GetAxis ("Vertical");
Vector3 movement = new Vector3 (moveHorizontal, 0.0f, moveVertical);
rigidBody.velocity = movement * speed;
}
}
玩家戰機移動控制 2/5
16
 將PlayerController之Speed欄設定為10
 執行測試,Player戰機移動速度變快了,但是…
 Player戰機會跑出螢幕
 修改PlayerController程式腳本,限制Player戰機移動範圍
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[System.Serializable]
public class Boundary {
public float xMin, xMax, zMin, zMax;
}
玩家戰機移動控制 3/5
17
public class PlayerController : MonoBehaviour {
public float speed;
public float tilt;
public Boundary boundary;
void FixedUpdate () {
Rigidbody rigidBody = GetComponent<Rigidbody> ();
float moveHorizontal = Input.GetAxis ("Horizontal");
float moveVertical = Input.GetAxis ("Vertical");
Vector3 movement = new Vector3 (moveHorizontal, 0.0f, moveVertical);
rigidBody.velocity = movement * speed;
rigidBody.position = new Vector3 (
Mathf.Clamp (rigidBody.position.x, boundary.xMin, boundary.xMax),
0.0f,
Mathf.Clamp (rigidBody.position.z, boundary.zMin, boundary.zMax));
rigidBody.rotation = Quaternion.Euler (
0.0f, 0.0f, rigidBody.velocity.x * -tilt);
}
}
玩家戰機移動控制 4/5
18
 設定Player戰機之Boundry值
 調整Player戰機Position之X值,測試最左及最右位置值
 調整Player戰機Position之Z值,測試上方及下方位置值
 執行測試,Player戰機不會跑出場景範圍了
玩家戰機移動控制 5/5
19
 選單命令GameObject> Create Empty,更名為Bolt
 重置Transform屬性值
 選單命令GameObject> 3D Object> Quad,更名為VFX
 重置Transform屬性值
 使VFX面向Main Camera
 Rotation(X, Y, Z) = (90, 0, 0)
 移除Mesh Collider元件
 拖曳VFX成為Bolt子物件
建立玩家戰機子彈 1/5
20
 選單命令Assets> Create> Material新增材質球,更名為
fx_bolt_orange
 Shader = Mobile/Particles/Additive
 Particle Texture = fx_lazer_orange_dff
 拖曳fx_bolt_orange材質到VFX物件上
建立玩家戰機子彈 2/5
21
 Bolt物件加上Rigidbody元件
 取消勾選Use Gravity
 Bolt物件加上Capsule Collider元件
 Direction = Z-Axis
 Radius = 0.03,Height = 0.5
 勾選Is Trigger
建立玩家戰機子彈 3/5
22
 Bolt物件加上Mover程式腳本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Mover : MonoBehaviour {
[SerializeField]
private float speed;
// Use this for initialization
void Start () {
GetComponent<Rigidbody>().velocity = transform.forward * speed;
}
}
建立玩家戰機子彈 4/5
23
 將Bolt物件拖到Prefabs資料夾做為預製物件
 Speed = 20
 刪除場景中的Bolt物件
 執行測試,將Bolt預製物件拖曳到Hierarchy面板,檢視Game視窗,
子彈是否向前飛去
建立玩家戰機子彈 5/5
24
 選單命令GameObject> Create Empty,更名為Shot Spawn
 重置Transform
 拖曳Shot Spawn成為Player子物件
 調整Shot Spawn位置到戰機之前端,Position(X, Y, Z) = (0, 0, 1)
玩家戰機子彈發射控制 1/3
25
 編輯Player物件之PlayerController程式腳本
public class PlayerController : MonoBehaviour {
public float speed;
public float tilt;
public Player_Boundary boundary;
public GameObject shot;
public Transform shotSpawn;
public float fireRate;
private float nextFire;
void Update () {
if (Input.GetButton("Fire1") && Time.time > nextFire) {
nextFire = Time.time + fireRate;
Instantiate(shot, shotSpawn.position, shotSpawn.rotation);
}
}
...
}
玩家戰機子彈發射控制 2/3
26
 拖曳Bolt預製物件到Player Controller之Shot欄
 拖曳Shot Spawn物件到Player Controller之Shot Spawn欄
 將Player Controller之Fire Rate欄值設為0.25
 執行測試,按下滑鼠左鍵可發射子彈,但是...
 檢視Hierarchy面板,子彈物件會不斷生成,但不會自動銷毀
玩家戰機子彈發射控制 3/3
27
 選單命令GameObject> 3D Object> Cube,更名為Boundary
 重置Transform屬性值
 勾選Box Collider之Is Trigger
 使Boundary物件置中顯示
 Position (X, Y, Z) = (0, 0, 5) 參照Main Camera之Z座標設定
 使Boundary物件涵蓋整個遊戲場景
 Scale (X, Y, Z) = (15, 1, 20)
 移除Mesh Renderer元件
 移除Mesh Filter元件
設定玩家戰機子彈邊界 1/3
28
設定玩家戰機子彈邊界 2/3
29
 Boundary物件加上DestroyByBoundry程式腳本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DestroyByBoundary : MonoBehaviour {
void OnTriggerExit(Collider other) {
Destroy(other.gameObject);
}
}
 測試程式,檢視Hirarchy面板,子彈物件碰到Bundary後會自動銷毀
設定玩家戰機子彈邊界 3/3
30
 選單命令GameObject> Create Empty,更名為Asteroid
 Position(X, Y, Z) = (0, 0, 8)
 拖曳Models/prop_asteroid_01成為Asteroid子物件
 重置prop_asteroid_01物件Transform設定
 在Asteroid物件加上Rigidbody元件
 取消勾選Use Gravity
 在Asteroid物件加上Capsule Collider元件
 調整Radius及Height使其符合物件大小
 Direction = Y-Axis
 Radius = 0.486
 Height = 1.524
建立隕石 1/4
31
 Asteroid物件加上RandomRotator程式腳本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class RandomRotator : MonoBehaviour {
[SerializeField]
private float tumble;
void Start () {
GetComponent<Rigidbody>().angularVelocity = Random.insideUnitSphere*tumble;
}
}
 Asteroid物件之Tumble設定為5
 執行測試,隕石會自主轉動,但是...
 子彈會直接穿過隕石
建立隕石 2/4
32
 Asteroid物件加上DestroyByContact程式腳本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DestroyByContact : MonoBehaviour {
void OnTriggerEnter(Collider other) {
if (other.CompareTag ("Boundary")) {
return;
}
Destroy(other.gameObject);
Destroy(gameObject);
}
}
建立隕石 3/4
33
 將Boundary物件之Tag設定為Boundary
 執行測試
 子彈碰到隕石時,子彈與隕石都會自動銷毀
建立隕石 4/4
34
 編輯DestroyByContact程式腳本
public class DestroyByContact : MonoBehaviour {
[SerializeField]
private GameObject explosion;
void OnTriggerEnter(Collider other) {
if (other.CompareTag ("Boundary")) {
return;
}
Instantiate(explosion, transform.position, transform.rotation);
Destroy(other.gameObject);
Destroy(gameObject);
}
}
 拖曳PrefabsVFXExplosionsexplosion_asteroid預製物件到
Explosion欄
爆破效果 1/4
35
 執行測試,子彈碰到隕石時隕石會有爆破效果,但是...
 戰機撞到隕石時,隕石會爆破效果而戰機則是直接銷毀
 將Player物件之Tag設定為Player
爆破效果 2/4
36
 編輯DestroyByContact程式腳本
public class DestroyByContact : MonoBehaviour {
[SerializeField]
private GameObject explosion;
[SerializeField]
private GameObject playerExplosion;
void OnTriggerEnter(Collider other) {
...
Instantiate(explosion, transform.position, transform.rotation);
if (other.CompareTag ("Player")) {
Instantiate(playerExplosion,
other.transform.position, other.transform.rotation);
}
Destroy(other.gameObject);
Destroy(gameObject);
}
 拖曳PrefabsVFXExplosionsexplosion_player預製物件到
Player Explosion欄
爆破效果 3/4
37
 執行測試,戰機撞到隕石時,隕石與戰機都會產生爆破效果後銷毀
 Asteroid物件加上Mover程式腳本
 設定Speed為-5
 執行測試,隕石會自動向下墜落
爆破效果 4/4
38
 將Asteroid物件拖曳到Prefabs資料夾做
成預製物件
 刪除場景上的Asteroid物件
 選單命令GameObject> Create Empty,更名為Game Controller
 重置Transform
 設定Tag為GameController
遊戲控制 1/3
39
 Game Controller物件加上GameController程式腳本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameController : MonoBehaviour {
[SerializeField]
private GameObject hazard;
[SerializeField]
private Vector3 spawnValues;
void Start () {
SpawnWaves ();
}
void SpawnWaves () {
Vector3 spawnPosition = new Vector3 (
Random.Range (-spawnValues.x, spawnValues.x), spawnValues.y, spawnValues.z);
Quaternion spawnRotation = Quaternion.identity;
Instantiate (hazard, spawnPosition, spawnRotation);
}
}
遊戲控制 2/3
40
 拖曳Asteroid預製物件到Hazard欄
 設定SpawnValues(X, Y, Z) = (6, 0, 16)
 執行測試,每次執行隕石會由不同位置墜落
遊戲控制 3/3
41
 編輯GameController程式腳本
public class GameController : MonoBehaviour {
[SerializeField]
private GameObject hazard;
[SerializeField]
private Vector3 spawnValues;
[SerializeField]
private int hazardCount;
[SerializeField]
private float spawnWait;
[SerializeField]
private float startWait;
[SerializeField]
private float waveWait;
void Start () {
StartCoroutine (SpawnWaves ());
}
設定隕石群 1/4
42
IEnumerator SpawnWaves () {
yield return new WaitForSeconds (startWait);
while (true) {
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 (hazard, spawnPosition, spawnRotation);
yield return new WaitForSeconds (spawnWait);
}
yield return new WaitForSeconds (waveWait);
}
}
}
設定隕石群 2/4
43
 Game Controller參數
 Hazard Count = 10
 Spawn Wait = 0.4
 Start Wait = 1
 Wave Wait = 4
 執行測試,隕石會定時自動生成,但是...
 擊破隕石時生成的爆破效果物件不會自動銷毀
設定隕石群 3/4
44
 建立DestroyByTime程式腳本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DestroyByTime : MonoBehaviour {
[SerializeField]
private float lifetime;
void Start () {
Destroy (gameObject, lifetime);
}
}
設定隕石群 3/4
45
 在explosion_asteroid預製物件及explosion_player預製物件加
入DestroyByTime程式腳本
 Lifetime設定為2
 執行測試,隕石會定時自動生成,爆破特效物件也會在2秒後自動銷毀
設定隕石群 4/4
46
 在explosion_asteroid預製物件加上Audio Source元件
 拖曳Audioexplosion_asteroid到explosion_asteroid預製物件之
AudioClip欄
 勾選Play On Awake
加入音效 1/4
47
 在explosion_player預製物件加上Audio Source元件
 拖曳Audioexplosion_player到AudioClip欄
 勾選Play On Awake
加入音效 2/4
48
 在Player物件加上Audio Source元件
 拖曳Audioweapon_player到AudioClip欄
 取消勾選Play On Awake
 Volume設為0.5
加入音效 3/4
49
 編輯Player物件之Player Controller程式腳本
void Update () {
if (Input.GetButton("Fire1") && Time.time > nextFire) {
nextFire = Time.time + fireRate;
Instantiate(shot, shotSpawn.position, shotSpawn.rotation);
GetComponent<AudioSource>().Play();
}
}
加入音效 4/4
50
 在GameController物件加上Audio Source元件
 拖曳Audiomusic_background到GameController物件之AudioClip欄
 勾選Play On Awake
 勾選Loop
 Volume設為0.5
加入背景音樂
51
 選單命令GameObject> UI> Text,更名為Score Text
 重置Rect Transform
 Color = white
 Anchor Presets = left, top (同時按Shift + Alt)
計算及顯示分數 1/6
52
 編輯GameController程式腳本
using UnityEngine.UI;
public class GameController : MonoBehaviour {
...
public float waveWait;
[SerializeField]
private Text scoreText;
private int score;
void Start () {
score = 0;
UpdateScore ();
StartCoroutine (SpawnWaves ());
}
計算及顯示分數 2/6
53
public void AddScore (int newScoreValue) {
score += newScoreValue;
UpdateScore ();
}
void UpdateScore () {
scoreText.text = "Score: " + score;
}
...
}
計算及顯示分數 3/6
54
 拖曳Score Text物件到Game Controller物件之Score Text欄
計算及顯示分數 4/6
55
 編輯DestroyByContact程式腳本
public class DestroyByContact : MonoBehaviour {
...
private GameObject playerExplosion;
[SerializeField]
private int scoreValue;
private GameObject gameController;
void Start () {
gameController = GameObject.Find ("Game Controller");
}
void OnTriggerEnter(Collider other) {
...
Destroy(other.gameObject);
Destroy(gameObject);
gameController.GetComponent<GameController> ().AddScore (scoreValue);
}
}
計算及顯示分數 2/6
56
 將Asteroid預製物件之Score Value設定為10
 執行測試,每擊破⼀個隕石增加10分
計算及顯示分數 3/6
57
 選單命令GameObject> UI> Text,更名為Restart Text
 重置Rect Transform
 Width = 200
 Anchor Presets = right, top (同時按Shift + Alt)
 Color = white
 Paragraph Alignment = left
結束遊戲作業 1/7
58
 選單命令GameObject> UI> Text,更名為Gameover Text
 重置Rect Transform
 Anchor Presets = middle, center
 Font Size = 18
 Paragraph Alignment = center
 Color = yellow
結束遊戲作業 2/7
59
 編輯GameController程式腳本
using UnityEngine.SceneManagement;
public class GameController : MonoBehaviour {
...
private int score;
[SerializeField]
private Text restartText;
[SerializeField]
private Text gameOverText;
private bool gameOver;
private bool restart;
void Start () {
gameOver = false;
restart = false;
restartText.text = "";
gameOverText.text = "";
score = 0;
...
}
結束遊戲作業 3/7
60
IEnumerator SpawnWaves () {
...
yield return new WaitForSeconds (waveWait);
if (gameOver) {
restartText.text = "Press 'R' for Restart";
restart = true;
break;
}
}
}
結束遊戲作業 4/7
61
void Update () {
if (restart) {
if (Input.GetKeyDown (KeyCode.R)) {
SceneManager.LoadScene ("Main");
}
}
}
public void GameOver () {
gameOverText.text = "Game Over!";
gameOver = true;
}
}
結束遊戲作業 5/7
62
 編輯DestroyByContact程式腳本
public class DestroyByContact : MonoBehaviour {
...
void OnTriggerEnter(Collider other) {
...
if (other.tag == "Player") {
Instantiate(playerExplosion,
other.transform.position,
other.transform.rotation);
gameController.GetComponent<GameController> ().GameOver();
}
...
}
}
結束遊戲作業 6/7
63
 拖曳Restart Text物件到Game Controller物件之Restart Text欄
 拖曳Gameover Text物件到Game Controller物件之Game Over
Text欄
 執行測試,玩家戰機會顯示Game over!,等下⼀波隕石群時顯示
Press 'R' for Restart,按下R鍵即可重玩
結束遊戲作業 7/7
64
 將PrefabsAsteroid預製物件更名為Asteroid01
 設定Asteroid01之Tag屬性值為"Enemy"
 拖曳PrefabsAsteroid01預製物件到場景上
 Ctrl-D複製2份,分別更名為Asteroid02及Asteroid03
 拖曳Asteroid02及Asteroid03到Prefabs資料夾做成預製物件
 刪除場景上的Asteroid01、Asteroid02及Asteroid03
建立不同隕石 1/5
65
 開啟Asteroid02預製物件
 拖曳Modelsprop_asteroid_02成為Asteroid02子物件
 刪除Asteroid02prop_asteroid_01子物件
 Capsule Collider
 Radius = 0.45
 Height = 1.37
 Direction = X-Axis
建立不同隕石 2/5
66
 開啟Asteroid03預製物件
 拖曳Modelsprop_asteroid_03成為Asteroid03子物件
 刪除Asteroid03prop_asteroid_01子物件
 Capsule Collider
 Radius = 0.55
 Height = 1.5
 Direction = X-Axis
建立不同隕石 3/5
67
 更新GameController程式腳本
public class GameController : MonoBehaviour {
[SerializeField]
private GameObject hazard;
private GameObject[] hazards;
...
IEnumerator SpawnWaves () {
yield return new WaitForSeconds (startWait);
while (true) {
for (int i = 0; i < hazardCount; i++) {
GameObject hazard = hazards[Random.Range (0, hazards.Length)];
...
}
}
}
...
}
建立不同隕石 4/5
68
 拖曳Asteroid01、Asteroid02及Asteroid03預製物件到Game
Controller之到Hazards欄
 測試遊戲,會隨機出現不同的隕石
建立不同隕石 5/5
69
 拖曳PrefabsVFXStarFieldStarField預製物件到場景上
加上星空粒子特效
70
 Ctrl-D複製Background
 拖曳Background (1)成為Background子物件
 調整Background (1)位置使它與Background串接
 Position(X, Y, Z) = (0, -1, 0)
 Rotation(X, Y, Z) = (0, 0, 0)
 Scale(X, Y, Z) = (1, 1, 1)
捲動背景 1/4
71
捲動背景 2/4
72
Background
Background (1)
 在Background加入BGScroller程式腳本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BGScroller : MonoBehaviour {
[SerializeField]
private float scrollSpeed;
[SerializeField]
private float tileSizeZ;
private Vector3 startPosition;
void Start() {
startPosition = transform.position;
}
void Update() {
float newPosition = Mathf.Repeat (Time.time * scrollSpeed, tileSizeZ);
transform.position = startPosition + Vector3.forward * newPosition;
}
}
捲動背景 3/4
73
 設定BG Scroller屬性值
 Scroll Speed = -0.25
 Tile Size Z = 30
 執行測試,背景會緩慢向下捲動
捲動背景 4/4
74
 在PrefabsVFXExplosionsexplosion_enemy預製物件加入
DestroyByTime程式腳本
 Lifetime設定為2
 在PrefabsVFXExplosionsexplosion_enemy預製物件加上
Audio Source元件
 拖曳Audioexplosion_enemy到AudioClip欄
 勾選Play On Awake
製作敵機爆炸特效
75
 選單命令GameObject> Create Empty,更名為Enemy Ship
 重置Transform屬性值
 Position(X, Y, Z) = (0, 0, 9)
 Tag = Enemy
 拖曳Modelsvehicle_enemyShip成為Enemy Ship子物件
 Rotation(X, Y, Z) = (0, 180, 0)
 拖曳PrefabsVFXEnginesengines_enemy預製物件成為Enemy
Ship子物件
建立敵機 1/4
76
 在Enemy Ship加入Rigidbody元件
 取消勾選Use Gravity
 在Enemy Ship加入Shpere Collider元件
 勾選Is Trigger
 Center(X, Y, Z) = (0, 0, -0.3)
 Radius = 0.85
建立敵機 2/4
77
 在Enemy Ship加入DestroyByContact程式腳本
 拖曳PrefabsVFXExplosionsexplosion_enemy預製物件到
Explosion欄
 拖曳PrefabsVFXExplosionsexplosion_player預製物件到Player
Explosion欄
 Score Value = 20
 執行測試,射擊敵機會爆炸,得20分,但…
 敵機不會移動,也不會主動攻擊
建立敵機 4/4
78
 Ctrl-D複製Materialsfx_bolt_orange,更名為fx_bolt_cyan
 Particle Texture = fx_lazer_cyan_dff
製作敵機雷射武器 1/4
79
 在Enemy Ship下新增空子物件,更名為Shot Spawn
 Position(X, Y, Z) = (0, 0, -0.5)
 Rotation(X, Y, Z) = (0, 180, 0)
 拖曳PrefabsBolt成為Shot Spawn子物件,更名為Bolt Enemy
 重設Bolt EnemyVFX之Materials = fx_bolt_cyan
製作敵機雷射武器 2/4
80
 在Bolt Enemy加入DestroyByContact程式腳本
 修改DestroyByContact程式腳本
void OnTriggerEnter(Collider other) {
if (other.CompareTag ("Boundary") || other.CompareTag ("Enemy")) {
return;
}
if (explosion != null)
Instantiate(explosion, transform.position, transform.rotation);
...
}
製作敵機雷射武器 3/4
81
 拖曳PrefabsVFXExplosionsexplosion_player到Destroy By
Contact之Player Explosion欄
 設定Bolt Enemy之Tag屬性值為"Enemy"
 拖曳Bolt Enemy到Prefabs資料夾做成預製物件
 刪除Hierarchy窗格中Shot Spawn之Bolt Enemy子物件
製作敵機雷射武器 4/4
82
 拖曳Audioweapon_enemy到Enemy Ship
 取消勾選Play on Awake
 在Enemy Ship加入WeaponController程式腳本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class WeaponController : MonoBehaviour {
[SerializeField]
private GameObject shot;
[SerializeField]
private Transform shotSpawn;
[SerializeField]
private float fireRate;
[SerializeField]
private float delay;
private AudioSource audioSource;
敵機雷射發射控制 1/3
83
void Start () {
audioSource = GetComponent<AudioSource> ();
InvokeRepeating ("Fire", delay, fireRate);
}
void Fire () {
Instantiate (shot, shotSpawn.position, shotSpawn.rotation);
audioSource.Play ();
}
}
敵機雷射發射控制 2/3
84
 設定Weapon Controller參數
 拖曳PrefabsBolt Enemy預製物件到Shot欄
 拖曳Enemy Ship之Shot Spawn子物件到Shot Spawn欄
 Fire Rate = 1.5
 執行測試,敵機自動發射雷射
敵機雷射發射控制 3/3
85
 在Enemy Ship加入Mover程式腳本
 Speed = -5
 執行測試,敵機如同隕石向下移動,但...
 敵機移動欠缺機動性
敵機移動
86
 在Enemy Ship新增EvasiveManeuver程式腳本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EvasiveManeuver : MonoBehaviour {
[SerializeField]
private float dodge;
[SerializeField]
private float smoothing;
[SerializeField]
private float tilt;
[SerializeField]
private Vector2 startWait;
[SerializeField]
private Vector2 maneuverTime;
[SerializeField]
private Vector2 maneuverWait;
[SerializeField]
private Boundary boundary;
敵機機動迴避 1/3
87
private float currentSpeed;
private float targetManeuver;
private Rigidbody rb;
void Start () {
rb = GetComponent <Rigidbody> ();
currentSpeed = rb.velocity.z;
StartCoroutine (Evade ());
}
IEnumerator Evade() {
yield return new WaitForSeconds (Random.Range (startWait.x, startWait.y));
while (true) {
targetManeuver =
Random.Range (1, dodge) * -Mathf.Sign (transform.position.x);
yield return new WaitForSeconds (
Random.Range (maneuverTime.x, maneuverTime.y));
targetManeuver = 0;
yield return new WaitForSeconds (
Random.Range (maneuverWait.x, maneuverWait.y));
}
}
敵機機動迴避 2/3
88
void FixedUpdate () {
float newManeuver = Mathf.MoveTowards (
rb.velocity.x,
targetManeuver,
Time.deltaTime * smoothing);
rb.velocity = new Vector3 (newManeuver, 0.0f, currentSpeed);
rb.position = new Vector3 (
Mathf.Clamp (rb.position.x, boundary.xMin, boundary.xMax),
0.0f,
Mathf.Clamp (rb.position.z, boundary.zMin, boundary.zMax));
rb.rotation = Quaternion.Euler (0.0f, 0.0f, rb.velocity.x * -tilt);
}
}
敵機機動迴避 3/3
89
 設定Evasive Maneuver屬性值
 Dodge = 5
 Smoothing = 7.5
 Tilt = 10
 startWait(X, Y) = (0.5, 1)
 Maneuver Time(X, Y) = (1, 2)
 Maneuver Wait(X, Y) = (1, 2)
 Boundary
 X Min = -6, X Max = 6
 Z Min = -20, Z Max = 20
敵機機動迴避 4/3
90
 執行測試,敵機會機動進行迴避移動
 拖曳Enemy Ship到Prefabs資料夾做成預製物件
 刪除Hierarchy窗格中的Enemy Ship
敵機機動迴避 5/3
91
 拖曳Enemy Ship預製物件到Game Controller之Hazards
 執行測試,會隨機產生隕石及敵機
更新遊戲控制
92

Unity 13 space shooter game

  • 1.
    Space shooter遊戲 Revised onMarch 28, 2019 ⼀款俯視⾓太空射擊遊戲,學習場景光 照設定、戰機移動控制、射擊控制、隕 石群生成、敵動迴避移動、爆炸效果、 背景音樂
  • 2.
  • 3.
  • 4.
     新增3D專案  選單命令File>New Project…  專案名稱Space shooter  儲存預設場景  Main.unity  滙入Space Shooter tutorial.unitypackage  選單命令Assets> Import Package> Custom Package… 專案設定 1/2 4
  • 5.
     設定場景大小  選單命令File>Build Settings…  點擊Player Settings...按鈕  Fullscreen Mode = Windowed  Default Screen Width: 600  Default Screen Height: 900 專案設定 2/2 5
  • 6.
     拖曳Modelsvehicle_playerShip到Hiearchy面板  重新命名:Player 重置Transform參數值  設定Player戰機為剛體物件  選單命令Component> Physics> Rigidbody  取消勾選Use Gravity以符合外太空無重力環境 建立玩家戰機物件 1/2 6
  • 7.
     在Player戰機上加上網格碰撞器  選單命令Component>Physics> Mesh Collider  勾選Convex  勾選Is Trigger  簡化網格,拖曳Modelsplayer_ship_collider到Mesh欄  設定Player戰機引擎噴射效果  拖曳PrefabsVFXEnginesengines_player預製物件成為Player子物件 建立玩家戰機物件 2/2 7
  • 8.
     重置Main Camera之Transform參數值 使Main Camera朝下  Rotation(X, Y, Z) = (90, 0, 0)  使Main Camera移動到Player戰機上方  Position(X, Y, Z) = (0, 10, 0)  將Main Camera投影方式設定為正交投影  Projection = Orthographic  Size = 10 攝影機設定 1/2 8
  • 9.
     調整Main Camera之Z軸位置來變更Player戰機初始顯示位置 Position(X, Y, Z) = (0, 10, 5)  設定Main Camera鏡頭底色  Clear Flags = Solid Color  Background = black 攝影機設定 2/2 9
  • 10.
     刪除Directional Light 取消環境光  選單命令Window> Rendering> Lighting Settings開啟Lighting面板  Skybox Material = none  Ambient Color = black 光照設定 1/3 10
  • 11.
     建立主光源  選單命令GameObject>Light> Directional Light,更名為Main Light  重置Transform  調整光線⾓度,Rotation(X, Y, Z) = (20, -115, 0)  建立補光  選取Main Light,Ctrl-D複製,更名為Fill Light  重置Transform  調整補光強度,Intensity = 0.5  調整補光⾓度,Rotation(X, Y, Z) = (5, 125, 0)  調整補光顏色,Color(R, G, B) = (128, 192, 192) 光照設定 2/3 11
  • 12.
     建立邊緣反射光  複製FillLight,更名為Rim Light  重置Transform  調整邊緣反射光顏色,Color = white  調整邊緣反射光⾓度,Rotation(X, Y, Z) = (-15, 65, 0)  調整邊緣反射光強度,Intensity = 0.25  管理光源物件  選單命令GameObject> Create Empty,更名為Lighting  重置Transform  Position(X, Y, Z) = (0, 100, 0)  將Main Light、Fill Light及Rim Light拖曳到Lighting下做為子物件 光照設定 3/3 12
  • 13.
     選單命令GameObject> 3DObject> Quad,新增Quad做為背景物件, 更名為Background  重置Transform屬性值  使Background物件面向Main Camera  Rotation(X, Y, Z) = (90, 0, 0)  調整Background物件大小,使其可以填滿整個遊戲畫面  Scale(X, Y, Z) = (15, 30, 0)  設定背景材質  拖曳Texturestile_nebula_green_diff到Background上 設定遊戲背景 1/2 13
  • 14.
     移除Mesh Collider 將材質Shader欄變更為Unlit/Texture  調整Background物件位置到Player戰機下方  Position(X, Y, Z) = (0, -10, 0) 設定遊戲背景 2/2 14
  • 15.
     在Player物件加上PlayerController程式腳本 using System.Collections; usingSystem.Collections.Generic; using UnityEngine; public class PlayerController : MonoBehaviour { void FixedUpdate () { Rigidbody rigidBody = GetComponent<Rigidbody> (); float moveHorizontal = Input.GetAxis ("Horizontal"); float moveVertical = Input.GetAxis ("Vertical"); Vector3 movement = new Vector3 (moveHorizontal, 0.0f, moveVertical); rigidBody.velocity = movement; } } 玩家戰機移動控制 1/5 15
  • 16.
     執行測試,可使用方向鍵移動Player戰機,但是…  移動速度很慢 在PlayerController程式腳本加入速度參數 public class PlayerController : MonoBehaviour { public float speed; void FixedUpdate () { Rigidbody rigidBody = GetComponent<Rigidbody> (); float moveHorizontal = Input.GetAxis ("Horizontal"); float moveVertical = Input.GetAxis ("Vertical"); Vector3 movement = new Vector3 (moveHorizontal, 0.0f, moveVertical); rigidBody.velocity = movement * speed; } } 玩家戰機移動控制 2/5 16
  • 17.
     將PlayerController之Speed欄設定為10  執行測試,Player戰機移動速度變快了,但是… Player戰機會跑出螢幕  修改PlayerController程式腳本,限制Player戰機移動範圍 using System.Collections; using System.Collections.Generic; using UnityEngine; [System.Serializable] public class Boundary { public float xMin, xMax, zMin, zMax; } 玩家戰機移動控制 3/5 17
  • 18.
    public class PlayerController: MonoBehaviour { public float speed; public float tilt; public Boundary boundary; void FixedUpdate () { Rigidbody rigidBody = GetComponent<Rigidbody> (); float moveHorizontal = Input.GetAxis ("Horizontal"); float moveVertical = Input.GetAxis ("Vertical"); Vector3 movement = new Vector3 (moveHorizontal, 0.0f, moveVertical); rigidBody.velocity = movement * speed; rigidBody.position = new Vector3 ( Mathf.Clamp (rigidBody.position.x, boundary.xMin, boundary.xMax), 0.0f, Mathf.Clamp (rigidBody.position.z, boundary.zMin, boundary.zMax)); rigidBody.rotation = Quaternion.Euler ( 0.0f, 0.0f, rigidBody.velocity.x * -tilt); } } 玩家戰機移動控制 4/5 18
  • 19.
     設定Player戰機之Boundry值  調整Player戰機Position之X值,測試最左及最右位置值 調整Player戰機Position之Z值,測試上方及下方位置值  執行測試,Player戰機不會跑出場景範圍了 玩家戰機移動控制 5/5 19
  • 20.
     選單命令GameObject> CreateEmpty,更名為Bolt  重置Transform屬性值  選單命令GameObject> 3D Object> Quad,更名為VFX  重置Transform屬性值  使VFX面向Main Camera  Rotation(X, Y, Z) = (90, 0, 0)  移除Mesh Collider元件  拖曳VFX成為Bolt子物件 建立玩家戰機子彈 1/5 20
  • 21.
     選單命令Assets> Create>Material新增材質球,更名為 fx_bolt_orange  Shader = Mobile/Particles/Additive  Particle Texture = fx_lazer_orange_dff  拖曳fx_bolt_orange材質到VFX物件上 建立玩家戰機子彈 2/5 21
  • 22.
     Bolt物件加上Rigidbody元件  取消勾選UseGravity  Bolt物件加上Capsule Collider元件  Direction = Z-Axis  Radius = 0.03,Height = 0.5  勾選Is Trigger 建立玩家戰機子彈 3/5 22
  • 23.
     Bolt物件加上Mover程式腳本 using System.Collections; usingSystem.Collections.Generic; using UnityEngine; public class Mover : MonoBehaviour { [SerializeField] private float speed; // Use this for initialization void Start () { GetComponent<Rigidbody>().velocity = transform.forward * speed; } } 建立玩家戰機子彈 4/5 23
  • 24.
     將Bolt物件拖到Prefabs資料夾做為預製物件  Speed= 20  刪除場景中的Bolt物件  執行測試,將Bolt預製物件拖曳到Hierarchy面板,檢視Game視窗, 子彈是否向前飛去 建立玩家戰機子彈 5/5 24
  • 25.
     選單命令GameObject> CreateEmpty,更名為Shot Spawn  重置Transform  拖曳Shot Spawn成為Player子物件  調整Shot Spawn位置到戰機之前端,Position(X, Y, Z) = (0, 0, 1) 玩家戰機子彈發射控制 1/3 25
  • 26.
     編輯Player物件之PlayerController程式腳本 public classPlayerController : MonoBehaviour { public float speed; public float tilt; public Player_Boundary boundary; public GameObject shot; public Transform shotSpawn; public float fireRate; private float nextFire; void Update () { if (Input.GetButton("Fire1") && Time.time > nextFire) { nextFire = Time.time + fireRate; Instantiate(shot, shotSpawn.position, shotSpawn.rotation); } } ... } 玩家戰機子彈發射控制 2/3 26
  • 27.
     拖曳Bolt預製物件到Player Controller之Shot欄 拖曳Shot Spawn物件到Player Controller之Shot Spawn欄  將Player Controller之Fire Rate欄值設為0.25  執行測試,按下滑鼠左鍵可發射子彈,但是...  檢視Hierarchy面板,子彈物件會不斷生成,但不會自動銷毀 玩家戰機子彈發射控制 3/3 27
  • 28.
     選單命令GameObject> 3DObject> Cube,更名為Boundary  重置Transform屬性值  勾選Box Collider之Is Trigger  使Boundary物件置中顯示  Position (X, Y, Z) = (0, 0, 5) 參照Main Camera之Z座標設定  使Boundary物件涵蓋整個遊戲場景  Scale (X, Y, Z) = (15, 1, 20)  移除Mesh Renderer元件  移除Mesh Filter元件 設定玩家戰機子彈邊界 1/3 28
  • 29.
  • 30.
     Boundary物件加上DestroyByBoundry程式腳本 using System.Collections; usingSystem.Collections.Generic; using UnityEngine; public class DestroyByBoundary : MonoBehaviour { void OnTriggerExit(Collider other) { Destroy(other.gameObject); } }  測試程式,檢視Hirarchy面板,子彈物件碰到Bundary後會自動銷毀 設定玩家戰機子彈邊界 3/3 30
  • 31.
     選單命令GameObject> CreateEmpty,更名為Asteroid  Position(X, Y, Z) = (0, 0, 8)  拖曳Models/prop_asteroid_01成為Asteroid子物件  重置prop_asteroid_01物件Transform設定  在Asteroid物件加上Rigidbody元件  取消勾選Use Gravity  在Asteroid物件加上Capsule Collider元件  調整Radius及Height使其符合物件大小  Direction = Y-Axis  Radius = 0.486  Height = 1.524 建立隕石 1/4 31
  • 32.
     Asteroid物件加上RandomRotator程式腳本 using System.Collections; usingSystem.Collections.Generic; using UnityEngine; public class RandomRotator : MonoBehaviour { [SerializeField] private float tumble; void Start () { GetComponent<Rigidbody>().angularVelocity = Random.insideUnitSphere*tumble; } }  Asteroid物件之Tumble設定為5  執行測試,隕石會自主轉動,但是...  子彈會直接穿過隕石 建立隕石 2/4 32
  • 33.
     Asteroid物件加上DestroyByContact程式腳本 using System.Collections; usingSystem.Collections.Generic; using UnityEngine; public class DestroyByContact : MonoBehaviour { void OnTriggerEnter(Collider other) { if (other.CompareTag ("Boundary")) { return; } Destroy(other.gameObject); Destroy(gameObject); } } 建立隕石 3/4 33
  • 34.
     將Boundary物件之Tag設定為Boundary  執行測試 子彈碰到隕石時,子彈與隕石都會自動銷毀 建立隕石 4/4 34
  • 35.
     編輯DestroyByContact程式腳本 public classDestroyByContact : MonoBehaviour { [SerializeField] private GameObject explosion; void OnTriggerEnter(Collider other) { if (other.CompareTag ("Boundary")) { return; } Instantiate(explosion, transform.position, transform.rotation); Destroy(other.gameObject); Destroy(gameObject); } }  拖曳PrefabsVFXExplosionsexplosion_asteroid預製物件到 Explosion欄 爆破效果 1/4 35
  • 36.
  • 37.
     編輯DestroyByContact程式腳本 public classDestroyByContact : MonoBehaviour { [SerializeField] private GameObject explosion; [SerializeField] private GameObject playerExplosion; void OnTriggerEnter(Collider other) { ... Instantiate(explosion, transform.position, transform.rotation); if (other.CompareTag ("Player")) { Instantiate(playerExplosion, other.transform.position, other.transform.rotation); } Destroy(other.gameObject); Destroy(gameObject); }  拖曳PrefabsVFXExplosionsexplosion_player預製物件到 Player Explosion欄 爆破效果 3/4 37
  • 38.
     執行測試,戰機撞到隕石時,隕石與戰機都會產生爆破效果後銷毀  Asteroid物件加上Mover程式腳本 設定Speed為-5  執行測試,隕石會自動向下墜落 爆破效果 4/4 38  將Asteroid物件拖曳到Prefabs資料夾做 成預製物件  刪除場景上的Asteroid物件
  • 39.
     選單命令GameObject> CreateEmpty,更名為Game Controller  重置Transform  設定Tag為GameController 遊戲控制 1/3 39
  • 40.
     Game Controller物件加上GameController程式腳本 usingSystem.Collections; using System.Collections.Generic; using UnityEngine; public class GameController : MonoBehaviour { [SerializeField] private GameObject hazard; [SerializeField] private Vector3 spawnValues; void Start () { SpawnWaves (); } void SpawnWaves () { Vector3 spawnPosition = new Vector3 ( Random.Range (-spawnValues.x, spawnValues.x), spawnValues.y, spawnValues.z); Quaternion spawnRotation = Quaternion.identity; Instantiate (hazard, spawnPosition, spawnRotation); } } 遊戲控制 2/3 40
  • 41.
     拖曳Asteroid預製物件到Hazard欄  設定SpawnValues(X,Y, Z) = (6, 0, 16)  執行測試,每次執行隕石會由不同位置墜落 遊戲控制 3/3 41
  • 42.
     編輯GameController程式腳本 public classGameController : MonoBehaviour { [SerializeField] private GameObject hazard; [SerializeField] private Vector3 spawnValues; [SerializeField] private int hazardCount; [SerializeField] private float spawnWait; [SerializeField] private float startWait; [SerializeField] private float waveWait; void Start () { StartCoroutine (SpawnWaves ()); } 設定隕石群 1/4 42
  • 43.
    IEnumerator SpawnWaves (){ yield return new WaitForSeconds (startWait); while (true) { 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 (hazard, spawnPosition, spawnRotation); yield return new WaitForSeconds (spawnWait); } yield return new WaitForSeconds (waveWait); } } } 設定隕石群 2/4 43
  • 44.
     Game Controller參數 Hazard Count = 10  Spawn Wait = 0.4  Start Wait = 1  Wave Wait = 4  執行測試,隕石會定時自動生成,但是...  擊破隕石時生成的爆破效果物件不會自動銷毀 設定隕石群 3/4 44
  • 45.
     建立DestroyByTime程式腳本 using System.Collections; usingSystem.Collections.Generic; using UnityEngine; public class DestroyByTime : MonoBehaviour { [SerializeField] private float lifetime; void Start () { Destroy (gameObject, lifetime); } } 設定隕石群 3/4 45
  • 46.
     在explosion_asteroid預製物件及explosion_player預製物件加 入DestroyByTime程式腳本  Lifetime設定為2 執行測試,隕石會定時自動生成,爆破特效物件也會在2秒後自動銷毀 設定隕石群 4/4 46
  • 47.
     在explosion_asteroid預製物件加上Audio Source元件 拖曳Audioexplosion_asteroid到explosion_asteroid預製物件之 AudioClip欄  勾選Play On Awake 加入音效 1/4 47
  • 48.
     在explosion_player預製物件加上Audio Source元件 拖曳Audioexplosion_player到AudioClip欄  勾選Play On Awake 加入音效 2/4 48
  • 49.
     在Player物件加上Audio Source元件 拖曳Audioweapon_player到AudioClip欄  取消勾選Play On Awake  Volume設為0.5 加入音效 3/4 49
  • 50.
     編輯Player物件之Player Controller程式腳本 voidUpdate () { if (Input.GetButton("Fire1") && Time.time > nextFire) { nextFire = Time.time + fireRate; Instantiate(shot, shotSpawn.position, shotSpawn.rotation); GetComponent<AudioSource>().Play(); } } 加入音效 4/4 50
  • 51.
     在GameController物件加上Audio Source元件 拖曳Audiomusic_background到GameController物件之AudioClip欄  勾選Play On Awake  勾選Loop  Volume設為0.5 加入背景音樂 51
  • 52.
     選單命令GameObject> UI>Text,更名為Score Text  重置Rect Transform  Color = white  Anchor Presets = left, top (同時按Shift + Alt) 計算及顯示分數 1/6 52
  • 53.
     編輯GameController程式腳本 using UnityEngine.UI; publicclass GameController : MonoBehaviour { ... public float waveWait; [SerializeField] private Text scoreText; private int score; void Start () { score = 0; UpdateScore (); StartCoroutine (SpawnWaves ()); } 計算及顯示分數 2/6 53
  • 54.
    public void AddScore(int newScoreValue) { score += newScoreValue; UpdateScore (); } void UpdateScore () { scoreText.text = "Score: " + score; } ... } 計算及顯示分數 3/6 54
  • 55.
     拖曳Score Text物件到GameController物件之Score Text欄 計算及顯示分數 4/6 55
  • 56.
     編輯DestroyByContact程式腳本 public classDestroyByContact : MonoBehaviour { ... private GameObject playerExplosion; [SerializeField] private int scoreValue; private GameObject gameController; void Start () { gameController = GameObject.Find ("Game Controller"); } void OnTriggerEnter(Collider other) { ... Destroy(other.gameObject); Destroy(gameObject); gameController.GetComponent<GameController> ().AddScore (scoreValue); } } 計算及顯示分數 2/6 56
  • 57.
     將Asteroid預製物件之Score Value設定為10 執行測試,每擊破⼀個隕石增加10分 計算及顯示分數 3/6 57
  • 58.
     選單命令GameObject> UI>Text,更名為Restart Text  重置Rect Transform  Width = 200  Anchor Presets = right, top (同時按Shift + Alt)  Color = white  Paragraph Alignment = left 結束遊戲作業 1/7 58
  • 59.
     選單命令GameObject> UI>Text,更名為Gameover Text  重置Rect Transform  Anchor Presets = middle, center  Font Size = 18  Paragraph Alignment = center  Color = yellow 結束遊戲作業 2/7 59
  • 60.
     編輯GameController程式腳本 using UnityEngine.SceneManagement; publicclass GameController : MonoBehaviour { ... private int score; [SerializeField] private Text restartText; [SerializeField] private Text gameOverText; private bool gameOver; private bool restart; void Start () { gameOver = false; restart = false; restartText.text = ""; gameOverText.text = ""; score = 0; ... } 結束遊戲作業 3/7 60
  • 61.
    IEnumerator SpawnWaves (){ ... yield return new WaitForSeconds (waveWait); if (gameOver) { restartText.text = "Press 'R' for Restart"; restart = true; break; } } } 結束遊戲作業 4/7 61
  • 62.
    void Update (){ if (restart) { if (Input.GetKeyDown (KeyCode.R)) { SceneManager.LoadScene ("Main"); } } } public void GameOver () { gameOverText.text = "Game Over!"; gameOver = true; } } 結束遊戲作業 5/7 62
  • 63.
     編輯DestroyByContact程式腳本 public classDestroyByContact : MonoBehaviour { ... void OnTriggerEnter(Collider other) { ... if (other.tag == "Player") { Instantiate(playerExplosion, other.transform.position, other.transform.rotation); gameController.GetComponent<GameController> ().GameOver(); } ... } } 結束遊戲作業 6/7 63
  • 64.
     拖曳Restart Text物件到GameController物件之Restart Text欄  拖曳Gameover Text物件到Game Controller物件之Game Over Text欄  執行測試,玩家戰機會顯示Game over!,等下⼀波隕石群時顯示 Press 'R' for Restart,按下R鍵即可重玩 結束遊戲作業 7/7 64
  • 65.
     將PrefabsAsteroid預製物件更名為Asteroid01  設定Asteroid01之Tag屬性值為"Enemy" 拖曳PrefabsAsteroid01預製物件到場景上  Ctrl-D複製2份,分別更名為Asteroid02及Asteroid03  拖曳Asteroid02及Asteroid03到Prefabs資料夾做成預製物件  刪除場景上的Asteroid01、Asteroid02及Asteroid03 建立不同隕石 1/5 65
  • 66.
     開啟Asteroid02預製物件  拖曳Modelsprop_asteroid_02成為Asteroid02子物件 刪除Asteroid02prop_asteroid_01子物件  Capsule Collider  Radius = 0.45  Height = 1.37  Direction = X-Axis 建立不同隕石 2/5 66
  • 67.
     開啟Asteroid03預製物件  拖曳Modelsprop_asteroid_03成為Asteroid03子物件 刪除Asteroid03prop_asteroid_01子物件  Capsule Collider  Radius = 0.55  Height = 1.5  Direction = X-Axis 建立不同隕石 3/5 67
  • 68.
     更新GameController程式腳本 public classGameController : MonoBehaviour { [SerializeField] private GameObject hazard; private GameObject[] hazards; ... IEnumerator SpawnWaves () { yield return new WaitForSeconds (startWait); while (true) { for (int i = 0; i < hazardCount; i++) { GameObject hazard = hazards[Random.Range (0, hazards.Length)]; ... } } } ... } 建立不同隕石 4/5 68
  • 69.
  • 70.
  • 71.
     Ctrl-D複製Background  拖曳Background(1)成為Background子物件  調整Background (1)位置使它與Background串接  Position(X, Y, Z) = (0, -1, 0)  Rotation(X, Y, Z) = (0, 0, 0)  Scale(X, Y, Z) = (1, 1, 1) 捲動背景 1/4 71
  • 72.
  • 73.
     在Background加入BGScroller程式腳本 using System.Collections; usingSystem.Collections.Generic; using UnityEngine; public class BGScroller : MonoBehaviour { [SerializeField] private float scrollSpeed; [SerializeField] private float tileSizeZ; private Vector3 startPosition; void Start() { startPosition = transform.position; } void Update() { float newPosition = Mathf.Repeat (Time.time * scrollSpeed, tileSizeZ); transform.position = startPosition + Vector3.forward * newPosition; } } 捲動背景 3/4 73
  • 74.
     設定BG Scroller屬性值 Scroll Speed = -0.25  Tile Size Z = 30  執行測試,背景會緩慢向下捲動 捲動背景 4/4 74
  • 75.
     在PrefabsVFXExplosionsexplosion_enemy預製物件加入 DestroyByTime程式腳本  Lifetime設定為2 在PrefabsVFXExplosionsexplosion_enemy預製物件加上 Audio Source元件  拖曳Audioexplosion_enemy到AudioClip欄  勾選Play On Awake 製作敵機爆炸特效 75
  • 76.
     選單命令GameObject> CreateEmpty,更名為Enemy Ship  重置Transform屬性值  Position(X, Y, Z) = (0, 0, 9)  Tag = Enemy  拖曳Modelsvehicle_enemyShip成為Enemy Ship子物件  Rotation(X, Y, Z) = (0, 180, 0)  拖曳PrefabsVFXEnginesengines_enemy預製物件成為Enemy Ship子物件 建立敵機 1/4 76
  • 77.
     在Enemy Ship加入Rigidbody元件 取消勾選Use Gravity  在Enemy Ship加入Shpere Collider元件  勾選Is Trigger  Center(X, Y, Z) = (0, 0, -0.3)  Radius = 0.85 建立敵機 2/4 77
  • 78.
     在Enemy Ship加入DestroyByContact程式腳本 拖曳PrefabsVFXExplosionsexplosion_enemy預製物件到 Explosion欄  拖曳PrefabsVFXExplosionsexplosion_player預製物件到Player Explosion欄  Score Value = 20  執行測試,射擊敵機會爆炸,得20分,但…  敵機不會移動,也不會主動攻擊 建立敵機 4/4 78
  • 79.
     Ctrl-D複製Materialsfx_bolt_orange,更名為fx_bolt_cyan  ParticleTexture = fx_lazer_cyan_dff 製作敵機雷射武器 1/4 79
  • 80.
     在Enemy Ship下新增空子物件,更名為ShotSpawn  Position(X, Y, Z) = (0, 0, -0.5)  Rotation(X, Y, Z) = (0, 180, 0)  拖曳PrefabsBolt成為Shot Spawn子物件,更名為Bolt Enemy  重設Bolt EnemyVFX之Materials = fx_bolt_cyan 製作敵機雷射武器 2/4 80
  • 81.
     在Bolt Enemy加入DestroyByContact程式腳本 修改DestroyByContact程式腳本 void OnTriggerEnter(Collider other) { if (other.CompareTag ("Boundary") || other.CompareTag ("Enemy")) { return; } if (explosion != null) Instantiate(explosion, transform.position, transform.rotation); ... } 製作敵機雷射武器 3/4 81
  • 82.
     拖曳PrefabsVFXExplosionsexplosion_player到Destroy By Contact之PlayerExplosion欄  設定Bolt Enemy之Tag屬性值為"Enemy"  拖曳Bolt Enemy到Prefabs資料夾做成預製物件  刪除Hierarchy窗格中Shot Spawn之Bolt Enemy子物件 製作敵機雷射武器 4/4 82
  • 83.
     拖曳Audioweapon_enemy到Enemy Ship 取消勾選Play on Awake  在Enemy Ship加入WeaponController程式腳本 using System.Collections; using System.Collections.Generic; using UnityEngine; public class WeaponController : MonoBehaviour { [SerializeField] private GameObject shot; [SerializeField] private Transform shotSpawn; [SerializeField] private float fireRate; [SerializeField] private float delay; private AudioSource audioSource; 敵機雷射發射控制 1/3 83
  • 84.
    void Start (){ audioSource = GetComponent<AudioSource> (); InvokeRepeating ("Fire", delay, fireRate); } void Fire () { Instantiate (shot, shotSpawn.position, shotSpawn.rotation); audioSource.Play (); } } 敵機雷射發射控制 2/3 84
  • 85.
     設定Weapon Controller參數 拖曳PrefabsBolt Enemy預製物件到Shot欄  拖曳Enemy Ship之Shot Spawn子物件到Shot Spawn欄  Fire Rate = 1.5  執行測試,敵機自動發射雷射 敵機雷射發射控制 3/3 85
  • 86.
     在Enemy Ship加入Mover程式腳本 Speed = -5  執行測試,敵機如同隕石向下移動,但...  敵機移動欠缺機動性 敵機移動 86
  • 87.
     在Enemy Ship新增EvasiveManeuver程式腳本 usingSystem.Collections; using System.Collections.Generic; using UnityEngine; public class EvasiveManeuver : MonoBehaviour { [SerializeField] private float dodge; [SerializeField] private float smoothing; [SerializeField] private float tilt; [SerializeField] private Vector2 startWait; [SerializeField] private Vector2 maneuverTime; [SerializeField] private Vector2 maneuverWait; [SerializeField] private Boundary boundary; 敵機機動迴避 1/3 87
  • 88.
    private float currentSpeed; privatefloat targetManeuver; private Rigidbody rb; void Start () { rb = GetComponent <Rigidbody> (); currentSpeed = rb.velocity.z; StartCoroutine (Evade ()); } IEnumerator Evade() { yield return new WaitForSeconds (Random.Range (startWait.x, startWait.y)); while (true) { targetManeuver = Random.Range (1, dodge) * -Mathf.Sign (transform.position.x); yield return new WaitForSeconds ( Random.Range (maneuverTime.x, maneuverTime.y)); targetManeuver = 0; yield return new WaitForSeconds ( Random.Range (maneuverWait.x, maneuverWait.y)); } } 敵機機動迴避 2/3 88
  • 89.
    void FixedUpdate (){ float newManeuver = Mathf.MoveTowards ( rb.velocity.x, targetManeuver, Time.deltaTime * smoothing); rb.velocity = new Vector3 (newManeuver, 0.0f, currentSpeed); rb.position = new Vector3 ( Mathf.Clamp (rb.position.x, boundary.xMin, boundary.xMax), 0.0f, Mathf.Clamp (rb.position.z, boundary.zMin, boundary.zMax)); rb.rotation = Quaternion.Euler (0.0f, 0.0f, rb.velocity.x * -tilt); } } 敵機機動迴避 3/3 89
  • 90.
     設定Evasive Maneuver屬性值 Dodge = 5  Smoothing = 7.5  Tilt = 10  startWait(X, Y) = (0.5, 1)  Maneuver Time(X, Y) = (1, 2)  Maneuver Wait(X, Y) = (1, 2)  Boundary  X Min = -6, X Max = 6  Z Min = -20, Z Max = 20 敵機機動迴避 4/3 90
  • 91.
     執行測試,敵機會機動進行迴避移動  拖曳EnemyShip到Prefabs資料夾做成預製物件  刪除Hierarchy窗格中的Enemy Ship 敵機機動迴避 5/3 91
  • 92.
     拖曳Enemy Ship預製物件到GameController之Hazards  執行測試,會隨機產生隕石及敵機 更新遊戲控制 92