Unity3D&Photon製做吃雞遊戲

Unity3D&Photon製做吃雞遊戲

https://study.163.com/course/courseMain.htm?share=1&shareId=8348227&courseId=1004507022html

11 -- 程序書寫規範

變量名使用Camel駝峯命名法:首字母小寫,其他單詞首字母大寫canvas

命名空間、類和方法名使用Pascal命名法:全部單詞首字母大寫服務器

若是使用到英文單詞的縮寫,則所有大寫:如ID網絡

20 -- 使用Unity3D製做吃雞手遊的準備工做

https://dashboard.photonengine.com/zh-cndom

爲何使用Photon服務器
  Photon服務器能夠很好地實現遊戲房間的匹配功能 -- 
    進入匹配房間,若是沒有匹配的房間,則新建房間
    當房間人數達到必定數量,則會開始遊戲ide

Photon插件:AssetStore下載:全名 Photon Unity Networking Classic - Free
  Import以後,會彈出一個設置嚮導(嚮導也可經過Window->PhotonUnityNeworking->PUN Wizard打開)
  點擊Cloud DashBoard Login後打開網頁,登陸優化

建立Photon App:
  1. Photon Type = Photon PUN
  2. 點擊建立動畫

將建立好的App的AppID複製,在Unity->Window->PhotonUnityNetworking->PUN Wizard中點擊Locate PhotonServerSettings,
  Hosting選擇PhotonCloud (這裏Region選擇Jp,延遲比較低),粘貼AppID,Protocol選擇Udp
  Client Settings的Pun Logging選擇Fullui

經過代碼與光子服務器進行鏈接this

https://doc.photonengine.com/en-us/pun/current/getting-started/pun-intro

命名空間Photon.PunBehaviour,PhotonNetwork.ConnectUsingSettings(string gameVersion)
  參數gameVersion表示客戶端版本號,用於分離版本

建立腳本Network.cs,在Awake()中寫上PhotonNetwork.ConnectUsingSettings("0.0.1");
  將腳本掛載空遊戲物體上,運行

與服務器鏈接成功

地形系統 -- Terrain

新建Terrain,發現沒有所需的地形Texture

從商店導入Standard Assets素材包
人物模型https://pan.baidu.com/s/1An5Ro8pHMdjgnPAoBoRA_A,nubq

Terrain建立 -- 略

使用Standard Assets中的RigidBodyFPSController,用於控制人物,並導入人物模型(RifleAnimsetPro--Models) -- 略

21 -- 實現武器基本功能並添加特效
22 -- 完善射擊功能

將槍模型RiflePlaceholder放在人物模型手上,並經過Animator實現動做

開槍功能 -- 在主角模型上掛腳本WeaponController.cs
  1. 當前彈夾中子彈數;2. 開槍動做播放;3. 開槍音效播放;4. Update()中檢測是否按下鼠標左鍵
  5. 彈夾中子彈數不足則卡殼,播放卡殼音效

換彈夾功能 --
  1. Update()中檢測是否按下R鍵;2. 子彈總數和當前彈夾中子彈數;3. 換彈動做播放;4. 換彈音效播放

開鏡功能 -- 
  Camera的Field of View參數 即爲開鏡效果
  準星 -- canvas - image實現

槍口特效 --
  RifleAnimsetPro/Particles/MuzzleFlash
  放置於槍口,並在Shoot中控制播放
  private ParticleSystem m_ShootFx;
  m_ShootFx.Play();

射擊功能的彈道 -- 
  暫時使用射線的方式實現射擊功能,以後會優化改成真正的有彈道的子彈

// 射線模擬子彈
RaycastHit raycastHit;
    if (Physics.Raycast(m_MainCamera.transform.position, m_MainCamera.transform.forward, out raycastHit, m_FarestBulletReachableDistance)) {
    Debug.LogError(raycastHit.transform + " Shot");
}

22 -- 實現匹配功能

匹配UI -- 在net scene下製做一個按鈕,實現點擊加入房間的功能

加入房間相關Photon API:

讓Net.cs類繼承自Photon.PunBehaviour

加入房間: PhotonNetwork.JoinRandomRoom();

加入房間失敗時的回調方法: OnPhotonRandomJoinFailed(object[] codeAndMsg) {}

建立房間: PhotonNetwork.CreateRoom(string roomName);

// 建立或加入房間:  PhotonNetwork.JoinOrCreateRoom(string roomName);
  沒有試過該API,無需知道該名字的房間是否已經存在,加入或建立它

建立房間成功的回調方法: OnCreatedRoom() {}

加入房間成功時的回調方法: OnJoinedRoom() {}

有玩家成功鏈接入/加入房間的回調方法: OnPhotonPlayerConnected(PhotonPlayer newPlayer) {}

