基于u3d_FPS_Demo

1、导入素材

1.1 素材

基于u3d_FPS_Demo_第1张图片

1.2 创建游戏对象

新建3d游戏对象Plane,作为地面
基于u3d_FPS_Demo_第2张图片
挂载贴图,调整地面尺寸
基于u3d_FPS_Demo_第3张图片

2、实现人物移动

2.1 创建胶囊体游戏对象

	自带胶囊碰撞体

基于u3d_FPS_Demo_第4张图片
为此对象创建脚本,添加角色控制器组件并去掉自带胶囊碰撞体组件
基于u3d_FPS_Demo_第5张图片

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就可以控制相机
基于u3d_FPS_Demo_第6张图片

3.2 创建脚本控制相机旋转

将脚本挂载到相机上,使用InputManager的鼠标控制X轴和Y轴
鼠标

public float mouseSensitivity = 100f;//视角灵敏度
    public Transform playerPos;//玩家位置

    public float xRotation = 0f;//存放累加值
    // Start is called before the first frame update
    void Start()
    {
        //隐藏光标,避免画面中出现鼠标
        Cursor.lockState = CursorLockMode.Locked;
    }

    // Update is called once per frame
    void Update()
    {
        //将轴映射到鼠标,值为当前鼠标增量乘以轴灵敏度
        float mouseX= Input.GetAxis("Mouse X")*mouseSensitivity*Time.deltaTime;
        float mouseY= Input.GetAxis("Mouse Y") * mouseSensitivity * Time.deltaTime;//坐标跟随鼠标移动,上下是Y,左右是X
        print("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;//奔跑键,左shift
    public 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 增加地面检查点

  1. 当物体与地面碰撞时,说明物体已经落地,向下的重力消失
  2. 新建Player的子物体checkGround
  3. 新建地面检测方法在移动前调用并每帧执行
 void Update()
    {
        GroundCheck();
        PlayerMove();
    }
  1. 为地面设置Layer层级
    基于u3d_FPS_Demo_第7张图片
    public bool isGround;//地面碰撞判断
    private Transform groundCheck;//地面检测点,初始化CheckGround
    private 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;
        }
    }

基于u3d_FPS_Demo_第8张图片

5.3 起跳后向下

    public float jumpForce = 3f;//跳跃力度
    public float gravity = -20f;//向下的重力
    public Vector3 velocity;//力,Y轴的一个冲量变化
        /// 
    /// 移动
    /// 
    public void PlayerMove()
    {
        if(isGround==false)//不接地,在空中累加向下的力
        {
            velocity.y += gravity * Time.deltaTime;
        }
        characterController.Move(velocity * Time.deltaTime);
        PlayerJump();

    }
    /// 
    /// 跳跃
    /// 
    public void PlayerJump()
    {
        isJump = Input.GetKey(jumpKeyInput);
        if(isJump&&isGround)//按下空格并且在地上
        {
            velocity.y =Mathf.Sqrt ( jumpForce * -2f*gravity);
        }
    }

6、人物上坡

cube搭建一个斜坡
运行发现人物无法在斜坡上跳跃:修改groundCheck坐标位置,或坐标到地面的半径大小
基于u3d_FPS_Demo_第9张图片
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 找到素材中枪械预制体

	预制体挂载到主相机下,采用射线检测实现射击的发生

基于u3d_FPS_Demo_第10张图片
枪械位置归零
基于u3d_FPS_Demo_第11张图片
基于u3d_FPS_Demo_第12张图片
修改摄像机渲染距离(Clipping Planes),解决枪视角穿模
添加UI,image作为准心

    public Transform shootPoint;//武器射击位置(子弹出膛位置
    public int range = 100;//子弹射程
     //右键瞄准,左键射击
    private bool gunShootInput;  
        void Update()
    {
        gunShootInput = Input.GetMouseButton(0);//左键按下
        if(gunShootInput)
        {
            GunFire();
        }
    }
    /// 
    /// 射击
    /// 
    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

基于u3d_FPS_Demo_第13张图片

    [Header("UI设置")]
    public Image crossHairUI;//初始化
    public Text AmmoTextUI;
    void Start()
    {
        currentBullets = bulletsMag;
        UpdateAmmoUI();//初始化一下
    }
   
    /// 
    /// 射击
    /// 
    public void GunFire()
    {
        currentBullets--;//子弹减少
        //每次子弹减少,就调用UI
        UpdateAmmoUI();
        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身上添加音源组件

基于u3d_FPS_Demo_第14张图片

8.2 行走和跑步音效

基于u3d_FPS_Demo_第15张图片
在PlayerMove()进行调用

    /// 
    /// 播放移动音效
    /// 
    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 添加开枪粒子特效

添加粒子特效预制体
基于u3d_FPS_Demo_第16张图片
脚本控制

    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 update
    void Start()
    {
        originPosition = transform.localPosition;//相对于父级物体的变换位置
    }

    // Update is called once per frame
    void 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组件管理动画
挂载到对应武器的状态机上

基于u3d_FPS_Demo_第17张图片

11.1 拿出武器、静止、查看武器

基于u3d_FPS_Demo_第18张图片

1、Exit:全部执行后退出
2、Any Status:任何状态
3、Entry:动画块入口
1. 添加拿出武器动作

在这里插入图片描述

2. 添加站立待机动画,对take_out右键make transition指向待机动画块

基于u3d_FPS_Demo_第19张图片

3. 添加查看武器动画,动画能从待机动画传递到检视武器动画,并且能传递回去
	设置进入动画条件:F键检视武器
	添加触发器类型参数

基于u3d_FPS_Demo_第20张图片
基于u3d_FPS_Demo_第21张图片
基于u3d_FPS_Demo_第22张图片
触发器设为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);

基于u3d_FPS_Demo_第23张图片

在Animator添加对应bool参数Run/Walk

基于u3d_FPS_Demo_第24张图片
基于u3d_FPS_Demo_第25张图片

11.3 换弹动画

  1. 弹匣打空换弹
  2. 弹匣未打空换弹
    基于u3d_FPS_Demo_第26张图片
    /// 
    /// 播放换弹动画
    /// 
    public void RelodAnimation()
    {
        //播放动画1
        if(currentBullets>0)
        {
            anim.Play("reload_ammo_left", 0, 0);//0表示Base Layer
            audioSource.clip=relodAmmoLeftClip;
            audioSource.Play();
        }
        if(currentBullets==0&&bulletsMag>0)
        {
            anim.Play("reload_out_of_ammo", 0, 0);
            audioSource.clip = relodOutOfAmmoClip;
            audioSource.Play();
        }
    }

基于u3d_FPS_Demo_第27张图片
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文件

你可能感兴趣的:(unity学习记录,unity,游戏引擎,3d)