第一人称射击游戏——角色控制与音频播放

第一人称射击游戏——角色控制与音频播放

角色控制器初解:

角色控制器的很多功能刚体没有的,第一个是有爬坡角度的设置就是你控制人物可以爬上多少度的斜坡,我猜测一些游戏(比如刺激战场)角色能在近乎垂直的悬崖底部爬上去可能是因为石头表面的碰撞器是由多个面构成的角色在往上爬的时候通过了某些面的判定进而一步一步的爬上去。第二个是可以设置角色可以上多高的坎,换种说法就是可以上多高的楼梯。第三个就是可以设置一个皮肤的厚度,大概意思就是角色能离一个有碰撞体的物体多近。这些东西刚体都没有如果要实现显然会增加很多工作量。
原文链接:https://blog.csdn.net/z159csdn/article/details/105893521
角色控制器的相关属性也可以参考一下这篇文章: 角色控制器 (Character Controller).
负责的角色控制器也可以参考超级角色控制器.

编程思路:

1.我们对于玩家的状态必须做一个区分,可以考虑用枚举来实现状态的区分。
2.我们一定会调用到fps_PlayerParameter中的参数做传参。
3.人物控制一定会用到:(1)是否着地,isGrounded或者自定义grounded。一般,isGrounded不推荐使用,可能经常会出现一些莫名其妙的错误。(2)人物移动方向与鼠标移动方向的一一对应。(3)player父物体的position是相对于世界坐标系的,因此需要将鼠标的相对移动增量转换成世界坐标系的绝对增量。(4)如果移动的同时按下空格键跳跃,那么需要播放跳跃对应的音频文件。
(5)注意Move没有重力,因此需要对游戏控制器施加重力,这样的话,物体不会一直处于跳跃状态。(6)利用Transform.Move函数对物体的移动方向做移动。并之后回到地面,利用Transform.Move返回的位掩码来进行判断是否着陆。着陆可以进行下一帧中动作的判定。
注意:1.我们利用枚举实现人物不同状态的标记,主要是用来决定当前前进的速度和跳跃的速度在不同状态下不同,因此,我们控制人物时统一用一个前进速度和一个跳跃速度,但是实际上需要用switch-case实现不同状态下两个速度的赋值。
2.特别地,角色控制器可以认为自带了碰撞体,但是没有collider组件。可能是为了不让修改。Move和SimpleMove都可以检测碰撞,但是一般不推荐用SimpleMove,这个是自带重力的,我们往往需要可控的重力。在Crouch动作中,人的蹲伏动作,我们需要对应改变player的碰撞体的高度height 和中心center,但是该碰撞体尺寸和中心上的改变对子物体Camera的position位置无影响。因此为了实现子物体位置上的改变,需要用函数自己调节其position。也就是Localposition。

知识储备

1.人物控制脚本的简单示例
人物控制的脚本是重复度非常高的
给出一个官方示例:

using UnityEngine;
using System.Collections;

public class ExampleClass : MonoBehaviour {
    public float speed = 6.0F;
    public float jumpSpeed = 8.0F;
    public float gravity = 20.0F;
    private Vector3 moveDirection = Vector3.zero;
    void Update() {
        CharacterController controller = GetComponent<CharacterController>();
        if (controller.isGrounded) {
            moveDirection = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));
            moveDirection = transform.TransformDirection(moveDirection);
            moveDirection *= speed;
            if (Input.GetButton("Jump"))
                moveDirection.y = jumpSpeed;      
        }
        moveDirection.y -= gravity * Time.deltaTime;
        controller.Move(moveDirection * Time.deltaTime);
    }
}

