Unity3d点滴记录(一)

记录最近学习Unity3D中的一些小知识点。

Unity3D的四种坐标系

Unity3D的四种坐标系

  1. World Space(世界坐标):我们在场景中添加物体(如:Cube),他们都是以世界坐标显示在场景中的。transform.position可以获得该位置坐标。与之类似的还有局部坐标系,是相对于父节点而言的,获取方法为transform.localPosition

  2. Screen Space(屏幕坐标):以像素来定义的,以屏幕的左下角为(0,0)点,右上角为(Screen.width,Screen.height),Z的位置是以相机的世界单位来衡量的。注:鼠标位置坐标属于屏幕坐标,Input.mousePosition可以获得该位置坐标,手指触摸屏幕也为屏幕坐标,Input.GetTouch(0).position可以获得单个手指触摸屏幕坐标。

  1. ViewPort Space(视口坐标):视口坐标是标准的和相对于相机的。相机的左下角为(0,0)点,右上角为(1,1)点,Z的位置是以相机的世界单位来衡量的。

  2. 绘制GUI界面的坐标系:这个坐标系与屏幕坐标系相似,不同的是该坐标系以屏幕的左上角为(0,0)点,右下角为(Screen.width,Screen.height)

四种坐标系的转换

  1. 世界坐标→屏幕坐标:
1
2
camera.WorldToScreenPoint(transform.position);
//将世界坐标转换为屏幕坐标。其中camera为场景中的camera对象。
  1. 屏幕坐标→视口坐标:

    1
    2
    camera.ScreenToViewportPoint(Input.GetTouch(0).position);
    //将屏幕坐标转换为视口坐标。其中camera为场景中的camera对象。
  2. 视口坐标→屏幕坐标:

    1
    camera.ViewportToScreenPoint();
  3. 视口坐标→世界坐标:

    1
    camera.ViewportToWorldPoint();

世界坐标中的固有方向

方向 描述
up 世界坐标的Y轴方向
right 世界坐标的X轴方向
forward 世界坐标的Z轴方向

访问其它的GameObject或Component

查找GameObject

通过Gameobject.Find来查找,参数为GameObject的名称(或带路径的名称):

1
2
3
4
5
GameObject hand;
hand = GameObject.Find("Hand");
hand = GameObject.Find("/Hand");
hand = GameObject.Find("/Monster/Arm/Hand");
hand = GameObject.Find("Monster/Arm/Hand");

根据Tag判断类型:

1
2
3
4
if(gameObject.tag == "Player")
{
//...
}

获取所有匹配标签的GameObject:

1
GameObject [] objs = GameObject.FindGameObjectsWithTag("标签名");

查找GameObject下挂载的Component

1
2
3
4
5
6
7
8
HingeJoint hinge = gameObject.GetComponent( typeof(HingeJoint) ) as HingeJoint;
// 根据type查找

HingeJoint hinge = gameObject.GetComponent<HingeJoint>();
// 泛型方式根据type查找

HingeJoint hinge = gameObject.GetComponent( "HingeJoint" ) as HingeJoint;
// 根据type的名称查找

类似的还有:

1
2
3
4
5
6
7
GetComponent()
GetComponents()
GetComponent()
GetComponentInChildren()
GetComponentsInChildren()
GetComponentInParent()
GetComponentsInParent()

内置事件函数执行顺序

Unity3D的脚本中有一些内置的事件函数,其执行顺序如下:

编辑器(Editor)

  • Reset
    Reset用于在脚本被第一次挂载到对象上时初始化脚本属性,或者当使用Reset命令时。

第一次加载场景(First Scene Load)

场景开始时会调用以下函数,场景中的每个对象只调用一次。

  • Awake
    在实例化prefab之后、所有的Start()之前调用。如果一个GameObject在启动期间处于非激活状态,则不会调用Awake(),直到它被激活,或者它挂载的任何脚本中有函数被调用。

  • OnEnable
    (只有当Object在激活状态才会被调用)该函数在Object被启用(enabled)时调用。当创建MonoBehaviour实例,如载入关卡、或挂载有该脚本组件的GameObject被实例化时会调用该函数。

注意:对于被添加到场景中的对象,AwakeOnEnable函数会在所有脚本的StartUpdate等之前被调用,但对于游戏过程中实例化的对象无法强制如此。

