天天看點

unity應用執行個體——從頭撸一個全新的FPS遊戲(1)

從今天開始打算從頭開始搞一個全新的FPS遊戲,引擎使用的是unity2017,所有美術資源都計劃從商店購買或是網上下載下傳(自己實在搞不來),主要玩法是玩家需要穿越多個地圖到達目的地,每個地圖類似于一個地牢,規定時間到地牢的門才會打開,在這之前玩家會遇到怪物追殺和哨兵把守,玩家突破重重阻礙直到逃出最後一個地牢即可勝利。在這裡主要是做一下記錄和總結,有不足之處希望指正。

這一篇是整個系列的準備工作。首先建立一個空物體作為玩家控制的對象,添加一個Character Controller元件。然後需要添加一個玩家控制角色的腳本,主要負責使玩家視角跟随對象,并控制角色移動、跳躍等。這種腳本比較熟悉了,因為是fps遊戲,不用給玩家自己設定動畫,是以我手撸了一個簡易版的,效果滿意。下面是腳本代碼。

using System;
using UnityEngine;
public class player : MonoBehaviour
{
    [HideInInspector]
    public float m_mouserate;//滑鼠靈敏度

    [SerializeField]
    private float m_gravity;
    [SerializeField]
    private float m_steptime;
    [SerializeField]
    private float m_jumpspeed;
    [SerializeField]
    private float m_movespeed;
    [SerializeField]
    private float m_crouchmovespeed;

    public Camera m_camera;
    private CharacterController m_player;

    public Transform m_eyes;//眼睛
    public AudioClip[] m_stepsound = new AudioClip[4];
    public AudioClip m_jumpsound;
    public AudioSource m_audio;

    private float m_y = 0;
    private float m_timer = 0;//用來計算播放走路音效的時間間隔

    void Start()
    {
        m_camera = GameObject.FindGameObjectWithTag("MainCamera").GetComponent<Camera>();

        m_player = gameObject.GetComponent<CharacterController>();
        m_audio = gameObject.GetComponent<AudioSource>();

    }


    private void FixedUpdate()
    {
        //聲音設定
        gameObject.GetComponent<AudioSource>().volume = datamanager.m_volume;

        playermove();

        float deltay = Input.GetAxis("Mouse X");
        float deltax = Input.GetAxis("Mouse Y");
        Cameramove(m_mouserate * deltax, m_mouserate * deltay);

        if (!GroundCheck())
        {
            m_y -= m_gravity * Time.deltaTime;
        }
    }

    #region 主角移動和腳步聲音
    public void playermove()
    {

        m_timer += Time.deltaTime;

        float x = Input.GetAxis("Horizontal");
        float z = Input.GetAxis("Vertical");

        if (GroundCheck())
        {
            if (Input.GetKeyDown(KeyCode.Space))//起跳
            {
                Jump();
                m_timer = -m_steptime / 2;//這個值是随意設定的,隻是為了起跳時不播放走路音效
            }
        }

            if (m_player.velocity.magnitude != 0 && m_timer > m_steptime)
            {
                m_audio.PlayOneShot(m_stepsound[UnityEngine.Random.Range(0, 4)]);//PlayOneShot可以直接播放一次指定AudioClip
                m_timer = 0;
            }

        //移動
        m_player.Move(gameObject.transform.TransformDirection(new Vector3(x, m_y, z) * m_movespeed * Time.deltaTime));
    }

    public void Jump()
    {
        m_y = m_jumpspeed;
        m_audio.PlayOneShot(m_jumpsound);
    }
    #endregion

    #region 攝像頭角度移動
    public void Cameramove(float deltax, float deltay)
    {
        //滑鼠控制攝像頭角度
        Vector3 angle = m_camera.transform.eulerAngles;
        angle.x -= deltax;
        angle.y += deltay;
        if (angle.x > 35 && angle.x < 90) angle.x = 35;
        if (angle.x < 325 && angle.x > 270) angle.x = 325;//控制上下視野

        m_camera.transform.eulerAngles = angle;
        //主角角度與攝像頭角度同步
        angle.x = angle.z = 0;
        transform.eulerAngles = angle;
        //錄影機跟随眼睛
        m_camera.transform.position = m_eyes.transform.position;
    }
    #endregion

    //檢測是否在地上
    private bool GroundCheck()
    {
        RaycastHit hitInfo;
        if (Physics.SphereCast
            (transform.position, m_player.radius, Vector3.down, out hitInfo,
             0.3f, Physics.AllLayers, QueryTriggerInteraction.Ignore))
            return true;
        else return false;
    }

    public void Relax()
    {
        m_movespeed /= 100;
    }
    public void Resume()
    {
        m_movespeed *= 100;
    }
}
           

這樣的話角色控制就實作了,再寫一個描述玩家血量和死亡的腳本,因為有多個腳本需要知道玩家死亡這件事(如ui腳本等),是以建立一個委托。這裡playui是後面建立的ui管理腳本。

using UnityEngine;

public  delegate  void  onedelegate();

public class playerhp : MonoBehaviour
{
    private int m_hp;
    public int M_hp { get { return m_hp; } }

    public onedelegate diedelegate;

    void Start()
    {
        m_hp = 100;
    }
    void Update()
    {
        if (m_hp <= 0)
        {
            if(diedelegate!=null)
                diedelegate();
        }
    }
    public void bedammaged(int dam)
    {
        FindObjectOfType<playui>().HurtEffect();
        m_hp -= dam;
    }
}
           

  到這裡準備工作還沒有做完,我們知道,很多unity項目都需要建立一個對象池來提高效率,在本項目中也不例外:

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
//對象池
public class pool : MonoBehaviour
{
    public static pool instance;

    //用字典構造多個池子,每個類型的池子的名字為string類型,一個池子本質上是一個棧
    private static Dictionary<string, Stack<GameObject>> m_pool;
   
    void Awake()
    {
        m_pool = new Dictionary<string, Stack<GameObject>>();
        instance = this;//單例模式
    }

    //從池子得到物體
    public static GameObject GetPrefab(GameObject prefab, Vector3 position,Quaternion rotation)
    {
        //池子的名字,這樣子命名是因為預制體執行個體化後會名字會加一個(Clone)
        string key = prefab.name + "(Clone)";

        if(!m_pool.ContainsKey(key))//沒有這個棧
        {
            GameObject one = Instantiate(prefab, position, rotation);
            m_pool.Add(key, new Stack<GameObject>());
            one.SetActive(true);
            return one;
        }

        else if ( m_pool[key].Count > 0)//如果棧存在且有東西
        {
            GameObject one = m_pool[key].Pop();
            one.transform.position = position;
            one.transform.rotation = rotation;
            one.SetActive(true);
            return one;
        }
        else //棧存在,棧裡沒東西
        {
            GameObject one = Instantiate(prefab,position,rotation);
            one.SetActive(true);
            return one;
        }
     
    }

    //将已經執行個體化出的gameobject放入對應的池子
    public static GameObject PrefabReturn(GameObject one)
    {
        m_pool[one.name].Push(one);
        one.SetActive(false);
        return one;
    }
}

           

對象池腳本寫好後,我把他挂在一個命名為pool的空物體上,這樣準備工作就完成了。

下一篇武器系統:https://blog.csdn.net/qq_37553152/article/details/81904033

繼續閱讀