1.X轴在平面坐标是左右方向,这里得到的是左右移动距离
2.移动距离得到了,接下来要考虑以那个轴为中心作旋转
3.这里要实现固定摄像机位置的情况下旋转物体,就以Y轴为中心,所以Rotate(0,mousX,0);
public float roate_Speed=100.0f;//旋转速度
void Update()
{
Transform target_transform = null;//不用绑定对象,下面代码动态获取对象
if (Input.GetMouseButton(0))
{
//在屏幕上转换坐标:将鼠标点转换成射线
Ray rayObj = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hitObj;
if (Physics.Raycast(rayObj, out hitObj))
{
//Debug.Log("射线得到的对象名称:" + hitObj.collider.name);
target_transform = hitObj.transform;
}
if (target_transform != null)
{
//Debug.Log("射线取得对象");
float mousX = Input.GetAxis("Mouse X") * roate_Speed;//得到鼠标移动距离
target_transform.transform.Rotate(new Vector3(0, -mousX, 0));
}
else
{
Debug.Log("无法取得对象");
}
}
}
这里主要用到RoateAround方法
transform.RotateAround (Vector3.zero, Vector3.up, 20 * Time.deltaTime);
参数1:目标的坐标(围绕哪个物体旋转)
参数2:目标的轴向(是以X轴旋转还是以Y轴旋转还是以Z轴旋转)
参数3:旋转速度(这个好明白)
using UnityEngine;
using System.Collections;
using System.IO;
public class ScaleAndRotate : MonoBehaviour
{
private Touch oldTouch1; //上次触摸点1(手指1)
private Touch oldTouch2; //上次触摸点2(手指2)
void Start()
{
}
void Update () {
//没有触摸
if ( Input.touchCount <= 0 ){
return;
}
//单点触摸, 水平上下旋转
if( 1 == Input.touchCount ){
Touch touch = Input.GetTouch (0);
Vector2 deltaPos = touch.deltaPosition;
transform.Rotate(Vector3.down * deltaPos.x , Space.World);
transform.Rotate(Vector3.right * deltaPos.y , Space.World);
}
//多点触摸, 放大缩小
Touch newTouch1 = Input.GetTouch (0);
Touch newTouch2 = Input.GetTouch (1);
//第2点刚开始接触屏幕, 只记录,不做处理
if( newTouch2.phase == TouchPhase.Began ){
oldTouch2 = newTouch2;
oldTouch1 = newTouch1;
return;
}
//计算老的两点距离和新的两点间距离,变大要放大模型,变小要缩放模型
float oldDistance = Vector2.Distance(oldTouch1.position, oldTouch2.position);
float newDistance = Vector2.Distance(newTouch1.position, newTouch2.position);
//两个距离之差,为正表示放大手势, 为负表示缩小手势
float offset = newDistance - oldDistance;
//放大因子, 一个像素按 0.01倍来算(100可调整)
float scaleFactor = offset / 100f;
Vector3 localScale = transform.localScale;
Vector3 scale = new Vector3(localScale.x + scaleFactor,
localScale.y + scaleFactor,
localScale.z + scaleFactor);
//最小缩放到 0.3 倍
if (scale.x > 0.3f && scale.y > 0.3f && scale.z > 0.3f) {
transform.localScale = scale;
}
//记住最新的触摸点,下次使用
oldTouch1 = newTouch1;
oldTouch2 = newTouch2;
}
}
所谓自由视角是指玩家可以按照自身坐标系向着四个不同的方向移动,当玩家按下鼠标右键时,可以绕Y轴按照一定的角度旋转摄像机,在旋转的过程中,角色将旋转相应的角度。在移动的过程中,摄像机会保持与玩家间的一定距离,然后跟随角色进行移动。在开始今天的内容前,首先让我们来学习下Unity3D中较为重要的一部分知识,理解这些知识是我们开始学习今天内容的基础。
1、Input.GetAxis():该方法用于在Unity3D中根据坐标轴名称返回虚拟坐标系中的值,通常情况下,使用控制器和键盘输入时此值范围在-1到1之间。这段话怎么理解呢?我们来看下面这段脚本:
using UnityEngine;
using System.Collections;
public class example : MonoBehaviour {
//水平速度
public float HorizontalSpeed = 2.0F;
//垂直速度
public float VerticalSpeed = 2.0F;
void Update()
{
//水平方向
float h = HorizontalSpeed * Input.GetAxis("Mouse X");
//垂直方向
float v = VerticalSpeed * Input.GetAxis("Mouse Y");
//旋转
transform.Rotate(v, h, 0);
}
}
这段脚本呢是根据鼠标的位置来旋转物体从而实现对物体的观察,从这段脚本中我们可以看出,通过获取输入轴的办法,我们可以获得鼠标移动的方向进而实现对于物体的旋转控制。在Unity3D中我们可以通过Edit->Project Setting->Input来查看项目中的坐标轴名称。
2、欧拉角eulerAngles:该值是Vector3类型的值,x、y、z分别代表绕x轴旋转x度,绕y轴旋转y度,绕z轴旋转z度。因此,该值最为直观的形式是可以允许我们直接以一个三维向量的形式来修改一个物体的角度,例如下面的脚本:
float mY = 5.0;
void Update ()
{
mY += Input.GetAxis("Horizontal");
transform.eulerAngles =new Vector3(0,mY, 0);
}
如果你已经理解了上面的话,那么不出意外的,这段脚本会如你所愿的,按照鼠标在水平方向上移动的方向绕Y轴旋转。通常情况下,我们不会单独设置欧拉角其中一个轴,例如eulerAngles.x = 10,因为这将导致偏移和不希望的旋转。当设置它们一个新的值时,要同时设置全部。好在我们可以通过Quaternion.Euler()方法将一个Vector3类型的值转化为一个四元数,进而通过修改Transform.Rotation来实现相同的目的。
void Rotating (float horizontal, float vertical)
{
// Create a new vector of the horizontal and vertical inputs.
Vector3 targetDirection = new Vector3(horizontal, 0f, vertical);
// Create a rotation based on this new vector assuming that up is the global y axis.
Quaternion targetRotation = Quaternion.LookRotation(targetDirection, Vector3.up);
// Create a rotation that is an increment closer to the target rotation from the player's rotation.
Quaternion newRotation = Quaternion.Lerp(rigidbody.rotation, targetRotation, turnSmoothing * Time.deltaTime);
// Change the players rotation to this new rotation.
rigidbody.MoveRotation(newRotation);
}
插值的方法很简单,只要我们给出初始和结束的状态、时间就可以了,大家可以自己看API。
好了,有了这三部分的基础,我们就可以开始今天的内容了,今天的脚本分为两个部分,第一部分是角色控制的部分,主要负责的角色在场景中的移动、转身和动画处理。第二部分是相机控制的部分,主要涉及相机旋转、相机缩放的相关内容。下面,我们分别来讲这两个部分:
在第一部分,主要的是完成角色向各个方向的转身,这里博主定义四个方向(其实八个方向是一样的!),脚本如下:
using UnityEngine;
using System.Collections;
public class NoLockiVew_Player : MonoBehaviour {
/*自由视角下的角色控制*/
//玩家的行走速度
public float WalkSpeed=1.5F;
//重力
public float Gravity=20;
//角色控制器
private CharacterController mController;
//动画组件
private Animation mAnim;
//玩家方向,默认向前
private DirectionType mType=DirectionType.Direction_Forward;
[HideInInspector]
//玩家状态,默认为Idle
public PlayerState State=PlayerState.Idle;
//定义玩家的状态枚举
public enum PlayerState
{
Idle,
Walk
}
//定义四个方向的枚举值,按照逆时针方向计算
protected enum DirectionType
{
Direction_Forward=90,
Direction_Backward=270,
Direction_Left=180,
Direction_Right=0
}
void Start ()
{
//获取角色控制器
mController=GetComponent();
//获取动画组件
mAnim=GetComponentInChildren();
}
void Update ()
{
MoveManager();
//MouseEvent();
}
//玩家移动控制
void MoveManager()
{
//移动方向
Vector3 mDir=Vector3.zero;
if(mController.isGrounded)
{
//将角色旋转到对应的方向
if(Input.GetAxis("Vertical")==1)
{
SetDirection(DirectionType.Direction_Forward);
mDir=Vector3.forward * Time.deltaTime * WalkSpeed;
mAnim.CrossFade("Walk",0.25F);
State=PlayerState.Walk;
}
if(Input.GetAxis("Vertical")==-1)
{
SetDirection(DirectionType.Direction_Backward);
mDir=Vector3.forward * Time.deltaTime * WalkSpeed;
mAnim.CrossFade("Walk",0.25F);
State=PlayerState.Walk;
}
if(Input.GetAxis("Horizontal")==-1)
{
SetDirection(DirectionType.Direction_Left);
mDir=Vector3.forward * Time.deltaTime * WalkSpeed;
mAnim.CrossFade("Walk",0.25F);
State=PlayerState.Walk;
}
if(Input.GetAxis("Horizontal")==1)
{
SetDirection(DirectionType.Direction_Right);
mDir=Vector3.forward * Time.deltaTime * WalkSpeed;
mAnim.CrossFade("Walk",0.25F);
State=PlayerState.Walk;
}
//角色的Idle动画
if(Input.GetAxis("Vertical")==0 && Input.GetAxis("Horizontal")==0)
{
mAnim.CrossFade("Idle",0.25F);
State=PlayerState.Idle;
}
}
//考虑重力因素
mDir=transform.TransformDirection(mDir);
float y=mDir.y-Gravity *Time.deltaTime;
mDir=new Vector3(mDir.x,y,mDir.z);
mController.Move(mDir);
}
//设置角色的方向,有问题
void SetDirection(DirectionType mDir)
{
if(mType!=mDir)
{
transform.Rotate(Vector3.up*(mType-mDir));
mType=mDir;
}
}
}
这里定义四个方向,是按照逆时针方向转的,相邻的两个方向间相差90度,所以我们只需要将当前的角度和目标角度相减就可以转到目标角度的方向(其实这是以前写的代码,现在回头再看,直接用欧拉角似乎更为简单啊,呵呵)。这里主要的内容就是这样了。下面我们来看相机控制部分的代码吧,这里的代码参考了MouseOrbit脚本,主要完成了鼠标右键旋转控制,博主在此基础上增加了相机缩放的代码。提到相机缩放,其实就是根据鼠标滚轮滚动的方向和大小重新计算角色与相机的距离,与之类似地还有小地图的放缩,其实同样是通过修改距离来实现的。博主今天的一个体会是官方的代码能自己写一遍的最好自己写一遍,这样好多东西就能在这个过程中给理解了。我们一起来看脚本:
using UnityEngine;
using System.Collections;
public class NoLockView_Camera : MonoBehaviour
{
//观察目标
public Transform Target;
//观察距离
public float Distance = 5F;
//旋转速度
private float SpeedX=240;
private float SpeedY=120;
//角度限制
private float MinLimitY = 5;
private float MaxLimitY = 180;
//旋转角度
private float mX = 0.0F;
private float mY = 0.0F;
//鼠标缩放距离最值
private float MaxDistance=10;
private float MinDistance=1.5F;
//鼠标缩放速率
private float ZoomSpeed=2F;
//是否启用差值
public bool isNeedDamping=true;
//速度
public float Damping=2.5F;
void Start ()
{
//初始化旋转角度
mX=transform.eulerAngles.x;
mY=transform.eulerAngles.y;
}
void LateUpdate ()
{
//鼠标右键旋转
if(Target!=null && Input.GetMouseButton(1))
{
//获取鼠标输入
mX += Input.GetAxis("Mouse X") * SpeedX * 0.02F;
mY -= Input.GetAxis("Mouse Y") * SpeedY * 0.02F;
//范围限制
mY = ClampAngle(mY,MinLimitY,MaxLimitY);
}
//鼠标滚轮缩放
Distance-=Input.GetAxis("Mouse ScrollWheel") * ZoomSpeed;
Distance=Mathf.Clamp(Distance,MinDistance,MaxDistance);
//重新计算位置和角度
Quaternion mRotation = Quaternion.Euler(mY, mX, 0);
Vector3 mPosition = mRotation * new Vector3(0.0F, 0.0F, -Distance) + Target.position;
//设置相机的角度和位置
if(isNeedDamping){
//球形插值
transform.rotation = Quaternion.Lerp(transform.rotation,mRotation, Time.deltaTime*Damping);
//线性插值
transform.position = Vector3.Lerp(transform.position,mPosition, Time.deltaTime*Damping);
}else{
transform.rotation = mRotation;
transform.position = mPosition;
}
//将玩家转到和相机对应的位置上
if(Target.GetComponent().State==NoLockiVew_Player.PlayerState.Walk)
{
Target.eulerAngles=new Vector3(0,mX,0);
}
}
private float ClampAngle (float angle,float min,float max)
{
if (angle < -360) angle += 360;
if (angle > 360) angle -= 360;
return Mathf.Clamp (angle, min, max);
}
}
这里很多朋友可能对我设置一个状态很不理解吧,这其实是为了让玩家有一个自由查看角色的机会,否则当玩家按下鼠标右键的话,角色就会转向相机正对着的位置,这样玩家就看不到角色的正面了。当然,这里用到了插值,这样能使角色在转身的时候平滑一点,效果会更好。
Bug:
//设置玩家跟随角度
if(Target.GetComponent().State==NoLockiVew_Player.PlayerState.Walk)
{
Target.rotation=Quaternion.Euler(new Vector3(0,mX,0));
}
该方法主要的作用是当玩家同时按下方向控制键和鼠标右键,玩家可以随着鼠标旋转到对应的角度,这主要是为了满足玩家双手操作的需求,不过由于这行代码,导致玩家在向左、向右、向后三个方向上的转身失效,如果除去这行代码,则原来的方向控制没有任何问题,可是没有这行代码,玩家的操作感就会下降。后来博主想到我们对角色的旋转实际上应该是放在鼠标右键事件里的,所以博主将代码修改如下,这样就解决了这个Bug:
using UnityEngine;
using System.Collections;
public class NoLockView_Camera : MonoBehaviour
{
//观察目标
public Transform Target;
//观察距离
public float Distance = 5F;
//旋转速度
private float SpeedX=240;
private float SpeedY=120;
//角度限制
private float MinLimitY = 5;
private float MaxLimitY = 180;
//旋转角度
private float mX = 0.0F;
private float mY = 0.0F;
//鼠标缩放距离最值
private float MaxDistance=10;
private float MinDistance=1.5F;
//鼠标缩放速率
private float ZoomSpeed=2F;
//是否启用差值
public bool isNeedDamping=true;
//速度
public float Damping=10F;
private Quaternion mRotation;
void Start ()
{
//初始化旋转角度
mX=transform.eulerAngles.x;
mY=transform.eulerAngles.y;
}
void LateUpdate ()
{
//鼠标右键旋转
if(Target!=null && Input.GetMouseButton(1))
{
//获取鼠标输入
mX += Input.GetAxis("Mouse X") * SpeedX * 0.02F;
mY -= Input.GetAxis("Mouse Y") * SpeedY * 0.02F;
//范围限制
mY = ClampAngle(mY,MinLimitY,MaxLimitY);
//计算旋转
mRotation = Quaternion.Euler(mY, mX, 0);
//根据是否插值采取不同的角度计算方式
if(isNeedDamping){
transform.rotation = Quaternion.Lerp(transform.rotation,mRotation, Time.deltaTime*Damping);
}else{
transform.rotation = mRotation;
}
//处理同时按下鼠标右键和方向控制键
if(Target.GetComponent().State==NoLockiVew_Player.PlayerState.Walk){
Target.rotation=Quaternion.Euler(new Vector3(0,mX,0));
}
}
//鼠标滚轮缩放
Distance-=Input.GetAxis("Mouse ScrollWheel") * ZoomSpeed;
Distance=Mathf.Clamp(Distance,MinDistance,MaxDistance);
//重新计算位置
Vector3 mPosition = mRotation * new Vector3(0.0F, 0.0F, -Distance) + Target.position;
//设置相机的角度和位置
if(isNeedDamping){
transform.position = Vector3.Lerp(transform.position,mPosition, Time.deltaTime*Damping);
}else{
transform.position = mPosition;
}
}
//角度限制
private float ClampAngle (float angle,float min,float max)
{
if (angle < -360) angle += 360;
if (angle > 360) angle -= 360;
return Mathf.Clamp (angle, min, max);
}
}
using UnityEngine;
using System.Collections;
public class SmoothFollowerObj : MonoBehaviour {
private Vector3 targetPosition;
private Vector3 position;
private Vector3 velocity;
private float smoothingTime;
private float prediction;
public SmoothFollowerObj(float smoothingTime)
{
targetPosition = Vector3.zero;
position = Vector3.zero;
velocity = Vector3.zero;
this.smoothingTime = smoothingTime;
prediction = 1;
}
public SmoothFollowerObj(float smoothingTime, float prediction)
{
targetPosition = Vector3.zero;
position = Vector3.zero;
velocity = Vector3.zero;
this.smoothingTime = smoothingTime;
this.prediction = prediction;
}
// Update should be called once per frame
public Vector3 Update(Vector3 targetPositionNew, float deltaTime)
{
Vector3 targetVelocity = (targetPositionNew - targetPosition) / deltaTime;
targetPosition = targetPositionNew;
float d = Mathf.Min(1, deltaTime / smoothingTime);
velocity = velocity * (1 - d) + (targetPosition + targetVelocity * prediction - position) * d;
position += velocity * Time.deltaTime;
return position;
}
public Vector3 Update(Vector3 targetPositionNew, float deltaTime, bool reset)
{
if (reset)
{
targetPosition = targetPositionNew;
position = targetPositionNew;
velocity = Vector3.zero;
return position;
}
return Update(targetPositionNew, deltaTime);
}
public Vector3 GetPosition() { return position; }
public Vector3 GetVelocity() { return velocity; }
}
下面该段脚本赋给摄像机,被跟随物体赋给character即可:
using UnityEngine;
using System.Collections;
public class NoGravityCamera : MonoBehaviour {
public GameObject character;
public Vector3 positionVector;
public Vector3 lookVector;
private SmoothFollowerObj posFollow;
private SmoothFollowerObj lookFollow;
private Vector3 lastVelocityDir;
private Vector3 lastPos;
// Use this for initialization
void Start()
{
//positionVector = new Vector3(0, 2, 4);
//lookVector = new Vector3(0, 0, 1.5f);
posFollow = new SmoothFollowerObj(0.5f, 0.5f);
lookFollow = new SmoothFollowerObj(0.1f, 0.0f);
posFollow.Update(transform.position, 0, true);
lookFollow.Update(character.transform.position, 0, true);
lastVelocityDir = character.transform.forward;
lastPos = character.transform.position;
}
// Update is called once per frame
void LateUpdate()
{
lastVelocityDir += (character.transform.position - lastPos) * 8;
lastPos = character.transform.position;
lastVelocityDir += character.transform.forward * Time.deltaTime;
lastVelocityDir = lastVelocityDir.normalized;
Vector3 horizontal = transform.position - character.transform.position;
Vector3 horizontal2 = horizontal;
Vector3 vertical = character.transform.up;
Vector3.OrthoNormalize(ref vertical, ref horizontal2);
if (horizontal.sqrMagnitude > horizontal2.sqrMagnitude) horizontal = horizontal2;
transform.position = posFollow.Update(
character.transform.position + horizontal * Mathf.Abs(positionVector.z) + vertical * positionVector.y,
Time.deltaTime
);
horizontal = lastVelocityDir;
Vector3 look = lookFollow.Update(character.transform.position + horizontal * lookVector.z - vertical * lookVector.y, Time.deltaTime);
transform.rotation = Quaternion.FromToRotation(transform.forward, look - transform.position) * transform.rotation;
}
}
当摄像机处于跟随主角状态时,那么主角移动后很有可能摄像机被别的模型挡住。这样用户体验就非常不好,一般3D游戏在处理视角的时候有两种方法,第一种是让被挡住的模型变成透明,第二种是拉近摄像机。前者时候固定视角游戏使用,后者适合变化视角游戏使用。
http://www.xuanyusong.com/archives/1991