第一帧更新之前(Before the first frame update)

  • Start
    如果脚本实例处于激活状态(enabled),会在第一帧刷新之前调用Start

对于场景中添加的游戏对象,所有脚本的Start都会在任意脚本的Update等函数之前调用,但对于游戏过程中实例化的对象无法强制如此。

帧之间(In between frames)

  • OnApplicationPause
    当帧内侦测到有暂停时,帧结束时会调用此函数。调用于正常的帧更新之间。在OnApplicationPause被调用后将会有额外的一帧以显示暂停状态的图像。

更新顺序(Update Order)

当要跟踪游戏逻辑、交互、动画、摄像机位置等时,有一些事件可供使用。通常的模式是将大多数工作放在Update函数中执行,但也有一些其它的函数供使用。

  • FixedUpdate
    通常FixedUpdate会比Update调用得更频繁。当帧率很低时,每帧可能有多次调用;当帧率很高时,可能在帧之间不会有调用。所有的物理计算及更新都是在FixedUpdate之后立即执行。当在FixedUpdate内进行运动计算时,相关数值可不必乘以Time.deltaTime,因为FixedUpdate是由可靠地计时器调用,而与帧率无关。

  • Update
    Update在每帧被调用,是帧更新的主要承载函数。

  • LateUpdate
    LateUpdate在每帧调用一次,调用时机为Update结束之后。Update中的所有计算都会在LateUpdate调用之前完成。LateUpdate一个比较通常的用法是跟随的第三人视角摄像机。如果角色在Update中移动或转动,则可以将所有的摄像机移动和转动放入LateUpdate。这样可以确保在摄像机跟随角色之前角色已经完成了所有的移动。

渲染(Rendering)

  • OnPreCull
    在摄像机对场景进行剔除(cull)之前调用。剔除将会决定对于摄像机来说哪些游戏对象可见或不可见。OnPreCull调用之后即是摄像机的剔除操作。

  • OnBecameVisible/OnBecameInvisible
    当游戏对象对于所有摄像机都变为可见(visible)/不可见(invisible)时调用

  • OnWillRenderObject
    如果游戏对象对于摄像机可见,则每有一个摄像机将会调用一次

  • OnPreRender
    在摄像机对场景进行渲染之前调用

  • OnRenderObject
    在常规场景渲染完成之后调用。此时可以使用GL类或Graphics.DrawMeshNow来绘制自定义的几何体

  • OnPostRender
    在摄像机完成对场景的渲染之后调用

  • OnRenderImage
    在场景渲染完成之后调用,用于屏幕图像的后处理(postprocessing)

  • OnGUI
    用于响应GUI事件,在每帧内会调用多次。首先处理LayoutRepaint事件,紧接着处理每个输入事件的Layout和键盘/鼠标事件

  • OnDrawGizmos
    在场景中绘制用于可视化的Gizmo

协程(Coroutines)

正常的协程会在Update函数返回后更新。协程是一个可以将其执行挂起(yield)直到其YieldInstruction完成的函数。以下是协程的不同用法:

  • yield
    协程将在下一帧的所有的Update完成调用之后继续执行

  • yield WaitForSeconds
    协程将在指定时间的延迟之后的一帧,调用全部Update函数之后

  • yield WaitForFixedUpdate
    协程在所有的FixedUpdate调用之后继续

  • yield WWW
    协程在一个WWW下载完成后继续

  • yield StartCoroutine
    将协程构成链,并且会先等待MyFunc协程完成

销毁对象(When the Object is Destroyed)

  • OnDestroy
    会在该对象存在的最后一帧所有帧更新函数调用完成之后调用(在调用Object.Destroy或是关闭场景时会销毁对象)

退出(When Quitting)

以下函数会对所有激活状态(active)的游戏对象调用

  • OnApplicationQuit
    会在退出应用程序时对所有的游戏对象调用。在编辑器中,则是在用户停止playmode时调用。在web player中则是在web视图被关闭时调用。

  • OnDisable:
    当对象变为禁用(disabled)或非激活状态(inactive)时调用

以上各函数的执行可参考下图:

fig

Transform主要变量和方法

主要变量

