課程簡介
這是 2017 年在嘉南藥理大學開設的「Unity 程式設計基本課程」,用兩週密集課程帶領學生從 Unity 基礎程式設計到完整的多人連線遊戲開發。
課程特色
目標: 快速掌握 Unity 程式設計,並開發一個多人連線射擊遊戲
課程設計:
- 第一週:Unity C# 程式基礎(變數、流程控制、物件操作、輸入處理)
- 第二週:多人連線遊戲開發(Unity Networking 完整 12 步驟)
技術棧:
Unity + C# + Unity Networking (UNET) → 多人連線 FPS 遊戲
課程大綱
第一週 (4/17) ♥♥ - Unity 程式基礎
千里之行,始於足下
學習內容:
1. 變數與資料型別
string 字串、int 整數、bool 布林
// 要買的飲料是什麼,要幾杯,老闆帥不帥
public string 飲料 = "珍奶";
public int 幾杯 = 1;
public bool 老闆帥 = true;
實用場景:
- string:儲存文字、名稱、訊息
- int:儲存數字、計數、分數
- bool:儲存真假、狀態、開關
2. Debug.Log() 輸出訊息
// 將要顯示的句子用 + 組合起來一次顯示出來
Debug.Log("老闆我要" + 幾杯 + "杯" + 飲料);
用途:
- 除錯(Debug)
- 測試程式邏輯
- 追蹤變數值
3. if-else 條件判斷
// 如果老闆帥就要電話,如果不帥就叫老闆快點
if (老闆帥) {
Debug.Log("老闆可以給我你的電話號碼嗎");
} else {
Debug.Log("老闆可以快點嗎 我趕時間");
}
應用:
- 遊戲邏輯判斷
- 條件分支
- 狀態檢查
4. for 迴圈
// 睡不著的時候用迴圈數羊
Debug.Log("睡不著覺來數羊");
// 從第 1 隻開始數,要數到 1000 隻
for (int 第幾 = 1; 第幾 < 1000; 第幾++) {
Debug.Log(第幾 + "隻羊"); // 現在數到第幾隻
// 數到 300 隻就睡著了
if (第幾 == 300) {
Debug.Log("zzzZZZ"); // 睡著了
break; // 已經睡著就不再數了
}
}
應用:
- 重複執行程式碼
- 遍歷陣列
- 批次處理
5. transform.Translate() 物件移動
void OnGUI() {
// X 軸控制
if (GUI.Button(new Rect(10, 10, 200, 30), "X ++")) {
Target.transform.Translate(1, 0, 0); // 向 X 軸移動 1 單位
}
if (GUI.Button(new Rect(210, 10, 200, 30), "X --")) {
Target.transform.Translate(-1, 0, 0);
}
// 鍵盤控制
if (Input.GetKey(KeyCode.W)) {
Target.transform.Translate(0, 0, 1); // 向前移動
}
if (Input.GetKey(KeyCode.S)) {
Target.transform.Translate(0, 0, -1); // 向後移動
}
}
控制方式:
- GUI 按鈕控制
- 鍵盤控制(WASD)
- 三軸移動(X、Y、Z)
6. transform.Rotate() 物件旋轉
void OnGUI() {
// X 軸旋轉
if (GUI.Button(new Rect(10, 10, 200, 30), "X ++")) {
Target.transform.Rotate(10, 0, 0); // 以 X 軸旋轉 10 度
}
// Y 軸旋轉(左右轉)
if (GUI.Button(new Rect(10, 40, 200, 30), "Y ++")) {
Target.transform.Rotate(0, 10, 0);
}
// Z 軸旋轉(翻滾)
if (GUI.Button(new Rect(10, 70, 200, 30), "Z ++")) {
Target.transform.Rotate(0, 0, 10);
}
}
7. Input.GetMouseButtonDown() 滑鼠輸入
void Update() {
// 滑鼠按鍵檢測
if (Input.GetMouseButtonDown(0)) {
Debug.Log("按下滑鼠左鍵");
}
if (Input.GetMouseButtonDown(1)) {
Debug.Log("按下滑鼠右鍵");
}
// 滑鼠移動控制旋轉
// Input.GetAxis("Mouse X") 可以取得滑鼠左右移動的差值
Target.transform.Rotate(0, Input.GetAxis("Mouse X"), 0);
}
8. Input.GetKey() 鍵盤輸入
void Update() {
// 方向鍵檢測
if (Input.GetKey(KeyCode.UpArrow)) {
print("向上方向鍵被按了");
}
if (Input.GetKey(KeyCode.DownArrow)) {
print("向下方向鍵被按了");
}
}
9. 完整的角色控制器
PlayerController.cs
void Update() {
// 鍵盤控制移動
if (Input.GetKey(KeyCode.W)) {
gameObject.transform.Translate(0, 0, .1f);
}
if (Input.GetKey(KeyCode.S)) {
gameObject.transform.Translate(0, 0, -.1f);
}
if (Input.GetKey(KeyCode.D)) {
gameObject.transform.Translate(.1f, 0, 0);
}
if (Input.GetKey(KeyCode.A)) {
gameObject.transform.Translate(-.1f, 0, 0);
}
// 鍵盤控制旋轉
if (Input.GetKey(KeyCode.E)) {
gameObject.transform.Rotate(0, 10, 0);
}
if (Input.GetKey(KeyCode.Q)) {
gameObject.transform.Rotate(0, -10, 0);
}
// 滑鼠控制旋轉
gameObject.transform.Rotate(0, Input.GetAxis("Mouse X") * 10, 0);
}
功能:
- WASD 控制移動
- QE 控制旋轉
- 滑鼠控制視角
第二週 (4/24) ♥♥♥ - 多人連線遊戲開發
從基礎到實戰
完整的多人連線 FPS 遊戲開發(12 步驟)
步驟 1-3:專案設定與玩家建立
- 建立新專案
- 開新 3D 專案
- 建立 Main 場景
- 設定 NetworkManager
- 建立空物件命名為 NetworkManager
- 加入 NetworkManager 組件
- 加入 NetworkManagerHUD 組件
- 建立 Player Prefab
- 製作 Player 遊戲物件
- 加入 NetworkIdentity 組件
- 勾選 Local Player Authority
- 將 Player Prefab 加入 NetworkManager/Spawn Info
步驟 4-6:玩家移動與同步
- 基本玩家控制
using UnityEngine;
public class PlayerController : MonoBehaviour
{
void Update()
{
var x = Input.GetAxis("Horizontal") * Time.deltaTime * 150.0f;
var z = Input.GetAxis("Vertical") * Time.deltaTime * 3.0f;
transform.Rotate(0, x, 0); // 旋轉
transform.Translate(0, 0, z); // 前進後退
}
}
- 網路同步移動
using UnityEngine;
using UnityEngine.Networking;
public class PlayerController : NetworkBehaviour
{
void Update()
{
if (!isLocalPlayer) {
return; // 只控制自己的角色
}
var x = Input.GetAxis("Horizontal") * Time.deltaTime * 150.0f;
var z = Input.GetAxis("Vertical") * Time.deltaTime * 3.0f;
transform.Rotate(0, x, 0);
transform.Translate(0, 0, z);
}
}
- 加入 NetworkTransform 組件
- 實現位置同步
- 區分本地玩家
public override void OnStartLocalPlayer()
{
GetComponent<MeshRenderer>().material.color = Color.blue;
}
步驟 7-9:射擊系統
- 建立子彈
- 建立 Bullet 遊戲物件
- 加入 Rigidbody
- 製作成 Prefab
- 本地射擊
public GameObject bulletPrefab;
public Transform bulletSpawn;
void Update()
{
// ... 移動程式碼 ...
if (Input.GetKeyDown(KeyCode.Space)) {
Fire();
}
}
void Fire()
{
var bullet = (GameObject)Instantiate(
bulletPrefab,
bulletSpawn.position,
bulletSpawn.rotation);
bullet.GetComponent<Rigidbody>().velocity = bullet.transform.forward * 6;
Destroy(bullet, 2.0f);
}
- 網路同步射擊
void Update()
{
// ... 移動程式碼 ...
if (Input.GetKeyDown(KeyCode.Space)) {
CmdFire(); // 改用 Command
}
}
[Command] // 在 Client 呼叫,在 Server 執行
void CmdFire()
{
var bullet = (GameObject)Instantiate(
bulletPrefab,
bulletSpawn.position,
bulletSpawn.rotation);
bullet.GetComponent<Rigidbody>().velocity = bullet.transform.forward * 6;
// 在所有 Client 上生成子彈
NetworkServer.Spawn(bullet);
Destroy(bullet, 2.0f);
}
- Bullet 加入 NetworkIdentity
- Bullet 加入 NetworkTransform
- 將 Bullet Prefab 加入 Registered Spawnable Prefabs
步驟 10-12:血量系統與死亡重生
- 碰撞與血量
Bullet.cs
void OnCollisionEnter(Collision collision)
{
var hit = collision.gameObject;
var health = hit.GetComponent<Health>();
if (health != null) {
health.TakeDamage(10);
}
Destroy(gameObject);
}
Health.cs
public const int maxHealth = 100;
public int currentHealth = maxHealth;
public RectTransform healthBar;
public void TakeDamage(int amount)
{
currentHealth -= amount;
if (currentHealth <= 0) {
currentHealth = 0;
Debug.Log("Dead!");
}
healthBar.sizeDelta = new Vector2(currentHealth, healthBar.sizeDelta.y);
}
Billboard.cs(血條面向鏡頭)
void Update()
{
transform.LookAt(Camera.main.transform);
}
- 網路同步血量
using UnityEngine.Networking;
public class Health : NetworkBehaviour
{
public const int maxHealth = 100;
[SyncVar(hook = "OnChangeHealth")] // 同步變數
public int currentHealth = maxHealth;
public RectTransform healthBar;
public void TakeDamage(int amount)
{
if (!isServer) // 只在 Server 處理
return;
currentHealth -= amount;
if (currentHealth <= 0) {
currentHealth = 0;
Debug.Log("Dead!");
}
}
void OnChangeHealth(int health)
{
// 當血量改變時更新 UI
healthBar.sizeDelta = new Vector2(health, healthBar.sizeDelta.y);
}
}
- 死亡與重生
public void TakeDamage(int amount)
{
if (!isServer)
return;
currentHealth -= amount;
if (currentHealth <= 0) {
currentHealth = maxHealth;
// 在 Server 呼叫,在所有 Client 執行
RpcRespawn();
}
}
[ClientRpc]
void RpcRespawn()
{
if (isLocalPlayer) {
// 回到起始位置
transform.position = Vector3.zero;
}
}
技術亮點
1. Unity Networking (UNET) 架構
Client-Server 模型:
Client 1 ←→ Server ←→ Client 2
關鍵組件:
- NetworkManager:管理網路連線
- NetworkIdentity:識別網路物件
- NetworkBehaviour:網路腳本基類
- NetworkTransform:同步位置與旋轉
2. 網路同步機制
[Command]:Client → Server
[Command]
void CmdFire() {
// 在 Client 呼叫,在 Server 執行
}
[ClientRpc]:Server → All Clients
[ClientRpc]
void RpcRespawn() {
// 在 Server 呼叫,在所有 Client 執行
}
[SyncVar]:自動同步變數
[SyncVar(hook = "OnChangeHealth")]
public int currentHealth;
3. 權威性設計
Server Authority(伺服器權威):
- 血量在 Server 計算
- 防止作弊
- 確保一致性
Local Player Authority(本地玩家權威):
- 移動由 Client 控制
- 減少延遲
- 提升手感
4. 完整的遊戲功能
玩家移動 → 射擊 → 子彈飛行 → 碰撞偵測 → 扣血 → 血條更新 → 死亡重生
每個環節都考慮網路同步!
課程特色
1. 循序漸進
第一週:打好 C# 基礎 第二週:實戰多人遊戲
從單機到聯機,步步為營。
2. 實作導向
- 不只講理論,每個概念都有程式碼範例
- 配有影片示範
- 可下載完整專案
3. 趣味教學
用生活化的例子教程式:
- 買飲料 → 變數
- 數羊 → 迴圈
- 老闆帥不帥 → 條件判斷
4. 令人興奮的進度
用 ♥ 符號表示課程的刺激程度:
- 第一週 ♥♥:基礎但有趣
- 第二週 ♥♥♥:超級刺激的多人遊戲!
學習成果
學生將學會:
- Unity C# 基礎
- 變數與資料型別
- 流程控制(if、for)
- 物件操作(移動、旋轉)
- 輸入處理(鍵盤、滑鼠)
- Unity Networking
- Client-Server 架構
- NetworkManager 設定
- 網路物件同步
- Command/ClientRpc/SyncVar
- 完整遊戲開發
- 角色控制系統
- 射擊系統
- 血量與 UI 系統
- 死亡與重生機制
- 多人遊戲設計思維
- 權威性設計
- 作弊防護
- 網路延遲處理
- 狀態同步
課程資源
完整教學網站: https://yazelin.github.io/cnu2017/
GitHub 開源專案: https://github.com/yazelin/cnu2017
範例下載:
參考資源:
相關課程:
課程資訊
- 學校:嘉南藥理大學
- 課程名稱:Unity 程式設計基本課程
- 授課時間:2017 年 4 月(共 2 週密集課程)
- 授課教師:Hungsiu
- 教案協作:Yaze Lin
- 專案主題:多人連線 FPS 射擊遊戲
- 技術棧:Unity + C# + UNET
- 特色:從零基礎到多人遊戲、趣味化教學、完整 12 步驟實作