赞
踩
推荐阅读
我们在使用官方的资源包的时候,会发现官方案例中自带的人物控制器非常好用,下面就将官方的资源包中的人物控制器的脚本进行分析。
一是为了理解学习代码,二是为了让大家有一个清晰的认识。
下载资源包:
有的就不用下载了,如果没有的话下载下来,导入到Unity中。
解压后,可以看到很多的.unitypackage后缀的文件:
将Characters.unitypackage这个文件导入到Unity中即可。
然后打开Assets->Standard Assets ->Characters ->FirstPersonCharacter ->Prefabs文件夹:
选择预制体拖入到场景中就可以使用了。
有两个预制体
主要组件有Character Controller、脚本First Person Controller、Rigidbody
这个是FPS第一人称控制器,模拟FPS游戏中人物移动的方式,是第一人称控制器。
鼠标锁定,视角跟随鼠标移动而移动。WSAD控制人物移动
主要组件有Capsule Collider、脚本RigidBody First Person Controller
与FPSController控制器不同的一点是,一个是用CharacterController控制移动,一个是控制人物本身的刚体,给刚体添加一个方向力,就可以移动
using System; using UnityEngine; using UnityStandardAssets.CrossPlatformInput; using UnityStandardAssets.Utility; using Random = UnityEngine.Random; namespace UnityStandardAssets.Characters.FirstPerson { //自动添加关联的脚本 [RequireComponent(typeof (CharacterController))] [RequireComponent(typeof (AudioSource))] public class FirstPersonController : MonoBehaviour { //判断是否在走 [SerializeField] private bool m_IsWalking; //走路的速度 [SerializeField] private float m_WalkSpeed; //奔跑的速度 [SerializeField] private float m_RunSpeed; //模仿随机行走的速度 [SerializeField] [Range(0f, 1f)] private float m_RunstepLenghten; //跳跃速度 [SerializeField] private float m_JumpSpeed; //判断是否在空中,如果在空中接给一个下降的力 [SerializeField] private float m_StickToGroundForce; //重力 [SerializeField] private float m_GravityMultiplier; //视角控制脚本 [SerializeField] private MouseLook m_MouseLook; [SerializeField] private bool m_UseFovKick; //FovKick脚本 [SerializeField] private FOVKick m_FovKick = new FOVKick(); [SerializeField] private bool m_UseHeadBob; [SerializeField] private CurveControlledBob m_HeadBob = new CurveControlledBob(); [SerializeField] private LerpControlledBob m_JumpBob = new LerpControlledBob(); [SerializeField] private float m_StepInterval; [SerializeField] private AudioClip[] m_FootstepSounds; // an array of footstep sounds that will be randomly selected from. [SerializeField] private AudioClip m_JumpSound; // the sound played when character leaves the ground. [SerializeField] private AudioClip m_LandSound; // the sound played when character touches back on ground. private Camera m_Camera; private bool m_Jump; private float m_YRotation; private Vector2 m_Input; private Vector3 m_MoveDir = Vector3.zero; private CharacterController m_CharacterController; private CollisionFlags m_CollisionFlags; private bool m_PreviouslyGrounded; private Vector3 m_OriginalCameraPosition; private float m_StepCycle; private float m_NextStep; private bool m_Jumping; private AudioSource m_AudioSource; // Use this for initialization private void Start() { m_CharacterController = GetComponent<CharacterController>(); m_Camera = Camera.main; m_OriginalCameraPosition = m_Camera.transform.localPosition; m_FovKick.Setup(m_Camera); m_HeadBob.Setup(m_Camera, m_StepInterval); m_StepCycle = 0f; m_NextStep = m_StepCycle/2f; m_Jumping = false; m_AudioSource = GetComponent<AudioSource>(); m_MouseLook.Init(transform , m_Camera.transform); } // Update is called once per frame private void Update() { //视角控制 RotateView(); // the jump state needs to read here to make sure it is not missed //跳转状态判断 if (!m_Jump) { m_Jump = CrossPlatformInputManager.GetButtonDown("Jump"); } //判断是否在地面上 if (!m_PreviouslyGrounded && m_CharacterController.isGrounded) { StartCoroutine(m_JumpBob.DoBobCycle()); PlayLandingSound(); m_MoveDir.y = 0f; m_Jumping = false; } //不在地面上,并且不在跳跃状态 if (!m_CharacterController.isGrounded && !m_Jumping && m_PreviouslyGrounded) { m_MoveDir.y = 0f; } m_PreviouslyGrounded = m_CharacterController.isGrounded; } //播放降落的声音 private void PlayLandingSound() { m_AudioSource.clip = m_LandSound; m_AudioSource.Play(); m_NextStep = m_StepCycle + .5f; } //控制人物行走 private void FixedUpdate() { float speed; GetInput(out speed); // always move along the camera forward as it is the direction that it being aimed at //始终沿着摄像机向前移动,因为它是瞄准的方向 Vector3 desiredMove = transform.forward*m_Input.y + transform.right*m_Input.x; // get a normal for the surface that is being touched to move along it //得到一个正常的表面,被触摸移动它 RaycastHit hitInfo; Physics.SphereCast(transform.position, m_CharacterController.radius, Vector3.down, out hitInfo, m_CharacterController.height/2f); desiredMove = Vector3.ProjectOnPlane(desiredMove, hitInfo.normal).normalized; m_MoveDir.x = desiredMove.x*speed; m_MoveDir.z = desiredMove.z*speed; if (m_CharacterController.isGrounded) { m_MoveDir.y = -m_StickToGroundForce; if (m_Jump) { m_MoveDir.y = m_JumpSpeed; PlayJumpSound(); m_Jump = false; m_Jumping = true; } } else { m_MoveDir += Physics.gravity*m_GravityMultiplier*Time.fixedDeltaTime; } m_CollisionFlags = m_CharacterController.Move(m_MoveDir*Time.fixedDeltaTime); ProgressStepCycle(speed); UpdateCameraPosition(speed); } //播放跳跃的声音 private void PlayJumpSound() { m_AudioSource.clip = m_JumpSound; m_AudioSource.Play(); } private void ProgressStepCycle(float speed) { if (m_CharacterController.velocity.sqrMagnitude > 0 && (m_Input.x != 0 || m_Input.y != 0)) { m_StepCycle += (m_CharacterController.velocity.magnitude + (speed*(m_IsWalking ? 1f : m_RunstepLenghten)))* Time.fixedDeltaTime; } if (!(m_StepCycle > m_NextStep)) { return; } m_NextStep = m_StepCycle + m_StepInterval; PlayFootStepAudio(); } //播放脚本的声音 private void PlayFootStepAudio() { if (!m_CharacterController.isGrounded) { return; } // pick & play a random footstep sound from the array, // excluding sound at index 0 int n = Random.Range(1, m_FootstepSounds.Length); m_AudioSource.clip = m_FootstepSounds[n]; m_AudioSource.PlayOneShot(m_AudioSource.clip); // move picked sound to index 0 so it's not picked next time m_FootstepSounds[n] = m_FootstepSounds[0]; m_FootstepSounds[0] = m_AudioSource.clip; } //控制摄像机的视角移动 private void UpdateCameraPosition(float speed) { Vector3 newCameraPosition; if (!m_UseHeadBob) { return; } if (m_CharacterController.velocity.magnitude > 0 && m_CharacterController.isGrounded) { m_Camera.transform.localPosition = m_HeadBob.DoHeadBob(m_CharacterController.velocity.magnitude + (speed*(m_IsWalking ? 1f : m_RunstepLenghten))); newCameraPosition = m_Camera.transform.localPosition; newCameraPosition.y = m_Camera.transform.localPosition.y - m_JumpBob.Offset(); } else { newCameraPosition = m_Camera.transform.localPosition; newCameraPosition.y = m_OriginalCameraPosition.y - m_JumpBob.Offset(); } m_Camera.transform.localPosition = newCameraPosition; } //获得键盘输入 private void GetInput(out float speed) { // Read input float horizontal = CrossPlatformInputManager.GetAxis("Horizontal"); float vertical = CrossPlatformInputManager.GetAxis("Vertical"); bool waswalking = m_IsWalking; #if !MOBILE_INPUT // On standalone builds, walk/run speed is modified by a key press. // keep track of whether or not the character is walking or running m_IsWalking = !Input.GetKey(KeyCode.LeftShift); #endif // set the desired speed to be walking or running speed = m_IsWalking ? m_WalkSpeed : m_RunSpeed; m_Input = new Vector2(horizontal, vertical); // normalize input if it exceeds 1 in combined length: if (m_Input.sqrMagnitude > 1) { m_Input.Normalize(); } // handle speed change to give an fov kick // only if the player is going to a run, is running and the fovkick is to be used if (m_IsWalking != waswalking && m_UseFovKick && m_CharacterController.velocity.sqrMagnitude > 0) { StopAllCoroutines(); StartCoroutine(!m_IsWalking ? m_FovKick.FOVKickUp() : m_FovKick.FOVKickDown()); } } //选择视角到正常角度 private void RotateView() { m_MouseLook.LookRotation (transform, m_Camera.transform); } //控制器碰撞反应 private void OnControllerColliderHit(ControllerColliderHit hit) { Rigidbody body = hit.collider.attachedRigidbody; //dont move the rigidbody if the character is on top of it if (m_CollisionFlags == CollisionFlags.Below) { return; } if (body == null || body.isKinematic) { return; } body.AddForceAtPosition(m_CharacterController.velocity*0.1f, hit.point, ForceMode.Impulse); } } }
其中核心的,让人物角色移动的代码:
m_CollisionFlags = m_CharacterController.Move(m_MoveDir*Time.fixedDeltaTime);
m_CollisionFlags 碰撞检测的旗标
m_CharacterController 角色的CharacterController组件
m_MoveDir 当前移动的方向乘上键盘获得的输入得到的值
Time.fixedDeltaTime 固定的时间增量
其中如果要解除鼠标锁定的话可以到这个脚本中修改。
//更新鼠标锁定的状态的 public void UpdateCursorLock() { //if the user set "lockCursor" we check & properly lock the cursos if (lockCursor) InternalLockUpdate(); } //控制鼠标锁定 private void InternalLockUpdate() { if (Input.GetKeyUp(KeyCode.Escape)) { m_cursorIsLocked = false; } else if (Input.GetMouseButtonUp(1)) { m_cursorIsLocked = true; } if (m_cursorIsLocked) { Cursor.lockState = CursorLockMode.Locked; Cursor.visible = false; } else if (!m_cursorIsLocked) { Cursor.lockState = CursorLockMode.None; Cursor.visible = true; } }
using System; using UnityEngine; using UnityStandardAssets.CrossPlatformInput; namespace UnityStandardAssets.Characters.FirstPerson { //自动添加关联的脚本 [RequireComponent(typeof (Rigidbody))] [RequireComponent(typeof (CapsuleCollider))] public class RigidbodyFirstPersonController : MonoBehaviour { [Serializable] public class MovementSettings { //前进速度 public float ForwardSpeed = 8.0f; // Speed when walking forward //后退速度 public float BackwardSpeed = 4.0f; // Speed when walking backwards //走路时速度横向 public float StrafeSpeed = 4.0f; // Speed when walking sideways //奔跑的速度 public float RunMultiplier = 2.0f; // Speed when sprinting //奔跑键设置为LeftShift public KeyCode RunKey = KeyCode.LeftShift; //跳跃的力 public float JumpForce = 30f; //动画曲线,用在了模型动画播放时的碰撞盒缩放及重力调节 public AnimationCurve SlopeCurveModifier = new AnimationCurve(new Keyframe(-90.0f, 1.0f), new Keyframe(0.0f, 1.0f), new Keyframe(90.0f, 0.0f)); //当前的目标速度 [HideInInspector] public float CurrentTargetSpeed = 8f; #if !MOBILE_INPUT private bool m_Running; #endif //更新所需的目标速度 public void UpdateDesiredTargetSpeed(Vector2 input) { if (input == Vector2.zero) return; if (input.x > 0 || input.x < 0) { //strafe CurrentTargetSpeed = StrafeSpeed; } if (input.y < 0) { //backwards CurrentTargetSpeed = BackwardSpeed; } if (input.y > 0) { //forwards //handled last as if strafing and moving forward at the same time forwards speed should take precedence CurrentTargetSpeed = ForwardSpeed; } #if !MOBILE_INPUT if (Input.GetKey(RunKey)) { CurrentTargetSpeed *= RunMultiplier; m_Running = true; } else { m_Running = false; } #endif } #if !MOBILE_INPUT public bool Running { get { return m_Running; } } #endif } //高级设置 [Serializable] public class AdvancedSettings { //检查控制器是否接地的距离(0.01f似乎最适合这个) public float groundCheckDistance = 0.01f; // distance for checking if the controller is grounded ( 0.01f seems to work best for this ) //停止这个角色 public float stickToGroundHelperDistance = 0.5f; // stops the character //当没有输入时控制器到达停止的速度 public float slowDownRate = 20f; // rate at which the controller comes to a stop when there is no input //用户能够控制在空气中移动的方向吗 public bool airControl; // can the user control the direction that is being moved in the air } public Camera cam; public MovementSettings movementSettings = new MovementSettings(); public MouseLook mouseLook = new MouseLook(); public AdvancedSettings advancedSettings = new AdvancedSettings(); private Rigidbody m_RigidBody; private CapsuleCollider m_Capsule; private float m_YRotation; private Vector3 m_GroundContactNormal; private bool m_Jump, m_PreviouslyGrounded, m_Jumping, m_IsGrounded; public Vector3 Velocity { get { return m_RigidBody.velocity; } } public bool Grounded { get { return m_IsGrounded; } } public bool Jumping { get { return m_Jumping; } } public bool Running { get { #if !MOBILE_INPUT return movementSettings.Running; #else return false; #endif } } private void Start() { m_RigidBody = GetComponent<Rigidbody>(); m_Capsule = GetComponent<CapsuleCollider>(); mouseLook.Init (transform, cam.transform); } private void Update() { //控制视角转动到正常位置 RotateView(); //跳跃状态判断转移 if (CrossPlatformInputManager.GetButtonDown("Jump") && !m_Jump) { m_Jump = true; } } //移动函数 private void FixedUpdate() { //判断底部与地面的距离 GroundCheck(); //获得输入 Vector2 input = GetInput(); //如果检测到键盘有输入的话 if ((Mathf.Abs(input.x) > float.Epsilon || Mathf.Abs(input.y) > float.Epsilon) && (advancedSettings.airControl || m_IsGrounded)) { // always move along the camera forward as it is the direction that it being aimed at //始终沿着摄像机向前移动,因为它是瞄准的方向 Vector3 desiredMove = cam.transform.forward*input.y + cam.transform.right*input.x; desiredMove = Vector3.ProjectOnPlane(desiredMove, m_GroundContactNormal).normalized; desiredMove.x = desiredMove.x*movementSettings.CurrentTargetSpeed; desiredMove.z = desiredMove.z*movementSettings.CurrentTargetSpeed; desiredMove.y = desiredMove.y*movementSettings.CurrentTargetSpeed; //判断刚体上面的速度向量的平方是否 小于 当前移动速度的平方 if (m_RigidBody.velocity.sqrMagnitude < (movementSettings.CurrentTargetSpeed*movementSettings.CurrentTargetSpeed)) { //给刚体添加一个向前的作用力 m_RigidBody.AddForce(desiredMove*SlopeMultiplier(), ForceMode.Impulse); } } //是否在地面上,判断跳跃 if (m_IsGrounded) { m_RigidBody.drag = 5f; if (m_Jump) { m_RigidBody.drag = 0f; m_RigidBody.velocity = new Vector3(m_RigidBody.velocity.x, 0f, m_RigidBody.velocity.z); m_RigidBody.AddForce(new Vector3(0f, movementSettings.JumpForce, 0f), ForceMode.Impulse); m_Jumping = true; } if (!m_Jumping && Mathf.Abs(input.x) < float.Epsilon && Mathf.Abs(input.y) < float.Epsilon && m_RigidBody.velocity.magnitude < 1f) { m_RigidBody.Sleep(); } } else { m_RigidBody.drag = 0f; if (m_PreviouslyGrounded && !m_Jumping) { StickToGroundHelper(); } } m_Jump = false; } //斜率乘数 private float SlopeMultiplier() { float angle = Vector3.Angle(m_GroundContactNormal, Vector3.up); return movementSettings.SlopeCurveModifier.Evaluate(angle); } //判断是否在地面上,不在地面上就落在地面上 private void StickToGroundHelper() { RaycastHit hitInfo; if (Physics.SphereCast(transform.position, m_Capsule.radius, Vector3.down, out hitInfo, ((m_Capsule.height/2f) - m_Capsule.radius) + advancedSettings.stickToGroundHelperDistance)) { if (Mathf.Abs(Vector3.Angle(hitInfo.normal, Vector3.up)) < 85f) { m_RigidBody.velocity = Vector3.ProjectOnPlane(m_RigidBody.velocity, hitInfo.normal); } } } //检测键盘输入 private Vector2 GetInput() { Vector2 input = new Vector2 { x = CrossPlatformInputManager.GetAxis("Horizontal"), y = CrossPlatformInputManager.GetAxis("Vertical") }; movementSettings.UpdateDesiredTargetSpeed(input); return input; } //视角移动 private void RotateView() { //avoids the mouse looking if the game is effectively paused if (Mathf.Abs(Time.timeScale) < float.Epsilon) return; // get the rotation before it's changed float oldYRotation = transform.eulerAngles.y; mouseLook.LookRotation (transform, cam.transform); if (m_IsGrounded || advancedSettings.airControl) { // Rotate the rigidbody velocity to match the new direction that the character is looking Quaternion velRotation = Quaternion.AngleAxis(transform.eulerAngles.y - oldYRotation, Vector3.up); m_RigidBody.velocity = velRotation*m_RigidBody.velocity; } } /// sphere cast down just beyond the bottom of the capsule to see if the capsule is colliding round the bottom //球体在胶囊的底部被压下,看看胶囊是否在底部碰撞 private void GroundCheck() { m_PreviouslyGrounded = m_IsGrounded; RaycastHit hitInfo; if (Physics.SphereCast(transform.position, m_Capsule.radius, Vector3.down, out hitInfo, ((m_Capsule.height/2f) - m_Capsule.radius) + advancedSettings.groundCheckDistance)) { m_IsGrounded = true; m_GroundContactNormal = hitInfo.normal; } else { m_IsGrounded = false; m_GroundContactNormal = Vector3.up; } if (!m_PreviouslyGrounded && m_IsGrounded && m_Jumping) { m_Jumping = false; } } } }
其中核心的人物角色移动代码:
m_RigidBody.AddForce(desiredMove*SlopeMultiplier(), ForceMode.Impulse);
desiredMove: 这个是获取到摄像机正前方 x 键盘输入 Vertical的值,摄像机右边 x 键盘输入 Horizontal的值
SlopeMuliplier: 是斜率乘数,说白了就是摩擦力 反作用力
ForceMode 作用力方式 枚举类型
(1)ForceMode.Force:默认方式,使用刚体的质量计算,以每帧间隔时间为单位计算动量。
(2)ForceMode.Acceleration:在此种作用方式下会忽略刚体的实际质量而采用默认值1.0f,时间间隔以系统帧频间隔计算(默认值为0.02s)
(3)ForceMode.Impulse:此种方式采用瞬间力作用方式,即把t的值默认为1,不再采用系统的帧频间隔
(4)ForceMode.VelocityChange:此种作用方式下将忽略刚体的实际质量,采用默认质量1.0,同时也忽略系统的实际帧频间隔,采用默认间隔1.0
接下来介绍这几种预制体
用于赛车游戏的跨平台输入控制。可选择两种输入形式:
一,Vertical轴输入值由一对按钮控制,Horizontal轴输入值由设备重力感应控制(目标平台为PC时使用鼠标位置模拟,下同);
二,两个方向的输入值均由触屏滑动(移动设备)或鼠标拖拽(PC)控制。
using System; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; namespace UnityStandardAssets.CrossPlatformInput { //自动添加的组件 [RequireComponent(typeof(Image))] public class TouchPad : MonoBehaviour, IPointerDownHandler, IPointerUpHandler { // Options for which axes to use //使用那个轴 public enum AxisOption { Both, // Use both OnlyHorizontal, // Only horizontal OnlyVertical // Only vertical } //控制方式 public enum ControlStyle { //绝对的,从图片的中心操作 Absolute, // operates from teh center of the image //相对的,从触碰到的中心开始操作 Relative, // operates from the center of the initial touch //从滑动触碰开始,没有中心 Swipe, // swipe to touch touch no maintained center } public AxisOption axesToUse = AxisOption.Both; // The options for the axes that the still will use public ControlStyle controlStyle = ControlStyle.Absolute; // control style to use public string horizontalAxisName = "Horizontal"; // The name given to the horizontal axis for the cross platform input public string verticalAxisName = "Vertical"; // The name given to the vertical axis for the cross platform input public float Xsensitivity = 1f; public float Ysensitivity = 1f; //起始点 Vector3 m_StartPos; //上一个点 Vector2 m_PreviousDelta; //Joytick输出 Vector3 m_JoytickOutput; //使用X或者Y轴 bool m_UseX; // Toggle for using the x axis bool m_UseY; // Toggle for using the Y axis //参考交叉平台输入中的操纵杆 CrossPlatformInputManager.VirtualAxis m_HorizontalVirtualAxis; // Reference to the joystick in the cross platform input CrossPlatformInputManager.VirtualAxis m_VerticalVirtualAxis; // Reference to the joystick in the cross platform input //是否在拖动 bool m_Dragging; int m_Id = -1; //刷卡方式控制触摸 Vector2 m_PreviousTouchPos; // swipe style control touch #if !UNITY_EDITOR private Vector3 m_Center; private Image m_Image; #else //鼠标开始的位置 Vector3 m_PreviousMouse; #endif void OnEnable() { //创建虚拟轴 CreateVirtualAxes(); #if !UNITY_EDITOR m_Image = GetComponent<Image>(); m_Center = m_Image.transform.position; #endif } //创建虚拟轴 void CreateVirtualAxes() { // set axes to use //设置轴使用 m_UseX = (axesToUse == AxisOption.Both || axesToUse == AxisOption.OnlyHorizontal); m_UseY = (axesToUse == AxisOption.Both || axesToUse == AxisOption.OnlyVertical); // create new axes based on axes to use //创建新的基于使用的坐标轴 if (m_UseX) { m_HorizontalVirtualAxis = new CrossPlatformInputManager.VirtualAxis(horizontalAxisName); CrossPlatformInputManager.RegisterVirtualAxis(m_HorizontalVirtualAxis); } if (m_UseY) { m_VerticalVirtualAxis = new CrossPlatformInputManager.VirtualAxis(verticalAxisName); CrossPlatformInputManager.RegisterVirtualAxis(m_VerticalVirtualAxis); } } //更新轴的位置 void UpdateVirtualAxes(Vector3 value) { value = value.normalized; if (m_UseX) { m_HorizontalVirtualAxis.Update(value.x); } if (m_UseY) { m_VerticalVirtualAxis.Update(value.y); } } //按下指示器 public void OnPointerDown(PointerEventData data) { m_Dragging = true; m_Id = data.pointerId; #if !UNITY_EDITOR if (controlStyle != ControlStyle.Absolute ) m_Center = data.position; #endif } //获得拖动的坐标并且更新坐标轴 void Update() { if (!m_Dragging) { return; } if (Input.touchCount == 2) { if (Input.GetTouch(0).phase == TouchPhase.Moved || Input.GetTouch(1).phase == TouchPhase.Moved) { var tempPosition1 = Input.GetTouch(0).position; var tempPosition2 = Input.GetTouch(1).position; float sprtTemp = Mathf.Sqrt((tempPosition1.x - tempPosition2.x) * (tempPosition1.x - tempPosition2.x) + (tempPosition1.y - tempPosition2.y) * (tempPosition1.y - tempPosition2.y)); } } //手指点击的时候id是1,但是如果点住之后不松开然后滑动的话下面的if就会执行 if (Input.touchCount >= m_Id + 1 && m_Id != -1) { #if !UNITY_EDITOR if (controlStyle == ControlStyle.Swipe) { m_Center = m_PreviousTouchPos; m_PreviousTouchPos = Input.touches[m_Id].position; } Vector2 pointerDelta = new Vector2(Input.touches[m_Id].position.x - m_Center.x , Input.touches[m_Id].position.y - m_Center.y).normalized; pointerDelta.x *= Xsensitivity; pointerDelta.y *= Ysensitivity; #else Vector2 pointerDelta; pointerDelta.x = Input.mousePosition.x - m_PreviousMouse.x; pointerDelta.y = Input.mousePosition.y - m_PreviousMouse.y; m_PreviousMouse = new Vector3(Input.mousePosition.x, Input.mousePosition.y, 0f); #endif UpdateVirtualAxes(new Vector3(pointerDelta.x, pointerDelta.y, 0)); } } //松开指示器 public void OnPointerUp(PointerEventData data) { m_Dragging = false; m_Id = -1; UpdateVirtualAxes(Vector3.zero); } //程序关闭调用 void OnDisable() { if (CrossPlatformInputManager.AxisExists(horizontalAxisName)) CrossPlatformInputManager.UnRegisterVirtualAxis(horizontalAxisName); if (CrossPlatformInputManager.AxisExists(verticalAxisName)) CrossPlatformInputManager.UnRegisterVirtualAxis(verticalAxisName); } } }
左边手指滑动可以控制行走
右边手指滑动可以控制视角转动
Jump就是跳
演示了多点触控情景下TouchPad脚本的使用方式,通过将不同区域的滑动或拖拽数据映射到不同的虚拟轴来避免冲突。
用于飞行器的跨平台输入控制。主要演示了ButtonHandler脚本的使用,自动将触控或鼠标指针的按下和抬起映射为特定虚拟轴的状态变化。同时将重力感应数据映射为横向和纵向输入值。
主要演示Joystick脚本的使用,通过滑动或拖拽控制输入,与TouchPad的区别在于
MobileJoystick的使用是根据手指拖动的距离来移动
而TouchPad是与上一帧位置间的距离作为输入值
无UI元素,可在代码中通过CrossPlatformInputManager获取其映射轴的值,输入来源为重力感应数据(移动平台)或鼠标位置(PC)。
首先如果不是在安卓平台的话,直接把预制体拖入层级视图Hierarchy是不会显示的
首先切换成安卓平台 File->BUild Settings->选中安卓平台Android->Switch platform就行了
然后把预制体拖入到层级视图里面就显示了
最后附上一段Joystick脚本在不同方向显示不同sprite的代码
也就是人们常说的八方向控制摇杆移动的方法
判断角度控制摇杆移动
Joystick脚本,这个脚本我已经修改过了,加了一个8个方向显示不同的Sprite的效果
using System; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; namespace UnityStandardAssets.CrossPlatformInput { public class Joystick : MonoBehaviour, IPointerDownHandler, IPointerUpHandler, IDragHandler { public Sprite[] sourceImage; private Image mobileJoystickImage; private Image arrowImage; private Image backgroundImage; public Material[] materialsImage; //虚拟轴的选择 public enum AxisOption { // Options for which axes to use //两种都有 Both, // Use both //只有横轴 OnlyHorizontal, // Only horizontal //只有纵轴 OnlyVertical // Only vertical } //移动的幅度 public int MovementRange = 100; //使用的是个虚拟轴 public AxisOption axesToUse = AxisOption.Both; // The options for the axes that the still will use public string horizontalAxisName = "Horizontal"; // The name given to the horizontal axis for the cross platform input public string verticalAxisName = "Vertical"; // The name given to the vertical axis for the cross platform input //初始点 Vector3 m_StartPos; //使用x轴 bool m_UseX; // Toggle for using the x axis //使用y轴 bool m_UseY; // Toggle for using the Y axis //处理跨平台的虚拟轴input数据,然后移动 CrossPlatformInputManager.VirtualAxis m_HorizontalVirtualAxis; // Reference to the joystick in the cross platform input CrossPlatformInputManager.VirtualAxis m_VerticalVirtualAxis; // Reference to the joystick in the cross platform input void Start() { mobileJoystickImage = GameObject.Find("MobileJoystick").GetComponent<Image>(); arrowImage = GameObject.Find("ArrowImage").GetComponent<Image>(); backgroundImage = GameObject.Find("BackgroundImage").GetComponent<Image>(); arrowImage.enabled = false; backgroundImage.enabled = false; } void OnEnable() { //初始点的位置 m_StartPos = transform.localPosition; //创建虚拟轴的位置 CreateVirtualAxes(); } //更新虚拟轴的位置 void UpdateVirtualAxes(Vector3 value) { //value是手指拖动虚拟轴移动后的位置 var delta = value - m_StartPos; delta /= MovementRange; if (m_UseX) { //更新x轴的坐标 m_HorizontalVirtualAxis.Update(delta.x); } if (m_UseY) { //更新y轴的坐标 m_VerticalVirtualAxis.Update(delta.y); } } //创建虚拟轴的位置 void CreateVirtualAxes() { // set axes to use //设置轴使用 m_UseX = (axesToUse == AxisOption.Both || axesToUse == AxisOption.OnlyHorizontal); m_UseY = (axesToUse == AxisOption.Both || axesToUse == AxisOption.OnlyVertical); // create new axes based on axes to use //创建基于坐标轴的新轴使用 if (m_UseX) { m_HorizontalVirtualAxis = new CrossPlatformInputManager.VirtualAxis(horizontalAxisName); CrossPlatformInputManager.RegisterVirtualAxis(m_HorizontalVirtualAxis); } if (m_UseY) { m_VerticalVirtualAxis = new CrossPlatformInputManager.VirtualAxis(verticalAxisName); CrossPlatformInputManager.RegisterVirtualAxis(m_VerticalVirtualAxis); } } //手指拖动虚拟杆的时候调用这个函数 public void OnDrag(PointerEventData data) { arrowImage.enabled = true; backgroundImage.enabled = true; Vector3 newPos = Vector3.zero; //根据拖动的坐标值返回角度 float angles = Mathf.Atan2(data.position.y - m_StartPos.x, data.position.x - m_StartPos.y) * Mathf.Rad2Deg; #region 四个方向 //if (angles > -45 && angles < 0 || angles > 45 && angles < 90) //{ // //right // mobileJoystickImage.sprite = sourceImage[3]; //} //else if (angles > 90 && angles < 135) //{ // //forware // mobileJoystickImage.sprite = sourceImage[0]; //} //else if (angles > 135 && angles < 179 || angles > -145 && angles < -180) //{ // //left // mobileJoystickImage.sprite = sourceImage[2]; //} //else if (angles < -45 && angles > -135) //{ // //back // mobileJoystickImage.sprite = sourceImage[1]; //} //if (data.position.x == 200 && data.position.y == 200) //{ // //origin // mobileJoystickImage.sprite = sourceImage[4]; //} #endregion #region 八个方向 if (angles > 67.5 && angles < 112.5) { //up arrowImage.sprite = sourceImage[0]; } else if (angles > 112.5 && angles < 157.5) { //leftup arrowImage.sprite = sourceImage[4]; } else if (angles > 157.5 && angles < 180 || angles > -180 && angles < -157.5) { //left arrowImage.sprite = sourceImage[2]; } else if (angles > -157.5 && angles < -112.5) { //leftdown arrowImage.sprite = sourceImage[5]; } else if (angles > -112.5 && angles < -67.5) { //down arrowImage.sprite = sourceImage[1]; } else if (angles > -67.5 && angles < -22.5) { //rightdown arrowImage.sprite = sourceImage[7]; } else if (angles > -22.5 && angles < 22.5) { //right arrowImage.sprite = sourceImage[3]; } else if (angles > 22.5 && angles < 67.5) { //rightup arrowImage.sprite = sourceImage[6]; } #endregion //返回x轴的值 if (m_UseX) { int delta = (int)(data.position.x - m_StartPos.x); delta = Mathf.Clamp(delta, -MovementRange, MovementRange); newPos.x = delta; } //返回y轴的值 if (m_UseY) { int delta = (int)(data.position.y - m_StartPos.y); delta = Mathf.Clamp(delta, -MovementRange, MovementRange); newPos.y = delta; } //虚拟轴移动到的位置 transform.position = new Vector3(m_StartPos.x + newPos.x, m_StartPos.y + newPos.y, m_StartPos.z + newPos.z); //更新虚拟轴的位置 UpdateVirtualAxes(transform.position); } //松开虚拟轴 public void OnPointerUp(PointerEventData data) { mobileJoystickImage.sprite = sourceImage[8]; arrowImage.enabled = false; backgroundImage.enabled = false; transform.position = m_StartPos; UpdateVirtualAxes(m_StartPos); } //按下虚拟轴 public void OnPointerDown(PointerEventData data) { } //从交叉平台的输入中删除操纵杆 void OnDisable() { // remove the joysticks from the cross platform input if (m_UseX) { m_HorizontalVirtualAxis.Remove(); } if (m_UseY) { m_VerticalVirtualAxis.Remove(); } } } }
还有就是如果想要joystick的按钮有动态的显示,比如按下之后高亮,或者开始隐藏,点击之后出现,或者拖动按钮出位置之后隐藏这些功能的话可以添加 UGUI的Button组件
设置这些颜色的alpha的值就可以了
有两个预制体,两个预制体不同的地方在于一个是AI有NavMeshAgent控制器
一个没有
AIThirdPersonController
重要组件 Animator、Rigidbody 、CapsuleCollider、NavMeshAgent、AICharacterControl、ThirdPersonCharactterr
using System; using UnityEngine; namespace UnityStandardAssets.Characters.ThirdPerson { [RequireComponent(typeof (NavMeshAgent))] [RequireComponent(typeof (ThirdPersonCharacter))] public class AICharacterControl : MonoBehaviour { //获得自动寻路的NavMeshAgent public NavMeshAgent agent { get; private set; } // the navmesh agent required for the path finding //脚本ThirdPersonCHaracter控制行走 public ThirdPersonCharacter character { get; private set; } // the character we are controlling //目标点 public Transform target; // target to aim for // Use this for initialization private void Start() { // get the components on the object we need ( should not be null due to require component so no need to check ) agent = GetComponentInChildren<NavMeshAgent>(); character = GetComponent<ThirdPersonCharacter>(); agent.updateRotation = false; agent.updatePosition = true; } // Update is called once per frame private void Update() { if (target != null) { agent.SetDestination(target.position); // use the values to move the character //使用值来移动字符 character.Move(agent.desiredVelocity, false, false); } else { // We still need to call the character's move function, but we send zeroed input as the move param. // 仍然调用Move函数,但是输入zero点 character.Move(Vector3.zero, false, false); } } //获得目标点 public void SetTarget(Transform target) { this.target = target; } } }
using UnityEngine; namespace UnityStandardAssets.Characters.ThirdPerson { //自动关联组件 [RequireComponent(typeof(Rigidbody))] [RequireComponent(typeof(CapsuleCollider))] [RequireComponent(typeof(Animator))] public class ThirdPersonCharacter : MonoBehaviour { //人物转向的速度 [SerializeField] float m_MovingTurnSpeed = 360; //固定的转动速度 [SerializeField] float m_StationaryTurnSpeed = 180; //跳跃的高度 [SerializeField] float m_JumpPower = 12f; //重力乘数 [Range(1f, 4f)][SerializeField] float m_GravityMultiplier = 2f; //跑的时候的偏移值 [SerializeField] float m_RunCycleLegOffset = 0.2f; //specific to the character in sample assets, will need to be modified to work with others //移动速度乘数 [SerializeField] float m_MoveSpeedMultiplier = 1f; //动画播放速度乘数 [SerializeField] float m_AnimSpeedMultiplier = 1f; //地面检查距离 [SerializeField] float m_GroundCheckDistance = 0.1f; Rigidbody m_Rigidbody; Animator m_Animator; //是否接地 bool m_IsGrounded; //接地坚持距离 float m_OrigGroundCheckDistance; //一半 const float k_Half = 0.5f; //转动的数量 float m_TurnAmount; //正向的数量 float m_ForwardAmount; //接地的坐标轴 Vector3 m_GroundNormal; //胶囊体的高度 float m_CapsuleHeight; //胶囊体的中心点 Vector3 m_CapsuleCenter; //胶囊碰撞体 CapsuleCollider m_Capsule; //蹲伏 bool m_Crouching; void Start() { m_Animator = GetComponent<Animator>(); m_Rigidbody = GetComponent<Rigidbody>(); m_Capsule = GetComponent<CapsuleCollider>(); //获得组件上面的胶囊碰撞体的高度和中心点 m_CapsuleHeight = m_Capsule.height; m_CapsuleCenter = m_Capsule.center; //m_Rigidbody.constraints控制自由度的自由度可以用来模拟这个刚性体 m_Rigidbody.constraints = RigidbodyConstraints.FreezeRotationX | RigidbodyConstraints.FreezeRotationY | RigidbodyConstraints.FreezeRotationZ; m_OrigGroundCheckDistance = m_GroundCheckDistance; } //人物移动函数 public void Move(Vector3 move, bool crouch, bool jump) { // convert the world relative moveInput vector into a local-relative // turn amount and forward amount required to head in the desired // direction. //如果获得的向量的长度大于1,就让它标准化向量 if (move.magnitude > 1f) move.Normalize(); //逆向 move = transform.InverseTransformDirection(move); //地面检测 CheckGroundStatus(); //Vector3.ProjectOnPlane将向量投影到平面上的正交正交平面上的向量 move = Vector3.ProjectOnPlane(move, m_GroundNormal); m_TurnAmount = Mathf.Atan2(move.x, move.z); m_ForwardAmount = move.z; //转向的速度 ApplyExtraTurnRotation(); // control and velocity handling is different when grounded and airborne: //地面和空中的控制和速度处理是不同的: if (m_IsGrounded) //在地面上 { HandleGroundedMovement(crouch, jump); } else //不在地面上 { HandleAirborneMovement(); } //如果胶囊体是蹲伏状态 ScaleCapsuleForCrouching(crouch); //防止站在低处 PreventStandingInLowHeadroom(); // send input and other state parameters to the animator //向动画器发送输入和其他状态参数 UpdateAnimator(move); } //胶囊体是蹲伏状态 void ScaleCapsuleForCrouching(bool crouch) { if (m_IsGrounded && crouch) { if (m_Crouching) return; m_Capsule.height = m_Capsule.height / 2f; m_Capsule.center = m_Capsule.center / 2f; m_Crouching = true; } else { Ray crouchRay = new Ray(m_Rigidbody.position + Vector3.up * m_Capsule.radius * k_Half, Vector3.up); float crouchRayLength = m_CapsuleHeight - m_Capsule.radius * k_Half; if (Physics.SphereCast(crouchRay, m_Capsule.radius * k_Half, crouchRayLength)) { m_Crouching = true; return; } m_Capsule.height = m_CapsuleHeight; m_Capsule.center = m_CapsuleCenter; m_Crouching = false; } } //防止站在低处 void PreventStandingInLowHeadroom() { // prevent standing up in crouch-only zones if (!m_Crouching) { Ray crouchRay = new Ray(m_Rigidbody.position + Vector3.up * m_Capsule.radius * k_Half, Vector3.up); float crouchRayLength = m_CapsuleHeight - m_Capsule.radius * k_Half; if (Physics.SphereCast(crouchRay, m_Capsule.radius * k_Half, crouchRayLength)) { m_Crouching = true; } } } //向动画器发送输入和其他状态参数 void UpdateAnimator(Vector3 move) { // update the animator parameters m_Animator.SetFloat("Forward", m_ForwardAmount, 0.1f, Time.deltaTime); m_Animator.SetFloat("Turn", m_TurnAmount, 0.1f, Time.deltaTime); m_Animator.SetBool("Crouch", m_Crouching); m_Animator.SetBool("OnGround", m_IsGrounded); if (!m_IsGrounded) { m_Animator.SetFloat("Jump", m_Rigidbody.velocity.y); } // calculate which leg is behind, so as to leave that leg trailing in the jump animation // (This code is reliant on the specific run cycle offset in our animations, // and assumes one leg passes the other at the normalized clip times of 0.0 and 0.5) float runCycle = Mathf.Repeat( m_Animator.GetCurrentAnimatorStateInfo(0).normalizedTime + m_RunCycleLegOffset, 1); float jumpLeg = (runCycle < k_Half ? 1 : -1) * m_ForwardAmount; if (m_IsGrounded) { m_Animator.SetFloat("JumpLeg", jumpLeg); } // the anim speed multiplier allows the overall speed of walking/running to be tweaked in the inspector, // which affects the movement speed because of the root motion. if (m_IsGrounded && move.magnitude > 0) { m_Animator.speed = m_AnimSpeedMultiplier; } else { // don't use that while airborne m_Animator.speed = 1; } } //处理机载运动 void HandleAirborneMovement() { // apply extra gravity from multiplier: Vector3 extraGravityForce = (Physics.gravity * m_GravityMultiplier) - Physics.gravity; m_Rigidbody.AddForce(extraGravityForce); m_GroundCheckDistance = m_Rigidbody.velocity.y < 0 ? m_OrigGroundCheckDistance : 0.01f; } //处理接地运动 void HandleGroundedMovement(bool crouch, bool jump) { // check whether conditions are right to allow a jump: if (jump && !crouch && m_Animator.GetCurrentAnimatorStateInfo(0).IsName("Grounded")) { // jump! m_Rigidbody.velocity = new Vector3(m_Rigidbody.velocity.x, m_JumpPower, m_Rigidbody.velocity.z); m_IsGrounded = false; m_Animator.applyRootMotion = false; m_GroundCheckDistance = 0.1f; } } //转向的速度 void ApplyExtraTurnRotation() { // help the character turn faster (this is in addition to root rotation in the animation) //帮助字符转得更快(这是在动画的根旋转之外) float turnSpeed = Mathf.Lerp(m_StationaryTurnSpeed, m_MovingTurnSpeed, m_ForwardAmount); transform.Rotate(0, m_TurnAmount * turnSpeed * Time.deltaTime, 0); } //动画移动 public void OnAnimatorMove() { // we implement this function to override the default root motion. // this allows us to modify the positional speed before it's applied. if (m_IsGrounded && Time.deltaTime > 0) { Vector3 v = (m_Animator.deltaPosition * m_MoveSpeedMultiplier) / Time.deltaTime; // we preserve the existing y part of the current velocity. v.y = m_Rigidbody.velocity.y; m_Rigidbody.velocity = v; } } //地面检测 void CheckGroundStatus() { RaycastHit hitInfo; #if UNITY_EDITOR // helper to visualise the ground check ray in the scene view //在场景视图中显示地面检查光线的助手 Debug.DrawLine(transform.position + (Vector3.up * 0.1f), transform.position + (Vector3.up * 0.1f) + (Vector3.down * m_GroundCheckDistance)); #endif // 0.1f is a small offset to start the ray from inside the character // it is also good to note that the transform position in the sample assets is at the base of the character // 0.1 f是一个小的偏移量,可以从字符内部开始射线 // 值得注意的是,示例资产中的转换位置位于字符的底部 if (Physics.Raycast(transform.position + (Vector3.up * 0.1f), Vector3.down, out hitInfo, m_GroundCheckDistance)) { m_GroundNormal = hitInfo.normal; m_IsGrounded = true; m_Animator.applyRootMotion = true; } else { m_IsGrounded = false; m_GroundNormal = Vector3.up; m_Animator.applyRootMotion = false; } } } }
重要组件 Animator、Rigidbody 、CapsuleCollider、ThirdPersonUserControl、ThirdPersonCharactterr
using System; using UnityEngine; using UnityStandardAssets.CrossPlatformInput; namespace UnityStandardAssets.Characters.ThirdPerson { [RequireComponent(typeof (ThirdPersonCharacter))] public class ThirdPersonUserControl : MonoBehaviour { //引用对象上的ThirdPersonCharacter private ThirdPersonCharacter m_Character; // A reference to the ThirdPersonCharacter on the object private Transform m_Cam; // A reference to the main camera in the scenes transform private Vector3 m_CamForward; // The current forward direction of the camera private Vector3 m_Move; private bool m_Jump; // the world-relative desired move direction, calculated from the camForward and user input. private void Start() { // get the transform of the main camera if (Camera.main != null) { m_Cam = Camera.main.transform; } else { Debug.LogWarning( "Warning: no main camera found. Third person character needs a Camera tagged \"MainCamera\", for camera-relative controls."); // we use self-relative controls in this case, which probably isn't what the user wants, but hey, we warned them! } // get the third person character ( this should never be null due to require component ) m_Character = GetComponent<ThirdPersonCharacter>(); } private void Update() { if (!m_Jump) { m_Jump = CrossPlatformInputManager.GetButtonDown("Jump"); } } //移动函数 // Fixed update is called in sync with physics private void FixedUpdate() { // read inputs float h = CrossPlatformInputManager.GetAxis("Horizontal"); float v = CrossPlatformInputManager.GetAxis("Vertical"); bool crouch = Input.GetKey(KeyCode.C); // calculate move direction to pass to character if (m_Cam != null) { // calculate camera relative direction to move: m_CamForward = Vector3.Scale(m_Cam.forward, new Vector3(1, 0, 1)).normalized; m_Move = v*m_CamForward + h*m_Cam.right; } else { // we use world-relative directions in the case of no main camera m_Move = v*Vector3.forward + h*Vector3.right; } #if !MOBILE_INPUT // walk speed multiplier if (Input.GetKey(KeyCode.LeftShift)) m_Move *= 0.5f; #endif // pass all parameters to the character control script m_Character.Move(m_Move, crouch, m_Jump); m_Jump = false; } } }
using UnityEngine; namespace UnityStandardAssets.Characters.ThirdPerson { //自动关联组件 [RequireComponent(typeof(Rigidbody))] [RequireComponent(typeof(CapsuleCollider))] [RequireComponent(typeof(Animator))] public class ThirdPersonCharacter : MonoBehaviour { //人物转向的速度 [SerializeField] float m_MovingTurnSpeed = 360; //固定的转动速度 [SerializeField] float m_StationaryTurnSpeed = 180; //跳跃的高度 [SerializeField] float m_JumpPower = 12f; //重力乘数 [Range(1f, 4f)][SerializeField] float m_GravityMultiplier = 2f; //跑的时候的偏移值 [SerializeField] float m_RunCycleLegOffset = 0.2f; //specific to the character in sample assets, will need to be modified to work with others //移动速度乘数 [SerializeField] float m_MoveSpeedMultiplier = 1f; //动画播放速度乘数 [SerializeField] float m_AnimSpeedMultiplier = 1f; //地面检查距离 [SerializeField] float m_GroundCheckDistance = 0.1f; Rigidbody m_Rigidbody; Animator m_Animator; //是否接地 bool m_IsGrounded; //接地坚持距离 float m_OrigGroundCheckDistance; //一半 const float k_Half = 0.5f; //转动的数量 float m_TurnAmount; //正向的数量 float m_ForwardAmount; //接地的坐标轴 Vector3 m_GroundNormal; //胶囊体的高度 float m_CapsuleHeight; //胶囊体的中心点 Vector3 m_CapsuleCenter; //胶囊碰撞体 CapsuleCollider m_Capsule; //蹲伏 bool m_Crouching; void Start() { m_Animator = GetComponent<Animator>(); m_Rigidbody = GetComponent<Rigidbody>(); m_Capsule = GetComponent<CapsuleCollider>(); //获得组件上面的胶囊碰撞体的高度和中心点 m_CapsuleHeight = m_Capsule.height; m_CapsuleCenter = m_Capsule.center; //m_Rigidbody.constraints控制自由度的自由度可以用来模拟这个刚性体 m_Rigidbody.constraints = RigidbodyConstraints.FreezeRotationX | RigidbodyConstraints.FreezeRotationY | RigidbodyConstraints.FreezeRotationZ; m_OrigGroundCheckDistance = m_GroundCheckDistance; } //人物移动函数 public void Move(Vector3 move, bool crouch, bool jump) { // convert the world relative moveInput vector into a local-relative // turn amount and forward amount required to head in the desired // direction. //如果获得的向量的长度大于1,就让它标准化向量 if (move.magnitude > 1f) move.Normalize(); //逆向 move = transform.InverseTransformDirection(move); //地面检测 CheckGroundStatus(); //Vector3.ProjectOnPlane将向量投影到平面上的正交正交平面上的向量 move = Vector3.ProjectOnPlane(move, m_GroundNormal); m_TurnAmount = Mathf.Atan2(move.x, move.z); m_ForwardAmount = move.z; //转向的速度 ApplyExtraTurnRotation(); // control and velocity handling is different when grounded and airborne: //地面和空中的控制和速度处理是不同的: if (m_IsGrounded) //在地面上 { HandleGroundedMovement(crouch, jump); } else //不在地面上 { HandleAirborneMovement(); } //如果胶囊体是蹲伏状态 ScaleCapsuleForCrouching(crouch); //防止站在低处 PreventStandingInLowHeadroom(); // send input and other state parameters to the animator //向动画器发送输入和其他状态参数 UpdateAnimator(move); } //胶囊体是蹲伏状态 void ScaleCapsuleForCrouching(bool crouch) { if (m_IsGrounded && crouch) { if (m_Crouching) return; m_Capsule.height = m_Capsule.height / 2f; m_Capsule.center = m_Capsule.center / 2f; m_Crouching = true; } else { Ray crouchRay = new Ray(m_Rigidbody.position + Vector3.up * m_Capsule.radius * k_Half, Vector3.up); float crouchRayLength = m_CapsuleHeight - m_Capsule.radius * k_Half; if (Physics.SphereCast(crouchRay, m_Capsule.radius * k_Half, crouchRayLength)) { m_Crouching = true; return; } m_Capsule.height = m_CapsuleHeight; m_Capsule.center = m_CapsuleCenter; m_Crouching = false; } } //防止站在低处 void PreventStandingInLowHeadroom() { // prevent standing up in crouch-only zones if (!m_Crouching) { Ray crouchRay = new Ray(m_Rigidbody.position + Vector3.up * m_Capsule.radius * k_Half, Vector3.up); float crouchRayLength = m_CapsuleHeight - m_Capsule.radius * k_Half; if (Physics.SphereCast(crouchRay, m_Capsule.radius * k_Half, crouchRayLength)) { m_Crouching = true; } } } //向动画器发送输入和其他状态参数 void UpdateAnimator(Vector3 move) { // update the animator parameters m_Animator.SetFloat("Forward", m_ForwardAmount, 0.1f, Time.deltaTime); m_Animator.SetFloat("Turn", m_TurnAmount, 0.1f, Time.deltaTime); m_Animator.SetBool("Crouch", m_Crouching); m_Animator.SetBool("OnGround", m_IsGrounded); if (!m_IsGrounded) { m_Animator.SetFloat("Jump", m_Rigidbody.velocity.y); } // calculate which leg is behind, so as to leave that leg trailing in the jump animation // (This code is reliant on the specific run cycle offset in our animations, // and assumes one leg passes the other at the normalized clip times of 0.0 and 0.5) float runCycle = Mathf.Repeat( m_Animator.GetCurrentAnimatorStateInfo(0).normalizedTime + m_RunCycleLegOffset, 1); float jumpLeg = (runCycle < k_Half ? 1 : -1) * m_ForwardAmount; if (m_IsGrounded) { m_Animator.SetFloat("JumpLeg", jumpLeg); } // the anim speed multiplier allows the overall speed of walking/running to be tweaked in the inspector, // which affects the movement speed because of the root motion. if (m_IsGrounded && move.magnitude > 0) { m_Animator.speed = m_AnimSpeedMultiplier; } else { // don't use that while airborne m_Animator.speed = 1; } } //处理机载运动 void HandleAirborneMovement() { // apply extra gravity from multiplier: Vector3 extraGravityForce = (Physics.gravity * m_GravityMultiplier) - Physics.gravity; m_Rigidbody.AddForce(extraGravityForce); m_GroundCheckDistance = m_Rigidbody.velocity.y < 0 ? m_OrigGroundCheckDistance : 0.01f; } //处理接地运动 void HandleGroundedMovement(bool crouch, bool jump) { // check whether conditions are right to allow a jump: if (jump && !crouch && m_Animator.GetCurrentAnimatorStateInfo(0).IsName("Grounded")) { // jump! m_Rigidbody.velocity = new Vector3(m_Rigidbody.velocity.x, m_JumpPower, m_Rigidbody.velocity.z); m_IsGrounded = false; m_Animator.applyRootMotion = false; m_GroundCheckDistance = 0.1f; } } //转向的速度 void ApplyExtraTurnRotation() { // help the character turn faster (this is in addition to root rotation in the animation) //帮助字符转得更快(这是在动画的根旋转之外) float turnSpeed = Mathf.Lerp(m_StationaryTurnSpeed, m_MovingTurnSpeed, m_ForwardAmount); transform.Rotate(0, m_TurnAmount * turnSpeed * Time.deltaTime, 0); } //动画移动 public void OnAnimatorMove() { // we implement this function to override the default root motion. // this allows us to modify the positional speed before it's applied. if (m_IsGrounded && Time.deltaTime > 0) { Vector3 v = (m_Animator.deltaPosition * m_MoveSpeedMultiplier) / Time.deltaTime; // we preserve the existing y part of the current velocity. v.y = m_Rigidbody.velocity.y; m_Rigidbody.velocity = v; } } //地面检测 void CheckGroundStatus() { RaycastHit hitInfo; #if UNITY_EDITOR // helper to visualise the ground check ray in the scene view //在场景视图中显示地面检查光线的助手 Debug.DrawLine(transform.position + (Vector3.up * 0.1f), transform.position + (Vector3.up * 0.1f) + (Vector3.down * m_GroundCheckDistance)); #endif // 0.1f is a small offset to start the ray from inside the character // it is also good to note that the transform position in the sample assets is at the base of the character // 0.1 f是一个小的偏移量,可以从字符内部开始射线 // 值得注意的是,示例资产中的转换位置位于字符的底部 if (Physics.Raycast(transform.position + (Vector3.up * 0.1f), Vector3.down, out hitInfo, m_GroundCheckDistance)) { m_GroundNormal = hitInfo.normal; m_IsGrounded = true; m_Animator.applyRootMotion = true; } else { m_IsGrounded = false; m_GroundNormal = Vector3.up; m_Animator.applyRootMotion = false; } } } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。