Unity开发FPS游戏之完结篇

这个系列的前几篇文章介绍了如何从头开始用Unity开发一个FPS游戏,感兴趣的朋友可以回顾一下。这个系列的文章如下:

Unity开发一个FPS游戏_unity 模仿开发fps 游戏-CSDN博客

Unity开发一个FPS游戏之二_unity 模仿开发fps 游戏-CSDN博客

Unity开发一个FPS游戏之三-CSDN博客

Unity开发一个FPS游戏之四_unity fps-CSDN博客

在这篇文章中,我将计划完善以下几方面:

1. 增加一把狙击枪,实现通过瞄准镜来进行放大瞄准。

2. 新增手榴弹,实现投掷手榴弹的爆炸效果。

3. 重新调整瞄准准星

4. 对模型的材质进行优化。

1. 增加狙击枪武器

模型与动画

在Sketchfab网站上有很多制作精美的狙击枪,这里我选择的是https://skfb.ly/onBJQ,这是英国出品的著名的AWP狙击步枪。下载之后导入到我们之前的Blender文件中,把其中的枪本体,弹匣,枪机分别绑定到对应的武器骨骼,然后调整手臂骨骼来适配武器,如下图

这里需要注意的是,因为AWP是栓动步枪,即每打一枪需要旋转并拉动枪机,因此和之前其他自动步枪相比,还需增加多一个旋转的动作,我把对应的枪机分成两部分,为此要增加多一个枪机的骨骼。

对应狙击步枪的动画制作,和其他枪械的制作过程类似,这里就不再重复,可以查看我之前的文章。

最后把制作好的狙击枪的模型和动画导出为FBX文件,导入到Unity项目中。

瞄准镜效果

下面我们要制作瞄准镜的瞄准效果,当从瞄准镜瞄准时,应能看到放大的瞄准图像。最简单的一个思路时直接调整主相机的FOV,把整个画面拉近,但是这样做不符合实际情况,因为我们只希望瞄准镜内的图像放大。另一个思路是增加多一个相机,把这个相机放在主相机前面,调整其FOV,把放大后的图像投到瞄准镜内。这种方式更好,但是性能上会消耗较大。下面是这种思路的实现方式。

首先在weapon.cs脚本里面增加一个属性hasScope,表示这个武器是否带瞄准镜,在Awake方法中增加以下代码:

[Header("Scope")]
public bool hasScope = false;
public Material ScopeMat;void Awake() {...if (hasScope) {_renderTex = new RenderTexture(1024, 1024, 16, RenderTextureFormat.ARGB32);_scopeCamObj = new GameObject("ScopeCamera");_scopeCamObj.transform.SetParent(transform.Find("pose_controller/weapon"));_scopeCamObj.transform.localPosition = transform.Find("pose_controller/weapon/Aimpoint").localPosition;_scopeCamObj.transform.localRotation = transform.Find("pose_controller/weapon/Aimpoint").localRotation;_scopeCam = _scopeCamObj.AddComponent<Camera>();_scopeCam.fieldOfView = 10;_scopeCam.targetTexture = _renderTex;ScopeMat.SetTexture("_MainTex", _renderTex);}
}

在代码中,如果判断武器是带瞄准镜的,我们将新建一个渲染材质和一个相机,把相机挂载到武器的相关位置,然后设置这个相机的FOV,使得其能获得放大后的图像,然后把相机拍摄的图像赋予给这个材质。最后把这个材质设置为ScopeMat这个材质的主材质。ScopeMat这个材质将作为瞄准镜镜片的材质。

下面我们新建一个材质,例如命名为ScopeMergeMat,定义一个新的Shader,把瞄准镜的十字图像和相机拍摄的图像材质融合起来。这里我是采用URP的Shader Graph Shader来定义一个新的Shader,如下图所示:

可以看到这个Shader定义了两个输入,MainTex和SecondTex,把他们简单的相乘即可。 在SecondTex里面,我找了一张带有瞄准镜十字线的图片作为输入。然后MainTex就用以上提到的相机拍摄的相片作为输入。

新建一个名为ScopeMergeMat的材质,其设置如上图的右半部分所示。

最后就是在我们导入的狙击枪的模型中,新建一个Cylinder的GameObject,调整其大小使得能跟瞄准镜的镜片相匹配,然后把刚才创建的ScopeMergeMat材质赋予给这个GameObject即可。这样我们就能把放大后的图像投射到瞄准镜上了。

子弹抛壳效果

