从今天开始打算从头开始搞一个全新的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