房間內全部玩家是否同步場景的屬性: PhotonNetwork.automaticallySyncScene == true;
   // defines if all clients in a room should load the same level/scene as the Master client (if that used PhotonNetwork.LoadLevel())

判斷當前客戶端是否爲主機(房主): PhotonNetwork.isMasterClient

加載場景: PhotonNetwork.LoadLevel(string sceneName); // 好比 PhotonNetwork.LoadLevel("main");

如何使用:

1. 將以前在Start()中調用的PhotonNetwork.ConnectUsingSettings("0.0.1"); 註冊到一個Connect按鈕上,成功鏈接上服務器後才啓用加入房間的按鈕
2. 通常來講,隨機加入房間若是失敗時,則自動建立一個房間
3. 一個房間中玩家數足夠時,則自動開始遊戲
4. 只有主機才須要加載場景,其餘的客戶端須要設置同步場景屬性

功能擴展: 能夠再加一個DropDown來控制房間玩家數(Photon好像最大支持20人同房)

24 -- 玩家位置同步

一場遊戲裏有不少個玩家,這些玩家的模型和位置都須要同步,所以一個場景中須要多個士兵模型

玩家腳本 -- PlayerUnit.cs

1. 若是玩家不是客戶端控制的玩家,則不須要開啓模型中的攝像機
2. 不是客戶端控制的玩家,有一些組件是不須要開啓的(包括1中的攝像機、角色控制腳本、武器腳本)
3. 有關同步的API: PhotonView

PlayerUnit腳本掛載角色身上,角色作成一個預製體,用於動態生成

用於管理生成玩家的腳本 -- PlayerGenerator.cs

在遊戲場景main scene加載完成時,進行玩家的生成(PhotonNetwork.Instantiate())
-- SceneManager.sceneLoaded 註冊事件

PhotonNetwork.Instantiate(string prefabName, Vector3 position, Quaternion rotation, byte group)
  -- 經過網絡,實例化預製體,預製體須要位於Resources文件夾下
  -- group指的是PhotonView的組,填0便可

在須要經過Photon進行網絡同步的物體(角色)上掛載腳本

PhotonView

PhotonTransformView
  勾選須要同步的參數

須要將PhotonTransformView賦值給PhotonView的OvservedComponents屬性中
  表示須要監測並同步PhotonTransformView中勾選的參數

將角色作成預製體,並在場景中刪除

25 -- 同步傷害信息

玩家血量屬性存在玩家信息類PlayerInfo中
  public int hp = 100;

在武器腳本中存放傷害值
  public int damage = 50;

在開火射線檢測代碼塊斷定是否打中玩家
  思路: 經過玩家tag判斷
  若是是玩家,則玩家受傷,並須要將傷害同步給全部客戶端

API: PhotonView.RPC(string methodName, PhotonPlayer targetPlayer, params object[] parameters)
  Call a RPC method of this GameObject on remote clients of this room( or on all, including this client)
  methodName: 調用的同步方法名 the name of the fitting method which has the RPC attribute
  targetPlayer: 須要同步給的客戶端The group of targets and the way RPC gets sent
  parameters: 傳入methodName方法的參數

if(raycastHit.collider.tag == "Player") {
    // 打到角色
    PhotonView pv = raycastHit.collider.GetComponent<PhotonView>();
    pv.RPC("GetDamage", PhotonTargets.All, m_Damage);
}

注意,在定義methodName方法時,須要寫上 [PunRPC] -- 表示這是一個須要被實時同步的方法
  並繼承自接口 IPunObserable的OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info) {}
  在該方法中進行血量的同步

[PunRPC]
public void GetDamage(int damage) {
    m_CurrentHp -= damage;
}

public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info) {
    // 會發送給全部客戶端(包括本身)
    if (stream.isWriting) {
        // 若是是寫入者
        stream.SendNext(this.m_CurrentHp);
    } else {
        // 若是是讀出者
        this.m_CurrentHp = (int)stream.ReceiveNext();
    }
}

26 -- 血條的繪製

在Player預製體下建立Canvas,用Slider製做簡易血條(玩家本身的血條,位於屏幕下方)

血條的屏幕分辨率自適應
  將Canvas Scaler的UI Scale Mode設爲Scale With Screen Size
  將Match調至Height處,表示以Height爲基礎作的自適應

在PlayerInfo中控制Slider的value值
  在同步方法中同步Slider的value值

[PunRPC]
    m_CurrentHp -= damage;
    if(m_CurrentHp < 0) {
        m_CurrentHp = 0;
    }
    slider.value = m_CurrentHp;
}

