電子工程系
Unity遊戲程式設計(15) Space shooter遊戲
吳錫修
June 12, 2017
shap
e th
e fu
ture
Space Shooter tutorial套件
2 Wu, ShyiShiou Dept. of E.E., NKUT
shap
e th
e fu
ture
https://unity3d.com/learn/tutorials/projects/space-shooter-tutorial
線上教學影片
3 Wu, ShyiShiou Dept. of E.E., NKUT
shap
e th
e fu
ture
新增3D專案
選單命令File> New Project…
專案名稱Space shooter
儲存預設場景
Main.unity
滙入Space Shooter tutorial.unitypackage
選單命令Assets> Import Package> Custom Package…
專案設定 1/2
4 Wu, ShyiShiou Dept. of E.E., NKUT
shap
e th
e fu
ture
設定場景大小
選單命令File> Build Settings…
點擊Player Settings
取消勾選Default Is Full Screen*
Default Screen Width: 600
Default Screen Height: 900
專案設定 2/2
5 Wu, ShyiShiou Dept. of E.E., NKUT
shap
e th
e fu
ture
拖曳Models/vehicle_playerShip到Hiearchy面板
重新命名:Player
重置Transform參數值
設定Player戰機為剛體物件
選單命令Component> Physics> Rigidbody
取消勾選Use Gravity以符合外太空無重力環境
建立玩家戰機物件 1/2
6 Wu, ShyiShiou Dept. of E.E., NKUT
shap
e th
e fu
ture
在Player戰機上加上網格碰撞器
選單命令Component> Physics> Mesh Collider
勾選Convex
勾選Is Trigger
簡化網格,拖曳Models/player_ship_collider到Mesh欄
設定Player戰機引擎噴射效果
拖曳Prefabs\VFX\Engines\engines_player預製物件做為Player子物件
建立玩家戰機物件 2/2
7 Wu, ShyiShiou Dept. of E.E., NKUT
shap
e th
e fu
ture
重置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
調整Player戰機在畫面初始顯示位置
Main Camera Position (X, Y, Z) = (0, 10, 5)
攝影機設定 1/2
8 Wu, ShyiShiou Dept. of E.E., NKUT
shap
e th
e fu
ture
設定Main Camera鏡頭底色
Clear Flags = Solid Color
Background = black
攝影機設定 2/2
9 Wu, ShyiShiou Dept. of E.E., NKUT
shap
e th
e fu
ture
刪除Directional Light
取消環境光
選單命令Window> Lighting> Settings開啟Lighting面板
Skybox Material = none
Ambient Color = black
光照設定 1/3
10 Wu, ShyiShiou Dept. of E.E., NKUT
shap
e th
e fu
ture
建立主光源
選單命令GameObject> Light> Directional Light,更名為Main Light
重置Position
調整光線角度,Rotation (X, Y, Z) = (20, -115, 0)
建立補光
選單命令Edit> Duplicate (Ctrl-D)複製⼀份Main Light,更名為Fill Light
重置Rotation
調整補光強度,Intensity = 0.5
調整補光角度,Rotation (X, Y, Z) = (5, 125, 0)
調整補光顏色,Color (R, G, B) = (128, 192, 192)
光照設定 2/3
11 Wu, ShyiShiou Dept. of E.E., NKUT
shap
e th
e fu
ture
建立邊緣反射光
複製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 Wu, ShyiShiou Dept. of E.E., NKUT
shap
e th
e fu
ture
選單命令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)
設定背景材質
拖曳Materials\tile_nebula_green_diff到Background上
設定遊戲背景 1/2
13 Wu, ShyiShiou Dept. of E.E., NKUT
shap
e th
e fu
ture
移除Mesh Collider
將材質Shader欄變更為Unlit/Texture
調整Background物件位置到Player戰機下方
Position (X, Y, Z) = (0, -10, 0)
設定遊戲背景 2/2
14 Wu, ShyiShiou Dept. of E.E., NKUT
shap
e th
e fu
ture
在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 Wu, ShyiShiou Dept. of E.E., NKUT
shap
e th
e fu
ture
執行測試,可使用方向鍵移動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 Wu, ShyiShiou Dept. of E.E., NKUT
shap
e th
e fu
ture
將PlayerController之Speed欄設定為10
執行測試,Player戰機移動速度變快了,但是…
Player戰機會跑出螢幕
修改PlayerController程式腳本,限制Player戰機移動範圍
using System.Collections;using System.Collections.Generic;using UnityEngine;
[System.Serializable]public class Player_Boundary{
public float xMin, xMax, zMin, zMax;}
玩家戰機移動控制 3/5
17 Wu, ShyiShiou Dept. of E.E., NKUT
shap
e th
e fu
ture
public class PlayerController : MonoBehaviour {public float speed;public float tilt;public Player_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 Wu, ShyiShiou Dept. of E.E., NKUT
shap
e th
e fu
ture
設定Player戰機之Boundry值
調整Player戰機Position之X值,測試最左及最右位置值
調整Player戰機Position之Z值,測試上方及下方位置值
執行測試,Player戰機不會跑出場景範圍了
玩家戰機移動控制 5/5
19 Wu, ShyiShiou Dept. of E.E., NKUT
shap
e th
e fu
ture
選單命令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/4
20 Wu, ShyiShiou Dept. of E.E., NKUT
shap
e th
e fu
ture
選單命令Assets> Create>Material新增材質球,更名為fx_bolt_orange
Shader = Particles/Additive
Particle Texture = fx_lazer_orange_dff
拖曳fx_bolt_orange材質到VFX物件上
Bolt物件加上Rigidbody元件
取消勾選Use Gravity
Bolt物件加上Capsule Collider元件
Direction = Z-Axis
Radius = 0.03,Height = 0.5
勾選Is Trigger
建立玩家戰機子彈 2/4
21 Wu, ShyiShiou Dept. of E.E., NKUT
shap
e th
e fu
ture
Bolt物件加上Mover程式腳本using System.Collections;using System.Collections.Generic;using UnityEngine;
public class Mover : MonoBehaviour {public float speed;// Use this for initializationvoid Start () {
GetComponent<Rigidbody>().velocity = transform.forward * speed;}
}
建立玩家戰機子彈 3/4
22 Wu, ShyiShiou Dept. of E.E., NKUT
shap
e th
e fu
ture
將Bolt物件拖到Prefabs資料夾做為預製物件
Speed = 20
刪除場景中的Bolt物件
執行測試,將Bolt預製物件拖曳到Hierarchy面板,檢視Game視窗,子彈是否向前飛去
建立玩家戰機子彈 4/4
23 Wu, ShyiShiou Dept. of E.E., NKUT
shap
e th
e fu
ture
選單命令GameObject> Create Empty,更名為Shot Spawn
重置Transform
拖曳Shot Spawn成為Player子物件
調整Shot Spawn位置到戰機之前端,Position (X, Y, Z) = (0, 0, 1)
玩家戰機子彈發射控制 1/3
24 Wu, ShyiShiou Dept. of E.E., NKUT
shap
e th
e fu
ture
編輯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
25 Wu, ShyiShiou Dept. of E.E., NKUT
shap
e th
e fu
ture
拖曳Bolt預製物件到Player Controller之Shot欄
拖曳Shot Spawn物件到Player Controller之Shot Spawn欄
將Player Controller之Fire Rate欄值設為0.25
執行測試,按下滑鼠左鍵可發射子彈,但是…
檢視Hierarchy面板,子彈物件會不斷生成,但不會自動銷毀
玩家戰機子彈發射控制 3/3
26 Wu, ShyiShiou Dept. of E.E., NKUT
shap
e th
e fu
ture
選單命令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/2
27 Wu, ShyiShiou Dept. of E.E., NKUT
shap
e th
e fu
ture
Boundary物件加上DestroyByBoundry程式腳本
using System.Collections;using System.Collections.Generic;using UnityEngine;
public class DestroyByBoundary : MonoBehaviour {
void OnTriggerExit(Collider other){
Destroy(other.gameObject);}
}
測試程式,檢視Hirarchy面板,子彈物件碰到Bundary後會自動銷毀
設定玩家戰機子彈邊界 2/2
28 Wu, ShyiShiou Dept. of E.E., NKUT
shap
e th
e fu
ture
選單命令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 = Z-Axis
Radius = 0.486
Height = 1.524
建立隕石 1/4
29 Wu, ShyiShiou Dept. of E.E., NKUT
shap
e th
e fu
ture
Asteroid物件加上RandomRotator程式腳本
using System.Collections;using System.Collections.Generic;using UnityEngine;public class RandomRotator : MonoBehaviour {
public float tumble;
void Start (){
GetComponent<Rigidbody>().angularVelocity = Random.insideUnitSphere*tumble;}
}
Asteroid物件之Tumble設定為5
執行測試,隕石會自主轉動,但是...
子彈會直接穿過隕石
建立隕石 2/4
30 Wu, ShyiShiou Dept. of E.E., NKUT
shap
e th
e fu
ture
Asteroid物件加上DestroyByContact程式腳本
using System.Collections;using System.Collections.Generic;using UnityEngine;public class DestroyByContact : MonoBehaviour {
void OnTriggerEnter(Collider other){
if (other.tag == "Boundary"){
return;}Destroy(other.gameObject);Destroy(gameObject);
}}
建立隕石 3/4
31 Wu, ShyiShiou Dept. of E.E., NKUT
shap
e th
e fu
ture
將Boundary物件之Tag設定為Boundary
執行測試
子彈碰到隕石時,子彈與隕石都會自動銷毀
建立隕石 4/4
32 Wu, ShyiShiou Dept. of E.E., NKUT
shap
e th
e fu
ture
編輯DestroyByContact程式腳本public class DestroyByContact : MonoBehaviour {
public GameObject explosion;
void OnTriggerEnter(Collider other){
if (other.tag == "Boundary"){
return;}Instantiate(explosion, transform.position, transform.rotation);Destroy(other.gameObject);Destroy(gameObject);
}}
將Prefabs\VFX\Explosions\explosion_asteroid預製物件拖曳到explosion欄
爆破效果 1/4
33 Wu, ShyiShiou Dept. of E.E., NKUT
shap
e th
e fu
ture
執行測試,子彈碰到隕石時隕石會爆破,但是...
戰機撞到隕石時,隕石會爆破但戰機直接銷毀
將Player物件之Tag設定為Player
編輯DestroyByContact程式腳本public class DestroyByContact : MonoBehaviour {
public GameObject explosion;public GameObject playerExplosion;
爆破效果 2/4
34 Wu, ShyiShiou Dept. of E.E., NKUT
shap
e th
e fu
ture
void OnTriggerEnter(Collider other){
...Instantiate(explosion, transform.position, transform.rotation);if (other.tag == "Player"){
Instantiate(playerExplosion,other.transform.position,other.transform.rotation);
}Destroy(other.gameObject);Destroy(gameObject);
}}
將Prefabs\VFX\Explosions\explosion_player預製物件拖曳到playerExplosion欄
爆破效果 3/4
35 Wu, ShyiShiou Dept. of E.E., NKUT
shap
e th
e fu
ture
執行測試,戰機撞到隕石時,隕石與戰機都會爆破
Asteroid物件加上Mover程式腳本
設定Speed為-5
執行測試,隕石會自動向下墜落
將Asteroid物件拖曳到Prefabs資料夾做成預製物件
刪除場景上的Asteroid物件
爆破效果 4/4
36 Wu, ShyiShiou Dept. of E.E., NKUT
shap
e th
e fu
ture
選單命令GameObject> Create Empty,更名為Game Controller
重置Transform
設定Tag為GameController
遊戲控制 1/3
37 Wu, ShyiShiou Dept. of E.E., NKUT
shap
e th
e fu
ture
Game Controller物件加上GameController程式腳本using System.Collections;using System.Collections.Generic;using UnityEngine;public class GameController : MonoBehaviour {
public GameObject hazard;public 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
38 Wu, ShyiShiou Dept. of E.E., NKUT
shap
e th
e fu
ture
拖曳Asteroid預製物件到Hazard欄
設定SpawnValues(X, Y, Z) = (6, 0, 16)
執行測試,每次執行隕石會由不同位置墜落
遊戲控制 3/3
39 Wu, ShyiShiou Dept. of E.E., NKUT
shap
e th
e fu
ture
編輯GameController程式腳本public class GameController : MonoBehaviour {
public GameObject hazard;public Vector3 spawnValues;public int hazardCount;public float spawnWait;public float startWait;public float waveWait;
void Start (){
StartCoroutine (SpawnWaves ());}
設定隕石群 1/4
40 Wu, ShyiShiou Dept. of E.E., NKUT
shap
e th
e fu
ture
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
41 Wu, ShyiShiou Dept. of E.E., NKUT
shap
e th
e fu
ture
執行測試,隕石會定時自動生成,但是...
擊破隕石時生成的爆破效果物件不會自動銷毀
建立DestroyByTime程式腳本using System.Collections;using System.Collections.Generic;using UnityEngine;
public class DestroyByTime : MonoBehaviour {public float lifetime;void Start (){
Destroy (gameObject, lifetime);}
}
設定隕石群 3/4
42 Wu, ShyiShiou Dept. of E.E., NKUT
shap
e th
e fu
ture
在explosion_asteroid預製物件及explosion_player預製物件加入DestroyByTime程式腳本
Lifetime設定為2
執行測試,隕石會定時自動生成,爆破特效物件也會在2秒後自動銷毀
設定隕石群 4/4
43 Wu, ShyiShiou Dept. of E.E., NKUT
shap
e th
e fu
ture
在explosion_asteroid預製物件加上Audio Source元件
拖曳Audio\explosion_asteroid到explosion_asteroid預製物件之AudioClip欄
勾選Play On Awake
在explosion_player預製物件加上Audio Source元件
拖曳Audio\explosion_player到explosion_player預製物件之AudioClip欄
勾選Play On Awake
在Player物件加上Audio Source元件
拖曳Audio\weapon_player到Player物件之AudioClip欄
取消勾選Play On Awake
Volume設為0.5
加入音效 1/2
44 Wu, ShyiShiou Dept. of E.E., NKUT
shap
e th
e fu
ture
編輯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();
}}
加入音效 2/2
45 Wu, ShyiShiou Dept. of E.E., NKUT
shap
e th
e fu
ture
在GameController物件加上Audio Source元件
拖曳Audio\music_background到GameController物件之AudioClip欄
勾選Play On Awake
勾選Loop
Volume設為0.5
加入背景音樂
46 Wu, ShyiShiou Dept. of E.E., NKUT
shap
e th
e fu
ture
選單命令GameObject> Create Empty,更名為Score Text
重置Transform
Position (X, Y, Z) = (0, 1, 0)
加入GUI Text元件
Pixel Offset (X, Y) = (10, -10)
編輯GameController程式腳本public class GameController : MonoBehaviour {
...public float waveWait;public GUIText scoreText;private int score;
計算及顯示分數 1/4
47 Wu, ShyiShiou Dept. of E.E., NKUT
shap
e th
e fu
ture
void Start (){
score = 0;UpdateScore ();StartCoroutine (SpawnWaves ());
}public void AddScore (int newScoreValue){
score += newScoreValue;UpdateScore ();
}void UpdateScore (){
scoreText.text = "Score: " + score;}
...}
計算及顯示分數 2/4
48 Wu, ShyiShiou Dept. of E.E., NKUT
shap
e th
e fu
ture
編輯DestroyByContact程式腳本public class DestroyByContact : MonoBehaviour {
public GameObject explosion;public GameObject playerExplosion;public 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);
}}
計算及顯示分數 3/4
49 Wu, ShyiShiou Dept. of E.E., NKUT
shap
e th
e fu
ture
將Asteroid預製物件之Score Value設定為10
拖曳Score Text物件到Game Controller物件之Score Text欄
執行測試,每擊破⼀個隕石增加10分
計算及顯示分數 4/4
50 Wu, ShyiShiou Dept. of E.E., NKUT
shap
e th
e fu
ture
選單命令GameObject> Create Empty,更名為Display Text
重置Transform
選單命令GameObject> Create Empty,更名為Restart Text
重置Transform
Position (X, Y, Z) = (1, 1, 0)
加入GUI Text元件
Anchor = Upper right
Alignment = Right
Pixel Offset (X, Y) = (-10, -10)
結束遊戲作業 1/7
51 Wu, ShyiShiou Dept. of E.E., NKUT
shap
e th
e fu
ture
選單命令GameObject> Create Empty,更名為Gameover Text
重置Transform
Position (X, Y, Z) = (0.5, 0.6, 0)
加入GUI Text元件
Anchor = Middle center
Alignment = Center
Pixel Offset (X, Y) = (0, 0)
將Score Text物件、Restart Text物件及Gameover Text物件拖曳到Display Text下成為子物件
結束遊戲作業 2/7
52 Wu, ShyiShiou Dept. of E.E., NKUT
shap
e th
e fu
ture
編輯GameController程式腳本using UnityEngine.SceneManagement;public class GameController : MonoBehaviour {
...private int score;public GUIText scoreText;public GUIText restartText;public GUIText gameOverText;private bool gameOver;private bool restart;
void Start (){
gameOver = false;restart = false;restartText.text = "";gameOverText.text = "";score = 0;...
}
結束遊戲作業 3/7
53 Wu, ShyiShiou Dept. of E.E., NKUT
shap
e th
e fu
ture
IEnumerator SpawnWaves (){
...yield return new WaitForSeconds (waveWait);if (gameOver){
restartText.text = "Press 'R' for Restart";restart = true;break;
}}
}
void UpdateScore (){
scoreText.text = "Score: " + score;}
結束遊戲作業 4/7
54 Wu, ShyiShiou Dept. of E.E., NKUT
shap
e th
e fu
ture
void Update (){
if (restart){
if (Input.GetKeyDown (KeyCode.R)){
SceneManager.LoadScene ("Main");}
}}
public void GameOver (){
gameOverText.text = "Game Over!";gameOver = true;
}}
結束遊戲作業 5/7
55 Wu, ShyiShiou Dept. of E.E., NKUT
shap
e th
e fu
ture
編輯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
56 Wu, ShyiShiou Dept. of E.E., NKUT
shap
e th
e fu
ture
拖曳Restart Text物件到Game Controller物件之Restart Text欄
拖曳Gameover Text物件到Game Controller物件之Game Over Text欄
執行測試,玩家戰機會顯示Game over!,等下⼀波隕石群時顯示Press 'R' for Restart,按下R鍵即可重玩
結束遊戲作業 7/7
57 Wu, ShyiShiou Dept. of E.E., NKUT