文档章节

【原创】Unity3D 模仿《魔兽世界》的第三人称角色控制器

MrBlack
 MrBlack
发布于 2015/07/25 11:04
字数 1639
阅读 110
收藏 0

玩过《魔兽世界》的朋友都知道,《魔兽世界》中的角色控制器非常的出色,Unity3D 的标准包中自带了第三人称与第一人称角色控制器,但是感觉上面差了很多,下面这个第三人称角色控制器模仿的《魔兽世界》,希望大家喜欢。

与往常一样,我们先来看看最终效果,我们可以通过键盘以及鼠标控制角色旋转、移动,视野拉近拉远等等,如图:

新建一个测试场景,要保证场景中有一个角色对象,如图:

在角色对象中添加一个空对象,取名:HeadItem,主要用来挂载主摄像机,如图:

第三人称角色控制器主要的类有 UPlayerController.cs,代码如下:

using UnityEngine;
using System.Collections;

public class UPlayerController : MonoBehaviour
{
	/// <summary>
	/// 待机动画
	/// </summary>
	public AnimationClip idleClip;

	/// <summary>
	/// 行走动画
	/// </summary>
	public AnimationClip walkClip;

	/// <summary>
	/// 跑步动画
	/// </summary>
	public AnimationClip runClip;

	/// <summary>
	/// 跳跃动画
	/// </summary>
	public AnimationClip jumpClip;

	/// <summary>
	/// 后退动画
	/// </summary>
	public AnimationClip retreatClip;

	/// <summary>
	/// 跑步移动速度
	/// </summary>
	public float runSpeed = 6.0f;
	
	/// <summary>
	/// 行走移动速度
	/// </summary>
	public float walkSpeed = 3.0f;
	
	/// <summary>
	/// 后退移动速度
	/// </summary>
	public float retreatSpeed = 2.0f;
	
	/// <summary>
	/// 移动速度
	/// </summary>
	public float speed = 0f;

	/// <summary>
	/// 摄像机对象
	/// </summary>
	public Transform cameraTransform;

	/// <summary>
	/// 角度头部对象
	/// </summary>
	public Transform headTransform;

	/// <summary>
	/// 摄像机偏离距离
	/// </summary>
	public float cameraDistance = 5f;

	/// <summary>
	/// 摄像机最近视角
	/// </summary>
	public float cameraZoomNear = 0.01f;

	/// <summary>
	/// 摄像机最远视角
	/// </summary>
	public float cameraZoomFar = 30.0f;

	/// <summary>
	/// 鼠标移动 X 坐标轴
	/// </summary>
	public float mouseXAxis = 15.0f;

	/// <summary>
	/// 鼠标移动 Y 坐标轴
	/// </summary>
	public float mouseYAxis = 15.0f;

	/// <summary>
	/// 鼠标滚轮速率
	/// </summary>
	public float mouseWheelVelocity = 3.0f;

	/// <summary>
	/// 跳起速率
	/// </summary>
	public float jumpVelocity = 15f;

	/// <summary>
	/// 下落速率
	/// </summary>
	public float dropVelocity = 10f;

	/// <summary>
	/// 摄像机位置修正
	/// </summary>
	public float cameraReviseDistance = 1.0f;

	/// <summary>
	/// 最小位置
	/// </summary>
	public float cameraClampMin = -70f;

	/// <summary>
	/// 最大位置
	/// </summary>
	public float cameraClampMax = 90f;

	/// <summary>
	/// 偏移角度
	/// </summary>
	public float offsetAngle = 90f;
	
	/// <summary>
	/// 移动向量,通过计算得来
	/// </summary>
	protected Vector3 moveDirection;

	/// <summary>
	/// 垂直速率
	/// </summary>
	protected float verticalVelocity;

	/// <summary>
	/// 屏幕鼠标 X 坐标
	/// </summary>
	protected float mouseX;

	/// <summary>
	/// 屏幕鼠标 Y 坐标
	/// </summary>
	protected float mouseY;

	/// <summary>
	/// 前一次鼠标 X 坐标
	/// </summary>
	protected float prevMouseX;

	/// <summary>
	/// 前一次鼠标 Y 坐标
	/// </summary>
	protected float prevMouseY;

	/// <summary>
	/// 前一次旋转
	/// </summary>
	protected Quaternion prevQuaternion;