当狙击枪射击后,需要玩家手动拉枪栓,这时子弹会抛出。为此我们可以在动画中增加一个事件,当播放到拉动枪栓时,生成一个子弹并抛出,对weapon.cs脚本做如以下改动:

[SerializeField] GameObject casingBoltPrefab;
public int fireCasingEventFrame = 0;void Awake() {...foreach (AnimationClip clip in _animator.runtimeAnimatorController.animationClips) {if (clip.name.Contains("Shoot") && fireCasingEventFrame != 0 ) {AnimationEvent animationFireCasingEvent = new AnimationEvent{time = fireCasingEventFrame/clip.frameRate,functionName = "CasingHandler"};clip.AddEvent(animationFireCasingEvent);}}
}private void CasingHandler() {_casing = Instantiate(casingBoltPrefab, _eject.position, _eject.rotation);_casing.transform.localScale = new Vector3(2, 2, 2);
}    

以下视频是狙击枪的效果:

2. 增加手榴弹

模型与动画

同样是在Sketchfab上找到一个模型,https://skfb.ly/6Rxtp。导入Blender之后进行相应的骨骼绑定,并制作对应的动画效果。投掷手榴弹的动画比开枪的动画要简单一些,不用制作最复杂的重新装弹的动画。在原模型中没有把手榴弹的拉环单独出来,为此需要在Blender的编辑模式里面编辑,选择拉环相关的面,按P进行分离后保存为一个单独的物体。最后和手臂进行结合,效果如下图:

最后把模型导出为FBX文件,再导入到Unity项目即可。

投掷效果

和之前做的枪支开枪的效果不同,手榴弹是整个扔出去的。因此也是要采用之前的给动画增加事件的方式,当投弹的动画播放到手榴弹要出手时,需要新建一个手榴弹的GameObject,然后根据测量到的目标的距离,给新建的手榴弹GameObject施加一个作用力。

改造一下Weapon.cs文件,增加以下代码:

[SerializeField] GameObject grenadePrefab;public bool gunType = false;
public int throwGrenadeEventFrame = 0;
public Vector3 targetPos; private Transform _grenadeTrans;
private bool _registerOnce = false;void Awake() {...foreach (AnimationClip clip in _animator.runtimeAnimatorController.animationClips) {...if (clip.name.Contains("Shoot") && !gunType && throwGrenadeEventFrame != 0 && !_registerOnce) {AnimationEvent animationGrenadeEvent = new AnimationEvent{time = throwGrenadeEventFrame/clip.frameRate,functionName = "GrenadeHandler"};clip.AddEvent(animationGrenadeEvent);AnimationEvent animationEndEvent = new AnimationEvent{time = clip.length - 0.1f,functionName = "EndAnimationHandler",stringParameter = clip.name};clip.AddEvent(animationEndEvent);_registerOnce = true;}}if (!gunType) {_grenadeTrans = transform.Find("pose_controller/weapon/base/Gran_base");}
}private void GrenadeHandler() {_grenade = Instantiate(grenadePrefab, _grenadeTrans.position, _grenadeTrans.rotation);_grenade.transform.localScale = new Vector3(30, 30, 30);_grenadeTrans.GameObject().SetActive(false);// Base on the target position to calculate the force.Vector3 direction = (Vector3.up + (targetPos - _grenadeTrans.position).normalized).normalized;float distance = (targetPos - _grenadeTrans.position).magnitude;float force = distance * 0.6f;if (force > 50f) {force = 50f;}_grenade.GetComponent<Rigidbody>().AddForce(direction * force, ForceMode.Impulse);
}

在以上代码中,当播放投掷手榴弹的Shoot动画时,如果播放到手榴弹要出手的那一帧,就会触发注册的GrenadeHandler函数,这时将新建一个手榴弹GameObject,然后设置其位置。之后就要根据目标的位置来计算要施加的力的大小和方向。这里我简单的设置方向为手榴弹指向目标方向的往Y轴倾斜45度,可以理解为水平方向往上倾斜45度,这样手榴弹就会以一个抛物线来飞行。然后力量的大小根据与目标的距离来决定。最后给这个手榴弹的刚体添加一个即时类型的力即可。多说一句,ForceMode.Impulse代表一个即时类型的力,例如我施加一个10牛顿的力,在这种类型下,F=MV,假设物体质量为1Kg,那么可知物体的速度为10米/秒。Unity的物理引擎会帮我们处理手榴弹之后的飞行路线。

修改以下PlayerController.cs脚本文件,当玩家按下鼠标左键,触发开火时,我们需要获取当前瞄准对象的位置,传递给Weapon.cs,以下是相关改动:

