通宵一晚做出来的一款类似CS的第一人称射击游戏Demo!原来做游戏也不是很难,连憨憨学妹都学会了!
Posted God Y.
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了通宵一晚做出来的一款类似CS的第一人称射击游戏Demo!原来做游戏也不是很难,连憨憨学妹都学会了!相关的知识,希望对你有一定的参考价值。
- 📢博客主页:https://blog.csdn.net/zhangay1998
- 📢欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正!
- 📢本文由 God Y.原创,首发于 CSDN🙉
- 📢未来很长,值得我们全力奔赴更美好美好的生活✨
目录
📢引言
- ☔️上海已经入梅,小雨蒙蒙笼罩了整个上海。
- 🐵窗外雨声滴滴答,室内键盘咔咔咔。
- 👻正在无聊的时候呢,之前的学妹小云儿发来了消息~
- 小云儿👩:学长哥哥,上次看完你的打飞机游戏,发现我差不多学会了哎~
- 小Y(博主):那挺好呀,可以跟你的小姐妹们一起玩打飞机游戏了~
- 小云儿👩:打飞机我们已经玩腻啦,想玩一款真正的射击游戏~就像CS那样刺激的啦!
- 小Y:emm…你倒是挺喜欢玩刺激的哈~
- 小云儿👩:嘿嘿~哥哥你不是说下次带人家玩更刺激的嘛,我这就来找你啦!
- 小Y:好伐,但是一口气吃不成胖子,我现在先教你使用动画系统实现CS中的一些基本游戏操作,比- 如开枪、换枪、开火和换弹药等操作~
- 小云儿👩:好咧哥哥,我小脑袋瓜本来也不够灵光,学这些正好!
- 小Y:好咧,上一篇文章已经对Unity的动画基础做介绍了,现在我们就开始动手吧!
- 小云儿👩:好咧哥哥,动手可以,不阔以动脚哦!
🌟通宵一晚做出来的一款类似CS的第一人称射击游戏Demo!原来做游戏也不是很难,憨憨学妹都学会了!
🎄导入一个资源包
- 小Y:话说小云儿,我上一篇文章介绍[Unity中的动画系统]你有认真看完一遍嘛?
- 小云儿👩:看完啦,小Y哥哥~ 就是有的地方还没有太明白~
- 小Y:那正好,借助制作这个游戏能让你对Unity的动画有一个更深刻的认识!
- 小云儿👩:好呀好呀~
- 小Y:第一步要新建一个工程,然后才能正式开始!
- 小云儿👩:哎呀,小Y哥哥 这个我都会了,这么简单就不用再介绍啦~
- 小Y:猴急猴急的呢,咱们慢慢来~
既然想要做一个CS那样的射击游戏,场景跟模型肯定得有啊
我们这就来导入一个简单的资源包,里面有简单的一个场景和几把枪械
- 小云儿:场景和枪械都有啦,接下来该写脚本了吧?
- 我:是的呢,小云儿你变聪明了!
- 小云儿:哼唧,跟着哥哥学必须变聪明!
🎅第一人称视角控制、人物自由移动和移动声音控制
- 小Y:那第一步就是写一个挂在人物身上的脚本,让角色可以自由移动。
- 小云儿👩:好啊,先让角色动起来~
- 小Y:还要加上第一人称视角控制,这样才有CS的既视感
- 小云儿👩:对的对的,如果能给人物移动加上声音就更好了
- 小Y:没错,还要给人物移动,奔跑和跳跃加上声音,这样就更有感觉了!
Unity场景设置
-
首先,我们在场景中新建一个游戏对象GameObject,将其改名为FPSController
-
然后给他添加上角色控制器Character Controller和Audio Source
-
角色控制器Character Controller可以更容易的处理有碰撞的运动
-
Audio Source 是 Unity 中的 Audio 组件,其主要用来播放游戏场景中的 - AudioClip,AudioClip 就是导入到 Unity 中的声音文件。
-
其次,将场景中默认创建的Camera删掉或者取消激活,我们这里会自己新建一个Camera使用,所以暂时用不到这个默认的相机
-
在这个游戏对象下再新建一个GameObject,改名为FirstPersonCharacter,给他添加上Camera和AudioListener组件
-
这个Camera就是我们能看到的第一视角,AudioListener是游戏中的声音接收器,用来接收场景中的声音,一般都挂在相机上
脚本控制
-
主要是控制人物的行走、移动、跳跃
-
还有人物身上的一些声音的控制
-
定义一个值用来声明人物是否是行走状态,再定义两个参数分别用来用来处理移动速度和奔跑速度
-
然后还有对第一人称视角的控制
-
以及几个声音数组,用来控制人物行走,奔跑和跳跃的不同声音
-
这块地方实现的方法有好多种,所以这里只是提供一种方法作为参考
-
这里写的有点多,嫌麻烦的可直接复制这个脚本的代码挂在我们的FPSController身上即可
直接上代码,挂载到我们的角色控制器FPSController上就行
using System;
using UnityEngine;
using UnityStandardAssets.CrossPlatformInput;
using UnityStandardAssets.Utility;
using Random = UnityEngine.Random;
namespace UnityStandardAssets.Characters.FirstPerson
{
[RequireComponent(typeof (CharacterController))]
[RequireComponent(typeof (Audiosource))]
public class FirstPersonController : MonoBehaviour
{
[SerializeField] private bool m_IsWalking;
[SerializeField] private float m_WalkSpeed;
[SerializeField] private float m_RunSpeed;
[SerializeField] [Range(0f, 1f)] private float m_RunstepLenghten;
[SerializeField] private float m_JumpSpeed;
[SerializeField] private float m_StickToGroundForce;
[SerializeField] private float m_GravityMultiplier;
[SerializeField] private MouseLook m_MouseLook;
[SerializeField] private bool m_UseFovKick;
[SerializeField] private FOVKick m_FovKick = new FOVKick();
[SerializeField] private bool m_UseHeadBob;
[SerializeField] private CurveControlledBob m_HeadBob = new CurveControlledBob();
[SerializeField] private LerpControlledBob m_JumpBob = new LerpControlledBob();
[SerializeField] private float m_StepInterval;
[SerializeField] private AudioClip[] m_FootstepSounds; //从中随机选择的一组足迹声音。
[SerializeField] private AudioClip m_JumpSound; //角色离开地面时发出的声音。
[SerializeField] private AudioClip m_LandSound; //角色触地时播放的声音。
private Camera m_Camera;
private bool m_Jump;
private float m_YRotation;
private Vector2 m_Input;
private Vector3 m_MoveDir = Vector3.zero;
private CharacterController m_CharacterController;
private CollisionFlags m_CollisionFlags;
private bool m_PreviouslyGrounded;
private Vector3 m_OriginalCameraPosition;
private float m_StepCycle;
private float m_NextStep;
private bool m_Jumping;
private AudioSource m_AudioSource;
private void Start()
{
m_CharacterController = GetComponent<CharacterController>();
m_Camera = Camera.main;
m_OriginalCameraPosition = m_Camera.transform.localPosition;
m_FovKick.Setup(m_Camera);
m_HeadBob.Setup(m_Camera, m_StepInterval);
m_StepCycle = 0f;
m_NextStep = m_StepCycle/2f;
m_Jumping = false;
m_AudioSource = GetComponent<AudioSource>();
m_MouseLook.Init(transform , m_Camera.transform);
}
private void Update()
{
RotateView();
// 跳转状态需要在这里读取,以确保它不会丢失
if (!m_Jump)
{
m_Jump = CrossPlatformInputManager.GetButtonDown("Jump");
}
if (!m_PreviouslyGrounded && m_CharacterController.isGrounded)
{
StartCoroutine(m_JumpBob.DoBobCycle());
PlayLandingSound();
m_MoveDir.y = 0f;
m_Jumping = false;
}
if (!m_CharacterController.isGrounded && !m_Jumping && m_PreviouslyGrounded)
{
m_MoveDir.y = 0f;
}
m_PreviouslyGrounded = m_CharacterController.isGrounded;
}
private void PlayLandingSound()
{
m_AudioSource.clip = m_LandSound;
m_AudioSource.Play();
m_NextStep = m_StepCycle + .5f;
}
private void FixedUpdate()
{
float speed;
GetInput(out speed);
//始终沿着相机向前移动,因为这是它瞄准的方向
Vector3 desiredMove = transform.forward*m_Input.y + transform.right*m_Input.x;
//获取被接触曲面的法线以沿其移动
RaycastHit hitInfo;
Physics.SphereCast(transform.position, m_CharacterController.radius, Vector3.down, out hitInfo,
m_CharacterController.height/2f, Physics.AllLayers, QueryTriggerInteraction.Ignore);
desiredMove = Vector3.ProjectOnPlane(desiredMove, hitInfo.normal).normalized;
m_MoveDir.x = desiredMove.x*speed;
m_MoveDir.z = desiredMove.z*speed;
if (m_CharacterController.isGrounded)
{
m_MoveDir.y = -m_StickToGroundForce;
if (m_Jump)
{
m_MoveDir.y = m_JumpSpeed;
PlayJumpSound();
m_Jump = false;
m_Jumping = true;
}
}
else
{
m_MoveDir += Physics.gravity*m_GravityMultiplier*Time.fixedDeltaTime;
}
m_CollisionFlags = m_CharacterController.Move(m_MoveDir*Time.fixedDeltaTime);
ProgressStepCycle(speed);
UpdateCameraPosition(speed);
m_MouseLook.UpdateCursorLock();
}
private void PlayJumpSound()
{
m_AudioSource.clip = m_JumpSound;
m_AudioSource.Play();
}
private void ProgressStepCycle(float speed)
{
if (m_CharacterController.velocity.sqrMagnitude > 0 && (m_Input.x != 0 || m_Input.y != 0))
{
m_StepCycle += (m_CharacterController.velocity.magnitude + (speed*(m_IsWalking ? 1f : m_RunstepLenghten)))*
Time.fixedDeltaTime;
}
if (!(m_StepCycle > m_NextStep))
{
return;
}
m_NextStep = m_StepCycle + m_StepInterval;
PlayFootStepAudio();
}
private void PlayFootStepAudio()
{
if (!m_CharacterController.isGrounded)
{
return;
}
// 从数组中拾取并播放随机的足迹声音,不包括索引0处的声音
int n = Random.Range(1, m_FootstepSounds.Length);
m_AudioSource.clip = m_FootstepSounds[n];
m_AudioSource.PlayOneShot(m_AudioSource.clip);
// 将拾取的声音移动到索引0,以便下次不再拾取
m_FootstepSounds[n] = m_FootstepSounds[0];
m_FootstepSounds[0] = m_AudioSource.clip;
}
private void UpdateCameraPosition(float speed)
{
Vector3 newCameraPosition;
if (!m_UseHeadBob)
{
return;
}
if (m_CharacterController.velocity.magnitude > 0 && m_CharacterController.isGrounded)
{
m_Camera.transform.localPosition =
m_HeadBob.DoHeadBob(m_CharacterController.velocity.magnitude +
(speed*(m_IsWalking ? 1f : m_RunstepLenghten)));
newCameraPosition = m_Camera.transform.localPosition;
newCameraPosition.y = m_Camera.transform.localPosition.y - m_JumpBob.Offset();
}
else
{
newCameraPosition = m_Camera.transform.localPosition;
newCameraPosition.y = m_OriginalCameraPosition.y - m_JumpBob.Offset();
}
m_Camera.transform.localPosition = newCameraPosition;
}
private void GetInput(out float speed)
{
// Read input
float horizontal = CrossPlatformInputManager.GetAxis("Horizontal");
float vertical = CrossPlatformInputManager.GetAxis("Vertical");
bool waswalking = m_IsWalking;
#if !MOBILE_INPUT
// 在独立构建中,步行/跑步速度通过按键进行修改。
//跟踪角色是在行走还是在跑步
m_IsWalking = !Input.GetKey(KeyCode.LeftShift);
#endif
// set the desired speed to be walking or running
speed = m_IsWalking ? m_WalkSpeed : m_RunSpeed;
m_Input = new Vector2(horizontal, vertical);
// 如果组合长度超过1,则规范化输入:
if (m_Input.sqrMagnitude > 1)
{
m_Input.Normalize();
}
// 只有当运动员准备跑步、正在跑步并且要使用fovkick时,处理速度变化以提供fovkick
if (m_IsWalking != waswalking && m_UseFovKick && m_CharacterController.velocity.sqrMagnitude > 0)
{
StopAllCoroutines();
StartCoroutine(!m_IsWalking ? m_FovKick.FOVKickUp() : m_FovKick.FOVKickDown());
}
}
private void RotateView()
{
m_MouseLook.LookRotation (transform, m_Camera.transform);
}
private void OnControllerColliderHit(ControllerColliderHit hit)
{
Rigidbody body = hit.collider.attachedRigidbody;
//如果角色在刚体上,不要移动刚体
if (m_CollisionFlags == CollisionFlags.Below)
{
return;
}
if (body == null || body.isKinematic)
{
return;
}
body.AddForceAtPosition(m_CharacterController.velocity*0.1f, hit.point, ForceMode.Impulse);
}
}
}
🎈枪械开火、换弹、换枪动画设置
- 小Y:角色的基本移动和视角切换有了,那接下来就是对枪械进行动画设置了,接下来是重点,你可要好好听哦!
- 小云儿👩:那必须的,小Y哥哥说的话我都要认真听!
在这个资源包中已经有基本的动画片段了,如下图,我们要做的就是使用Animation Controller将动画片段控制起来
拿MP5举例,看一下动画片段
动画控制值Animator配置
这里我们先来给神器AK47(阿卡47)来进行动画配置
点开这个Animation Controller就是这个默认界面
- 然后将AK47的动画片段直接拖进去,分别是默认动画、开火动画和换弹动画
- 然后新建两个Trigger类型的过度参数,因为我们的开火和换弹都是点击触发的,类似开关,所以这里选择使用Trigger,其实使用Bool也可以。这里只是提供一方法。
如下图:
- 然后将过度参数,分别添加到对应的箭头上
- 还要注意将过度参数从Normal向其他两个动画过度时,将这个Has Exit Time 参数取消勾选,上篇文章动画基础也介绍过了
- 还没有理解的可以去上一篇文章看一下哦,把链接摆在这了,嘿嘿
Unity零基础到进阶 ☀️| 近万字教程 对 Unity 中的 动画系统基础 全面解析+实战演练
-
这里一同样的方式来给MP5和喷子设置上动画控制器,方法都是一样的
-
先添加动画过度参数,然后点击小箭头,将动画过度的条件添加上,Has Exit Time 参数配置好
-
看一下过度的实际动画过度效果,这里可能不够明显,因为两个动画间并没有很大的差异:
-
小云儿👩:小Y哥哥,这样动画控制器就配置好啦,接下来就是使用代码和实现动画的调用了对叭!
-
小Y:没错,因为动画片段都是现成的,我们只需要把这个动画控制器 Animation Controller做好就可以了,是不是很简单呢~
-
小云儿👩:动画很流畅,就是这个开火换弹太慢了,我想枪射的更快一点,有什么办法解决吗?
-
小Y:Emma…有办法的!简单给你说一下,但是射太快也有缺点哦,就是子弹浪费的太快了哈哈~
我们在这个动画窗口中选择对应的动画状态,然后右面的属性面板中有一个Speed属性
从名字就可以知道他是控制动画的播放速度的,所以我们在这里将速度的值改的越大,它播放的速度就越快,那我们的换弹速度就会更快啦!
🎉枪械开火、换弹、换枪脚本实现
- 小Y:这里要进行代码编写控制动画了,你可要好好听哦!
- 小云儿👩:好咧,小Y哥哥~ 我会做认真听讲的好孩子的!
其实这一块也很简单
我们首先在FPSController下新建一个GameObject,改名为:GunMansger
然后将AK47、MP5和喷子这三把枪的预制体拖到这个GunMansger下,并根据摄像机视角调整好视角。
我们这里只演示这三把枪,其实再多也一样,就是体力活啦~
如下图所示:
将三把枪的位置和视角都调整好之后,就开始上代码了
-
首先 ,写一个挂在每个枪械上的脚本FPSGun,用于控制开火和换枪,包括动画和声音的播放
代码很简单,先在Awake中找到挂载该脚本的游戏对象身上的Animator组件 -
在OnEnable中播放readyClip音频,readyClip是切换到当前枪械时的声音
然后定义了三个方法,分别是Fire、Reload和Switch,自然是开火、换弹和换枪的方法
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FPSGun : MonoBehaviour
{
private Animator animator;
public AudioClip fireClip;
public AudioClip reloadClip;
public AudioClip readyClip;
private void Awake()
{
animator = GetComponent<Animator>();
}
private void OnEnable()
{
//播放准备声音
AudioSource.PlayClipAtPoint(readyClip,transform.position);
}
public void Fire()
{
//将开火名称转换为哈希
int fire = Animator.StringToHash("Fire");
//播放开火动画
animator.SetTrigger(fire);
//播放声音
AudioSource.PlayClipAtPoint(fireClip ,transform.position);
}
public void Reload()
{
int reload = Animator.StringToHash手机上值得玩的5款射击类手游,非常好玩