	/// <summary>
	/// 前一次朝向
	/// </summary>
	protected Vector3 prevForward;

	/// <summary>
	/// 是否正在起跳
	/// </summary>
	protected bool isJumping;

	/// <summary>
	/// 插值转向变量
	/// </summary>
	protected float mouseXVelocity;

	/// <summary>
	/// 插值跳起变量
	/// </summary>
	protected float currentJumpVelocity;

	/// <summary>
	/// 角色当前状态
	/// </summary>
	protected string roleActionEnum;

	/// <summary>
	/// Q、E 移动向量
	/// </summary>
	protected float horizontalQEDirection;

	/// <summary>
	/// 水平方向向量
	/// </summary>
	protected float horizontalDirection;

	/// <summary>
	/// 垂直方向向量
	/// </summary>
	protected float verticalDirection;

	/// <summary>
	/// 鼠标 X 属性
	/// </summary>
	protected float mouseXDirection;

	/// <summary>
	/// 鼠标 Y 属性
	/// </summary>
	protected float mouseYDirection;

	/// <summary>
	/// 鼠标滚轮属性
	/// </summary>
	protected float mouseScrollWheelDirection;

	/// <summary>
	/// 水平移动数据
	/// </summary>
	protected float horizontalValue;

	/// <summary>
	/// 垂直移动数据
	/// </summary>
	protected float verticalValue;

	/// <summary>
	/// 跳起状态
	/// </summary>
	protected bool jumpStatus;

	/// <summary>
	/// 鼠标左键按下状态
	/// </summary>
	protected bool buttonDownLeftStatus;

	/// <summary>
	/// 鼠标左键弹起状态
	/// </summary>
	protected bool buttonUpLeftStatus;

	/// <summary>
	/// 鼠标左键状态
	/// </summary>
	protected bool buttonLeftStatus;

	/// <summary>
	/// 鼠标右键状态
	/// </summary>
	protected bool buttonRightStatus;

	/// <summary>
	/// 左边 Shift 状态
	/// </summary>
	protected bool shiftLeftStatus;
	
	/// <summary>
	/// 右边 Shift 状态
	/// </summary>
	protected bool shiftRightStatus;

	/// <summary>
	/// 角色控制器
	/// </summary>
	private CharacterController characterController;

	/// <summary>
	/// 动画
	/// </summary>
	private Animation animation;

	void Awake ()
	{
		if (this.cameraTransform == null && Camera.main != null) this.cameraTransform = Camera.main.transform;
		if (this.cameraTransform == null) return;

		this.characterController = this.GetComponentInChildren<CharacterController> ();
		this.animation = this.GetComponentInChildren<Animation> ();

		this.cameraTransform.parent = this.headTransform;
		this.cameraTransform.localPosition = Vector3.zero;
		
		this.cameraTransform.position = this.transform.position;

		this.verticalVelocity = 0f;

		this.animation.CrossFade (this.idleClip.name);
	}

	/// <summary>
	/// 初始化输入数据
	/// </summary>
	protected virtual void InitInputData()
	{
		if (this.cameraTransform == null) return;

		this.horizontalQEDirection = 0f;
		this.horizontalDirection = Input.GetAxisRaw ("Horizontal");
		this.verticalDirection = Input.GetAxisRaw ("Vertical");
		this.mouseXDirection = Input.GetAxis ("Mouse X") * this.mouseXAxis;
		this.mouseYDirection = Input.GetAxis ("Mouse Y") * this.mouseYAxis;
		this.mouseScrollWheelDirection = Input.GetAxis ("Mouse ScrollWheel");
		this.buttonDownLeftStatus = Input.GetMouseButtonDown ((int)UMouseTypeEnum.LEFT);
		this.buttonLeftStatus = Input.GetMouseButton ((int)UMouseTypeEnum.LEFT);
		this.buttonRightStatus = Input.GetMouseButton ((int)UMouseTypeEnum.RIGHT);
		this.buttonUpLeftStatus = Input.GetMouseButtonUp ((int)UMouseTypeEnum.LEFT);
		if (!buttonLeftStatus && Input.GetKey (KeyCode.Q)) this.horizontalQEDirection = -1f;
		if (!buttonLeftStatus && Input.GetKey (KeyCode.E)) this.horizontalQEDirection = 1f;
		this.jumpStatus = Input.GetKey (KeyCode.Space);
		this.shiftLeftStatus = Input.GetKey (KeyCode.LeftShift);
		this.shiftRightStatus = Input.GetKey (KeyCode.RightShift);
	}