private void Update() {...if (_input.shoot) {...if (_weaponType == WeaponType.Grenade) {Ray ray = new Ray(_mainCamera.transform.position, _mainCamera.transform.forward);RaycastHit hit;Vector3 targetPosition;if (Physics.SphereCast(ray, 0.5f, out hit, maxThrowGrenadeDistance)) {targetPosition = hit.collider.gameObject.transform.position;} else {targetPosition = _mainCamera.transform.position + _mainCamera.transform.forward * maxThrowGrenadeDistance;}StartCoroutine(ThrowGrenade(targetPosition));}}
}private IEnumerator ThrowGrenade(Vector3 targetPos) {_weaponBehavior.targetPos = targetPos;_playerAnimator.SetTrigger("Shoot");_weaponAnimator.SetTrigger("Shoot");yield return new WaitUntil(()=>_weaponBehavior.ClipFinishedName.Contains("Shoot"));_weaponType = WeaponType.Gun;// Restore the previous weaponif (_weaponIndex == 0) {_weaponIndex = weaponPrefabs.Count - 1;} else {_weaponIndex -= 1;}StartCoroutine(SwitchWeapon());
}

以上代码中,当判断开火触发,并且当前的武器类型是手榴弹时,通过从当前摄像机发出的射线检测来获取目标的位置,并传递给weapon.cs对象,当手榴弹投掷完成后,切换回之前的枪支武器。

爆炸效果

通常手榴弹都是延时爆炸,当拉开拉环后,过3-4秒即爆炸。在之前的系列中,我已实现了当子弹射击到油桶时触发油桶爆炸,我们按照这个思路稍加修改即可。

首先是制作手榴弹爆炸后分裂为几个部分的模型,这个同样可以利用Blender的Cell Fracture插件来完成,与之前油桶模型的制作类似。

修改之前的debris代码,改动如下:

public float destroyAfter = 2f;void Start()
{if (destroyAfter > 0) {StartCoroutine (DestroyAfter ());}
}private IEnumerator DestroyAfter () 
{//Wait for set amount of timeyield return new WaitForSeconds (destroyAfter);OnDestroy ();
}public void OnDestroy()
{GameObject o = Instantiate(Explosive_debris, transform.position, transform.rotation);GameObject explode = Instantiate(explodeEffect, transform.position, transform.rotation);Destroy(gameObject);
}

把这个脚本添加到投掷手榴弹新创建的GameObject上。

以下视频是投掷手榴弹的效果演示:

3. 调整瞄准准星

在之前的文章中,我是把瞄准的准星直接放置在模型中的,但是这会有一些问题,由于模型在游戏中不是放置在正中间,因此准星和实际瞄准的物体会有偏差,无法实现精准的瞄准。另外在其他一些流行的FPS游戏,例如使命召唤中,我们可以看到瞄准准星是会随着玩家的动作而发生变化的,例如在静止时准星会更精准,当玩家运动时,准星的十字会扩大,导致准度下降。当玩家重装子弹时准星会消失等等。因此我也仿照使命召唤的设计来重新调整瞄准准星。

具体做法是,在2D UI的GameScreen这个Prefab里面,新建一个名为Crosshair的Empty GameObject,然后在其下新建四个GameObject,分别对应准星十字的上下左右4个部分,每个部分都是一个白色的小方块,如下图所示:

这个Crosshair准星放置在屏幕的正中间。

给这个GameScreen增加一个名为Crosshair.cs的脚本文件,代码如下:

public class Crosshair : MonoBehaviour
{[SerializeField] GameObject crosshair;public float smoothness = 10f;private RectTransform _rectCrosshair;private Vector2 _rectCrosshairSize;private PlayerController.PlayerStatus _status;// Start is called before the first frame updatevoid Start(){_rectCrosshair = crosshair.GetComponent<RectTransform>();_rectCrosshairSize = _rectCrosshair.sizeDelta;}// Update is called once per framevoid Update(){switch (_status) {case PlayerController.PlayerStatus.Idle:crosshair.SetActive(true);ExpandCrossUpdate(0.5f);break;case PlayerController.PlayerStatus.Walk:crosshair.SetActive(true);ExpandCrossUpdate(1.0f);break;case PlayerController.PlayerStatus.Shoot:crosshair.SetActive(true);ExpandCrossUpdate(0.5f);break;case PlayerController.PlayerStatus.Switch:crosshair.SetActive(true);ExpandCrossUpdate(0.5f);break;default:crosshair.SetActive(false);break;}}public void SetStatus(PlayerController.PlayerStatus status) {_status = status;}private void ExpandCrossUpdate(float expandDegree){Vector2 targetSize = _rectCrosshairSize * expandDegree;_rectCrosshair.sizeDelta = Vector2.Lerp(_rectCrosshair.sizeDelta, targetSize, Time.deltaTime * smoothness);}
}

 这个代码的作用是,根据当前玩家的状态,来调整准星的大小以及是否可见。