变量 描述
childCount 返回Transform的子节点的个数
eulerAngles 角度单位的欧拉角形式的旋转参数
forward 世界坐标下蓝色轴(Z)的正向
hasChanged 从上次被置为false之后transform是否发生过变化
hierarchyCapacity transform的层级结构的transform容量
hierarchyCount transform层级结构中transform的数量
localEulerAngles 相对于父节点的角度单位的欧拉角形式的旋转参数
localPosition 相对于父节点的位置参数
localRotation 相对于父节点的旋转参数
localScale 相对于父节点的缩放比例参数
localToWorldMatrix 将局部坐标转化为世界坐标的矩阵
lossyScale 全局尺度的缩放比例
parent 父节点
position 世界坐标下的位置参数
right 世界坐标下红色轴(X)的正向
root 层级结构的最顶端
rotation 世界坐标下四元数形式的旋转参数
up 世界坐标下绿色轴(Y)的正向
worldToLocalMatrix 将世界坐标转化为局部坐标的矩阵

主要方法

方法 描述
DetachChildren 断开与所有子节点的父子关系
Find 根据名字查找子节点并返回
GetChild 根据索引值返回子节点
GetSiblingIndex 获取在同级节点中的索引
InverseTransformDirection 将方向direction从世界空间转化到局部空间
InverseTransformPoint 将位置position从世界空间转化到局部空间
InverseTransformVector 将矢量vector从世界空间转化到局部空间
IsChildOf 是否是parent的子节点
LookAt 旋转transform,以使得forward指向target当前位置
Rotate 按照Z->X->Y的顺序,沿Z轴旋转eulerAngles.z角度,沿X轴旋转eulerAngles.x角度,沿Y轴旋转eulerAngles.y角度
RotateAround 在世界坐标中沿着穿过指定点point的轴axis旋转指定角度angle
SetAsFirstSibling 将transform移至同级结点列表的头部
SetAsLastSibling 将transform移至同级结点列表的末尾
SetParent 设置父节点
SetPositionAndRotation 设置transform组件在全局坐标系下的位置和旋转参数
SetSiblingIndex 设置在同级结点中的索引
TransformDirection 将方向direction从局部空间转化到世界空间
TransformPoint 将位置position从局部空间转化到世界空间
TransformVector 将矢量vector从局部空间转化到世界空间
Translate 使用translation的方向和距离移动transform

读写xml

需要额外引入文件/命名空间:

1
2
using System.Xml;
using System.IO;

读取xml

现有文件test.xml文件如下,文件内容为:

1
2
3
4
5
6
7
8
9
10
<root>
<enemy id="1000" name="enemy1" lv="1">
<attack>10</attack>
<hitpoint>100</hitpoint>
</enemy>
<enemy id="1001" name="enemy2" lv="2">
<attack>20</attack>
<hitpoint>180</hitpoint>
</enemy>
</root>

加载并输出xml文件的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
string filepath = Application.dataPath + @"/XML/test.xml";
if (File.Exists (filepath)) {
XmlDocument xmlDoc = new XmlDocument ();
xmlDoc.Load (filepath);
XmlNodeList nodeList = xmlDoc.SelectSingleNode ("root").ChildNodes;
foreach (XmlElement xe in nodeList) {
Debug.Log ("Name :" + xe.Name);
Debug.Log ("Attribute name:" + xe.GetAttribute ("name"));
Debug.Log ("Attribute lv:" + xe.GetAttribute ("lv"));
foreach (XmlElement x1 in xe.ChildNodes) {
Debug.Log ("Inner Name :" + x1.Name);
Debug.Log ("Inner Value :" + x1.InnerText);
}
}
Debug.Log ("complete XML = " + xmlDoc.OuterXml);
} else {
Debug.Log ("file not exists");
}

写入xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
string filepath = Application.dataPath + @"/XML/test1.xml";
if(!File.Exists (filepath))
{
XmlDocument xmlDoc = new XmlDocument();
XmlElement root = xmlDoc.CreateElement("root");
XmlElement child = xmlDoc.CreateElement("enemy");
child.SetAttribute("id","1000");
child.SetAttribute("name","enemy1");
child.SetAttribute("lv","1");
XmlElement subChild1 = xmlDoc.CreateElement("attack");
subChild1.InnerText = "10";
XmlElement subChild2 = xmlDoc.CreateElement("hitpoint");
subChild2.InnerText = "100";
child.AppendChild(subChild1);
child.AppendChild(subChild2);
root.AppendChild(child);
xmlDoc.AppendChild(root);
xmlDoc.Save(filepath);
}