注意:
(1)不推荐使用isGrounded判断是否着陆,可以利用Move函数的返回值来判断。如果着陆,自定义一个bool类型的gounded变量。
(2)public CollisionFlags Move(Vector3 motion)
作用:给定方向让物体运动,记得给定的方向是需要绝对移动增量,也就是说,一定是在世界坐标系下的位移量。另外,返回值为CollisionFlags类型,这种类型主要包含了None,Sides,Above,Below四种。说明了没有碰撞,四周被碰撞,上面有碰撞,下面有碰撞。CollisionFlags是一种位掩码,可以通过位操作(位与)来看返回的Flag和CollisionFlags中的哪一个碰撞关系对应。
(3)Vector3 TransformDirection(Vector3 direction)或者Vector3 TransformDirection(float x,float y,float z)
鼠标的位移量一定是一个相对位移增量,而我们需要对角色控制,需要用到世界坐标系下的绝对位移增量,那么上述两个方法可以实现将一个三维向量从自身坐标系转化成世界坐标系。特点是转换不会受到scale和position的影响,返回一个具有相同长度但是是不同坐标系下的三维向量。InverseTransformDirection和其作用相反。
(4)moveDirection.y -= gravity * Time.deltaTime;的含义
我们注意,自定义的重力加速度gravity是有单位的,m/S^2。我们定义moveDirection是一个向量,必须有大小,有方向。方向就是通过鼠标移动的方向,用GetAxis返回的轴。而大小就是定义的速度Speed。那么方向向量的单位就是m/S。因此,重力加速度*deltaTime就是速度的单位。然后在Move函数中再乘一个deltaTime,就是距离单位。
(5)关于Time.deltaTime,我们再次明确一下。假设一秒有60帧,那么deltaTime就是1/60。deltaTime是为了我们画面帧数有变化的话,会自动的变成帧数的倒数。这样,乘以一个速度的时候,我们可以稳定在一秒内得到速度对应的距离。否则,帧数的变化会影响我们得到一秒内前进的真实距离。
2.枚举
枚举在调用的时候用法和指针很像,因为和指针类似,枚举类型的变量在初始化的时候一定要有指针对应的量,避免空指针的问题。

public enum Season
{	
	spring,
	summer,
	autumn,
	winter
}
class Program
{
	public state void main(){
		Season s =Season.autumn;
		Console.WriteLine(s+1);
	}
}

温馨提示:1.枚举通常和Switch-Case组合使用。例如我们根据s这一变量,对应的状态,来有响应的处理方式。
2.另外一些枚举变量我们通常不允许外界修改其内容:包括了其中字段以及字段的顺序。那么我们需要用属性访问器的方法对于枚举变量保护起来。(属性访问器中的Get要用return,但是不加“;”,Set中用value代替外界的输入值)
3.音频播放
这里,我们用到两种音频播放的方式:1.通过AudioSource组件,控制其中AudioClip属性,利用Play(),Pause(),Stop()实现音频播放与控制。当多个音频需要播放,可以通过给每AudioSource添加响应的Clip来实现。2.AudioSource.PlayClipAtPoint(AudioClip clip,Vector3 position,float volume=1.0f)这个方式,播放音频时,会自动生成一个“One shot audio”物体,并自动添加audiosource和相应地clip,同时播放多个声音会生成多个同名的物体,播放完成以后自动销毁audiosource。简单的音频播放可以采用第二种方式。

角色控制与音频播放

创建脚本fps_PlayerController,作为FP_Player的组件。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public enum PlayerState
{
    None,
    Idle,
    Walk,
    Crouch,
    Run
}
public class fps_PlayerControl : MonoBehaviour
{
    private PlayerState state = PlayerState.None;
    private bool crouching = false;
    private bool running = false;
    private bool walking = false;
    private bool stopCrouching = false;//以后再用
    private float timer = 0;//以后再用
    public PlayerState State
    {
        get
        {
            if(crouching)
                state = PlayerState.Crouch;
            else if (running)
                state = PlayerState.Run;
            else if (walking)
                state = PlayerState.Walk;
            else
                state = PlayerState.Idle;
            return state;    
        }
    }

    public float normalSpeed = 6.0f;
    public float normalJumpSpeed = 7.0f;
    public float crouchSpeed = 2.0f;
    public float crouchJumpSpeed = 5.0f;
    public float sprintSpeed = 10.0f;
    public float sprintJumpSpeed = 12.0f;
    private float speed;
    private float jumpSpeed;
    private void CurrentSpeed()
    {
        switch(State)
    
        {
            case PlayerState.Idle:
                speed = normalSpeed;
                jumpSpeed = normalJumpSpeed;
                break;
            case PlayerState.Walk:
                speed = normalSpeed;
                jumpSpeed = normalJumpSpeed;
                break;
            case PlayerState.Crouch:
                speed = crouchSpeed;
                jumpSpeed = crouchJumpSpeed;
                break;
            case PlayerState.Run:
                speed = sprintSpeed;
                jumpSpeed = sprintJumpSpeed;
                break;
        }
    }
    private fps_PlayerParameter parameter;
    private CharacterController controller;
    private AudioSource audioSource;
    private Transform mainCamera;

    private Vector3 normalControllerCenter = Vector3.zero;
    private float normalControllerHeight = 0;
    //temp,记录当前角色控制器的高度和中心,便于站起来时复原