	void Update()
	{
		if (this.cameraTransform == null) return;

		this.InitInputData ();

		// 获取地面状态
		bool groundStatus = this.IsGrounded ();

		if (groundStatus && this.jumpStatus && !this.isJumping) 
		{
			this.isJumping = true;
			this.verticalVelocity = this.jumpVelocity;
		}

		if (this.isJumping && this.verticalVelocity >= 0f) 
		{
			this.verticalVelocity = Mathf.SmoothDamp(this.verticalVelocity, -this.dropVelocity, ref this.currentJumpVelocity, 0.15f);
		}
		else
		{
			this.isJumping = false;
			this.verticalVelocity = -this.dropVelocity;
		}

		// 如果按住滚轮 
		if (this.mouseScrollWheelDirection != 0) 
		{ 
			if (this.cameraDistance >= this.cameraZoomNear && this.cameraDistance <= this.cameraZoomFar) 
			{ 
				this.cameraDistance -= this.mouseScrollWheelDirection * this.mouseWheelVelocity; 
			} 
			if (this.cameraDistance < this.cameraZoomNear) 
			{ 
				this.cameraDistance = this.cameraZoomNear; 
			} 
			if (this.cameraDistance > this.cameraZoomFar) 
			{ 
				this.cameraDistance = this.cameraZoomFar; 
			} 
			this.cameraDistance = Mathf.Max(0.01f, this.cameraDistance);
		}
		// 如果鼠标左键按下状态
		if (this.buttonDownLeftStatus) 
		{
			this.prevMouseX = mouseX;
			this.prevMouseY = mouseY;
			this.prevForward = this.cameraTransform.TransformDirection(Vector3.forward);
		}
		// 如果按下鼠标左键或者按下鼠标右键
		if (this.buttonLeftStatus || this.buttonRightStatus) 
		{
			this.mouseX += this.mouseXDirection;
			this.mouseY += this.mouseYDirection;
		} 
		else 
		{
			this.mouseX += 0f;
			this.mouseY += 0f;
		}
		// 如果鼠标左键松开
		if (this.buttonUpLeftStatus) 
		{
			this.mouseX = this.prevMouseX;
			this.mouseY = this.prevMouseY;
		}
		// 如果按下了水平方向键,并且鼠标未按下,左右转换视角
		if (this.horizontalDirection != 0f && (!this.buttonLeftStatus && !this.buttonRightStatus)) 
		{
			if (this.horizontalDirection > 0) 
			{
				this.mouseX = Mathf.SmoothDamp (this.mouseX, this.mouseX + 90f, ref this.mouseXVelocity, 0.2f);
			} 
			else 
			{
				this.mouseX = Mathf.SmoothDamp (this.mouseX, this.mouseX - 90f, ref this.mouseXVelocity, 0.2f);
			}
		} 
		// 如果按下了水平方向键,并且鼠标右键,左右移动
		else if(this.horizontalDirection != 0f && this.buttonRightStatus)
		{
			this.horizontalQEDirection = this.horizontalDirection;
		}

		this.mouseY = Mathf.Clamp (this.mouseY, this.cameraClampMin, this.cameraClampMax);

		Quaternion xQuaternion = Quaternion.AngleAxis (this.mouseX, Vector3.up);
		Quaternion yQuaternion = Quaternion.AngleAxis (this.mouseY, Vector3.left);
		// 当前旋转角度
		this.prevQuaternion = xQuaternion * yQuaternion;
		// 如果Q、E水平移动
		if (this.horizontalQEDirection != 0) 
		{
			if (this.verticalDirection != 0f)
			{
				// 如果水平轴大于零,比如按下了 D 键
				if(this.horizontalQEDirection > 0)
				{
					xQuaternion = Quaternion.AngleAxis(this.mouseX + this.offsetAngle * 0.5f, Vector3.up);
				}
				else // 如果水平轴小于零,比如按下了 A 键
				{
					xQuaternion = Quaternion.AngleAxis(this.mouseX - this.offsetAngle * 0.5f, Vector3.up);
				}
			}
			else
			{
				// 如果水平轴大于零,比如按下了 D 键
				if(this.horizontalQEDirection > 0)
				{
					xQuaternion = Quaternion.AngleAxis(this.mouseX + this.offsetAngle, Vector3.up);
				}
				else // 如果水平轴小于零,比如按下了 A 键
				{
					xQuaternion = Quaternion.AngleAxis(this.mouseX - this.offsetAngle, Vector3.up);
				}
			}
		}
		if (!this.buttonLeftStatus) 
		{
			this.transform.rotation = xQuaternion;
		}
		// 摄像机对象的偏离位置,相对于角色头部位置
		this.cameraTransform.localPosition = new Vector3 (0f, 0f, this.headTransform.transform.localPosition.z - this.cameraDistance);
		// 如果按住了 Shift 键
		if (this.shiftLeftStatus || this.shiftRightStatus) 
		{
			this.speed = this.walkSpeed;
			this.roleActionEnum = URoleActionEnum.WALK;
		} else {
			this.speed = this.runSpeed;
			this.roleActionEnum = URoleActionEnum.RUN;
		}
		
		if (verticalDirection < 0f) 
		{
			this.speed = this.retreatSpeed;
			this.roleActionEnum = URoleActionEnum.RETREAT;
		}
		
		this.horizontalValue = this.horizontalQEDirection * this.speed * Time.deltaTime;
		this.verticalValue = this.verticalDirection * this.speed * Time.deltaTime;
		// 如果在跳起状态
		if (this.isJumping) this.roleActionEnum = URoleActionEnum.JUMP;
		
		if (this.horizontalQEDirection == 0f && this.horizontalDirection == 0f && this.verticalValue == 0f && !this.isJumping) this.roleActionEnum = URoleActionEnum.IDLE;
		
		Vector3 forward = this.cameraTransform.TransformDirection (Vector3.forward);
		if (this.buttonLeftStatus) forward = this.prevForward;

		forward.y = 0;
		forward = forward.normalized;
		
		Vector3 right = new Vector3 (forward.z, 0, -forward.x);
		// 设置移动向量
		this.moveDirection = this.horizontalValue * right + this.verticalValue * forward + new Vector3 (0, this.verticalVelocity * Time.deltaTime, 0);
		
		// 设置对象状态动画
		this.ChangeAction (this.roleActionEnum);
		
		this.characterController.Move (this.moveDirection);
	}