4. 代码及资源

最后我把所有的代码和资源都发布到Unity商店了FPS basic package | Systems | Unity Asset Store,这些资源包括了5种不同的现代武器枪械(Adaptive combat rifle, AR 15, HK 416, AK74, AWP)和手榴弹,以及一个包含了各种道具和交互效果的演示场景。

演示效果可见视频:

如果想了解详细的,欢迎下载并点赞好评,多多支持,感谢! ^_^

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/61589.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

浅析RPC—基础知识

该文章会简单介绍一下 RPC 相关的基础概念。 什么是RPC&#xff1f; RPC&#xff08;Remote Procedure Call&#xff09; 即远程过程调用&#xff0c;通过名字我们就能看出 RPC 关注的是远程调用而非本地调用。 为什么要 RPC &#xff1f; 因为&#xff0c;两个不同的服务器…

mysql数据库varchar截断问题

用了这么多年mysql数据库&#xff0c;才发现varchar是可以截断的&#xff0c;而且是在我们线上数据库。个人觉得dba的这个设置是非常有问题的&#xff0c;用户往数据库里存东西&#xff0c;就是为了以后用的&#xff0c;截断了存放&#xff0c;数据不完整&#xff0c;就用不了了…

EwoMail邮箱服务器软件安装教程

EwoMail是基于Linux的开源邮件服务器软件,集成了众多优秀稳定的组件,是一个快速部署、简单高效、多语言、安全稳定的邮件解决方案,帮助你提升运维效率,降低 IT 成本,兼容主流的邮件客户端,同时支持电脑和手机邮件客户端。 一、系统版本 二、关闭selinux vi /etc/sysconf…

【机器学习】机器学习的基本分类-监督学习-支持向量机(Support Vector Machine, SVM)

支持向量机是一种强大的监督学习算法&#xff0c;主要用于分类问题&#xff0c;但也可以用于回归和异常检测。SVM 的核心思想是通过最大化分类边界的方式找到数据的最佳分离超平面。 1. 核心思想 目标 给定训练数据 &#xff0c;其中 是特征向量&#xff0c; 是标签&#xf…

Linux命令进阶·如何切换root以及回退、sudo命令、用户/用户组管理,以及解决创建用户不显示问题和Ubuntu不显示用户名只显示“$“符号问题

目录 1. root用户&#xff08;超级管理员&#xff09; 1.1 用于账户切换的系统命令——su 1.2 退回上一个用户命令——exit 1.3 普通命令临时授权root身份执行——sudo 1.3.1 为普通用户配置sudo认证 2. 用户/用户组管理 2.1 用户组管理 2.2 用户管理 2.2.1 …

Zero to JupyterHub with Kubernetes中篇 - Kubernetes 常规使用记录

前言&#xff1a;纯个人记录使用。 搭建 Zero to JupyterHub with Kubernetes 上篇 - Kubernetes 离线二进制部署。搭建 Zero to JupyterHub with Kubernetes 中篇 - Kubernetes 常规使用记录。搭建 Zero to JupyterHub with Kubernetes 下篇 - Jupyterhub on k8s。 参考&…

《Python基础》之Python中可以转换成json数据类型的数据

目录 一、JSON简介 JSON有两种基本结构 1、对象&#xff08;Object&#xff09; 2、数组&#xff08;Array&#xff09; 二、将数据装换成json数据类型方法 三、在Python中&#xff0c;以下数据类型可以直接转换为JSON数据类型 1、字典&#xff08;Dictionary&#xff09…

若依项目源码阅读

源码阅读 前端代码分析 代码生成器生成的前端代码有两个&#xff0c;分别是course.js用于向后端发送ajax请求的接口代码&#xff0c;另一个是index.vue&#xff0c;用于在浏览器展示课程管理的视图组件。前端的代码是基于vue3elementplus。 template用于展示前端组件别的标签…

C#tabcontrol如何指定某个tabItem为默认页

// Selects tabPage2 using SelectedTab.this.tabControl1.SelectedTab tabPage2; 参考链接 TabControl.SelectedTab 属性 (System.Windows.Forms) | Microsoft Learnhttps://learn.microsoft.com/zh-cn/dotnet/api/system.windows.forms.tabcontrol.selectedtab?viewnetfr…

