基于u3d_FPS_Demo
1、导入素材
1.1 素材
1.2 创建游戏对象
新建3d游戏对象Plane,作为地面
挂载贴图,调整地面尺寸
2、实现人物移动
2.1 创建胶囊体游戏对象
自带胶囊碰撞体
为此对象创建脚本,添加角色控制器组件并去掉自带胶囊碰撞体组件
2.2 写移动脚本
移动物体的方法有两种1、CharacterController组件实现2、RigidBody刚体实现
public float moveSpeed = 10f;//移动速度public Vector3 moveDirection;//三维向量,表示移动方向public void PlayerMove(){float h_Value= Input.GetAxis("Horizontal");float v_Value = Input.GetAxis("Vertical");moveDirection = (transform.right * h_Value + transform.forward * v_Value).normalized;//红色方向轴和蓝色方向轴,形成三维方向向量,并标准化characterController.Move(moveDirection*moveSpeed*Time.deltaTime);}
3、实现镜头跟随
3.1 使主摄像机成为Player的子物体
调整坐标位置使摄像机刚好在Player头上,控制Player就可以控制相机
3.2 创建脚本控制相机旋转
将脚本挂载到相机上,使用InputManager的鼠标控制X轴和Y轴
public float mouseSensitivity = 100f;//视角灵敏度public Transform playerPos;//玩家位置public float xRotation = 0f;//存放累加值// Start is called before the first frame updatevoid Start(){//隐藏光标,避免画面中出现鼠标Cursor.lockState = CursorLockMode.Locked;}// Update is called once per framevoid Update(){//将轴映射到鼠标,值为当前鼠标增量乘以轴灵敏度float mouseX= Input.GetAxis("Mouse X")*mouseSensitivity*Time.deltaTime;float mouseY= Input.GetAxis("Mouse Y") * mouseSensitivity * Time.deltaTime;//坐标跟随鼠标移动,上下是Y,左右是Xprint("Mouse X:" + mouseX);print("Mouse Y:" + mouseY);//玩家左右旋转视角playerPos.Rotate(Vector3.up* mouseX);//(0,1,0),物体绕Y轴横向旋转//相机旋转xRotation -= mouseY;//累加绕X轴上下旋转的轴值xRotation = Mathf.Clamp(xRotation, -80f, 80f);//限制旋转角度(轴值累积)transform.localRotation = Quaternion.Euler(xRotation, 0f, 0f);//相机绕X轴上下旋转改变角度}
4、实现人物奔跑
可以动态改变速度,分别声明行走速度和奔跑速度,获取键盘(KeyCode)输入
ps:声明变量前加上[Tooltip("xxx")],即可在面板对应变量悬停显示提示信息;[SerializeField]强制序列化,使私有变量显示在面板;[Header("xxx")]给变量加上标题,方便后续区分变量用途
public float moveSpeed = 10f;//移动速度public float walkSpeed = 20f;//按键区分速度public float runSpeed = 15f;public bool isRun;//奔跑判断[Header("按键设置")]public KeyCode runKeyInput;//奔跑键,左shiftpublic void PlayerMove(){isRun = Input.GetKey(runKeyInput);if(isRun){moveSpeed = runSpeed;}else{moveSpeed = walkSpeed;}}
5、实现人物跳跃
Y轴坐标矢量变换,需要向上的力和向下的重力
5.1 向上
public bool isJump;public float jumpForce = 3f;//跳跃力度public Vector3 velocity;//力,Y轴的一个冲量变化public KeyCode jumpKeyInput;//跳跃键,空格public void PlayerMove(){PlayerJump();characterController.Move(velocity * Time.deltaTime);}public void PlayerJump(){isJump = Input.GetKey(jumpKeyInput);if(isJump){velocity.y = jumpForce * 2f;}}
5.2 增加地面检查点
- 当物体与地面碰撞时,说明物体已经落地,向下的重力消失
- 新建Player的子物体checkGround
- 新建地面检测方法在移动前调用并每帧执行
void Update(){GroundCheck();PlayerMove();}
- 为地面设置Layer层级
public bool isGround;//地面碰撞判断private Transform groundCheck;//地面检测点,初始化CheckGroundprivate float groundDistance = 0.1f;//与地面的距离,私有变量public LayerMask groundMesh;//地面层级,使小球与地面一个层,所以物理小球只与地面碰撞public void GroundCheck(){//生成物理球体与地面碰撞,球体位置,半径,层级,层级与地面相同//碰撞点是CheckGround坐标,调整其位置到接近地面处isGround= Physics.CheckSphere(groundCheck.position,groundDistance,groundMesh);//Debug.Log(isGround);//在地面时给一个向下的力if(isGround &&velocity.y<=0){velocity.y = -2f;}}
5.3 起跳后向下
public float jumpForce = 3f;//跳跃力度public float gravity = -20f;//向下的重力public Vector3 velocity;//力,Y轴的一个冲量变化/// <summary>/// 移动/// </summary>public void PlayerMove(){if(isGround==false)//不接地,在空中累加向下的力{velocity.y += gravity * Time.deltaTime;}characterController.Move(velocity * Time.deltaTime);PlayerJump();}/// <summary>/// 跳跃/// </summary>public void PlayerJump(){isJump = Input.GetKey(jumpKeyInput);if(isJump&&isGround)//按下空格并且在地上{velocity.y =Mathf.Sqrt ( jumpForce * -2f*gravity);}}
6、人物上坡
cube搭建一个斜坡
运行发现人物无法在斜坡上跳跃:修改groundCheck坐标位置,或坐标到地面的半径大小
Player在斜坡上行走不平稳,解决:检测Player在斜坡上时,额外施加一个向下的力
public bool OnSlpe(){if(isJump)return false;RaycastHit hit;//存放反射出的射线//向下打出射线//(Player)向场景中所有碰撞体投射一条射线,反射射线不垂直向上(0,1,0),则Player在斜边if(Physics.Raycast(transform.position,Vector3.down,out hit,characterController.height/2*slopForcrRayLength)){ if(hit.normal!=Vector3.up)//normal是法线,方向与(0,1,0)不一致{return true;}}return false;}
if(OnSlpe())//如果在斜坡{characterController.Move(Vector3.down * characterController.height / 2 * slopForce * Time.deltaTime);}
7、实现射击
7.1 找到素材中枪械预制体
预制体挂载到主相机下,采用射线检测实现射击的发生
枪械位置归零
修改摄像机渲染距离(Clipping Planes),解决枪视角穿模
添加UI,image作为准心
public Transform shootPoint;//武器射击位置(子弹出膛位置public int range = 100;//子弹射程//右键瞄准,左键射击private bool gunShootInput; void Update(){gunShootInput = Input.GetMouseButton(0);//左键按下if(gunShootInput){GunFire();}}/// <summary>/// 射击/// </summary>public void GunFire(){//向前射击Vector3 shootDirection = shootPoint.forward;RaycastHit hit;//发出射线if(Physics.Raycast(shootPoint.position,shootDirection,out hit,range))//击中目标信息存入hit{Debug.Log(hit.transform.name+"打到惹");}}
发出的射线击中目标物体(但是可以对Player对象进行射击)
在Update函数一秒60帧,需要自己手动控制射速
7.2 控制射速
//射速控制,计时器public float fireRate = 0.1f;public float fireTimer;void Update(){if(fireTimer<fireRate)//射速越小,打出的射线越少{fireTimer += Time.deltaTime;}}public void GunFire(){//计时器值比射速还小if (fireTimer < fireRate) return;//直接返回跳出函数//向前射击//发出射线fireTimer = 0f;//重置计时器}
}
7.2 实现打空弹匣
public int bulletsMag = 30;//一个弹匣子弹数量public int range = 100;//子弹射程public int bulletsLeft = 300;//剩余备用子弹public int currentBullets;//当前子弹数void Start(){currentBullets = bulletsMag;}public void GunFire(){//计时器值比射速还小if (fireTimer < fireRate||currentBullets<=0) return;//直接返回跳出函数//向前射击//发出射线currentBullets--;//子弹减少}
7.3 子弹数UI
添加文本(Text)UI再脚本设置UI
[Header("UI设置")]public Image crossHairUI;//初始化public Text AmmoTextUI;void Start(){currentBullets = bulletsMag;UpdateAmmoUI();//初始化一下}/// <summary>/// 射击/// </summary>public void GunFire(){currentBullets--;//子弹减少//每次子弹减少,就调用UIUpdateAmmoUI();fireTimer = 0f;//重置计时器} public void UpdateAmmoUI(){AmmoTextUI.text = currentBullets + "/" + bulletsLeft;}
7.4 实现换弹装弹
R键换弹
[Header("键位设置")][Tooltip("装填子弹的按键")][SerializeField] private KeyCode relodInputKey;//换弹键public void Relod(){if (bulletsLeft <= 0) return;//余弹不足//计算需要装填的子弹数量=弹匣子弹总数-当前子弹数int bulletRelod = bulletsMag - currentBullets;//备弹需要扣除子弹数int bulletReduce = (bulletsLeft > bulletRelod) ? bulletRelod: bulletsLeft;bulletsLeft -= bulletReduce;//备弹减少currentBullets += bulletReduce;//弹匣加装填弹UpdateAmmoUI();}
8、添加音效
8.1 添加音源
在Player身上添加音源组件
8.2 行走和跑步音效
在PlayerMove()进行调用
/// <summary>/// 播放移动音效/// </summary>public void PlayMoveAudio(){if(isGround&&moveDirection.sqrMagnitude>0.9f)//在地上且在走动{audioSource.clip = isRun ? runningAudio : walkingAudio;//走或跑if(!audioSource.isPlaying){audioSource.Play();//没在播放时播放}}else{if(audioSource.isPlaying){audioSource.Pause();}}}
9、开枪特效
9.1 添加开枪粒子特效
添加粒子特效预制体
脚本控制
public ParticleSystem muzzleFlash;//粒子特效public Light muzzleFlashLight;//灯光//粒子火花特效muzzleFlash.Play();muzzleFlashLight.enabled = true;//启用灯光
9.2 生成弹孔
public GameObject hitParticle;//击中特效public GameObject bulletHole;//弹孔//发出射线if(Physics.Raycast(shootPoint.position,shootDirection,out hit,range))//击中目标信息存入hit{Debug.Log(hit.transform.name+"打到惹");GameObject hitParticleEffect= Instantiate(hitParticle, hit.point, Quaternion.FromToRotation(Vector3.up, hit.normal));//实例化克隆对象,在被击中位置生成火花特效GameObject bulletHoleEffect= Instantiate(bulletHole, hit.point, Quaternion.FromToRotation(Vector3.up, hit.normal));//实例化克隆对象,在被击中位置生成弹孔Destroy(hitParticleEffect, 1f);Destroy(bulletHoleEffect, 3f);//一定时间后销毁}
10、Player手臂摇晃
public class WeaponSway : MonoBehaviour
{/*摇摆参数*/public float amout;//摆动幅度public float smoothAmout;//摇摆平滑值public float maxAmout;//最大幅度摇摆[SerializeField]private Vector3 originPosition;//起始位置// Start is called before the first frame updatevoid Start(){originPosition = transform.localPosition;//相对于父级物体的变换位置}// Update is called once per framevoid Update(){//获取鼠标轴值float mouseX= -Input.GetAxis("Mouse X")*amout;float mouseY = -Input.GetAxis("Mouse Y") * amout;//限制mouseX = Mathf.Clamp(mouseX, -maxAmout, maxAmout);mouseY = Mathf.Clamp(mouseY, -maxAmout, maxAmout);//手臂位置变化Vector3 finallyPosition = new Vector3(mouseX, mouseY, 0);//左右摇晃transform.localPosition = Vector3.Lerp(transform.localPosition, finallyPosition + originPosition, Time.deltaTime * smoothAmout);}
}
11、动画
Animator组件:动画状态机,管理动画
项目中新建Animation文件夹并新建Animator Controller组件管理动画
挂载到对应武器的状态机上
11.1 拿出武器、静止、查看武器
1、Exit:全部执行后退出
2、Any Status:任何状态
3、Entry:动画块入口
1. 添加拿出武器动作
2. 添加站立待机动画,对take_out右键make transition指向待机动画块
3. 添加查看武器动画,动画能从待机动画传递到检视武器动画,并且能传递回去设置进入动画条件:F键检视武器添加触发器类型参数
触发器设为idle->inspect_weapon的触发条件
[Tooltip("查看武器的按键")] [SerializeField] private KeyCode inspectWeaponKey;//检查武器动画void Start(){anim = GetComponent<Animator>();//获取组件}void Update(){//检查武器if(Input.GetKeyDown(inspectWeaponKey)){anim.SetTrigger("Inspect");//触发器获取参数}}
11.2 行走和奔跑动画
在PlayerMovement脚本
设置动画的触发条件
public bool isWalk;//动画触发条件
void Update(){//处于行走状态isWalk = (Mathf.Abs(h_Value) > 0 || Mathf.Abs(v_Value) > 0) ? true : false;//不大于零就说明没在走
}
在WeaponController脚本调用PlayerMovement中的变量public PlayerMovement PM;//调用PlayerMovement中变量anim.SetBool("Run", PM.isRun);//键,值anim.SetBool("Walk", PM.isWalk);
在Animator添加对应bool参数Run/Walk
11.3 换弹动画
- 弹匣打空换弹
- 弹匣未打空换弹
/// <summary>/// 播放换弹动画/// </summary>public void RelodAnimation(){//播放动画1if(currentBullets>0){anim.Play("reload_ammo_left", 0, 0);//0表示Base LayeraudioSource.clip=relodAmmoLeftClip;audioSource.Play();}if(currentBullets==0&&bulletsMag>0){anim.Play("reload_out_of_ammo", 0, 0);audioSource.clip = relodOutOfAmmoClip;audioSource.Play();}}
anim.Play()直接播放动画,就不需要画箭头传递,只需要动画播放完毕传回待机状态
处理奔跑、换弹时可以射击的问题
private bool isRelod;//判断是否装弹AnimatorStateInfo info = anim.GetCurrentAnimatorStateInfo(0);//获取当前第一层动画信息
void Update(){ if(info.IsName("reload_ammo_left")||info.IsName("reload_out_of_ammo"))//获取两个动画名称{isRelod = true;//正在换弹}else{isRelod = false;}}public void GunFire(){//计时器值比射速还小if (fireTimer < fireRate||currentBullets<=0||isRelod||PM.isRun) return;//直接返回跳出函数 }
最终结果:demo文件
基于u3d_FPS_Demo相关推荐
- 基于Golang的简单web服务程序开发——CloudGo
基于Golang的简单web服务程序开发--CloudGo[阅读时间:约10分钟] 一.概述 二.系统环境&项目介绍 1.系统环境 2.项目的任务要求 (1)基本要求 (2)扩展要求 三.具体 ...
- 【ReactiveX】基于Golang pmlpml/RxGo程序包的二次开发
基于Golang pmlpml/RxGo程序包的二次开发[阅读时间:约20分钟] 一.ReactiveX & RxGo介绍 1.ReactiveX 2.RxGo 二.系统环境&项目介绍 ...
- 基于Golang的对象序列化的程序包开发——myJsonMarshal
基于Golang的对象序列化的程序包开发--myJsonMarshal[阅读时间:约10分钟] 一.对象序列化概述 二.系统环境&项目介绍 1.系统环境 2.项目的任务要求 三.具体程序设计及 ...
- 基于Golang的监听读取配置文件的程序包开发——simpleConfig_v1
基于Golang的监听&读取配置文件的程序包开发--simpleConfig_v1 [阅读时间:约10分钟] 一.配置文件概述 二.系统环境&项目介绍 1.系统环境 2.项目的任务要求 ...
- 基于Golang的CLI 命令行程序开发
基于Golang的CLI 命令行程序开发 [阅读时间:约15分钟] 一. CLI 命令行程序概述 二. 系统环境&项目介绍&开发准备 1.系统环境 2.项目介绍 3.开发准备 三.具体 ...
- etcd 笔记(08)— 基于 etcd 实现分布式锁
1. 为什么需要分布式锁? 在分布式环境下,数据一致性问题一直是个难点.分布式与单机环境最大的不同在于它不是多线程而是多进程.由于多线程可以共享堆内存,因此可以简单地采取内存作为标记存储位置.而多进程 ...
- OpenCV 笔记(08)— 二维点、三维点、基于 Mat 的 std::vector 等常用数据结构的定义和输出
1. 定义和输出二维点 Point2f p2(3, 4);cout << "[二维点] is "<< endl << p2 << e ...
- python中的新式类与旧式类的一些基于descriptor的概念(下)
3. Descriptor介绍 3.1 Descriptor代码示例 3.2 定义 3.3 Descriptor Protocol(协议) 3.4 Descriptor调用方法 4. 基于Descri ...
- python中的新式类与旧式类的一些基于descriptor的概念(上)
python中基于descriptor的一些概念(上) 1. 前言 2. 新式类与经典类 2.1 内置的object对象 2.2 类的方法 2.2.1 静态方法 2.2.2 类方法 2.3 新式类(n ...
最新文章
- 初学 Java Web 开发,请远离各种框架,从 Servlet 开发
- 体积小巧、功能强大的代理工具 -- 3proxy
- linux命令行怎么注释,Bash Shell 注释多行的几种方法
- python中选择结构通过什么语句实现_Python中选择结构通过什么语句实现
- Shell脚本中$的用法
- 前端学习(2887):如何短时间内实现v-for createApp解决方案
- 防止arp***方法
- 2021年国货彩妆品牌推广营销趋势
- mysql完全卸载大全
- 正则表达式:re.match、re.search、re.sub、re.compile、findall、re.finditer、re.split
- dup和dup2(摘 )
- 【Python】python list 迭代删除
- wifi技术扫盲-MIMO
- 奥城大学计算机专业,美国研究生双录取的大学及可提供学位详情
- dtu虚拟服务器,DTU服务器云
- background 渐变背景
- 越狱软件可带来千万量级用户
- 全国计算机四级薪资,全国计算机四级通过率有多少
- 安装minidwep-gtk出现了“离开目录”的错误
- 阿里云国际版ECS云服务器ping不通的原因分析
热门文章
- 让你的站点(Web)一键变成APP(应用程序)(上)
- -XX:+UseParallelGC与 -XX:+UseParNewGC 区别
- Ubuntu下图片转pdf和pdf合并
- 用STM32CubeMX生成STM32F407ZG + LAN8720A 的LWIP
- 正式版上线、登录币安NFT市场,PlatoFarm近况
- audio音频不能自动播放的解决方法
- Mega软件操作教程
- eNSP 路由器配置-静态路由和缺省路由
- 电镀行业水处理分析:褪镀废水回收重金属,用什么工艺解决
- 2015美国大学计算机科学专业排名,2015美国大学本科计算机专业排名前一百