	private void ChangeAction(string roleActionEnum)
	{
		if(roleActionEnum == URoleActionEnum.IDLE)
		{
			this.animation.CrossFade(this.idleClip.name);
		}
		else if(roleActionEnum == URoleActionEnum.WALK)
		{
			this.animation.CrossFade(this.walkClip.name);
		}
		else if(roleActionEnum == URoleActionEnum.RUN)
		{
			this.animation.CrossFade(this.runClip.name);
		}
		else if(roleActionEnum == URoleActionEnum.JUMP)
		{
			this.animation.CrossFade(this.jumpClip.name);
		}
		else if(roleActionEnum == URoleActionEnum.RETREAT)
		{
			this.animation.CrossFade(this.retreatClip.name);
		}
	}

	void LateUpdate ()
	{
		if (this.cameraTransform == null) return;

		// 设置头部旋转
		this.headTransform.rotation = this.prevQuaternion;
		// 下面代码保证视角内没有透视区域
		RaycastHit hitCamera = new RaycastHit ();
		RaycastHit[] hits = Physics.SphereCastAll (this.headTransform.position, 0.3f * 0.5f, -this.cameraTransform.forward, this.cameraDistance);
		hitCamera.distance = Mathf.Infinity;
		foreach (RaycastHit hit in hits) 
		{
			//判断是否是地形
			Terrain terrain = hit.transform.GetComponent<Terrain>();
			if (hit.distance < hitCamera.distance && hit.transform != this.transform.transform && terrain != null) 
			{
				bool found = false;
				if (this.transform.transform.childCount > 0) 
				{
					foreach (Transform childTransform in this.transform) 
					{
						if (hit.transform == childTransform) 
						{
							found = true;
							break;
						}
					}
				}
				if (!found) hitCamera = hit;
			}
		}
		if (hitCamera.distance != Mathf.Infinity) 
		{
			this.cameraDistance -= this.cameraReviseDistance;
		}
	}

