点积示例图:
1. 相机根据玩家与玩家位置 进行智能跟随 方法
在使用相机跟随玩家对象的时候。往往会使用多种模式进行。本文将介绍两种方式。
其实,就是就是transform.LookAt 不同参数的效果。
但是,都是可以根据玩家与敌人的距离进行相应的 高度,距离变化的。
由于比较简单,所以直接老规矩上脚本吧。
using UnityEngine;
public class FollowCamera : MyMonoSingleton<FollowCamera>
{
public enum CamerMotionMode
{
FollowOnly, // 只跟随
Trailing //尾随,会根据玩家旋转进行旋转
}
public CamerMotionMode camerMotionMode = CamerMotionMode.FollowOnly;
[Tooltip("玩家对象")]
public Transform SelfTarget;
[Tooltip("敌人对象")]
public Transform EnemyTarget;
[Tooltip("只用于观察")]
public float spaceBetween = 0; //敌人与自己之间的距离
public float Distance = 10f; //后方距离
public float Height = 5f; //高度
public float smooth = 2f; // 插值系数
private Vector3 tagerPosition; // 目标位置
void Awake()
{
if (Instance != null)
Debug.LogError("Instance FollowCamera x2");
else
{
Instance = this;
}
}
float dis_register = 0; //寄存距离值
float height_register = 0; //寄存高度值
void LateUpdate()
{
//根据双方距离调节相机高度以及距离
if(EnemyTarget != null) // 如果存在敌人。则时刻对自己与敌人的距离进行一个插值
{
dis_register = Vector3.Distance(SelfTarget.position, EnemyTarget.transform.position);
spaceBetween = Mathf.Lerp(spaceBetween, dis_register, Time.deltaTime);
}
else // 如果不存在敌人,则缓慢的从新回到最初高度。
{
if(spaceBetween > 0 )
{
spaceBetween = Mathf.Lerp(spaceBetween, 0, Time.deltaTime);
}
else
{
spaceBetween = 0;
}
}
height_register = Height + (spaceBetween / 4); // 得到最终高度
float currentHeight = Mathf.Lerp(transform.position.y, SelfTarget.position.y + height_register, smooth * Time.deltaTime); // 对高度进行插值计算。防止出现摄像机位置突然变化
if(camerMotionMode == CamerMotionMode.FollowOnly)
{
// 和下面逻辑一下。
transform.position = SelfTarget.position; // 得到玩家位置
transform.position -= Vector3.forward * (Distance + (spaceBetween / 1.5f)); // 向玩家位置后方移动
transform.position = new Vector3(transform.position.x, currentHeight, transform.position.z); // 向上方移动
transform.LookAt(SelfTarget.position); // 只看向位置
}
else
{
/* 下面的代码和上面的代码 效果差不多。逻辑都是一样的。 得到,玩家位置向后以及向上的 一个 向量。然后对让相机去这个向量的位置。 但是如果,使用下面这个方式
就要对smooth 进行加大,不然,两次插值计算,得到的运动会很慢。或者读者改其他方式,因为这里我不用这个方法所以不多做改动了。哈哈哈 ~ ~ ~ ~
*/
tagerPosition = SelfTarget.position + Vector3.up * currentHeight - SelfTarget.forward * (Distance + (spaceBetween / 1.5f));
transform.position = Vector3.Lerp(transform.position, tagerPosition, Time.deltaTime * smooth);
transform.LookAt(SelfTarget); // 看向位置和更具旋转进行相应变化
}
}
public void SetTarget(Transform target)
{
SelfTarget = target;
}
public void SetParame(float dis, float height, float heightDamp)
{
Distance = dis;
Height = height;
smooth = heightDamp;
}
}
2. 对象平滑跟随目标
咱还是直接代码吧.官方案例
using UnityEngine;
public class SmoothFollow : MonoBehaviour
{
// The target we are following
[SerializeField]
private Transform target;
// The distance in the x-z plane to the target
[SerializeField]
private float distance = 10.0f;
// the height we want the camera to be above the target
[SerializeField]
private float height = 5.0f;
[SerializeField]
private float rotationDamping;
[SerializeField]
private float heightDamping;
// Use this for initialization
void Start() { }
// Update is called once per frame
void LateUpdate()
{
// Early out if we don't have a target
if (!target)
return;
// Calculate the current rotation angles
var wantedRotationAngle = target.eulerAngles.y;
var wantedHeight = target.position.y + height;
var currentRotationAngle = transform.eulerAngles.y;
var currentHeight = transform.position.y;
// Damp the rotation around the y-axis
currentRotationAngle = Mathf.LerpAngle(currentRotationAngle, wantedRotationAngle, rotationDamping * Time.deltaTime);
// Damp the height
currentHeight = Mathf.Lerp(currentHeight, wantedHeight, heightDamping * Time.deltaTime);
// Convert the angle into a rotation
var currentRotation = Quaternion.Euler(0, currentRotationAngle, 0);
// Set the position of the camera on the x-z plane to:
// distance meters behind the target
transform.position = target.position;
transform.position -= currentRotation * Vector3.forward * distance;
// Set the height of the camera
transform.position = new Vector3(transform.position.x ,currentHeight , transform.position.z);
// Always look at the target
transform.LookAt(target);
}
}
3. 确定一个对象是否在这个对象的锥形范围之内。可以不使用碰撞或者粒子检测**
咱还是直接代码吧.
public class GameAlgorithm
{
public static bool JudgmentRoundRange(Transform self , Transform tager, float high = 1, float wide = 1,float distance = 100)
{
if (Vector3.Distance(self.position, tager.position) > distance || Vector3.Dot(self.position, (tager.position - self.position)) < 0) // 距离 范围内部
{
Debug.Log("car of distances : Ok");
return false;
}
else
{
Debug.Log("two of distances : Ok");
Vector3 shootdir = (tager.position - self.position).normalized; // 射击的方向
Vector3 right = self.transform.right; // 玩家 右边
Vector3 forward = self.transform.forward; // 玩家 前面
Vector3 up = self.transform.up; //玩家 下面
//算出在xoz方向上的投影
Vector3 dirRight = Vector3.Cross(Vector3.Cross(up, shootdir).normalized, up).normalized;
//yoz方向上的投影
Vector3 dirUp = Vector3.Cross(Vector3.Cross(right, shootdir).normalized, right).normalized;
//左右cos,递减区间(-60,60):(>=)
bool condition3 = Vector3.Dot(forward, dirRight) >= Mathf.Cos(35 * Mathf.Deg2Rad); // 大于 20 度的 弧
//上下cos(-45,45)
return condition3 && Vector3.Dot(forward, dirUp) >= Mathf.Cos(35 * Mathf.Deg2Rad);
}
}
}
4. 获取网络时间(用户软件的限时操作,或者需要网络时间的操作)
哭笑,还是直接代码吧.
public class NetWorkManage : MonoBehaviour
{
private float timeStamp;
// private string timeStamp_Str;
private StringBuilder timeStamp_Str = new StringBuilder(25, 30);
Double timerStr;
DateTime time = DateTime.MinValue;
DateTime startTime = TimeZone.CurrentTimeZone.ToLocalTime(new DateTime(1970, 1, 1));
public void Awake()
{
StartCoroutine(GetTimeStamp()); //开始 获取网络时间
}
// 获取一次网络时间之后,让时间自己进行累加。不需要重复获取。
private void FixedUpdate()
{
timerStr += Time.deltaTime * 1000;
time = startTime.AddMilliseconds(timerStr);
timeStamp_Str.Remove(0, timeStamp_Str.Length);
timeStamp_Str.Append(time.ToString());
timeStamp_Str.Append("\n");
timeStamp_Str.Append(time.Millisecond.ToString());
// timeStamp_Str = time.ToString();
timeStamp = time.Millisecond;
}
#region 网络时间
/// <summary>
/// 获取 时间的 当前 秒
/// </summary>
public int GetGameTimeSecond
{
get
{
return time.Second;
}
}
/// <summary>
/// 获取当前时间戳
/// </summary>
public float GetGameTimeStamp
{
get
{
return timeStamp;
}
}
/// <summary>
/// 获取当前时间,以及当前毫秒数值
/// </summary>
public string GetGameTimeStampStr
{
get
{
if (timeStamp_Str != null)
{
return timeStamp_Str.ToString();
}
else
{
return null;
}
}
}
float waitTime = 1;
private IEnumerator GetTimeStamp()
{
WWW www = new WWW("http://www.hko.gov.hk/cgi-bin/gts/time5a.pr?a=1");
yield return www;
try
{
if(!Double.TryParse(www.text.Substring(2),out timerStr))
{
StartCoroutine(GetTimeStamp());
}
}
catch (Exception e)
{
PublicClass.Log("NetWork error : " + e);
}
yield return new WaitForSeconds(waitTime); // 防止 时间有偏差,刚开始会快速更新。后面十秒间隔更新一次
waitTime++;
waitTime = Mathf.Min(waitTime,10);
StartCoroutine(GetTimeStamp());
}
#endregion 网络时间
}
5.第三人称的人物摄像头控制,中间控制远近,右键滑动控制旋转,如果检测到障碍物,自己调节相机远近距离
咱还是直接代码吧
using UnityEngine;
using System.Collections;
public class PlayerCamera : MonoBehaviour
{
public Transform target;
public float attackTimer;
public float targetHeight = 1.7f;
public float distance = 5.0f;
public float offsetFromWall = 0.1f;
public float maxDistance = 20;
public float minDistance = .6f;
public float xSpeed = 200.0f;
public float ySpeed = 200.0f;
public int yMinLimit = -80;
public int yMaxLimit = 80;
public int zoomRate = 40;
public float rotationDampening = 3.0f;
public float zoomDampening = 5.0f;
public LayerMask collisionLayers = -1;
private float xDeg = -53.2f;
private float yDeg = 22.4f;
private float currentDistance;
private float desiredDistance;
private float correctedDistance;
void Start ()
{
attackTimer=0.05f;
currentDistance = distance;
desiredDistance = distance;
correctedDistance = distance;
if (GetComponent<Rigidbody>())
GetComponent<Rigidbody>().freezeRotation = true;
}
void LateUpdate ()
{
if(attackTimer>0)
attackTimer-=Time.deltaTime;
if(attackTimer<0)
attackTimer=0;
if(attackTimer==0){
target = GameObject.FindGameObjectWithTag("Player").transform;
}
Vector3 vTargetOffset;
if (!target){
return;
}
if (Input.GetMouseButton(1) )
{
xDeg += Input.GetAxis ("Mouse X") * xSpeed * 0.02f;
yDeg -= Input.GetAxis ("Mouse Y") * ySpeed * 0.02f;
}
yDeg = ClampAngle (yDeg, yMinLimit, yMaxLimit);
xDeg = ClampAngle (xDeg, -360, 360);
Quaternion rotation = Quaternion.Euler (yDeg, xDeg, 0);
desiredDistance -= Input.GetAxis ("Mouse ScrollWheel") * Time.deltaTime * zoomRate * Mathf.Abs (desiredDistance);
desiredDistance = Mathf.Clamp (desiredDistance, minDistance, maxDistance);
correctedDistance = desiredDistance;
vTargetOffset = new Vector3 (0, -targetHeight, 0);
Vector3 position = target.position - (rotation * Vector3.forward * desiredDistance + vTargetOffset);
RaycastHit collisionHit;
Vector3 trueTargetPosition = new Vector3 (target.position.x, target.position.y + targetHeight, target.position.z);
bool isCorrected = false;
if (Physics.Linecast (trueTargetPosition, position, out collisionHit, collisionLayers.value))
{
correctedDistance = Vector3.Distance (trueTargetPosition, collisionHit.point) - offsetFromWall;
isCorrected = true;
}
currentDistance = !isCorrected || correctedDistance > currentDistance ? Mathf.Lerp (currentDistance, correctedDistance, Time.deltaTime * zoomDampening) : correctedDistance;
currentDistance = Mathf.Clamp (currentDistance, minDistance, maxDistance);
position = target.position - (rotation * Vector3.forward * currentDistance + vTargetOffset);
transform.rotation = rotation;
transform.position = position;
}
private static float ClampAngle (float angle, float min, float max)
{
if (angle < -360)
angle += 360;
if (angle > 360)
angle -= 360;
return Mathf.Clamp (angle, min, max);
}
}
6. 在游戏运行的时候,往往需要 在 正交Orthographic (无消失点投影)
与透视Perspective (有消失点投影)
两个视角中来回转化。 以达到 不同的 2D 与 3D 视角。**
So! 今天的 实例代码就是 描述了 这一个功能:
using UnityEngine;
public enum Views
{
_2DView = 1,
_3DView
}
public class BackupCameraProjectionChange
{
/// <summary>
/// 相机透视改变是否触发(调用只需把此值改为true)
/// </summary>
public bool ChangeProjection = false;
private bool _changing = false;
private float ProjectionChangeTime = 0.5f;
private float _currentT = 0.0f;
private Camera m_Camera;
protected Views cur_Views = Views._3DView;
public Views GetCurViews
{
get
{
return cur_Views;
}
}
public BackupCameraProjectionChange(Camera camera = null , float speed = 0.5f)
{
ProjectionChangeTime = speed;
if (m_Camera == null && camera == null)
{
m_Camera = Camera.main;
}
else
{
m_Camera = camera;
}
}
///这个 Update 需要在 其他继承自 MonoBehaviour 类的 Update 中 调用
public void Update()
{
if (m_Camera == null)
return;
if (_changing)
{
ChangeProjection = false;
}
else if (ChangeProjection)
{
_changing = true;
_currentT = 0.0f;
}
LateUpdate();
}
void LateUpdate()
{
if (!_changing)
{
return;
}
//将当前的 是否正视图值 赋值给currentlyOrthographic变量
bool currentlyOrthographic = m_Camera.orthographic;
//定义变量存放当前摄像机的透视和正视矩阵信息;
Matrix4x4 orthoMat, persMat;
if (currentlyOrthographic)//如果当前摄像机为正视状态,则切换为3D
{
orthoMat = m_Camera.projectionMatrix; //保留 2D矩阵信息
m_Camera.orthographic = false; //为 3D
m_Camera.ResetProjectionMatrix();
persMat = m_Camera.projectionMatrix; //保留 3D 矩阵信息
cur_Views = Views._3DView;
}
else//否则当前摄像机为透视状态, 则切换为2D
{
persMat = m_Camera.projectionMatrix; //保留 3D 矩阵信息
m_Camera.orthographic = true; //为2D
m_Camera.ResetProjectionMatrix();
orthoMat = m_Camera.projectionMatrix; //保留 2D矩阵信息
cur_Views = Views._2DView;
}
m_Camera.orthographic = currentlyOrthographic;
_currentT += (Time.deltaTime / ProjectionChangeTime);
if (_currentT < 1.0f)
{
if (currentlyOrthographic)
{
m_Camera.projectionMatrix = MatrixLerp(orthoMat, persMat, _currentT * _currentT); //从2D 到 3D
}
else
{
m_Camera.projectionMatrix = MatrixLerp(persMat, orthoMat, Mathf.Sqrt(_currentT)); //从3D 到 2D
}
}
else
{
_changing = false;
m_Camera.orthographic = !currentlyOrthographic; //取反
m_Camera.ResetProjectionMatrix(); // 重置
}
}
private Matrix4x4 MatrixLerp(Matrix4x4 from, Matrix4x4 to, float t)
{
t = Mathf.Clamp(t, 0.0f, 1.0f);
Matrix4x4 newMatrix = new Matrix4x4();
newMatrix.SetRow(0, Vector4.Lerp(from.GetRow(0), to.GetRow(0), t));
newMatrix.SetRow(1, Vector4.Lerp(from.GetRow(1), to.GetRow(1), t));
newMatrix.SetRow(2, Vector4.Lerp(from.GetRow(2), to.GetRow(2), t));
newMatrix.SetRow(3, Vector4.Lerp(from.GetRow(3), to.GetRow(3), t));
return newMatrix;
}
}
7. 曲线算法。使用此算法把向量之间曲线话。使用与 AI 的 位移,导弹的曲线轨迹,等等一些曲线路径。
So! 今天的 实例代码就是 描述了 这一个功能:
更多曲线算法:插值与样条
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CatmullRom : MonoBehaviour {
public List<Transform> Ponits;
public Transform car;
public float v=2;
public float t;
float dt;
float timer = 0;
float GetThreeBSplineValue(float p0, float p1, float p2, float p3, float t)
{
float A0 = (p0 + 4 * p1 + p2) / 6;
float A1 = -(p0 - p2) / 2;
float A2 = (p0 - 2 * p1 + p2) / 2;
float A3 = -(p0 - 3 * p1 + 3 * p2 - p3) / 6;
return A0 + A1 * t + A2 * t * t + A3 * t * t * t;
}
Vector3 GetThreeBSplineValue(Vector3 P0, Vector3 P1, Vector3 P2, Vector3 P3, float t)
{
Vector3 dot;
dot.x = GetThreeBSplineValue(P0.x, P1.x, P2.x, P3.x, t);
dot.y = GetThreeBSplineValue(P0.y, P1.y, P2.y, P3.y, t);
dot.z = GetThreeBSplineValue(P0.z, P1.z, P2.z, P3.z, t);
return dot;
}
private void Awake()
{
Ponits.Reverse();
}
public static Vector3 Caculate(Vector3 P0, Vector3 P1, Vector3 P2, Vector3 P3, float t)
{
float factor = 0.5f;
Vector3 c0 = P1;
Vector3 c1 = (P2 - P0) * factor;
Vector3 c2 = (P2 - P1) * 3.0f - (P3 - P1) * factor - (P2 - P0) * 2.0f * factor;
Vector3 c3 = (P2 - P1) * -2.0f + (P3 - P1) * factor + (P2 - P0) * factor;
Vector3 curvePoint = c3 * t * t * t + c2 * t * t + c1 * t + c0;
return curvePoint;
}
public void OnGUI()
{
if(GUILayout.Button("Run"))
{
float dis = Vector3.Distance(transform.position, Ponits[0].position);
for(int i = 0;i < Ponits.Count - 1; i++)
{
dis += Vector3.Distance(Ponits[i].position , Ponits[i+1].position);
}
t = dis / v;
m_Pair = Ponits.Count / 4;
t = t / m_Pair;
dt = Time.time;
m_index = 0;
m_timer = 0;
}
}
private void OnDrawGizmos()
{
Gizmos.color = Color.yellow;
float step = 100;
if (Ponits.Count < 4) return;
Vector3 pp1 = Ponits[0].position;
for (int j = 0; j < Ponits.Count - 3; j+=1)
{
for (int i = 1; i < step; i++)
{
Vector3 pp2 = GetThreeBSplineValue(Ponits[j].position, Ponits[j + 1].position, Ponits[j + 2].position, Ponits[j + 3].position, i / step);
Gizmos.DrawLine(pp1, pp2);
pp1 = pp2;
}
}
}
float m_timer = 0;
int m_index = -2;
int m_Pair = 0;
// Update is called once per frame
void Update () {
if (Vector3.Distance(car.position, Ponits[Ponits.Count-1].position) > 1)
{
if (dt < Time.time && t > 0)
{
m_timer += Time.deltaTime;
if (m_index < Ponits.Count - 3)
{
Vector3 head1;
Vector3 head2;
Vector3 head3;
Vector3 head4;
float tt = (Time.time - dt) / t;
if (m_index == -2)
{
head1 = transform.position;
head2 = transform.position;
head3 = Ponits[0].position;
head4 = Ponits[1].position;
tt = (Time.time - dt) / t;
}
else if (m_index == -1)
{
head1 = transform.position;
head2 = Ponits[0].position;
head3 = Ponits[1].position;
head4 = Ponits[2].position;
}
else
{
head1 = Ponits[m_index].position;
head2 = Ponits[m_index+1].position;
head3 = Ponits[m_index+2].position;
head4 = Ponits[m_index+3].position;
}
car.position = GetThreeBSplineValue(head1, head2, head3, head4, tt);
}
if(m_timer >= t)
{
if(m_index < Ponits.Count - 3)
{
Debug.Log(" : " + m_index + " : " + t);
m_index += 1;
}
dt = Time.time;
m_timer = 0;
}
}
}
}
}
我是李本心明
首先谢谢大家的支持,其次如果你碰到什么其他问题的话,欢迎来 我自己的一个 讨论群559666429
来(扫扫下面二维码或者点击群链接 Unity3D[ 交流] ),大家一起找答案,共同进步。
由于工作生活太忙了,对于大家的帮助时间已经没有之前那么充裕了。如果有志同道合的朋友,可以接受无偿的帮助别人,可以加我QQ单独联系我,一块经营一下。