得到的xml文件内容如下:

1
2
3
4
5
6
<root>
<enemy id="1000" name="enemy1" lv="1">
<attack>10</attack>
<hitpoint>100</hitpoint>
</enemy>
</root>

编辑xml

在刚才的文件test1.xml中,将enemylv属性改为10attack节点内容改为30,删除enemyhitpoint节点,在root下增加一个新的enemy节点并为其设置一些属性和子节点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
string filepath = Application.dataPath + @"/test1.xml";
if(File.Exists (filepath))
{
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(filepath);
XmlNodeList nodeList=xmlDoc.SelectSingleNode("root").ChildNodes;

foreach(XmlElement xe in nodeList)
{
if(xe.GetAttribute("id")=="1000")
{
xe.SetAttribute("lv","10");
foreach(XmlElement x1 in xe.ChildNodes)
{
if(x1.Name=="attack")
{
x1.InnerText="30";
}
}
break;
}
}

Debug.Log("modify end");

XmlNode root = xmlDoc.SelectSingleNode("root");
XmlElement newChild = xmlDoc.CreateElement("enemy");
newChild.SetAttribute("id","1000");
newChild.SetAttribute("name","enemy2");
newChild.SetAttribute("lv","2");
XmlElement subChild1 = xmlDoc.CreateElement("attack");
subChild1.InnerText = "20";
XmlElement subChild2 = xmlDoc.CreateElement("hitpoint");
subChild2.InnerText = "200";
newChild.AppendChild(subChild1);
newChild.AppendChild(subChild2);
root.AppendChild(newChild);
xmlDoc.Save(filepath);
Debug.Log("add node end");
}

得到的xml文件为:

1
2
3
4
5
6
7
8
9
10
<root>
<enemy id="1000" name="enemy1" lv="10">
<attack>30</attack>
<hitpoint>100</hitpoint>
</enemy>
<enemy id="1000" name="enemy2" lv="2">
<attack>20</attack>
<hitpoint>200</hitpoint>
</enemy>
</root>

json序列化与反序列化

json加载与读取(反序列化)

使用的json文件如下:

1
2
3
4
5
6
7
8
9
10
{
"enemyDataList" : [
{ "id":"1" , "lv":"1" , "score" : "1" , "speed" : "4" , "hitPoint" : "1" , "attackInterval" : "100"},
{ "id":"2" , "lv":"2" , "score" : "2" , "speed" : "4" , "hitPoint" : "2" , "attackInterval" : "100"},
{ "id":"3" , "lv":"3" , "score" : "4" , "speed" : "4.5" , "hitPoint" : "5" , "attackInterval" : "10"},
{ "id":"4" , "lv":"4" , "score" : "7" , "speed" : "5" , "hitPoint" : "10" , "attackInterval" : "3"},
{ "id":"5" , "lv":"5" , "score" : "10" , "speed" : "5.5" , "hitPoint" : "18" , "attackInterval" : "1"},
{ "id":"6" , "lv":"6" , "score" : "14" , "speed" : "6" , "hitPoint" : "30" , "attackInterval" : "0.5"}
]
}

读取并加载json:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

using System.IO;
using System;

public class EnemyManager : MonoBehaviour {

private EnemyDataList data = new EnemyDataList();

//...略去无关代码

[Serializable]
public class EnemyData {
public int lv;
public int score;
public float speed;
public int hitPoint;
public float attackInterval;
}

[Serializable]
public class EnemyDataList {
public EnemyData[] enemyDataList;
}

private void LoadJson(){

string filepath = Application.dataPath + "/Json/Enemy.json";

if (!File.Exists(filepath))
{
Debug.Log (filepath + "do not exist");
return;
}
StreamReader sr = new StreamReader(filepath);
if (sr == null)
{
Debug.Log (filepath + "read failed");
return;
}
string json = sr.ReadToEnd();

if (json.Length > 0) {
data = JsonUtility.FromJson<EnemyDataList> (json);
}
else {
Debug.Log (filepath + "empty file");
}
//Debug.Log (data.enemyDataList.Length.ToString());
}
}