	protected virtual bool IsGrounded () 
	{
		return (this.characterController.collisionFlags == CollisionFlags.CollidedBelow);
	}
}

UMouseTypeEnum.cs 代码如下:

using UnityEngine;
using System.Collections;

public enum UMouseTypeEnum
{
	LEFT = 0,
	RIGHT = 1
}

URoleActionEnum.cs 代码如下:

using UnityEngine;
using System.Collections;

public class URoleActionEnum
{
	public const string IDLE = "idle";

	public const string WALK = "walk";

	public const string RUN = "run";

	public const string RETREAT = "retreat";

	public const string JUMP = "jump";

	public const string ATTACK = "attack";

	public const string MAGIC = "magic";

	public const string DIE = "die";
}

然后给角色对象挂载 CharacterController 组件 和 UPlayerController.cs,如图:

运行游戏,我们就可以灵活的控制角色在场景中移动、旋转了!

下载地址:链接: http://pan.baidu.com/s/1kTzlYWB 密码: aa8r

© 著作权归作者所有

MrBlack
粉丝 0
博文 14
码字总数 11101
作品 0
闸北
高级程序员
私信 提问
Unity2018.2中文更新日志速览版

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zhenghongzhi6/article/details/81036150 本文首发于洪流学堂微信公众号。 洪流学堂,学Unity快人几步 Unity2...

关尔Manic
2018/07/13
0
0
SpectatorView For Hololens

原文链接:https://docs.microsoft.com/zh-cn/windows/mixed-reality/spectator-view#spectatorview-preview 文章目录 当我们戴上 Hololens 时,一个没有戴上它的人是无法体验到我们所能体验...

Jitwxs
2018/11/20
0
0
游戏巨头齐聚 Unite Shanghai 2019,揭秘爆款游戏制作精彩亮点!

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/csdnnews/article/details/89397837 从三人行的公寓创业到全球知名游戏引擎的发展、从局限的 macOS 到高达 25...

CSDN资讯
04/19
0
0
最后1天,购票渠道即将关闭!Unite 2018开发者大会全日程公布

Unite Beijing 2018将于5月11日-13日在北京国家会议中心盛大召开!(网络购票渠道将于明日中午关闭,有需要的朋友抓紧购买,购票地址:大会官网) 作为全球规模最大的Unity开发者聚会,历年U...

yssycz
2018/05/09
0
0
2018.1之后Standard Assets如何安装?

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 https://blog.csdn.net/zhenghongzhi6/article/details/90236937 洪流学堂,让你快人几步。你...

关尔Manic
05/15
0
0

没有更多内容

加载失败,请刷新页面

加载更多

golang-字符串-地址分析

demo package mainimport "fmt"func main() {str := "map.baidu.com"fmt.Println(&str, str)str = str[0:5]fmt.Println(&str, str)str = "abc"fmt.Println(&s......

李琼涛
46分钟前
3
0
Spring Boot WebFlux 增删改查完整实战 demo

03:WebFlux Web CRUD 实践 前言 上一篇基于功能性端点去创建一个简单服务,实现了 Hello 。这一篇用 Spring Boot WebFlux 的注解控制层技术创建一个 CRUD WebFlux 应用,让开发更方便。这里...

泥瓦匠BYSocket
今天
6
0
从0开始学FreeRTOS-(列表与列表项)-3

FreeRTOS列表&列表项的源码解读 第一次看列表与列表项的时候,感觉很像是链表,虽然我自己的链表也不太会,但是就是感觉很像。 在FreeRTOS中,列表与列表项使用得非常多,是FreeRTOS的一个数...

杰杰1号
今天
4
0
Java反射

Java 反射 反射是框架设计的灵魂(使用的前提条件:必须先得到代表的字节码的 Class,Class 类 用于表示.class 文件(字节码)) 一、反射的概述 定义:JAVA 反射机制是在运行状态中,对于任...

zzz1122334
今天
4
0
聊聊nacos的LocalConfigInfoProcessor

序 本文主要研究一下nacos的LocalConfigInfoProcessor LocalConfigInfoProcessor nacos-1.1.3/client/src/main/java/com/alibaba/nacos/client/config/impl/LocalConfigInfoProcessor.java p......

go4it
昨天
8
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部