射击demo
Day 2025/3/1
FPS射击第一人称射击demo
玩家移动
void Update()
{
speed = walkSpeed;
float h = Input.GetAxisRaw("Horizontal");
float v = Input.GetAxisRaw("Vertical");
direction = (transform.right*h+transform.forward*v).normalized;
characterController.Move(direction*speed*Time.deltaTime);
}
视野旋转
上下挪动相机的上下轴
Transform.localRotation = qua.euler(moveX,0,0);
相机的上下默认是反转的,并且要限制上下可转动的值
所以是
Movex -= movex
MoveX = mathf.clamp(moveX,-60,60)
RotateY -= mouseY;
RotateY = Mathf.Clamp(RotateY,-60f,60f);
transform.localRotation = Quaternion.Euler(RotateY,0f,0f);
这里犯了一个错误就是直接用mouseY移动镜头,但起始每次update都会重置mouseY,所以实际上要累加到rotateY中
视野旋转的左右挪动玩家自身左右轴
Vector3的基本函数
Vector3.right == (1,0,0)
Vector3.up== (0,1,0)
所以是
playerTransform.rotate(vector3.up*mouse.x)
奔跑和行走的判断
奔跑用shitf input.getkey判断
行走用水平和垂直是否为0判断
玩家跳跃
一个isJump和isGround的判定
按下空格后获得一个升空有效值并且isJump = true
玩家控制器会用升空值每次进行累加+并且每次执行升空值会有衰减
衰减值也要设置比较大的
Fallforce = 10f
升空值衰减到负数会下降,碰到地面后isjump = false
升空值归零
Day 2025/3/2
蹲下
几个判断变量
当前是否在蹲下
当前是否可蹲下
蹲下时高度
站立时高度
逻辑:获取任务头部位置(站立)
1然后在头部生成一个检测范围,检测到头部有物体则isCanCrouch = false
- 根据按ctrl和松开控制角色高度和中心位置,当在隧道时(检测头顶有东西),返回crouch
// 相机随人物蹲起移动0.9偏移
float targetHeight = characterController.height*0.9f;
// 相机设置平滑值
cameraHeight = Mathf.Lerp(cameraHeight,targetHeight,12f*Time.deltaTime);
transform.localPosition = Vector3.up*cameraHeight;
private void checkHead(){
// 获取头部位置并在头部生成一个检测范围
Vector3 headLocation = transform.position+Vector3.up*standHeight+new Vector3(0,0.3f,0);
// Physics.OverlapSphere(生成位置,检测范围,检测层)
hasObs = Physics.OverlapSphere(headLocation,characterController.radius,crouchLayerMask).Length>0?true:false;
}
public void crouch(){
if (hasObs)
{
return;
}
if (Input.GetKey(KeyCode.LeftControl))
{
isCrouch = true;
characterController.height = crouchHeight;
}else
{
isCrouch = false;
characterController.height = standHeight;
}
characterController.center =new Vector3(0,characterController.height/2,0);
}
音效播放
玩家正在移动时播放音效
引入枪械和相机
创建自动步枪和动画状态机
连线操作:
ExitTime:播放xx进度后转换
Fixduration:转换时间
枪械脚本,添加特效
抽象类
父类
在class前+abstract
子类
去除monobehaviour继承,继承wepon方法,右键实现抽象类
然后在父类中写常用方法
public abstract class weapon : MonoBehaviour
{
public abstract void fire();
public abstract void reload();
public abstract void reloadAnimation();
public abstract void expaningCrossUpdate(float expanDegree);
public abstract void aimIn();
public abstract void aimOut();
}
射线检测:
Raycasthit hit;
Physics.raycast(起始位置, 射出方向,out hit输出信息,射线最大距离)
枪有后坐力,射出方向添加偏移值
转换成世界坐标,然后x,y轴随机数
Day 2025/3/5
控制射速
比较简单,就是计时器累加执行fire函数
打空弹匣
三个参数
单弹匣子弹数
当前子弹数
备弹数
开火粒子特效和灯光效果
Light 灯光
灯光持续时间
PracticalSystem两个粒子特效
最小粒子数1
最大粒子数7
使用协程函数-灯光打开后0.02s关闭
协程的使用方法
Private IEnumerator void lightOn(){
Lght.enable = true
Yield return new waitforsecond(waitTime);
Light.enable = false;
}
调用
Startcoroutine(lightOn())
然后发射粒子特效个数
Muzzlepatic.emit(random)
开火音效
开火音效
带消音开火音效
换子弹
空弹匣换子弹
开镜
不同的枪械音效不同,所以要设置内部类
内部类不可以直接访问,要加上序列化
[System serializable]
Public class soudClips{
}
不知道是什么原因我的音效类序列化后并没有显示在面板上,所以就不用序列化了
准星
准星UI(这里视频里用的是img数组形式,不过我感觉可以直接引入准星,后坐力直接与准星大小正正比)
视频中用的是每次射击移动4个准星的位置,更新当前准星,使用math.clamp限制准星大小
当前准星开合度curr
默认准星开合度cross
最大开合度max
弹药数
射击模式
当前玩家行动状态
(可选)
使用协程每帧执行5次准星变大
IEnumerator crs(){
Yeild return null;
For(i<0;i=5;i++){
crossHit(time.detaltime *500 )
}
}
准星更新函数:传入目标准星,小于则逐步扩大,大于则逐步缩小
Crossupdate(num){
If(curr<num-5){
Cross(150*time.detaltime)
}elseif(curr>num+5){
Cross(-150*time.detaltime)
}
} //目标准星值要要划出一个范围,因为准星累加和累减不是整数,会反复跳动
这样写的好处是每次射击后扩大准星,但是会根据当前人物行动状态弹回
Day 2025/3/6
子弹和弹孔特效
子弹
子弹抛壳
Rigid body.velocity:向刚体设置速度
子弹向前射出的同时添加shootPointDirection后坐力范畴)*子弹力度
换子弹逻辑和子弹UI
子弹文本
射击模式文本
换弹:备弹小于等于零,返回
需要填充子弹量:弹夹上限-当前子弹,备弹小于需要补充子弹-填充剩下备弹
当前子弹数是满的不用换弹
Day 2025/3/7
添加开火动画
添加开火状态淡入淡出效果0.1s
Animator.crossfadeinfixedTime(“fire”,0.1f)
当前执行取出枪械动画不可射击
If(Animator.getCurrentAnimatorStateInfo(0).isName(“take_out”))
半自动和全自动
引入靶子
射击模式切换UI 这个直接修改就好了,就不用做方法了
CheckWeapon
射击模式枚举值
是否是全自动武器
模式切换的中间参数1,2,3
布尔值input
Public enum shootMode{
auto,
semi
}
按下切换中间值,文本,shootingmode
枚举值Switch
简单来说就是全自动inputMode =1,半自动inputMode = 2
然后switch 1和2
进入1时一直按下左键 input就为true
半自动时修改射速为0.2
开火根据input值判断开火
换弹动画
Animator.play(“”,0,0)
引入第二个audio Source
动画播完后再换弹
换弹时不能射击
行走,奔跑,检查武器动画
Animator.setbool(“run”,player.isRun)
F键检查武器
Animator.settrigger
Bug:多次按F会在结束动画后延迟多次执行检视动画
解决:在inpect动画中添加行为:清除信号量
Public string[] clearAtEnter
Public string[] clearAtExit
onstateExit(){
For i<clearatExit.length
Animator.resettrigger(clearatExit[i]) //表示清除信号
}
添加瞄准
是否正在瞄准
主摄像机
枪摄像机
瞄准扩大视野
Setbool aim = true
调整枪械位置(这里我感觉可以不移动枪械位置,中心点不变的就行,没有太大影响)
以下视频中根据播放进度执行,这边不浪费时间就按下直接执行了
Camera.filedOfView = mathf.smooth(30,60,平滑值,转换时间)
隐藏准星
播放开镜音效
开镜与腰射子弹偏移值
后坐力
Day 2025/3/9
消音武器
瞄准镜
特效
脚本-是否装备消音器-切换开枪音效
Day 2025/3/10
切换武器
当前武器编号
Input.getaxis(“mouse scrollwheel”) //鼠标滚轮
如果切换的枪超出数组范围则切回第一把,小于长度则切回最后一把
先获取所有武器下标
Ayyay.indexOf
然后取最大值
Array.Max()
拾取武器和丢武器
// 拾取
private void addWeapon(GameObject gameObject){
// 假设现在武器仓只能拿3把,武器满了不可拾取
if (inven.Count>3 || inven.Contains(gameObject))
{
return;
}
// 拾取之后切换到最后一把(新)
inven.Add(gameObject);
switchWeapon(inven.Count-1);
}
// 丢弃
private void removeWeapon(GameObject weapon){
// 当前没有武器不可丢弃
if (inven.Count == 0)
{
return;
}
// 删除,隐藏,切换
switchWeapon(currWeapon+1);
inven.Remove(weapon);
}
手枪
day 2025/3/11
散弹枪,狙击枪
散弹枪3个换弹状态
Reload_open,reload_insert,reload_close
Has_exittime = false
Conditions - shotGun_reload = true
6个reload_insert
1次打出的子弹数 = 8
循环8次射线检测
判断是否是散弹枪生成8颗子弹
If(){}
Else{
Bullet = instaniate(bulletprefab,hit.point,quaternion.fromToRotation(vector3.up,hit.normal))
}
动态改变打出的子弹数:只要gameobject.name == 4的就射出8发
狙击镜瞄准时颜色
没瞄准时颜色
狙击镜材质
狙击镜开瞄准时视野为15
Day 2025/3/12
可拾取物品
PickUp节点
添加物品组件-两倍缩放-60度
添加boxCollider
武器旋转速度
Transform.eulerangles += vector3 (0,speed*time.detaltime,0)
武器编号
武器gameobject
If enterCollider
Inventory中添加该枪械到数组,如果已有枪械则重置备弹
Player中调用inventory的add方法
If inventory.wepons.contains(weapon) return
添加玩家血量
玩家生命值
是否死亡
是否收到伤害
更新生命值
Player.health-=damage
Isdamage = true
PlayerhealthUI.text =
If health<0 player.isdead = true;playerhealthUI = “死亡”
Timescale = 0
敌人AI
敌人模型:mixamo.com
下载模型
了解导航系统并设置3条巡逻路线
Nav mesh agent网格系统
Enemy.cs
引入Animator
引入Nav mesh agent网格系统
不同敌人不同路线
Gameobject[] wayPoint
Lsit<vector3>
当前状态
加载路线void(game object)
敌人向目标点前进void moveTarget{
Vector3.moveTowards(position,targetTrans)
}
wayPoint空节点-3个点
EnemybaseState.cs
AttackState.cs 继承basestate
Patrolstate.cs 继承basestate
Day 2025/3/14
很多技术不错的程序员都喜欢用一些复杂的代码写简单的功能,不可否认这样的代码看起来更严谨代码报错率也更低,但是这样真的会令我这种新手看起来晕乎乎的
比如这段:
Up主限制了index的范围
Mathf.clamp(enemy.index,0,enemy.waypoint.count-1)
又从物理层面上设置了敌人到达最后一个导航点后下标归零的操作
If vector3.distance(enemy.transform.position,enemy.waypoint[enemy.count-1])<0.5f{
Index = 0;
}
而我只想到了下标自增到最后一个导航点就归零
If index == waypoint.count{ index = 0}
Vector3.moveToWards() //朝目标点移动,返回移动后的距离
Vector3.moveToWards(起始位置,目标位置,移动距离)
然后再调用角色导航组件navmeshagent的destination角色会朝目标点平滑移动
将公共抽象类中的主方法持续执行并传入敌人脚本,使子类可以调用敌人脚本
BaseState.cs
public abstract void onUpdate(enemy enemy); //持续执行
Enemy.cs
void Update()
{
// 当前状态持续执行
currentState.onUpdate(this);
}
到达导航点后敌人会站立一段时间
// 等待站立状态结束后继续行走
if (enemy.animState == 0)
{
timer += Time.deltaTime;
If(timer>=standTime){ timer = 0;enemy.animState = 1; }
}
If(enemy.animState == 1){
moveToTarget()
}
多个敌人随机路线的思路
一个路线管理器
将所有路线id打乱
// raw是按序分配的路线,using是打乱的路线
// 随机,添加,删除
for (int i = 0; i < rawIndex.Count; i++) //这里循环是固定的,会循环完整个数组
{
int rIndex = Random.Range(0,rawIndex.Count); //这里数组cout是动态的,每次删除元素后只会随机剩下的元素
usingIndex.Add(rIndex);
rawIndex.RemoveAt(i);
}
敌人的id是固定的
加载路线时按敌人顺序分配打乱的路线id
制作敌人血条并添加血条逻辑
敌人血量float
敌人血条UI
受到伤害UI
敌人死亡特效gameobject
是否死亡
Health方法传入damage
死亡返回
更新收到伤害
更新血量
更新血条UI
If health<0 die animator.settriger(“dying”)
枪射线判断
If hit == enemy
调用health方法,传入随机数伤害
每把枪设置minDamage和maxDamage
给敌人加上碰撞组件boxcollider
敌人脚本死亡后停止更新
敌人攻击状态切换
List<transform> 敌人检测范围
攻击间隔
攻击阶段时间
攻击距离
攻击特效
攻击位置
攻击声音
播放器
Entertrigger
If !isdead && !list.conttains && tag !=bullet
attackList.add
Exittrgger
Attack.remove
Move方法
If list.cout == 0 targetPos = Vector3.MoveTowards(transform.position,wayPoint[index],agent.speed*Time.deltaTime);
Else
targetPos = Vector3.MoveTowards(transform.position,list[0].transform.position,agent.speed*Time.deltaTime);
onupdate方法
If list.count>0
Enemy.transitiontoState(enemy.attackstate)
Animator-攻击层
Day 2025/3/15
攻击状态
Attack.cs
在攻击状态中的三个判断
没有敌人-切换回巡逻状态
有多个敌人-寻找距离最近的敌人
有1个敌人-攻击敌人
最后if targetpoint.tag == player{}
Enemy.moveToTarget()
Day 2025/3/16
玩家血雾效果
Image 玩家血雾
Color 红色
Color.clear清空色
制作敌人攻击音效
玩家死亡后两个播放器暂停声音
完成,最终效果展示