json序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[Serializable]
public class EnemyData {
public int lv;
public int score;
public float speed;
public int hitPoint;
public float attackInterval;
}

private void WriteJson()
{
EnemyData enemyData = new EnemyData();
enemyData.lv = 1;
enemyData.score = 10;
enemyData.speed = 100;
enemyData.hitPoint = 100;
enemyData.attackInterval = 10;

string json = JsonUtility.ToJson(enemyData);

Debug.Log(json);
}

在游戏预览窗口调整角度和修改数据

如对于FPS游戏,可以在游戏预览窗口对武器的位置进行调整,调整好以后在游戏预览窗口将需要保存的游戏对象拖拽生成Prefab即可保存即时的状态。

在编辑窗口中,选中GameObject,使用快捷键command+shift+F(win下是 control+shift+F),可以使选中的游戏对象与当前Scene窗口对齐,可用于调整灯光、摄像机等。

扩展函数

例如,可以为Transform定义扩展函数如下:

1
2
3
4
5
6
public static class MyExt {
public static void SetPositionX (this Transform trans , float x)
{
trans.position = new Vector3 (x , trans.position.y , trans.position.z);
}
}

脚本内调用:

1
this.transform.SetPositionX(10);

Coroutine使用案例

延迟调用

指定在一段时间后指定某函数,首先有以下封装:

1
2
3
4
5
6
7
8
9
10
11
using UnityEngine;
using System.Collections;
using System;
public class DelayToInvoke : MonoBehaviour
{
public static IEnumerator DelayToInvokeDo(Action action, float delaySeconds)
{
yield return new WaitForSeconds(delaySeconds);
action();
}
}

调用

1
2
3
4
5
6
7
void OnClick()
{
StartCoroutine(DelayToInvoke.DelayToInvokeDo(() =>
{
Debug.Log("Delay");
}, 0.1f));
}

将一次move分解到每帧

1
2
3
4
5
6
7
8
9
10
11
12
13
public virtual void Move(){
StartCoroutine(IMove());
}

protected IEnumerator IMove(){
float movedDis = 0;
while(movedDis < cubeDistance){
movedDis += updateMove.magnitude * Time.deltaTime;
transform.localPosition = transform.localPosition + updateMove * Time.deltaTime;
yield return new WaitForFixedUpdate ();
}
transform.localPosition = targetPos;
}

自动寻路

涉及到三个游戏对象

寻路地形

  1. 需要勾选Navigation Static
  2. 在Window-Navigation中设置地形的参数
  3. Bake

自动寻路的对象

  1. 添加组件NavMeshAgent
  2. 在挂载的脚本中添加以下内容:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//...略去无关内容

Transform m_target;
UnityEngine.AI.NavMeshAgent m_agent;
float m_speed = 1.0f;

void Start () {

m_target = GameObject.FindGameObjectWithTag ("Target");
m_agent = GetComponent<UnityEngine.AI.NavMeshAgent> ();
m_agent.SetDestination (m_target.transform.position);
}

void Update () {
m_agent.SetDestination (m_target.transform.position);
MoveTo ();
}

void MoveTo(){
float speed = m_speed * Time.deltaTime;
m_agent.Move (m_transform.TransformDirection(new Vector3(0,0,speed)));
}

寻路目标

  1. 放置于寻路地形上
  2. 控制目标移动,自动寻路对象会跟随目标

使用单例

使用单例方法一

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Singleton<T> : MonoBehaviour where T : MonoBehaviour {
protected static T instance;
public static T Instance {
get {
if(instance == null) {
instance = (T) FindObjectOfType(typeof(T));
if (instance == null) {
Debug.LogError("An instance of " + typeof(T) + " is needed in the scene, but there is none.");
}
}
return instance;
}
}
}

定义的单例类需要继承Singleton

使用单例方法二

1
2
3
4
5
6
7
8
9
10
11
12
public static ClassSingleton instance { get; private set; }
...
protected void OnEnable() {
if (ClassSingleton.instance == null) {
ClassSingleton.instance = this;
}
#if UNITY_EDITOR
else {
Debug.LogWarning("Multiple ClassSingleton in scene... this is not supported");
}
#endif
}