public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info) {
    // 會發送給全部客戶端(包括本身)
    if (stream.isWriting) {
        // 若是是寫入者
        stream.SendNext(this.m_CurrentHp);
        stream.SendNext(this.slider.value);
    } else {
        // 若是是讀出者
        this.m_CurrentHp = (int)stream.ReceiveNext();
        this.slider.value = (int)stream.ReceiveNext();
        // 我的理解這裏沒必要再傳slider.value的值,直接在m_CurrentHp變化的地方觸發更新便可
    }
}

視頻教程中用的方法是:每個PlayerUnit都配備一個Slider用於顯示血條,可是在PlayerUnit的ComponentsToBeHiden的列表里加上該血條
  我的作法: 只作一個血條Slider,用PhotonView.isMine判斷是否爲本客戶端的玩家,在PlayerManager(做爲總控制(全部玩家)的腳本中)控制HpBar

27 -- IK的使用

https://docs.unity3d.com/Manual/InverseKinematics.html ---- 看一看,介紹地很詳細
https://www.jianshu.com/p/7aec3699f29c -- 很詳細

完善玩家視角變更(擡高壓低)時,槍口跟隨的功能

IK: Inverse Kinematics -- 反向動力學

通常而言,多數動畫是經過旋轉骨架關節的角度來實現的,這個稱爲 Forward Kinematics

而IK指的是從下至上的驅動
  是根據骨骼的終節點來推算其餘父節點的位置的一種方法。好比經過手的位置推算手腕、胳膊肘的骨骼的位置。
  好比設置好手部位置後,角色起身時手部會保持不動,而小臂和大臂的古河會自動旋轉到合適角度;
  或者在腳步踩入地面的行走動畫(沼澤地)狀況下,在運行時經過調整IK target來實現角色在不平坦的地面行走的效果
  僅支持配置正確的人形角色 (supported in Mecanim for any humanoid character with a correctly configured Avatar)

人形角色指的是:預製體設置的Rig->AnimationType=Humanoid

IK的使用

1. 人物模型預製體的設置Rig->AnimationType設爲Humanoid

設置爲Humanoid後,會自動在人物模型預製體下生成一我的物骨骼CharacterAvatar

2. 在動畫控制器中的Layers Pane勾選IK

3. 在Animator組件中勾選Apply Root Motion

To set up IK for a character, you typically have objects around the scene that a character interacts with, and then set up the IK through script, in particular, Animator functions like SetIKPositionWeight, SetIKRotationWeight, SetIKPosition, SetIKRotation, SetLookAtPositionbodyPositionbodyRotation

4. Animator組件中的Avatar賦值爲Humanoid自動生成的CharacterAvatar(這一步驟作了以後 播放動畫的問題很大)

5. 實現IK實際功能的腳本 (即https://docs.unity3d.com/Manual/InverseKinematics.html 中的案例)

using UnityEngine;
using System;
using System.Collections;

[RequireComponent(typeof(Animator))]
public class IKController : MonoBehaviour {

    protected Animator animator;

    public bool ikActive = false;
    public Transform rightHandObj = null;
    public Transform lookObj = null;

    void Start() {
        animator = GetComponent<Animator>();
    }

    //a callback for calculating IK
    void OnAnimatorIK() {
        if (animator) {

            //if the IK is active, set the position and rotation directly to the goal. 
            if (ikActive) {
            
            // 這裏只須要用到這個
            // Set the look target position, if one has been assigned
                if (lookObj != null) {
                    // 看向某個目標點時,各個部位旋轉的比重
                    animator.SetLookAtWeight(1, 1, 1, 1);
                    animator.SetLookAtPosition(lookObj.position);
                }

                // Set the right hand target position and rotation, if one has been assigned
                if (rightHandObj != null) {
                    animator.SetIKPositionWeight(AvatarIKGoal.RightHand, 1);
                    animator.SetIKRotationWeight(AvatarIKGoal.RightHand, 1);
                    animator.SetIKPosition(AvatarIKGoal.RightHand, rightHandObj.position);
                    animator.SetIKRotation(AvatarIKGoal.RightHand, rightHandObj.rotation);
                }
            }
            //if the IK is not active, set the position and rotation of the hand and head back to the original position
            else {
                animator.SetIKPositionWeight(AvatarIKGoal.RightHand, 0);
                animator.SetIKRotationWeight(AvatarIKGoal.RightHand, 0);
                animator.SetLookAtWeight(0);
}}}}

 

6. 在Player下的Camera下建立空物體(由於須要看向的位置是與攝像機有關的,因此做爲Camera的子物體)
  將該空物體放置在Camera前的合適位置,做爲槍口指向的點
  並將該空物體賦值給IKController.lookObj

28 -- 使用WheelCollider控制車輛

素材: pan.baidu.com/wap/init?surl=sA1JWo39facMXIa4M97dFQ 5n1b

導入模型,並加上四個WheelCollider
  注:使用WheelCollider的必要條件是,父物體上須要掛有RigidBody組件

相關文章
相關標籤/搜索