    private float standardCamHeight;
    public float crouchDeltaHeight = 0.5f;
    private float crouchingCamHeight;
    //相机在蹲伏过程中,需要自己改变position即localPosition	
    void Start()
    {
        crouching = false;
        running = false;
        walking = false;
        speed = normalSpeed;
        jumpSpeed = normalJumpSpeed;
        parameter = this.GetComponent<fps_PlayerParameter>();
        controller = this.GetComponent<CharacterController>();
        audioSource = this.GetComponent<AudioSource>();
        mainCamera = GameObject.FindGameObjectWithTag(Tags.mainCamera).transform;
        normalControllerCenter = controller.center;
        normalControllerHeight = controller.height;
        standardCamHeight = mainCamera.localPosition.y;
        crouchingCamHeight = standardCamHeight - crouchDeltaHeight;
    }

    private bool grounded = false;
    private Vector3 moveDirection = Vector3.zero;
    public AudioClip jumpAudio;
    public float gravity = 20.0f;
    private void MoveUpdate()
    {
        if (grounded)
        {
            moveDirection = new Vector3(parameter.inputMoveVector.x, 0, parameter.inputMoveVector.y);
            moveDirection = transform.TransformDirection(moveDirection);
            moveDirection *= speed;
            if (parameter.inputJump)
            {
                moveDirection.y = jumpSpeed;
                AudioSource.PlayClipAtPoint(jumpAudio, transform.position);
                CurrentSpeed();
            }
        }
        moveDirection.y -= gravity * Time.deltaTime;
        CollisionFlags flags = controller.Move(moveDirection * Time.deltaTime);
        grounded = (flags & CollisionFlags.Below) != 0;
    }

    private void stateInput()
    {
        if (Mathf.Abs(parameter.inputMoveVector.x)> 0 && grounded || Mathf.Abs(parameter.inputMoveVector.y)> 0 && grounded)
        {
            if (parameter.inputCrouch)
            {
                crouching = true;
                running = false;
                walking = false;
            }
            else if (parameter.inputSprint)
            {
                crouching = false;
                running = true;
                walking = false;
            }
            else
            {
                crouching = false;
                running = false;
                walking = true;
            }
        }
        else
        {
            if (walking)
                walking = false;
            if (running)
                running = false;
            if (parameter.inputCrouch)
                crouching = true;
            else
                crouching = false;
        }
        CurrentSpeed();//开启不同的状态还是为了速度的变化
        if (crouching)
        {
            controller.height = normalControllerHeight - crouchDeltaHeight;
            controller.center = normalControllerCenter - new Vector3(0, crouchDeltaHeight / 2, 0);   
        }
        else
        {
            controller.height = normalControllerHeight;
            controller.center = normalControllerCenter;
        }
        UpdateCamCrouch();
    }

    public float cameraMoveSpeed = 8.0f;
    private void UpdateCamCrouch()
    {
        if (crouching)
        {
            if (mainCamera.localPosition.y > crouchingCamHeight)
            {
                if (mainCamera.localPosition.y - cameraMoveSpeed * Time.deltaTime < crouchingCamHeight)
                    mainCamera.localPosition = new Vector3(mainCamera.localPosition.x, crouchingCamHeight, mainCamera.localPosition.z);
                else
                    mainCamera.localPosition -= new Vector3(0, cameraMoveSpeed * Time.deltaTime,0);
                //注意对于localPosition的修改是用三维向量,而不能对于其中一个x或者y做修改。读的话可以单个读
            }
            else
                mainCamera.localPosition = new Vector3(mainCamera.localPosition.x, crouchingCamHeight, mainCamera.localPosition.z);
        }
        else
        {
            if (mainCamera.localPosition.y < standardCamHeight)
            {
                if (mainCamera.localPosition.y + cameraMoveSpeed * Time.deltaTime > standardCamHeight)
                    mainCamera.localPosition = new Vector3(mainCamera.localPosition.x, standardCamHeight, mainCamera.localPosition.z);
                else
                    mainCamera.localPosition += new Vector3(0, cameraMoveSpeed * Time.deltaTime, 0);
            }
            else
                mainCamera.localPosition = new Vector3(mainCamera.localPosition.x, standardCamHeight, mainCamera.localPosition.z);
        }
    }

    private void FixedUpdate()
    {
        stateInput();

        MoveUpdate();
    

        AudioManagement();
    }
    private void AudioManagement()
    {
        if (State == PlayerState.Walk)
        {
            audioSource.pitch = 0.8f;
            if (!audioSource.isPlaying)
                audioSource.Play();
        }
        else if (State == PlayerState.Run)
        {
            audioSource.pitch = 1.3f;
            if (!audioSource.isPlaying)
                audioSource.Play();
        }
        else
            audioSource.Stop();
    }
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值