文件比较和文件流

文件比较和文件流 一、文本比较工具 diff1.基本用法1.1输出格式 2.常用选项 二、文件流1.文件的打开模式2.文件流的分类ifstreamofstreamfstrem区别 3.文件流的函数1. 构造函数2. is_open 用于判断文件是否打开3. open4. getline5. close6. get()7. read8. write9. put10. gcou…

【网络篇】HTTP知识

键入网址到网页显示&#xff0c;期间发生了什么&#xff1f; 浏览器第一步是解析URL&#xff0c;这样就得到了服务器名称和文件的路径名&#xff0c;然后根据这些信息生成http请求&#xff0c;通过DNS查询得到我们要请求的服务器地址&#xff0c;然后添加TCP头、IP头以及MAC头&…

【解决安全扫描漏洞】---- 检测到目标站点存在 JavaScript 框架库漏洞

1. 漏洞结果 JavaScript 框架或库是一组能轻松生成跨浏览器兼容的 JavaScript 代码的工具和函数。如果网站使用了存在漏洞的 JavaScript 框架或库&#xff0c;攻击者就可以利用此漏洞来劫持用户浏览器&#xff0c;进行挂马、XSS、Cookie劫持等攻击。 1.1 漏洞扫描截图 1.2 具体…

互联网基础

TCP/IP协议&#xff08;协议组&#xff09; 分层名称TCP/IP协议应用层HTTP,FTP,mDNS,WebSocket,OSC...传输层TCP&#xff0c;UDP网络层IP链路层&#xff08;网络接口层&#xff09;Ethernet&#xff0c;Wi-Fi... 链路层&#xff08;网络接口层&#xff09; 链路层的主要作用…

【分组去重】.NET开源 ORM 框架 SqlSugar 系列

&#x1f4a5; .NET开源 ORM 框架 SqlSugar 系列 &#x1f389;&#x1f389;&#x1f389; 【开篇】.NET开源 ORM 框架 SqlSugar 系列【入门必看】.NET开源 ORM 框架 SqlSugar 系列【实体配置】.NET开源 ORM 框架 SqlSugar 系列【Db First】.NET开源 ORM 框架 SqlSugar 系列…

hdlbits系列verilog解答(Exams/m2014 q4d)-89

文章目录 一、问题描述二、verilog源码三、仿真结果一、问题描述 本节实现以下电路。 模块声明 module top_module ( input clk, input in, output out); 思路: 它的输入是一个组合逻辑异或门,将输入和输出异或后输入D触发器,这意味着输出与历史输出及当前输入都有关系,…

Cesium K-means自动聚合点的原理

Cesium K-means自动聚合点的原理 Cesium 是一个开源的 JavaScript 库&#xff0c;用于在 Web 环境中创建 3D 地球和地图应用。它能够处理地理空间数据&#xff0c;并允许开发者对大规模的地理数据进行可视化展示。在一些应用中&#xff0c;尤其是当处理大量地理坐标点时&#…

Kafka如何保证消息可靠?

大家好&#xff0c;我是锋哥。今天分享关于【Kafka如何保证消息可靠&#xff1f;】面试题。希望对大家有帮助&#xff1b; Kafka如何保证消息可靠&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 Kafka通过多种机制来确保消息的可靠性&#xff0c;主要包…

yolov5 解决:export GIT_PYTHON_REFRESH=quiet

当我们在第一次运行YOLOv5中的train.py程序时&#xff1a;可能会出现以下报错&#xff1a; This initial warning can be silenced or aggravated in the future by setting the $GIT_PYTHON_REFRESH environment variable. Use one of the following values: - quiet|q|silen…

【Linux】进程控制-----进程替换

目录 一、为什么要进行进程替换&#xff1a; 二、进程替换的原理&#xff1a; 三、exec家族&#xff1a; 1、execl&#xff1a; 2、execlp&#xff1a; 3、execv&#xff1a; 4、execvp&#xff1a; 5、execle和execve ​编辑 putenv&#xff1a; 一、为什么要进行进程…

基于hexo框架的博客搭建流程

这篇博文讲一讲hexo博客的搭建及文章管理&#xff0c;也算是我对于暑假的一个交代 &#xff01;&#xff01;&#xff01;注意&#xff1a;下面的操作是基于你已经安装了node.js和git的前提下进行的&#xff0c;并且拥有github账号 创建一个blog目录 在磁盘任意位置创建一个…