2D碰撞

碰撞二者必须都要有BoxCollider2D组件,并且其中之一要有Rigidbody2D组件。

组件之一需要在挂载的脚本中覆写OnTriggerEnter2D方法。

1
2
3
4
5
6
void OnTriggerEnter2D(Collider2D other){
if (other.gameObject.tag == "enemy") {
Destroy (this.gameObject);
other.gameObject.GetComponent<Enemy>().hit (this.damage);
}
}

FPS游戏玩家移动和视角转动

玩家对象需要挂载组件CharacterController,以及以下脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Player : MonoBehaviour {

public Transform m_transform;
CharacterController m_cc;

float m_speed = 5.0f;

Transform camTransform;
Vector3 camRot;
float camHeight = 5.5f;

void Start () {

m_transform = this.transform;
m_cc = this.GetComponent<CharacterController> ();

camTransform = Camera.main.transform;
Vector3 pos = m_transform.position ;
pos.y += camHeight;
camTransform.position = pos;

camTransform.rotation = m_transform.rotation;
camRot = camTransform.eulerAngles;

Cursor.lockState = CursorLockMode.Locked;
Cursor.visible = false;
}


void Update () {

ControlCheck ();

}


private void ControlCheck(){

//cam rotation
float rh = Input.GetAxis ("Mouse X");
float rv = Input.GetAxis ("Mouse Y");

camRot.x -= rv;
camRot.y += rh;
camTransform.eulerAngles = camRot;


// keep player the same rotation
Vector3 camR = camTransform.eulerAngles;
camR.x = 0;
camR.z = 0;
m_transform.eulerAngles = camR;


// move player with WASD
float speed_x = 0, speed_y = 0, speed_z = 0;

if (Input.GetKey (KeyCode.W)) {
speed_z += m_speed * Time.deltaTime;
}
else if(Input.GetKey (KeyCode.S)){
speed_z -= m_speed * Time.deltaTime;
}

if (Input.GetKey (KeyCode.A)) {
speed_x -= m_speed * Time.deltaTime;
}
else if(Input.GetKey (KeyCode.D)){
speed_x += m_speed * Time.deltaTime;
}

m_cc.Move (m_transform.TransformDirection(new Vector3(speed_x , speed_y , speed_z)));

// keep camera the same postion
Vector3 pos = m_transform.position;
pos.y = camHeight;
camTransform.position = pos;

}

}

获取点击或触摸操作

点击或触摸拖动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//...

bool isMouseDown = false;

void Update () {
mouseControl ();
}


void mouseControl (){

if (Input.GetMouseButtonDown (0)) {
isMouseDown = true;
}

if (Input.GetMouseButtonUp (0)) {
isMouseDown = false;
lastMousePosition = Vector3.zero;
}

if(isMouseDown){
if (Vector3.zero != lastMousePosition) {
Vector3 offset = Camera.main.ScreenToWorldPoint (Input.mousePosition) - lastMousePosition;
this.transform.position += offset;
}
lastMousePosition = Camera.main.ScreenToWorldPoint (Input.mousePosition);
}
}

获取点击或触摸点在世界中的位置并移动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class PlaceTargetWithMouse : MonoBehaviour
{
public float surfaceOffset = 1.5f;
public GameObject setTargetOn;

private void Update()
{
if (!Input.GetMouseButtonDown(0))
{
return;
}
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (!Physics.Raycast(ray, out hit))
{
return;
}
transform.position = hit.point + hit.normal*surfaceOffset;
//if (setTargetOn != null)
//{
// setTargetOn.SendMessage("SetTarget", transform);
//}
}
}

REFERENCE

https://docs.unity3d.com/500/Documentation/Manual/index.html
https://docs.unity3d.com/500/Documentation/ScriptReference/index.html
https://docs.unity3d.com/ScriptReference/Transform.html
http://www.xuanyusong.com/archives/1901
http://www.xuanyusong.com/archives/3763
http://blog.csdn.net/stalendp/article/details/46707079
http://blog.csdn.net/stalendp/article/details/17114135
http://blog.csdn.net/neil3d/article/details/38534809