當前Unity最新版本5.6.3f1,我使用的是5.5.1f1
場景搭建
1: 導入人物模型, 手持一把槍;
2: 導入碎片模型;
3: 創建一個平面;
4: 創建一個障礙物;
5: 導入人物模型;
6: 配置一個十字瞄准器, 設想機對准的中心就是瞄准的中心, 屏幕中心;
7: 配置一個第一人稱的攝像機,做人的眼睛;
開槍射擊
1: 鼠標左鍵按下開火;
2: 從攝像機位置開始,根據攝像機的正前方,在一定的射擊距離內如果碰撞到了某個物體,那么就表示這個物體被子彈射擊;
3: 播放射擊的子彈碎片: 在射擊碰撞點產生碎片物體,並給這些碎片物體一個反彈力(100)
方向是設計的反方向;
4: Physics.Raycast (起點, 發射方向向量, out hit, 最大有效距離(子彈的距離));
5: 播放開槍的聲音
PlayOneShot 固定時間播完你的audio文件;
用PlayOneShot,就能夠聽到連續的槍聲了;
人物的行走
1: 定義移動的速度;
2: 玩家在自己的坐標空間內向前(W),后(S), 左(A), 右(D);
3: 前后 Translate(0, 0, 距離, Space.Self);
4: 左右 Translate(距離, 0, 0, Space.Self);
5: 播放人物行走的腳步聲;
槍的上下左右移動
1: 槍的左右瞄准;
(1)計算繞Y軸旋轉的歐拉角 GetAxis(Mouse Y) * 移動的靈敏度(5);
(2)在轉向的時候,角色也要跟着轉,那么我們旋轉角色就可以了, 攝像機也會跟着轉;
修改角色的歐拉角;
(3) 給這個槍繞Y軸一個轉動范圍[-360, 360]
(4) Mathf.Clamp(value, min, max);
2: 槍的上下移動: localEulerAngles
(1)槍的上下移動,角色不動,所以不能作用到角色上;
(2)計算繞X軸旋轉的歐拉角 GetAxis(Mouse X) * 移動的靈敏度(5);
(3)只作用在瞄准上(eye),所以修改攝像機的相對歐拉角;
(4)同時手中的槍也要移動對應的歐拉角(right hand);
(5)抬頭低頭有一個范圍,我們在[-45, 45]的范圍內;
FPS第一人稱射擊類游戲實例
場景搭建
1.創建Unity項目工程和文件目錄,保存場景
2.導入人物模型和子彈碎片的資源包charactor.unitypackage(第74)
3.創建一個平面plane,X和Z拉長10倍,把主角模型Assets\Prefabs\person拉近Hierarchy視圖中
4.把走路的聲音Step.mp3,射擊的聲音Shot.mp3,平面貼圖Ground.jpg,瞄准的准心貼圖Crosshairs.png(第74)導入Resources文件夾
5.把Crosshairs.png直接拖進Scene視圖的平面plane上,自動幫我們生成了平面的材質並關聯
6.創建一個cube,放大4倍,放在主角模型的正前方,Z設置為20
7.配置一個十字瞄准器,UI和攝像機是成比例對應的,所以攝像機對准的中心就是等下Crosshairs瞄准的中心,屏幕中心,右鍵---->UI---->Image,命名為Crosshairs,再把Crosshairs.png的Texture Type設置為Sprite(2D and UI)
8.把Crosshairs.png拖進Crosshairs節點的Image組件的Source Image屬性中,調整Crosshairs的position為(0,0,0),屏幕正中央,Set Native Size
9.配置一個第一人稱的攝像機,做人的眼睛。就是把Main Camera拖到person下作為子節點重命名為eye,這樣可以跟隨主角移動。然后把攝像機的position設置為(0,1.5,0),正好在人物頭部
開始射擊
10.創建一個腳本open_fire,掛載在person節點下,里面實現開槍的邏輯。
a.查看虛擬按鍵Edit---->Project Settings---->Input---->Fire1---->mouse 0,可以用Fire1代替mouse 0
b.槍口噴火的特效如果噴的很分散的話,可以設置FireEffect---->Particle System---->Shape---->Shape---->類型設置為Mesh
c.FPS游戲通常不會有真的子彈在天空中飛來飛去的,一般是用射線實現,有真的子彈也沒關系。
打開open_fire.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; public class open_fire : MonoBehaviour { private ParticleSystem fire_effect;//特效節點person---->RightHand---->FireEffect public Transform eye;//攝像機節點person---->eye public GameObject piece;//碎片節點Prefabs文件夾---->piece //開槍的聲音Resources文件夾---->Shot,給person節點加一個AudioSource組件,關聯Shot,再把person節點拖進shoot_audio //AudioSource組件的Play On Awake打鈎去掉 public AudioSource shoot_audio; // Use this for initialization void Start () { //獲得開槍的特效節點的特效組件 this.fire_effect = this.transform.Find("RightHand/Pistol/FireEffect").GetComponent<ParticleSystem>(); } //生成槍打到障礙物的碎片特效 void play_pieces_effect(Vector3 w_pos) { for (int i = 0; i < 5; i++) { GameObject p = GameObject.Instantiate(this.piece); p.transform.position = w_pos;//坐標設置在碰撞點處 p.GetComponent<Rigidbody>().AddForce(-this.eye.forward * 100f);//撞到后給一個反彈力,方向和攝像機Z軸方向相反 // Component.Destroy(p, 0.3f);//在0.3s后刪除該組件 // MonoBehaviour.Destroy(p, 0.3f);//在0.3s后刪除該組件,MonoBehaviour繼承自Component //Destroy(p, 0.3f);//在0.3s后刪除節點,調用靜態函數Destroy GameObject.Destroy(p, 0.3f);//在0.3s后刪除節點 } } //槍發射子彈 void shoot_fire() { // 播放槍發射子彈時槍口的火焰 this.fire_effect.Play(); // end // 播放開槍的聲音 // this.shoot_audio.Play();//用這個播的比較慢,因為shot聲音前面一小段沒聲音 this.shoot_audio.PlayOneShot(this.shoot_audio.clip);//用這個播的速度比較快,固定時間內播完 // end // 當前這個槍打到了哪個物體 // 從這個eye的位置,沿着eye的前方方向,發射一條射線,看誰被命中了 // 子彈是有有效的射擊距離的,所以,我們要傳入子彈的最大距離 RaycastHit hit; // 命中的對象 //從this.eye.position出發,向this.eye.forward(Z軸方向)發射一條射線,撞到hit,射線最大距離100米,最后再out帶出hit命中對象 if (Physics.Raycast(this.eye.position, this.eye.forward, out hit, 100)) { // 槍打到了哪個障礙物,打印出障礙物的碰撞器的節點的名字 Debug.Log(hit.collider.gameObject.name); // .....其他的游戲邏輯 // end // 播放這個碎片特效,傳入碰撞點世界坐標hit.point this.play_pieces_effect(hit.point); // end } // end } // Update is called once per frame void Update () { //鼠標左鍵按下,在Edit---->Project Settings---->Input里面有配置Fire1就是鼠標左鍵mouse 0 if (Input.GetButtonDown("Fire1")) { this.shoot_fire(); } } }
人物的行走
11.創建一個腳本fps_ctrl,掛載在person節點下,里面實現控制人物行走的邏輯。
a.因為第10步的時候,person已經掛載了一個AudioSource組件,里面放的是開槍的聲音,所以我們如果要再加一個AudioSource組件,放走路的聲音,會導致隨機選取兩個聲音之中的一個聲音調用
解決方法是,可以在person的子節點下面掛載一個AudioSource組件,關聯走路的聲音,這樣節點不同,關聯的AudioSource組件也不同
打開fps_ctrl.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; public class fps_ctrl : MonoBehaviour { public float move_speed = 8.0f;//定義人物移動的速度 //開槍的聲音Resources文件夾---->Step,給person的子節點加一個AudioSource組件,關聯Step,再把person的這個子節點拖進walk_audio //AudioSource組件的Play On Awake打鈎去掉 public AudioSource walk_audio = null; // Use this for initialization void Start () { } // 單獨分支出一個update函數用來做移動,W(上)S(下)A(左)D(右) bool walk_update() { if (Input.GetKey(KeyCode.W)){//GetKey一直按下,一直觸發,GetKeyDown是按下再彈起來一次,觸發一次 float distance = this.move_speed * Time.deltaTime;//1秒移動的距離 this.transform.Translate(0, 0, distance, Space.Self);//沿着自己的空間,也就是本地坐標移動,不是世界坐標 return true; } if (Input.GetKey(KeyCode.S)) { float distance = this.move_speed * Time.deltaTime; this.transform.Translate(0, 0, -distance, Space.Self); return true; } if (Input.GetKey(KeyCode.A)) { float distance = this.move_speed * Time.deltaTime; this.transform.Translate(-distance, 0, 0, Space.Self); return true; } if (Input.GetKey(KeyCode.D)) { float distance = this.move_speed * Time.deltaTime; this.transform.Translate(distance, 0, 0, Space.Self); return true; } return false; } // Update is called once per frame void Update () { // 如果有移動,就播放行走的聲音 if (this.walk_update()) { if (!this.walk_audio.isPlaying) {//正在播放就不要再播了,減少調用 this.walk_audio.Play();//不是正在播放就播一下 } } } }
槍的上下左右移動
12.打開腳本fps_ctrl,寫好槍的上下左右移動的邏輯,就是鼠標轉動的時候,攝像機也會跟着轉動,整個畫面就跟着轉動
a.限制值的范圍的函數Mathf.Clamp(value, min, max);
b.父節點控制世界的歐拉角的時候,子節點控制本地的歐拉角,讓引擎幫我們把本地轉換為世界,不會亂
打開腳本fps_ctrl
using System.Collections; using System.Collections.Generic; using UnityEngine; public class fps_ctrl : MonoBehaviour { public float move_speed = 8.0f;//定義人物移動的速度 //開槍的聲音Resources文件夾---->Step,給person的子節點加一個AudioSource組件,關聯Step,再把person的這個子節點拖進walk_audio //AudioSource組件的Play On Awake打鈎去掉 public AudioSource walk_audio = null; float rot_y = 0.0f;//繞主角自身y軸總共旋轉了多少 float rot_x = 0.0f;//繞主角自身x軸總共旋轉了多少 public Transform eye = null;//獲得攝像機節點 public Transform right_hand = null;//握槍的手的節點 public float sensibility = 5.0f; //靈敏度,鼠標移動多少像素和旋轉轉動多少角度的單位比例 // Use this for initialization void Start () { } // 單獨分支出一個update函數用來做移動,W(上)S(下)A(左)D(右) bool walk_update() { if (Input.GetKey(KeyCode.W)){//GetKey一直按下,一直觸發,GetKeyDown是按下再彈起來一次,觸發一次 float distance = this.move_speed * Time.deltaTime;//1秒移動的距離 this.transform.Translate(0, 0, distance, Space.Self);//沿着自己的空間,也就是本地坐標移動,不是世界坐標 return true; } if (Input.GetKey(KeyCode.S)) { float distance = this.move_speed * Time.deltaTime; this.transform.Translate(0, 0, -distance, Space.Self); return true; } if (Input.GetKey(KeyCode.A)) { float distance = this.move_speed * Time.deltaTime; this.transform.Translate(-distance, 0, 0, Space.Self); return true; } if (Input.GetKey(KeyCode.D)) { float distance = this.move_speed * Time.deltaTime; this.transform.Translate(distance, 0, 0, Space.Self); return true; } return false; } // 單獨分支出一個update函數用來做槍的上下左右轉動 void look_update() { // 左右移動,主角繞自身Y軸左右旋轉,在Edit---->Project Settings---->Input里面有配置Mouse X,鼠標在屏幕的X軸上移動的量 //兩者相乘獲得一個旋轉的角度 this.rot_y += (Input.GetAxis("Mouse X") * sensibility); // 給這個旋轉一個范圍[-360, 360],之間的用this.rot_y,小於-360返回-360,超過360返回360 this.rot_y = Mathf.Clamp(this.rot_y, -360, 360); //獲得當前節點世界的歐拉角 Vector3 e_rot = this.transform.eulerAngles; //設置當前節點的歐拉角的y為我們變化后的y e_rot.y = this.rot_y; //賦值給原歐拉角 this.transform.eulerAngles = e_rot; // end // 上下旋轉, 攝像機繞自身X軸上下旋轉,在Edit---->Project Settings---->Input里面有配置Mouse Y,鼠標在屏幕的Y軸上移動的量 //往上抬時旋轉的角度為負,這樣旋轉的時候是逆時針,這樣攝像機就向上抬 this.rot_x -= (Input.GetAxis("Mouse Y") * sensibility); //范圍[-45, 45],之間的用this.rot_y,小於-45返回-45,超過45返回45 this.rot_x = Mathf.Clamp(this.rot_x, -45, 45); //獲得攝像機節點的本地的歐拉角,因為它是person的子節點,左右移動時person已經控制了世界的歐拉角,這里只能控制本地歐拉角 //如果用世界的歐拉角還要疊加父親的歐拉角的旋轉,我認為用世界坐標會導致減去父節點的世界坐標后得到的原來的本地坐標亂了 //用本地的歐拉角,讓引擎幫我們轉成世界的歐拉角,不會亂 e_rot = this.eye.localEulerAngles; //設置當前節點的歐拉角的x為我們變化后的x e_rot.x = this.rot_x; //賦值給原歐拉角 this.eye.localEulerAngles = e_rot; // end //握槍的手也要跟着攝像機轉動 e_rot = this.right_hand.localEulerAngles; e_rot.x = this.rot_x; this.right_hand.localEulerAngles = e_rot; // end } // Update is called once per frame void Update () { // 如果有移動,就播放行走的聲音 if (this.walk_update()) { if (!this.walk_audio.isPlaying) {//正在播放就不要再播了,減少調用 this.walk_audio.Play();//不是正在播放就播一下 } } this.look_update(); } }
13.最終效果