—— 系列文章链接
Unity 一些有用的碎片知识整理 之 一 点击可跳转链接
Unity 一些有用的碎片知识整理 之 二 点击可跳转链接
Unity 一些有用的碎片知识整理 之 四 点击可跳转链接
目录
四十一、unity 文件流读取图片和www读取图片的比较
四十二、unity+Loom实现多线程(Thread)和主线程(MainThread)交互
四十三、Unity Android 权限
四十四、(LitJson)Unity JsonException: Max allowed object depth reached while trying to export from type System.Single
四十五、某物体发射射线打在远处平面,射线与平面的交点,映射到屏幕上(物体旋转发射的射线,转为准心位置)
四十六、打印代码执行话费时间
四十七、关闭Unity Log 打印
四十八、判断目标物体是否在视野内 TargetCusorOnBecameVisible
四十九、Unity ref 和out、 params的使用
五十一、OnDrawGizmosSelected() 和 OnDrawGizmos() 的区别
using System.Collections;
using UnityEngine;
using UnityEngine.UI;
using System.IO;
public class TestReadAndWWW : MonoBehaviour
{
public RawImage img;
public RawImage img1;
string path;
// Start is called before the first frame update
void Start()
{
path = Application.dataPath + "/M1000_001.jpg";
LoadByIO();
StartCoroutine(LoadByWWW());
}
// Update is called once per frame
void Update()
{
}
void LoadByIO()
{
//float time = Time.time;
System.DateTime dtm = System.DateTime.Now;
float time = dtm.Millisecond;
FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read);
fs.Seek(0, SeekOrigin.Begin);
byte[] bytes = new byte[fs.Length];
fs.Read(bytes, 0, (int)fs.Length);
fs.Close();
fs.Dispose();
fs = null;
Texture2D t = new Texture2D(1, 1);
t.LoadImage(bytes);
img.texture = t;
Debug.Log("IO读取图片用时:" + (System.DateTime.Now.Millisecond - time));
//Debug.Log("IO读取图片用时:" + (Time.time - time));
}
IEnumerator LoadByWWW()
{
//float time = Time.time;
System.DateTime dtm = System.DateTime.Now;
float time = dtm.Millisecond;
WWW w = new WWW("file://" + path);
yield return w;
if (string.IsNullOrEmpty(w.error) == false)
{
Debug.Log("error");
}
else
{
img1.texture = w.texture;
}
Debug.Log("www读取图片用时:" + (System.DateTime.Now.Millisecond - time));
}
}
Loom代码不多,只有168行, 然而却具备了子线程运行Action, 子线程与主线程交互的能力!
public static Thread RunAsync(Action a)
public static void QueueOnMainThread(Action action)
public static void QueueOnMainThread(Action action, float time)
首先Loom类继承自MonoBehaviour,第一次使用静态的Loom.Current时,就会判断,是否初始化(其实就是将Loom 挂 载到 一个自己创建的GameObject上),这样,QueueOnMainThread放进来的action就可以在每一帧回调的Update方法进行action调用。
RunAsync用线程池,运行在子线程中。 使用的时候, 当子线程的工作完成后, 可以在后面加一句Loom.QueueOnMainThread()实现线程切换!
最近在做资源更新时,需要显示现在的进度调。我在Unity中开启了一个线程来做下载任务,然后实时刷新下载进度。然后Unity报了一个错误。
get_isActiveAndEnabled can only be called from the main thread.
意思是Unity中的组件只能运行在Unity的主线程中,无法在新开的线程中调用Unity的组件。
用Loom实现多线程与主线程交互
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;
using System.Threading;
using System.Linq;
public class Loom :MonoBehaviour
{
public static int maxThreads = 8;
static int numThreads;
private static Loom _current;
//private int _count;
public static Loom Current
{
get
{
Initialize();
return _current;
}
}
void Awake()
{
_current = this;
initialized = true;
}
static bool initialized;
public static void Initialize()
{
if (!initialized)
{
if (!Application.isPlaying)
return;
initialized = true;
var g = new GameObject("Loom");
_current = g.AddComponent();
#if !ARTIST_BUILD
UnityEngine.Object.DontDestroyOnLoad(g);
#endif
}
}
public struct NoDelayedQueueItem
{
public Action
未使用Loom报错方法
public Text mText;
void Start ()
{
Thread thread = new Thread(RefreshText);
thread.Start();
}
void Update ()
{
}
private void RefreshText()
{
mText.text = "Hello Loom!";
}
更改后
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using System.Threading;
public class testLoom : MonoBehaviour
{
public Text mText;
void Start ()
{
// 用Loom的方法调用一个线程
Loom.RunAsync(
() =>
{
Thread thread = new Thread(RefreshText);
thread.Start();
}
);
}
void Update ()
{
}
private void RefreshText()
{
// 用Loom的方法在Unity主线程中调用Text组件
Loom.QueueOnMainThread((param) =>
{
mText.text = "Hello Loom!";
},null);
}
}
熟悉Unity的developer都知道在Unity中的线程不能使用Unity的对象,但可以使用Unity的值类型变量,如Vector3等。这样就使得线程在Unity中显的很鸡肋和蹩脚,因为很多函数很都是UnityEngine类或函数的调用的,对于哪些是可以在多线程使用,风雨冲进行了如下总结:
0. 变量(都能指向相同的内存地址)都是共享的
1. 不是UnityEngine的API能在分线程运行
2. UnityEngine定义的基本结构(int,float,Struct定义的数据类型)可以在分线程计算,如 Vector3(Struct)可以 , 但Texture2d(class,根父类为Object)不可以。
3. UnityEngine定义的基本类型的函数可以在分线程运行,如
int i = 99;
print (i.ToString());
Vector3 x = new Vector3(0,0,9);
x.Normalize();
类的函数不能在分线程运行
obj.name
实际是get_name函数,分线程报错误:get_name can only be called from the main thread.
Texture2D tt = new Texture2D(10,10);
实际会调用UnityEngine里的Internal_Create,分线程报错误:Internal_Create can only be called from the main thread.
其他transform.position,Texture.Apply()等等都不能在分线程里运行。
结论: 分线程可以做 基本类型的计算, 以及非Unity(包括.Net及SDK)的API。
Unity做了这个限制,主要是Unity的函数执行机制是帧序列调用,甚至连Unity的协程Coroutine的执行机制都是确定的,如果可以使用多线程访问UnityEngine的对象和api就得考虑同步问题了,也就是说Unity其实根本没有多线程的机制,协程只是达到一个延时或者是当指定条件满足是才继续执行的机制。
我们的项目目前还有没有比较耗时的计算,所以还没有看到Thread的使用。本来一直没有太考虑着方面的事情,直到在UnityGems.com看到Loom这个类,叹为观止呀。直接贴出人家的介绍(没必要翻译了):
Threads on a Loom
Our class is called Loom. Loom lets you easily run code on another thread and have that other thread run code on the main game thread when it needs to.
There are only two functions to worry about:
You access Loom using Loom.Current - it deals with creating an invisible game object to interact with the games main thread.
我们只需要关系两个函数:RunAsync(Action)和QueueOnMainThread(Action, [optional] float time) 就可以轻松实现一个函数的两段代码在C#线程和Unity的主线程中交叉运行。原理也很简单:用线程池去运行RunAsync(Action)的函数,在Update中运行QueueOnMainThread(Acition, [optional] float time)传入的函数。
直接贴出源码,供拜读:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;
using System.Threading;
using System.Linq;
public class Loom : MonoBehaviour
{
public static int maxThreads = 8;
static int numThreads;
private static Loom _current;
private int _count;
public static Loom Current
{
get
{
Initialize();
return _current;
}
}
void Awake()
{
_current = this;
initialized = true;
}
static bool initialized;
static void Initialize()
{
if (!initialized)
{
if(!Application.isPlaying)
return;
initialized = true;
var g = new GameObject("Loom");
_current = g.AddComponent();
}
}
private List _actions = new List();
public struct DelayedQueueItem
{
public float time;
public Action action;
}
private List _delayed = new List();
List _currentDelayed = new List();
public static void QueueOnMainThread(Action action)
{
QueueOnMainThread( action, 0f);
}
public static void QueueOnMainThread(Action action, float time)
{
if(time != 0)
{
lock(Current._delayed)
{
Current._delayed.Add(new DelayedQueueItem { time = Time.time + time, action = action});
}
}
else
{
lock (Current._actions)
{
Current._actions.Add(action);
}
}
}
public static Thread RunAsync(Action a)
{
Initialize();
while(numThreads >= maxThreads)
{
Thread.Sleep(1);
}
Interlocked.Increment(ref numThreads);
ThreadPool.QueueUserWorkItem(RunAction, a);
return null;
}
private static void RunAction(object action)
{
try
{
((Action)action)();
}
catch
{
}
finally
{
Interlocked.Decrement(ref numThreads);
}
}
void OnDisable()
{
if (_current == this)
{
_current = null;
}
}
// Use this for initialization
void Start()
{
}
List _currentActions = new List();
// Update is called once per frame
void Update()
{
lock (_actions)
{
_currentActions.Clear();
_currentActions.AddRange(_actions);
_actions.Clear();
}
foreach(var a in _currentActions)
{
a();
}
lock(_delayed)
{
_currentDelayed.Clear();
_currentDelayed.AddRange(_delayed.Where(d=>d.time <= Time.time));
foreach(var item in _currentDelayed)
_delayed.Remove(item);
}
foreach(var delayed in _currentDelayed)
{
delayed.action();
}
}
}
怎么实现一个函数内使用多线程计算又保持函数体内代码的顺序执行,印象中使用多线程就是要摆脱代码块的顺序执行,但这里是把原本一个函数分拆成为两部分:一部分在C#线程中使用,另一部还是得在Unity的MainThread中使用,怎么解决呢,还得看例子:
//Scale a mesh on a second thread
void ScaleMesh(Mesh mesh, float scale)
{
//Get the vertices of a mesh
var vertices = mesh.vertices;
//Run the action on a new thread
Loom.RunAsync(()=>{
//Loop through the vertices
for(var i = 0; i < vertices.Length; i++)
{
//Scale the vertex
vertices[i] = vertices[i] * scale;
}
//Run some code on the main thread
//to update the mesh
Loom.QueueOnMainThread(()=>{
//Set the vertices
mesh.vertices = vertices;
//Recalculate the bounds
mesh.RecalculateBounds();
});
});
}
这个例子是对Mesh的顶点进行放缩,同时也是一个使用闭包(closure)和lambda表达式的一个很好例子。看完例子,是不是很有把项目中一些耗时的函数给拆分出来,想用这个方法来改进下NGUI的底层机制(看下性能不能改进)。
小结:
本来我以为Loom的实现会比较复杂,当我发现只有100多行的代码是大为惊叹,这也得益于现在语言的改进,至少从语言使用的便利性上还是有很大的进步的。
有了Loom这个工具类,在很多涉及UnityEngine对象的耗时计算还是可以得到一个解决方法的:
如在场景中用A*算法进行大量的数据计算
变形网格中操作大量的顶点
持续的要运行上传数据到服务器
二维码识别等图像处理
Loom简单而又巧妙,佩服Loom的作者。
首先Unity一般是避免使用多线程的,unity提供了一种协程的概念(coroutine) yield,但是这个协程归根到底也不是多线程,它只是起到一个延迟执行的效果。
但是为什么我们需要使用多线程呢?前段时间项目中有一些地方使用协程并不能达到很好的效果,比如在游戏内进行大批量的拷贝和移动文件时,UI显示上会出现卡死现象,我怀疑是主线程被阻塞了。换成多线程后成功解决了问题。
适合使用多线程的地方
使用多线程是请注意
如何使用多线程
多线程使用时必须非常谨慎,而unity自带的Thread,并不是mono而是.net,是两套框架。therad的使用晦涩复杂,同时还需要注意线程安全问题。
所以我选择了一个市面上封装比较好的Loom工具类,代码很少,将会在最后贴出来,非常好用。
//RunAsync 用线程池,运行在子线程。
public static Thread RunAsync(Action a)
//主线程
public static void QueueOnMainThread(Action action)
public static void QueueOnMainThread(Action action, float time)
举个栗子
Loom.RunAsync(delegate {
do something
});
全部代码
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;
using System.Threading;
using System.Linq;
public class Loom : MonoBehaviour
{
public static int maxThreads = 10;
public static int numThreads;
private static Loom _current;
private int _count;
public static Loom Current
{
get
{
Initialize();
return _current;
}
}
void Awake()
{
_current = this;
initialized = true;
}
public static bool initialized;
static void Initialize()
{
if (!initialized)
{
if (!Application.isPlaying) {
return;
}
initialized = true;
var g = new GameObject("Loom");
_current = g.AddComponent();
}
}
private List _actions = new List();
public struct DelayedQueueItem
{
public float time;
public Action action;
}
private List _delayed = new List();
List _currentDelayed = new List();
public static void QueueOnMainThread(Action action)
{
QueueOnMainThread(action, 0f);
}
public static void QueueOnMainThread(Action action, float time)
{
if (time != 0)
{
lock (Current._delayed)
{
Current._delayed.Add(new DelayedQueueItem { time = Time.time + time, action = action });
}
}
else
{
lock (Current._actions)
{
Current._actions.Add(action);
}
}
}
public static Thread RunAsync(Action a)
{
Initialize();
while (numThreads >= maxThreads)
{
Thread.Sleep(1);
}
Interlocked.Increment(ref numThreads);
ThreadPool.QueueUserWorkItem(RunAction, a);
return null;
}
private static void RunAction(object action)
{
try
{
((Action)action)();
}
catch
{
}
finally
{
Interlocked.Decrement(ref numThreads);
}
}
void OnDisable()
{
if (_current == this)
{
_current = null;
}
}
// Use this for initialization
void Start() {
}
List _currentActions = new List();
// Update is called once per frame
void Update() {
lock (_actions) {
_currentActions.Clear();
_currentActions.AddRange(_actions);
_actions.Clear();
}
foreach (var a in _currentActions) {
a();
}
lock (_delayed) {
_currentDelayed.Clear();
_currentDelayed.AddRange(_delayed.Where(d => d.time <= Time.time));
foreach (var item in _currentDelayed) {
_delayed.Remove(item);
}
}
foreach (var delayed in _currentDelayed) {
delayed.action();
}
}
}
Android权限
权限是一种限制,用于限制对部分代码或设备上数据的访问。施加限制是为了保护可能被误用以致破坏或损害用户体验的关键数据和代码。每种权限均由一个唯一的标签标识。标签通常指示受限制的操作。
如果应用需要访问受权限保护的功能,则必须在清单中使用 元素声明应用需要该权限。将应用安装到设备上之后,安装程序会通过检查签署应用证书的颁发机构并(在某些情况下)询问用户,确定是否授予请求的权限。如果授予权限,则应用能够使用受保护的功能。否则,其访问这些功能的尝试将会失败,并且不会向用户发送任何通知。
参考: https://developer.android.com/guide/topics/manifest/manifest-intro.html#perms
Unity 权限
Android的权限配置于AndroidManifest.xml中
Unity会自动生成一些权限(Unity生成一个AndroidManifest.xml),然后找到插件(AAR和Android Libraries)的所有Android Manifest。合并到unity生成的xml中。这些都是Unity自动完成的。即使你指定了一份AndroidManifest.xml,Unity仍然会修改或加入一些Unity工程中所需的权限。如要完全修改可以导出Android工程进行修改,生成APK。
Unity根据您的应用程序从脚本调用的设置和Unity API,自动添加必要的权限到清单。例如:
APK在Android6.0设备上运行( Android API大于等于23),则应用程序将使用Android 运行时权限系统。
Android运行时权限系统要求应用程序在运行时授予权限,而不是首次安装时就获取权限。当应用程序运行时,应用程序用户通常可以授予或拒绝每个权限(例如,在拍摄照片之前请求摄像机许可)。这允许应用程序在没有权限的情况下运行有限的功能。
Unity不支持运行时权限系统,所以您的应用程序提示用户允许Android在启动时称为“危险”权限。有关更多信息,请参阅Android关于危险权限的文档。
提示用户允许危险的权限是确保在缺少权限时不会导致崩溃的唯一方法。但是,如果你不想让程序运行开始就弹出这些权限的窗口,可以在AndroidManifest中添加
有关运行时权限系统和处理权限的更多信息,请参阅Android开发人员文档的请求权限部分。
手动修改Unity AndroidManifest的方法
要使用Unity之外创建的Android清单,请将自定义的Android Manifest文件导入到以下位置:Assets / Plugins / Android / AndroidManifest.xml。这将覆盖默认的Unity创建的清单。
在这种情况下,Android Libraries的清单随后会被合并到这份清单中,并且所生成的清单仍然被Unity调整,以确保配置正确。要完全控制清单,包括权限,您需要导出项目并修改Android Studio中的最终清单。
原因:
LitJosn的数据类型支持下面几种
public JsonData(bool boolean);
public JsonData(double number);
public JsonData(int number);
public JsonData(long number);
public JsonData(object obj);
public JsonData(string str);
解决办法:
用于序列化或者反序列化的数据,其类型必须是上面几种
(PS:使用 float 会出错的)
using UnityEngine;
public class RayCast : MonoBehaviour {
public RectTransform image;
// Use this for initialization
void Start () {
Debug.Log(image.position);
Debug.Log(image.localPosition);
}
// Update is called once per frame
void Update () {
RaycastHit hit;
if (Physics.Raycast(transform.position, transform.forward, out hit, 1000,1<<9))
{
Debug.DrawLine(transform.position, hit.point, Color.green);
//Debug.Log("hit.point " + hit.point);
Vector3 screenPoint = Camera.main.WorldToScreenPoint(hit.point);
//Debug.Log("screenPoint.point " + screenPoint);
// 请注意数据转化,这个位置与你的UIImage的 RenderMode 和 image 锚点也有关
image.localPosition = new Vector3(screenPoint.x - Screen.width/2,screenPoint.y-Screen.height/2,image.localPosition.z);
}
if (Input.GetKeyDown(KeyCode.UpArrow)) {
this.transform.localEulerAngles = this.transform.localEulerAngles + Vector3.right * -10;
}
if (Input.GetKeyDown(KeyCode.DownArrow))
{
this.transform.localEulerAngles = this.transform.localEulerAngles + Vector3.right * 10;
}
if (Input.GetKeyDown(KeyCode.LeftArrow))
{
this.transform.localEulerAngles = this.transform.localEulerAngles + Vector3.up * -10;
}
if (Input.GetKeyDown(KeyCode.RightArrow))
{
this.transform.localEulerAngles = this.transform.localEulerAngles + Vector3.up * 10;
}
}
}
Stopwatch sw = new Stopwatch();
UnityEngine.Debug.Log(ArrowBLEDebugHelper.DEBUGMARK + " ++++++++++++++++++ OnBluetoothMessage ");
sw.Start();
sw.Stop();
UnityEngine.Debug.Log(sw.ElapsedMilliseconds);
Debug.unityLogger.logEnabled = false;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TargetCusorOnBecameVisible : MonoBehaviour
{
// 目标物体
public Transform targetTr;
// 物体在视野内外的事件
public Action OnInVisibleEvent;
public Action OnOutVisibleEvent;
//适配双屏的数值操作
private float height = Screen.height / 2;
private float weight = Screen.width / 4;
// 目标物体是否在指定区域内
private bool isVisible = true;
private bool IsVisible {
get {
return isVisible;
}
set {
isVisible = value;
if (value == true) {
if (OnInVisibleEvent != null)
{
OnInVisibleEvent();
}
Debug.Log(ArrowBLEDebugHelper.DEBUGMARK + " IsVisible is true");
}
else
{
if (OnOutVisibleEvent != null)
{
OnOutVisibleEvent();
}
Debug.Log(ArrowBLEDebugHelper.DEBUGMARK + " IsVisible is false");
}
}
}
void Start() {
// 每秒调用两次
InvokeRepeating("IsInvisible", 0.5f,0.5f);
}
///
/// 判断物体是否在某区域内
///
///
public bool IsInvisible()
{
// 目标物体在视野外
if (targetTr.localPosition.x < -weight || targetTr.localPosition.x > weight
|| targetTr.localPosition.y < -height || targetTr.localPosition.y > height
) {
if (IsVisible == true) {
IsVisible = false;
}
Debug.Log(ArrowBLEDebugHelper.DEBUGMARK + "视野外");
return false;
}
if (IsVisible == false)
{
IsVisible = true;
}
Debug.Log(ArrowBLEDebugHelper.DEBUGMARK + "视野内");
return true;
}
}
相信很多刚接触C#或者unity的同学,都会看到很多这样那样的函数,带有ref ,out 或者params的参数,今天我就来讲一讲这三者的使用和区别:
ref
直接来看看实现的代码
public void UpdateScore(ref int score) {
score = UnityEngine.Random.Range(80, 100);
}
我使用unity在start里面调用测试
void Start() {
int myscore = 60;
UpdateScore(ref myscore);
print(myscore);
}
看看打印结果:
有C语言或者C++基础的同学可能看出来了,这不就像C语言的指针变量吗?是的,这跟指针变量差不多,其实这是C#的参数引用传递,在方法中对参数所做的任何更改都将反映在该变量中
什么时候使用ref关键字呢
我理解:有时候我们不需要执行完这个方法就可以改变参数的值,或者想要改变的参数值比较多的时候
out
看看代码实现:
public void GetScore(out int score) {
score = UnityEngine.Random.Range(80, 100);
}
调用
void Start() {
int score;
GetScore(out score);
print(score);
}
结果:
其实out跟ref编译原理是一样的,区别就是ref需要先初始化值,而out不需要,总结起来就是ref 有进有出,out只出不进
out什么时候使用呢
我理解:用在没有返回值的函数就能返回值,例如在返回值是void的函数就能返回,还有就是我们经常使用的都是返回一个值的,那么我们如果想在一个函数中返回多个值呢?那么out就起作用了,我们可以使用out返回多个值
params
看看代码实现
public void Getdd(params int[] pp) {
foreach (var p in pp)
{
print(p);
}
}
调用
void Start() {
Getdd(1,2,3);
}
结果:
是不是感觉有些不同,我们以往传递数组的时候,是不是不是这样传递的,params是为动态数组而准备的,我们直接输入数组的元素就行了,如果不加params,我们只能这样调用
public void Getdd( int[] pp) {
foreach (var p in pp)
{
print(p);
}
}
void Start() {
Getdd(new []{1,2,3});
}
五十、Unity3d 控制物体transform移动的几种方法
在Unity开发中我们难免要使用代码控制角色的移动,现将已知的几种方法总结如下:
1)transform.Translate()
function Update() {
//导弹相对于战斗机Fighter以ShootSpeed 的速度向前运动,Vector3.forward在此时表示导弹的正前方
transform.Translate(Vector3.forward * ShootSpeed * Time.deltaTime, Fighter.transform);
}
物体以relativeTo为参照系,沿着translation运动|translation|的距离。如果relativeTo缺省将
以Space.Self为默认值。举个例子:
function Update() { //导弹相对于战斗机Fighter以ShootSpeed 的速度向前运动,Vector3.forward在此时表示导弹的正前方 transform.Translate(Vector3.forward * ShootSpeed * Time.deltaTime, Fighter.transform); }
再举个例子:
在场景中有一个红球和一个蓝球,红球沿着世界坐标系的z轴正方向匀速运动,蓝球沿着红球坐标系的z轴正向以和红球同样的速度匀速运动。
红球运动脚本RedMove.cs:
using UnityEngine;
using System.Collections;
public class RedMove : MonoBehaviour {
public int MoveSpeed = 10;
// Update is called once per frame
void FixedUpdate () {
transform.Translate(Vector3.forward * MoveSpeed * Time.deltaTime, Space.World);
}
}
蓝球运动脚本BlueMove.cs:
using UnityEngine;
using System.Collections;
public class BlueMove : MonoBehaviour {
public GameObject RedBall;
public int MoveSpeed = 10;
// Update is called once per frame
void FixedUpdate () {
transform.Translate(Vector3.forward * MoveSpeed * Time.deltaTime, RedBall.transform);
}
}
1、我们先让红球的坐标系各轴方向与世界坐标系的各轴方向相同,则红球与蓝球在运动过程中是相对静止的:
2、接着我们将红球绕y轴顺时针旋转90度(即使红球的Transform.Rotation.y = 90),此时红球坐标系的z轴正向和世界坐标系的x轴正向重合,此时运动效果如下:
2)指定速度velocity
这种方法只能适用于刚体,因为velocity是刚体特有的属性。代码如下:
void Start () {
gameObject.GetComponent().velocity = Vector3.forward * MoveSpeed;
}
3)使用rigbody.MovePosition()
public void MovePosition(Vector3position);
让物体移动到新的位置position。
示例:
void FixedUpdate() {
//让物体向前运动Time.deltaTime距离
rb.MovePosition(transform.position + transform.forward * Time.deltaTime);
}
4)Vector3.MoveTowards()
static function MoveTowards(current: Vector3, target: Vector3, maxDistanceDelta: float): Vector3;
该方法一般以以下形式使用:
using UnityEngine;using System.Collections;
public class YellowMove : MonoBehaviour {
public int MoveSpeed = 10;
Vector3 target;
void Start () {
target = new Vector3(20, transform.position.y, 20);
}
void Update () {
transform.position = Vector3.MoveTowards(transform.position, target, MoveSpeed * Time.deltaTime);
}
}
5)使用lerp()
a、使用Mathf.Lerp()函数
static functionLerp (from
: float,to : float,t : float) : float
调用该函数会返回from与to之间的插值(from + to) * t,t在0~1之间。
使用方法如下:
using UnityEngine;using System.Collections;
public class YellowMove : MonoBehaviour {
public float MoveSpeed = 0.1f;
Vector3 Target = new Vector3(20, 20, 20);
//控制物体向Target移动
void Update () {
gameObject.transform.localPosition = new Vector3(
Mathf.Lerp(transform.position.x, Target.x, MoveSpeed * Time.deltaTime),
Mathf.Lerp(transform.position.y, Target.y, MoveSpeed * Time.deltaTime),
Mathf.Lerp(transform.position.z, Target.z, MoveSpeed * Time.deltaTime));
}
}
b、使用Vector3.Lerp()
public staticVector3
Lerp(Vector3a,Vector3b,
floatt);
其使用方法与Mathf.Lerp()用法相似,不同点是Vector3.Lerp()是对三维向量进行插值,而Mathf.Lerp()是对数字进行插值。
using UnityEngine;
using System.Collections;
public class YellowMove : MonoBehaviour {
public float MoveSpeed = 0.1f;
Vector3 Target = new Vector3(20, 20, 20);
//控制物体向Target移动 void Update () {
gameObject.transform.localPosition = Vector3.Lerp(transform.position, Target, MoveSpeed * Time.deltaTime;
}
}
6)使用SmoothDamp()
a、使用Vector3.SmoothDamp()
static function SmoothDamp (current : Vector3,target : Vector3,ref currentVelocity : Vector3,smoothTime : float,maxSpeed : float
= Mathf.Infinity,deltaTime : float = Time.deltaTime) : Vector3
在smoothTime的时间间隔内从current移动到target,其移动的当前速度为currentVelocity,此方法一般用于摄像机的平滑移动。需要注意的是currentVelocity值一般在开始时指定为零向量,每次调用该方法时该方法会自动给currentVelocity赋值。方便起见以Mathf.SmoothDamp()进行如下测试:
using UnityEngine;
using System.Collections;
public class YellowMove : MonoBehaviour {
public float MoveSpeed = 0f;
float CurrentNum = 0f;
float TargetNum = 20f;
float MoveTime = 10f;
void Update () {
Debug.Log("当前数值:" + CurrentNum + ";当前速度:" + MoveSpeed);
CurrentNum = Mathf.SmoothDamp(CurrentNum, TargetNum, ref MoveSpeed, MoveTime * Time.deltaTime);
}
}
控制台输出:
Vector3.SmoothDamp()用法如下:
using UnityEngine;
using System.Collections;
public class example : MonoBehaviour{
public Transform target;
public float smoothTime = 0.3F;
private Vector3 velocity = Vector3.zero;
void Update() {
//定义一个目标位置在目标变换的上方并且在后面
Vector3 targetPosition = target.TransformPoint(new Vector3(0, 5, -10));
//平滑地移动摄像机朝向目标位置
transform.position = Vector3.SmoothDamp(transform.position, targetPosition, ref velocity, smoothTime);
}
}
b、使用Mathf.SmoothDamp()
使用方法与Vector3.SmoothDamp()差不多,只是Mathf.SmoothDamp()是对float类型数字操作,而Vector3.SmoothDamp是对三维向量操作。
7)使用CharacterController组件控制角色移动
Unity使用CharacterController(角色控制器)来控制角色骨骼运动,包括移动、跳跃以及各种动作。CharacterController比较复杂,具体详情请参照博客Unity
CharacterController(角色控制器)
8)使用iTween
iTween是Unity3d的一个动画插件,可以让你更加轻松的实现各种动作,iTween实现移动的方式也比较多样,具体的可以参考博客Unity iTween动画库插件
9)使用协程
关于Unity的协程介绍请看博客:Unity协程介绍及使用。
协程和Update方法很类似,不过协程可以在执行切换到下一帧时局部变量任然会保存,但update方法在执行下一帧后局部变量又重新定义了。既然相似,那么我们就可以像在update中每执行一帧改变一次position那样,在协程中改变position然后再执行下一帧来改变物体的位置让物体运动起来。方法如下:
usingUnityEngine;
Using System.Collections;
Public class RedMove: MonoBehaviour {
public Vector3 TargetPosition;
public float MoveSpeed;
Void Start()
{
StartCoroutine(Move(TargetPosition));
}
IEnumerator Move(Vector3 target)
{
while(transform.position != target)
{
transform.position = Vector3.MoveTowards(transform.position, target, MoveSpeed * Time.deltaTime);
Yield return 0;
}
}
}
OnDrawGizmosSelected() 在挂在gameObject选中的时候绘制显示
OnDrawGizmos() 一直显示,无论 GameObject 选中与否
注意:Debug.DrwaXXX () 相关函数要在每帧调用的函数中才能看到显示,在Start() Awake() 中 可能看不到显示
void OnDrawGizmosSelected()
{
// Display the explosion radius when selected
Gizmos.color = new Color(1, 1, 0, 0.75F);
Gizmos.DrawSphere(transform.position, explosionRadius);
}
void OnDrawGizmos()
{
// Display the explosion radius when selected
Gizmos.color = new Color(1, 1, 0, 0.75F);
Gizmos.DrawSphere(transform.position, explosionRadius);
}