使物体位于其它物体之上的Shader
|  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
 | Shader "Custom/Self-Illumin/Diffuse" {
Properties {
	_Color ("Main Color", Color) = (1,1,1,1)
	_MainTex ("Base (RGB)", 2D) = "white" {}
	_Illum ("Illumin (A)", 2D) = "white" {}
	_Emission ("Emission (Lightmapper)", Float) = 1.0
}
SubShader {
	Tags { "RenderType"="Opaque" "Queue" = "Geometry+1" }
	LOD 200
    Pass {
        ZWrite On
        ZTest Always
    }
	
CGPROGRAM
#pragma surface surf Lambert
sampler2D _MainTex;
sampler2D _Illum;
fixed4 _Color;
fixed _Emission;
struct Input {
	float2 uv_MainTex;
	float2 uv_Illum;
};
void surf (Input IN, inout SurfaceOutput o) {
	fixed4 tex = tex2D(_MainTex, IN.uv_MainTex);
	fixed4 c = tex * _Color;
	o.Albedo = c.rgb;
	o.Emission = c.rgb * tex2D(_Illum, IN.uv_Illum).a;
#if defined (UNITY_PASS_META)
	o.Emission *= _Emission.rrr;
#endif
	o.Alpha = c.a;
}
ENDCG
} 
FallBack "Legacy Shaders/Self-Illumin/VertexLit"
CustomEditor "LegacyIlluminShaderGUI"
}
 | 
 
下面是使用方法,这里用Dotween做了材质颜色变换:
|  1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
 | private Shader modelOnTopShader;
modelOnTopShader = Shader.Find ("Custom/Self-Illumin/Diffuse");
void AddSelectedEffect (EntityObject entityObject)
{
    List<Renderer> renderList = new List<Renderer> ();
    renderList.AddRange (entityObject.GetComponentsInChildren<MeshRenderer> ());
    renderList.AddRange (entityObject.GetComponentsInChildren<SkinnedMeshRenderer> ());
    foreach (Renderer render in renderList) {
        foreach (Material material in render.materials) {
            if (material.HasProperty ("_Color")) {
                Shader oldShader = material.shader;
                material.shader = modelOnTopShader;
                material.DOColor (material.color * 0.5f, 0.8f).SetLoops (-1, LoopType.Yoyo)
                    .SetId (DOTweenIdForSelectedBuilding).OnRewind(()=>{
                        material.shader = oldShader;
                    });
            }
        }
    }
}
 | 
 
做一个显示网格的底板
要绘制网格,可以在OnDrawGizmos函数中动态绘制。不过,为了在场景中也能看到网格,我采用了Shader,然后把使用这个Shader的材质赋给一个Plane即可。
|  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
 | Shader "Custom/GridShader"
{
	Properties
	{
      _GridThickness ("Grid Thickness", Float) = 0.01
      _GridSpacing ("Grid Spacing", Float) = 10.0
      _GridColour ("Grid Colour", Color) = (0.5, 1.0, 1.0, 1.0)
      _BaseColour ("Base Colour", Color) = (0.0, 0.0, 0.0, 0.0)
	}
	SubShader
	{
		Tags { "Queue" = "Transparent" }
		Pass
		{
			ZWrite Off
			Blend SrcAlpha OneMinusSrcAlpha
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			uniform float _GridThickness;
	        uniform float _GridSpacing;
	        uniform float4 _GridColour;
	        uniform float4 _BaseColour;
			struct vertexInput
			{
				float4 vertex : POSITION;
			};
			struct vertexOutput
			{
				float4 pos : SV_POSITION;
				float2 worldPos : TEXCOORD0;
			};
			
			vertexOutput vert (vertexInput v)
			{
				vertexOutput o;
				o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
				// Calculate the world position coordinates to pass to the fragment shader
				o.worldPos = mul(_Object2World, v.vertex);
				return o;
			}
			
			float4 frag (vertexOutput i) : COLOR
			{
				// frac函数表示取小数部分,显示网格的原理就在这里
				if (frac(i.worldPos.x/_GridSpacing) < _GridThickness || frac(i.worldPos.y/_GridSpacing) < _GridThickness)
				{
	            	return _GridColour;
	            }
	            else 
	            {
	            	return _BaseColour;
	            }
			}
			ENDCG
		}
	}
}
 | 
 
如果想动态修改网格的大小,产生更多的格子,可以写个编辑器扩展。
|  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
 | [CustomEditor(typeof(MapGrid))]
public class MapGridEditor : Editor 
{
	public override void OnInspectorGUI()
	{
		MapGrid grid = (MapGrid)target;
		grid.Columns = EditorGUILayout.IntField ("Columns", grid.Columns);
		grid.Rows = EditorGUILayout.IntField ("Rows", grid.Rows);
		if (GUILayout.Button ("Update Grid Size")) 
		{
			UpdateGridSize (grid);
		}
	}
	private void UpdateGridSize(MapGrid grid)
	{
		if (grid.PlaneTransform && grid.GridLineTransform)
		{
			grid.PlaneTransform.localScale = new Vector3 (grid.Columns / 10f, 1, grid.Rows / 10f);
			grid.GridLineTransform.localScale = grid.PlaneTransform.localScale;
		}
	}
}
 | 
 
当某个物体添加了MapGrid脚本组件后,MapGrid脚本下方会自动显示一个自定义的按钮用来更新网格大小。注意,Editor定义了target属性,以便让你能够获得被检视的对象。
相机斜45度视角
斜45度视角主要用于正交的2D相机,可以让2D图片显示出立体感,不过我这里是用3D物体和正交相机来做的。为了形成斜45度视角,我的方法是底板不转,只要相机旋转即可。但是,按照一般的方法给相机设置旋转角度是不行的,正确的方法是新建一个空物体CameraContainer,然后把我们的正交相机Main Camera作为它的子节点。Main Camera不动,把CameraContainer的Rotation设置为(45, -45, 0)即可。
改变一个向量的方向
给向量乘一个表示旋转量的四元素
| 1
2
3
 | // rotate the vector for 45 degree camera
Vector3 movement = Vector3.forward;
movement = Quaternion.Euler(0, 0, 45) * movement;
 | 
 
UGUI中鼠标点击穿透问题
UGUI中鼠标点击穿透问题,即点击事件会同时传给UI层和3D物体层,解决办法是判断是否点击到了UI元素
|  1
 2
 3
 4
 5
 6
 7
 8
 9
10
 | private void DragCheck()
{
	// If click over a UI element, return
	if (EventSystem.current.IsPointerOverGameObject ())
		return;
	Ray ray = Camera.main.ScreenPointToRay (Input.mousePosition);
	RaycastHit hitInfo;
	Physics.Raycast (ray, out hitInfo, 100, 1 << gridPlaneLayer);
}
 | 
 
射线检测指定层
射线监测时用LayerMask只检测某个层的碰撞,即加入一个layer参数即可。
| 1
 | Physics.Raycast (ray, out hitInfo, 100, 1 << gridPlaneLayer);
 | 
 
UGUI做一个列表
第一层Panel加Mask、Scroll Rect组件,设置好Content属性为第二层Panel,还可以设置Scrollbar。
第二层Panel加Vertical Layout Group,还要加Content Size Filter并设置Preferred Size;注意第二层Panel的Pivot属性,把Y设置为1才能让它自动位于顶部。
在第二层下面添加Button,给Button加Layout Element,设置好Preferred Height和Flexible Width;注意,Layout Element中可以设置Min Height,但没有Max Height属性,所以还是有限制的。
最后把这个Button做成Prefab,代码中动态添加到第二层Panel下面。
文件选择对话框
跨平台的文件选择对话框,没有统一的办法,最好是做一个文件列表,其它功能自己实现。
|  1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
 | DirectoryInfo folder = new DirectoryInfo (MapEditorMain.Instance.FileDirectory);
foreach (FileInfo file in folder.GetFiles("*.json")) 
{
	GameObject fileButton = (GameObject)Instantiate (FileButton);
	string fileShortName = Path.GetFileNameWithoutExtension (file.FullName);
	fileButton.GetComponentInChildren<Text> ().text = fileShortName;
	fileButton.transform.SetParent (FileListParent);
	if (!needSaveFile) 
	{
		Button button = fileButton.GetComponent<Button> ();
		button.onClick.AddListener (() => OnFileButtonClick(button));
	}
}
 | 
 
数组转json
使用JsonUtility默认是不支持的,所以目前只能手动拼接,反序列化可以使用MiniJson来做。如果数据量比较大,拼接时应该用StringBuilder来提升性能。
|  1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 | StringBuilder json = new StringBuilder (200000);
json.Append ('{');
// pathfinding data begin
json.Append("\"walkabledata\":[");
for (int i = 0; i < mapGrid.Columns; i++)
{
	if (i != 0) json.Append(',');
	json.Append('[');
	for (int j = 0; j < mapGrid.Rows; j++) 
	{
		if (j != 0) json.Append(',');
		char result = mapGrid.WalkableCells [mapGrid.CalculateIndex (i, j)].IsEmpty ? '0' : '1';
		json.Append(result);
	}
	json.Append(']');
}
json.Append(']');
// pathfinding data end
json.Append('}');
String path = MapEditorMain.Instance.FileDirectory + fileName + ".json";
using (StreamWriter sw = new StreamWriter (path, false)) 
{
    sw.Write (json);
}
 | 
 
下面是读的部分
|  1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
 | string jsonString;
using (StreamReader sr = new StreamReader (path)) 
{
	jsonString = sr.ReadToEnd ();
}
Dictionary<string,object> json = MiniJSON.Json.Deserialize (jsonString) as Dictionary<string,object>;
// walkable data
List<object> walkable = (List<object>)json ["walkable"];
for (int i = 0; i < walkable.Count; i++)
{
	List<object> point = (List<object>)walkable [i];
	IntVector2 index = new IntVector2 (Convert.ToInt32 (point [0]), Convert.ToInt32 (point [1]));
	mapGrid.DeployIfPossible(index, deployableDictionary[Deployable.DeployableType._Walkable]);
}
 | 
 
修改按钮的颜色
颜色值类型默认是Color(0到1),不是Color32,两者可直接进行强制类型转换
| 1
 | button.image.color = new Color (0.5f, 0.5f, 0.5f, button.image.color.a);
 | 
 
物体被SetActive后触发的事件
| 1
2
3
4
5
6
7
8
9
 | void OnDisable()
{
	if (!gameObject.activeSelf) {}
}
void OnEnable()
{
	if (gameObject.activeSelf) {}
}
 | 
 
给Button添onClick事件时,可以传递一个匿名函数作为闭包。但在foreach中,传递的参数只能记住最后一个,所以需要复制一份参数来传递。也可以使用for语句来解决。这个问题的原因是foreach最终会编译为迭代器代码块,所以传递的参数永远是迭代器中的Current,在遍历完时Current指向了最后一个。
|  1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
 | foreach (Deployable deployableObject in DeployableList) 
{
	// dynamically create deployable buttons
	GameObject newButton = (GameObject) Instantiate(DeployableButton);
	newButton.transform.SetParent (DeployableButtonParent);
	Button button = newButton.GetComponent<Button> ();
	// this temp variable is useful
	Deployable deployableTemp = deployableObject;
	button.onClick.AddListener (() => OnDeployableButtonClick(deployableTemp));
}
void OnDeployableButtonClick(Deployable deployable)
{
	objectToDeploy = deployable;
}
 | 
 
异步加载场景
异步加载时,如果把allowSceneActivation设为false,那么加载会停在90%
|  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
 | /// 异步加载场景
public void LoadSceneAdditiveAsync (string sceneName, Action onFinish)
{
    StartCoroutine (LoadSceneAsync (sceneName, LoadSceneMode.Additive, onFinish));
}
IEnumerator LoadSceneAsync(string sceneName, LoadSceneMode mode, Action onFinish) {
    yield return null;
    AsyncOperation op = SceneManager.LoadSceneAsync (sceneName, mode);
    op.allowSceneActivation = false;
    while (!op.isDone)
    {
        if (op.progress >= 0.9f)
        {
            op.allowSceneActivation = true;
        }
        yield return null;
    }
    SceneManager.SetActiveScene (SceneManager.GetSceneByName (sceneName));
    yield return null;
    if (onFinish != null) {
        onFinish ();
    }
}
/// 卸载场景
public void UnloadScene (string sceneName)
{
    Scene scene = SceneManager.GetSceneByName ("Main");
    SceneManager.SetActiveScene (scene);
    // move existing draw calls to new active scene
    // Solve BUG --- NullReferenceException UIDrawCall.get_cachedTransform
    // 用到NGUI才需要下面的代码:
    /*
    foreach(var dc in UIDrawCall.activeList)
    {
        SceneManager.MoveGameObjectToScene(dc.gameObject, scene);
    }
    foreach (var dc in UIDrawCall.inactiveList)
    {
        SceneManager.MoveGameObjectToScene(dc.gameObject, scene);
    }
    */
    SceneManager.UnloadSceneAsync (sceneName);
}
 | 
 
显示FPS和内存
FPS表示每秒的帧数,Game窗口中点击stats也可以显示帧率,但这个FPS仅仅表示绘图部分,所以需要下面的代码。
|  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
 | using System;
using UnityEngine;
using UnityEngine.UI;
/// <summary>
/// 运行状态显示类
/// </summary>
public class Stats : MonoBehaviour
{
    private const string TEMPLATE = "FPS: <color=yellow>{0}</color> Target FPS: 
    	<color=yellow>{1}</color>\nMEM: <color=lime>{2}M</color> / <color=lime>{3}M</color>";
    private Text _text;
    private int _frame;
    private float _time;
    private string _targetFrameRate;
    void Start()
    {
        _frame = 0;
        _time = 0;
        _targetFrameRate = GetTargetFrameRate();
        _text = this.GetComponent<Text>();
        _text.text = String.Format(TEMPLATE, "--", _targetFrameRate, GetUseMemory(), GetTotalMemory());
    }
    private string GetTargetFrameRate()
    {
        if (Application.targetFrameRate == -1)
        {
            return "Fastest";
        }
        return Application.targetFrameRate.ToString();
    }
    
    void Update()
    {
        ++_frame;
        _time += Time.deltaTime;
        if(_time >= 1)
        {
            float fps = _frame / _time;
            _frame = 0;
            _time -= 1;
            _text.text = String.Format(TEMPLATE, fps.ToString("f1"), _targetFrameRate, GetUseMemory(), GetTotalMemory());
        }
    }
    private string GetUseMemory()
    {
        return (Profiler.GetMonoUsedSize() / 1048576).ToString("f1");
    }
    private string GetTotalMemory()
    {
        return (Profiler.GetMonoHeapSize() / 1048576).ToString("f1");
    }
}
 | 
 
实例化 MonoBehaviour
添加脚本组件,因为继承自MonoBehaviour,所以动态添加时不能用new,要用 gameObject.AddComponent 方法。
简单角色移动控制
| 1
2
3
4
5
6
7
 | void Update () 
{
	Vector3 moveDirection = targetPosition- transform.position;
	
	transform.rotation = Quaternion.Lerp (transform.rotation, Quaternion.LookRotation (moveDirection), Time.deltaTime * 8);
	transform.position += transform.forward * moveSpeed * Time.deltaTime;
}
 | 
 
使用 Coroutine 模拟更新
在Coroutine中模拟Update效果,下面的例子可以让物体在指定时间内慢慢转向某个点。
|  1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
 | public void LookAtObject(Vector3 targetPos)
{
	StartCoroutine (LookatTarget (targetPos));
}
IEnumerator LookatTarget (Vector3 pos)
{
	float duration = 2.0f;
	float counter = 0f;
	Quaternion targetRotation = Quaternion.LookRotation (pos - transform.position);
	while (Quaternion.Angle (transform.rotation, targetRotation) > 0.1f) {
		counter += Time.deltaTime;
		transform.rotation = Quaternion.Lerp (transform.rotation, targetRotation, counter / duration);
		yield return null;
	}
	Debug.Log("rotate finished");
}
 | 
 
动态加载光照贴图信息
烘焙的LightMap会和场景关联在一起,如果不使用加载场景方式,而是动态加载场景Prefab,可以把光照信息保存到Prefab中,然后动态加载光照信息。具体做法,可以把下面的代码添加到Prefab上,然后右键点击Prefab,生成相应的LightMap即可,LightMap的数据会自动保存到Prefab中。但是雾气效果可能无法动态加载,总之这不是一个很好的办法,不如直接加载场景方便。
|   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
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
 | #if UNITY_EDITOR
using UnityEditor;
#endif
using UnityEngine;
using System.Collections.Generic;
using System.Linq;
public class PrefabLightmapData : MonoBehaviour
{
    [System.Serializable]
    struct RendererInfo
    {
        public List<Renderer> renderer;
        public int lightmapIndex;
        public Vector4 lightmapOffsetScale;
    }
    [SerializeField]
    List<RendererInfo> m_RendererInfo;
    [SerializeField]
    Texture2D[] m_Lightmaps;
    [SerializeField]
    Texture2D[] m_Lightmaps2;
    const string LIGHTMAP_RESOURCE_PATH = "Assets/Resources/Lightmaps/";
    [System.Serializable]
    struct Texture2D_Remap
    {
        public int originalLightmapIndex;
        public Texture2D originalLightmap;
        public Texture2D lightmap;
        public Texture2D lightmap2;
    }
    static List<Texture2D_Remap> sceneLightmaps = new List<Texture2D_Remap>();
    void Awake()
    {
        ApplyLightmaps(m_RendererInfo, m_Lightmaps, m_Lightmaps2);
    }
    void Start ()
    {
        StaticBatchingUtility.Combine (this.gameObject);
    }
    static void ApplyLightmaps(List<RendererInfo> rendererInfo, Texture2D[] lightmaps, Texture2D[] lightmaps2)
    {
        bool existsAlready = false;
        int counter = 0;
        int[] lightmapArrayOffsetIndex;
        if (rendererInfo == null || rendererInfo.Count == 0)
            return;
        var settingslightmaps = LightmapSettings.lightmaps;
        var combinedLightmaps = new List<LightmapData>();
        lightmapArrayOffsetIndex = new int[lightmaps.Length];
        for (int i = 0; i < lightmaps.Length; i++)
        {
            existsAlready = false;
            for (int j = 0; j < settingslightmaps.Length; j++)
            {
                if (lightmaps[i] == settingslightmaps[j].lightmapColor)
                {
                    lightmapArrayOffsetIndex[i] = j;
                    existsAlready = true;
                }
            }
            if (!existsAlready)
            {
                lightmapArrayOffsetIndex[i] = counter + settingslightmaps.Length;
                var newLightmapData = new LightmapData();
                newLightmapData.lightmapColor = lightmaps[i];
                newLightmapData.lightmapDir = lightmaps2[i];
                combinedLightmaps.Add(newLightmapData);
                ++counter;
            }
        }
        var combinedLightmaps2 = new LightmapData[settingslightmaps.Length + combinedLightmaps.Count];
        settingslightmaps.CopyTo(combinedLightmaps2, 0);
        if (counter > 0)
        {
            for (int i = 0; i < combinedLightmaps.Count; i++)
            {
                combinedLightmaps2[i + settingslightmaps.Length] = new LightmapData();
                combinedLightmaps2[i + settingslightmaps.Length].lightmapColor = combinedLightmaps[i].lightmapColor;
                combinedLightmaps2[i + settingslightmaps.Length].lightmapDir = combinedLightmaps[i].lightmapDir;
            }
        }
        ApplyRendererInfo(rendererInfo, lightmapArrayOffsetIndex);
        LightmapSettings.lightmaps = combinedLightmaps2;
    }
    static void ApplyRendererInfo(List<RendererInfo> infos, int[] arrayOffsetIndex)
    {
        for (int i = 0; i < infos.Count; i++)
        {
            var info = infos[i];
            foreach (Renderer rer in info.renderer)
            {
                rer.lightmapIndex = arrayOffsetIndex[info.lightmapIndex];
                rer.lightmapScaleOffset = info.lightmapOffsetScale;
            }
        }
    }
    #if UNITY_EDITOR
    [MenuItem("Assets/Assign lightmaps to LOD")]
    static void UseMeBeforeUpdatint()
    {
        PrefabLightmapData[] prefabs = FindObjectsOfType<PrefabLightmapData>();
        foreach (var instance in prefabs)
        {
            List<RendererInfo> tempListOfRender = instance.m_RendererInfo;
            foreach (RendererInfo ren in tempListOfRender)
            {
                foreach (Renderer re in ren.renderer.ToList())
                {
                    Transform myParentForSib = re.transform.parent;
                    foreach (var chil in myParentForSib.GetComponentsInChildren<Transform>())
                    {
                        if (chil != ren.renderer[0].transform)
                        {
                            if (chil.gameObject.name.Contains("LOD"))
                            {
                                ren.renderer.Add(chil.GetComponent<MeshRenderer>());
                            }
                        }
                    }
                }
            }
        }
    }
    [MenuItem("Assets/Update Scene with Prefab Lightmaps")]
    static void UpdateLightmaps()
    {
        PrefabLightmapData[] prefabs = FindObjectsOfType<PrefabLightmapData>();
        foreach (var instance in prefabs)
        {
            ApplyLightmaps(instance.m_RendererInfo, instance.m_Lightmaps, instance.m_Lightmaps2);
        }
        Debug.Log("Prefab lightmaps updated");
    }
    [MenuItem("Assets/Bake Prefab Lightmaps")]
    static void GenerateLightmapInfo()
    {
        Debug.ClearDeveloperConsole();
        if (Lightmapping.giWorkflowMode != Lightmapping.GIWorkflowMode.OnDemand)
        {
            Debug.LogError("ExtractLightmapData requires that you have baked you lightmaps and Auto mode is disabled.");
            return;
        }
        Lightmapping.Bake();
        sceneLightmaps = new List<Texture2D_Remap>();
        var scene = UnityEngine.SceneManagement.SceneManager.GetActiveScene();
        var resourcePath = LIGHTMAP_RESOURCE_PATH + scene.name;
        var scenePath = System.IO.Path.GetDirectoryName(scene.path) + "/" + scene.name + "/";
        PrefabLightmapData[] prefabs = FindObjectsOfType<PrefabLightmapData>();
        foreach (var instance in prefabs)
        {
            var gameObject = instance.gameObject;
            var rendererInfos = new List<RendererInfo>();
            var lightmaps = new List<Texture2D>();
            var lightmaps2 = new List<Texture2D>();
            GenerateLightmapInfo(scenePath, resourcePath, gameObject, rendererInfos, lightmaps, lightmaps2);
            instance.m_RendererInfo = rendererInfos;
            instance.m_Lightmaps = lightmaps.ToArray();
            instance.m_Lightmaps2 = lightmaps2.ToArray();
            var targetPrefab = PrefabUtility.GetPrefabParent(gameObject) as GameObject;
            if (targetPrefab != null)
            {
                //Prefab
                PrefabUtility.ReplacePrefab(gameObject, targetPrefab);
            }
            ApplyLightmaps(instance.m_RendererInfo, instance.m_Lightmaps, instance.m_Lightmaps2);
        }
        Debug.Log("Update to prefab lightmaps finished");
    }
    static void GenerateLightmapInfo(string scenePath, string resourcePath, GameObject root, List<RendererInfo> rendererInfos, List<Texture2D> lightmaps, List<Texture2D> lightmaps2)
    {
        var renderers = root.GetComponentsInChildren<MeshRenderer>();
        foreach (MeshRenderer renderer in renderers)
        {
            if (renderer.lightmapIndex != -1 && renderer.enabled)
            {
                RendererInfo info = new RendererInfo();
                info.renderer = new List<Renderer>();
                info.renderer.Add(renderer);
                info.lightmapOffsetScale = renderer.lightmapScaleOffset;
                Texture2D lightmap = LightmapSettings.lightmaps[renderer.lightmapIndex].lightmapColor;
                Texture2D lightmap2 = LightmapSettings.lightmaps[renderer.lightmapIndex].lightmapDir;
                int sceneLightmapIndex = AddLightmap(scenePath, resourcePath, renderer.lightmapIndex, lightmap, lightmap2);
                info.lightmapIndex = lightmaps.IndexOf(sceneLightmaps[sceneLightmapIndex].lightmap);
                if (info.lightmapIndex == -1)
                {
                    info.lightmapIndex = lightmaps.Count;
                    lightmaps.Add(sceneLightmaps[sceneLightmapIndex].lightmap);
                    lightmaps2.Add(sceneLightmaps[sceneLightmapIndex].lightmap2);
                }
                rendererInfos.Add(info);
            }
        }
    }
    static int AddLightmap(string scenePath, string resourcePath, int originalLightmapIndex, Texture2D lightmap, Texture2D lightmap2)
    {
        int newIndex = -1;
        for (int i = 0; i < sceneLightmaps.Count; i++)
        {
            if (sceneLightmaps[i].originalLightmapIndex == originalLightmapIndex)
            {
                return i;
            }
        }
        if (newIndex == -1)
        {
            var lightmap_Remap = new Texture2D_Remap();
            lightmap_Remap.originalLightmapIndex = originalLightmapIndex;
            lightmap_Remap.originalLightmap = lightmap;
            var filename = scenePath + "Lightmap-" + originalLightmapIndex;
            lightmap_Remap.lightmap = GetLightmapAsset(filename + "_comp_light.exr", resourcePath + "_light", originalLightmapIndex, lightmap);
            if (lightmap2 != null)
            {
                lightmap_Remap.lightmap2 = GetLightmapAsset(filename + "_comp_dir.exr", resourcePath + "_dir", originalLightmapIndex, lightmap2);
            }
            sceneLightmaps.Add(lightmap_Remap);
            newIndex = sceneLightmaps.Count - 1;
        }
        return newIndex;
    }
    static Texture2D GetLightmapAsset(string filename, string resourcePath, int originalLightmapIndex, Texture2D lightmap)
    {
        AssetDatabase.ImportAsset(filename, ImportAssetOptions.ForceUpdate);
        var importer = AssetImporter.GetAtPath(filename) as TextureImporter;
        importer.isReadable = true;
        AssetDatabase.ImportAsset(filename, ImportAssetOptions.ForceUpdate);
        var assetLightmap = AssetDatabase.LoadAssetAtPath<Texture2D>(filename);
        var assetPath = resourcePath + "-" + originalLightmapIndex + ".asset";
        var newLightmap = Instantiate<Texture2D>(assetLightmap);
        AssetDatabase.CreateAsset(newLightmap, assetPath);
        newLightmap = AssetDatabase.LoadAssetAtPath<Texture2D>(assetPath);
        importer.isReadable = false;
        AssetDatabase.ImportAsset(filename, ImportAssetOptions.ForceUpdate);
        return newLightmap;
    }
    #endif
}
 | 
 
贝塞尔曲线
利用下面的函数可以得出曲线上的点,从而得到平滑的曲线。
|  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
 | using UnityEngine;
public class BezierUtil {
    public static Vector3 CalculateBezierPointForQuadratic(float t, Vector3 p0, Vector3 p1, Vector3 p2)
    {
        // [x,y]=(1–t)^2*P0+2(1–t)*t*P1+t^2*P2
        float tt = t * t;
        float u = 1.0f - t;
        float uu = u * u;
        Vector3 B = new Vector3();
        B = uu * p0;
        B += 2.0f * u * t * p1;
        B += tt * p2;
        return B;
    }
    public static Vector3 CalculateBezierPointForCubic(float t, Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3)
    {
        // [x,y]=(1–t)^3*P0+3(1–t)^2*t*P1+3(1–t)t^2*P2+t^3P3
        float tt = t * t;
        float ttt = t * tt;
        float u = 1.0f - t;
        float uu = u * u;
        float uuu = u * uu;
        Vector3 B = new Vector3();
        B = uuu * p0;
        B += 3.0f * uu * t * p1;
        B += 3.0f * u * tt * p2;
        B += ttt * p3;
        return B;
    }
}
 | 
 
文字跟随3D物体
|  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
86
 | using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MyText : MonoBehaviour {
    public Transform target { get; set; }
    public Vector3 offset { get; set; }
    public Vector3 originScale { get; private set; }
    private Camera uiCamera;
    private Camera gameCamera;
    private bool disableIfInvisible;
    private bool destroyWithTarget;
    int mIsVisible = -1;
    void Start () {
        originScale = transform.localScale;
        disableIfInvisible = true;
        destroyWithTarget = true;
        if (target) {
            if (gameCamera == null)
                gameCamera = NGUITools.FindCameraForLayer (target.gameObject.layer);
            if (uiCamera == null)
                uiCamera = NGUITools.FindCameraForLayer (gameObject.layer);
            LateUpdate ();
        } else {
            if (destroyWithTarget) Destroy(gameObject);
            else enabled = false;
        }
    }
    // Update is called once per frame
    void LateUpdate () {
        if (target && uiCamera != null)
        {
            Vector3 pos = gameCamera.WorldToViewportPoint(target.transform.position + offset);
            // Determine the visibility and the target alpha
            int isVisible = (gameCamera.orthographic || pos.z > 0f) && (pos.x > 0f && pos.x < 1f && pos.y > 0f && pos.y < 1f) ? 1 : 0;
            bool vis = (isVisible == 1);
            // If visible, update the position
            if (vis)
            {
                pos = uiCamera.ViewportToWorldPoint(pos);
                pos = transform.parent.InverseTransformPoint(pos);
                //pos.x = Mathf.RoundToInt(pos.x);
                //pos.y = Mathf.RoundToInt(pos.y);
                pos.z = 0f;
                transform.localPosition = pos;
            }
            // Update the visibility flag
            if (mIsVisible != isVisible)
            {
                mIsVisible = isVisible;
                if (disableIfInvisible)
                {
                    for (int i = 0, imax = transform.childCount; i < imax; ++i)
                        NGUITools.SetActive(transform.GetChild(i).gameObject, vis);
                }
            }
            // scale with camera
            transform.localScale = originScale * GetMainCameraScale(gameCamera, false);
        }
        else Destroy(gameObject);
    }
    private const float MaximumOrthographicSize = 7.4f;
    private const float MinimumOrthographicSize = 3.4f;
    private const float MaximumFOV = 10f;
    private const float MinimumFOV = 5.4f;
    public float GetMainCameraScale (Camera mainCamera, bool isCameraOrthographic)
    {
        if (isCameraOrthographic) {
            return 0.7f +  0.3f * (1 - (mainCamera.orthographicSize - MinimumOrthographicSize) / (MaximumOrthographicSize - MinimumOrthographicSize));
        } else {
            return 0.7f +  0.3f * (1 - (mainCamera.fieldOfView - MinimumFOV) / (MaximumFOV - MinimumFOV));
        }
    }
}
 | 
 
下面的是 UGUI 部分的玩家头顶血条效果
|   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
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
 | using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class HudNode : MonoBehaviour
{
    /// <summary>
    /// 整个血条父节点,屏幕外不显示
    /// </summary>
    [SerializeField]
    private RectTransform HpTrans;
    /// <summary>
    /// 整个血条,受击时才显示三秒
    /// </summary>
    [SerializeField]
    private RectTransform HpBarTrans;
    /// <summary>
    /// 血条图片,可调节长度
    /// </summary>
    [SerializeField]
    private Image HpImage;
    /// <summary>
    /// 整个UI的 RectTransform
    /// </summary>
    public RectTransform rootRectTrans { get; set; }
    public Transform target { get; set; }
    /// <summary>
    /// 主相机
    /// </summary>
    public Camera mainCamera { get; set; }
    private RectTransform rectTrans;
    private Vector3 worldPosOffset;
    private bool hudVisible;
    private bool needShowHpBar;
    private float showHpBarTime = 0;
    private void Start()
    {
        rectTrans = this.GetComponent<RectTransform>();
        worldPosOffset = new Vector3(0, 2, 0);
        needShowHpBar = true;
        LateUpdate();
        SetVisible(hudVisible);
        SetHpBarLength(2f);
        SetHpBarVisible(false);
    }
    private void Update()
    {
        if (showHpBarTime > 0)
        {
            showHpBarTime -= Time.deltaTime;
            if (showHpBarTime <= 0)
            {
                SetHpBarVisible(false);
            }
        }
    }
    private void LateUpdate()
    {
        if (target != null && mainCamera != null)
        {
            Vector3 worldPos = target.position + worldPosOffset;
            Vector3 pos = mainCamera.WorldToViewportPoint(worldPos);
            bool isVisible = (mainCamera.orthographic || pos.z > 0) && (pos.x > 0 && pos.x < 1 && pos.y > 0 && pos.y < 1);
            if (isVisible)
            {
                Vector3 screenPos = mainCamera.WorldToScreenPoint(worldPos);
                Vector2 localPos;
                bool isHit = RectTransformUtility.ScreenPointToLocalPointInRectangle(rootRectTrans, screenPos, null, out localPos);
                if (isHit)
                {
                    rectTrans.anchoredPosition = localPos;
                }
            }
            if (hudVisible != isVisible)
            {
                SetVisible(isVisible);
            }
            hudVisible = isVisible;
        }
        else
        {
            HandleHudDestroy();
        }
    }
    private void HandleHudDestroy()
    {
        // 暂时直接销毁
        Destroy(this.gameObject);
    }
    private void SetVisible(bool visible)
    {
        HpTrans.gameObject.SetActive(visible);
    }
    private void SetHpBarLength(float size)
    {
        if (size < 2f)
        {
            HpTrans.sizeDelta = new Vector3(75, HpTrans.sizeDelta.y);
        }
        else
        {
            HpTrans.sizeDelta = new Vector2(100, HpTrans.sizeDelta.y);
        }
    }
    private void SetHpBarVisible(bool visible)
    {
        HpBarTrans.gameObject.SetActive(visible && needShowHpBar);
    }
    /// <summary>
    /// 受击时触发显示血条
    /// </summary>
    private void OnHit()
    {
        showHpBarTime = 5f;
        SetHpBarVisible(true);
    }
}
 | 
 
3D相机抖动震屏
|  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
 | using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CameraVibration : MonoBehaviour
{
    private float shakeTime;
    private Vector3 shakeDirection;
    private AnimationCurve shakeSpeedCurve;
    private AnimationCurve shakeMaxOffsetCurve;
    private float offset;
    private bool isShaking = false;
    private Vector3 originalCameraPos;
    private float timer;
    private int flag;
	
	void Update ()
    {
		if (isShaking)
        {
            timer += Time.deltaTime;
            if (timer < shakeTime)
            {
                float rate = timer / shakeTime;
                float delta = shakeSpeedCurve.Evaluate(rate) * Time.deltaTime * flag;
                float maxOffset = shakeMaxOffsetCurve.Evaluate(rate);
                offset += delta;
                this.transform.localPosition += shakeDirection * delta;
                if (flag > 0)
                {
                    if (offset >= maxOffset) flag = -flag;
                }
                else
                {
                    if (offset <= -maxOffset) flag = -flag;
                }
            }
        }
	}
    public void ShakeCamera(float shakeTime, Vector3 shakeDirection, AnimationCurve shakeSpeedCurve, AnimationCurve shakeMaxOffsetCurve)
    {
        if (shakeTime <= 0) return;
        if (!isShaking)
        {
            isShaking = true;
            offset = 0;
            originalCameraPos = this.transform.localPosition;
            flag = 1;
            timer = 0;
            this.shakeTime = shakeTime;
            this.shakeDirection = shakeDirection.normalized;
            this.shakeSpeedCurve = shakeSpeedCurve;
            this.shakeMaxOffsetCurve = shakeMaxOffsetCurve;
        }
    }
}
 | 
 
使用 Ragdoll
在最外层骨骼节点,一般是中心点,需要添加 BoxCollider 和 Rigidbody 组件。接着在头部、左右手、左右腿、胸部等子节点上添加 BoxCollider 和 CharacterJoint 组件,CharacterJoint 组件需要设置 ConnectedBody 为其它的带有 BoxCollider 和 Rigidbody 的节点。注意 CharacterJoint 组件需要开启 EnableProjection 选项防止 Ragdoll 撕裂或乱飞。
在程序中开启Ragdoll 的方法是,开启所有 Collider 的 enable 属性,开启所有 rigidbody 的 useGravity 属性,关闭所有 rigidbody 的 isKinematic 属性,关闭 Animator 组件。如果需要击飞 Ragdoll,那么取找到的第一个即最外层那个 rigidbody 为 rb, 设置 rb.velocity 为一个方向向量即可。
相机截图
|   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
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
 | using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CaptureScreen : MonoBehaviour {
    public delegate void ReqResultCallback(bool success);
    private string uploadUrl = "http://127.0.0.1:3000";
    ///图片保存路径
    public string savePath { get; private set; }
    void Awake () {
        savePath = Application.persistentDataPath + "home.png";
    }
    public void SaveImage(Camera mCamera, Rect mRect, ReqResultCallback callback){
        if (mCamera == null) {
            StartCoroutine (CaptureByRect (mRect, savePath, callback));
        } else {
            StartCoroutine(CaptureByCamera(mCamera, mRect, savePath, callback));
        }
    }
    IEnumerator UploadPNG(byte[] bytesdata, ReqResultCallback callback) {
        WWWForm form = new WWWForm();
        form.AddBinaryData("pic", bytesdata, "pic", "image/png");
        form.AddField ("userid", "001");
        WWW w = new WWW(uploadUrl, form);
        yield return w;
        if (!string.IsNullOrEmpty(w.error)) {
            callback (false);
            Debug.Log ("图片上传错误: " + w.error);
        } else {
            callback (true);
            Debug.Log ("图片上传成功 ");
        }
    }
    /// <summary>
    /// 使用Application类下的CaptureScreenshot()方法实现截图
    /// 优点:简单,可以快速地截取某一帧的画面、全屏截图
    /// 缺点:不能针对摄像机截图,无法进行局部截图
    /// </summary>
    private void CaptureByUnity(string mFileName)
    {
        Application.CaptureScreenshot(mFileName, 0);
    }
    /// <summary>
    /// 根据一个Rect类型来截取指定范围的屏幕, 左下角为(0,0)
    /// </summary>
    private IEnumerator CaptureByRect(Rect mRect, string mFileName, ReqResultCallback callback)
    {
        yield return null;
        //等待渲染线程结束
        yield return new WaitForEndOfFrame();
        Texture2D mTexture=new Texture2D((int)mRect.width,(int)mRect.height,TextureFormat.RGB24,true);
        mTexture.ReadPixels(mRect,0,0);
        mTexture.Apply();
        byte[] bytes = mTexture.EncodeToPNG();
        System.IO.File.WriteAllBytes(mFileName, bytes);
        StartCoroutine(UploadPNG(bytes, callback));
    }
    /// <summary>
    /// 按照相机来截图
    /// </summary>
    private IEnumerator CaptureByCamera(Camera mCamera, Rect mRect, string mFileName, ReqResultCallback callback)
    {
        mCamera.gameObject.SetActive (true);
        yield return null;
        //等待渲染线程结束
        yield return new WaitForEndOfFrame();
        //初始化RenderTexture
        RenderTexture mRender=new RenderTexture((int)mRect.width, (int)mRect.height, 24);
        //设置相机的渲染目标
        mCamera.targetTexture=mRender;
        //开始渲染
        mCamera.Render();
        //激活渲染贴图读取信息
        RenderTexture.active=mRender;
        Texture2D mTexture = new Texture2D((int)mRect.width, (int)mRect.height, TextureFormat.RGB24, true);
        //读取屏幕像素信息并存储为纹理数据
        mTexture.ReadPixels(mRect,0,0);
        mTexture.Apply();
        //压缩图片大小
        Texture2D newScreenshot = new Texture2D(mTexture.width/2, mTexture.height/2);
        newScreenshot.SetPixels(mTexture.GetPixels(1));
        newScreenshot.Apply();
        //释放相机,销毁渲染贴图
        mCamera.targetTexture = null;
        RenderTexture.active = null;
        GameObject.Destroy(mRender);
        mCamera.gameObject.SetActive (false);
        //将图片信息编码为字节信息
        byte[] bytes = newScreenshot.EncodeToJPG();
        Destroy (mTexture);
        Destroy (newScreenshot);
        System.IO.File.WriteAllBytes(mFileName, bytes);
        //上传图片
        StartCoroutine(UploadPNG(bytes, callback));
    }
}
 | 
 
使用Coroutine完成一组边走边唱的动作
|   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
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
 | using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class HomePlayer : MonoBehaviour {
    private Animator animator;
    public AudioSource audioSource { get; set; }
    private Dictionary<string,AudioClip> audioDict = new Dictionary<string, AudioClip>();
    /// <summary>
    /// 动作组:动画名,时长或次数
    /// </summary>
    class ActionData {
        public string Name;
        public float Times;
        public ActionData(string name, float times) {
            Name = name;
            Times = times;
        }
    }
    void Start () {
        animator = GetComponent<Animator> ();
    }
    /// <summary>
    /// 执行一连串动作,比如边走边说话
    /// </summary>
    void PlayAction (List<ActionData> actionList, List<ActionData> audioList, System.Action onFinish)
    {
        if (actionList.Count > 0) {
            StartCoroutine (PlayActionCoroutine (actionList, onFinish));
            if (audioList.Count > 0) {
                StartCoroutine (PlayAudioCoroutine (audioList, true));
            }
        } else if (audioList.Count > 0) {
            // 边走边播放声音,所以立即完成动作
            if (onFinish != null) {
                onFinish ();
            }
            StartCoroutine (PlayAudioCoroutine (audioList, false));
        } else {
            if (onFinish != null) {
                onFinish ();
            }
        }
    }
    IEnumerator PlayActionCoroutine (List<ActionData> actionList, System.Action onFinish)
    {
        // Stop后先转到idle状态
        while (!animator.GetCurrentAnimatorStateInfo (0).IsName ("idle")) {
            yield return null;
        }
        foreach (var v in actionList) {
            yield return PlayActionClip (v.Name, v.Times);
        }
        if (onFinish != null) {
            onFinish ();
        }
    }
    IEnumerator PlayActionClip (string name, float times)
    {
        if (times > 0) {
            // 播放一段时间
            float time = Mathf.Abs (times);
            animator.SetBool (name, true);
            yield return new WaitForSeconds (time);
            animator.SetBool (name, false);
            // 自动返回idle状态后再返回进行下一个动作
            while (!animator.GetCurrentAnimatorStateInfo (0).IsName ("idle")) {
                yield return null;
            }
        } else if (times == 0) {
            // 一直播放
            animator.SetBool (name, true);
            while (true) {
                yield return null;
            }
        } else {
            // 播放几次
            yield return null;
            int time = (int)Mathf.Abs (times);
            for (int i = 0; i < time; i++) {
                animator.SetBool (name, true);
                yield return null;
                float length = animator.GetNextAnimatorStateInfo (0).length;
                yield return new WaitForSeconds (length);
                animator.SetBool (name, false);
                while (!animator.GetCurrentAnimatorStateInfo (0).IsName ("idle")) {
                    yield return null;
                }
            }
        }
    }
    IEnumerator PlayAudioCoroutine (List<ActionData> audioList, bool needAction) {
        // 先中断之前的声音
        audioSource.Stop ();
        // 如果需要动作,那么Stop后先转到idle状态再播放声音
        while (needAction && !animator.GetCurrentAnimatorStateInfo (0).IsName ("idle")) {
            yield return null;
        }
        foreach (var v in audioList) {
            yield return PlayAudioClip (audioSource, v.Name, v.Times);
        }
        audioSource.Stop ();
    }
    public IEnumerator PlayAudioClip (AudioSource source, string name, float times)
    {
        AudioClip audio = audioDict[name];
        if (audio == null) {
            yield return null;
        }
        // 只按次数播放声音
        if (times < 0) {
            int count = (int)Mathf.Abs (times);
            for (int i = 0; i < count; i++) {
                // 播放音乐,此处省略...
                yield return new WaitForSeconds (audio.length);
            }
        } else {
            yield return null;
        }
    }
    /// 点击玩家触发动作
    public void ClickOnPlayer ()
    {
        Reset ();
        List<ActionData> actionList = new List<ActionData> ();
        actionList.Add (new ActionData ("win", -3));
        actionList.Add (new ActionData ("sit", 3));
        List<ActionData> audioList = new List<ActionData> ();
        audioList.Add (new ActionData ("voice_free", -1));
        audioList.Add (new ActionData ("voice_fr", -1));
        PlayAction (actionList, audioList, () => { });
    }
    void Reset ()
    {
        StopAllCoroutines ();
        audioSource.clip = null;
        if (animator != null) {
            animator.Rebind ();
        }
    }
}
 | 
 
物体做抛物线运动
|  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
 | using System.Collections;
using UnityEngine;
public class Test : MonoBehaviour {
    public Transform target;
    private float speed = 10.0f;
    private float angle = 45.0f;
    private float distanceToTarget;
    private bool isMove = true;
    void Start()
    {
        distanceToTarget = Vector3.Distance(transform.position, target.position);
    }
    private void FixedUpdate()
    {
        if (isMove)
        {
            transform.LookAt(target);
            float ang = Mathf.Min(1, Vector3.Distance(transform.position, target.position) / distanceToTarget) * angle;
            // 因为物体正方向是朝向z轴正方形的,所以物体做抛物线时需要绕x轴转动
            transform.rotation = transform.rotation * Quaternion.Euler(Mathf.Clamp(-ang, -angle, angle), 0, 0);
            float currentDist = Vector3.Distance(transform.position, target.position);
            if (currentDist < 0.5f)
            {
                isMove = false;
            }
            transform.Translate(Vector3.forward * Mathf.Min(speed * Time.fixedDeltaTime, currentDist));
        }
    }
}
 | 
 
上面的方法利用旋转,然后朝目标方向移动来做,适合弓箭等需要头部弯曲的物体。如果是角色简单的跳跃,就不适合,可以用下面的方法模拟。逻辑最好放在 FixedUpdate 中,这里简单的放在了 Update 中。
|  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
86
87
88
89
 | using System.Collections;
using UnityEngine;
public class Jump : MonoBehaviour {
    public Transform target;
    public float m_xSpeed = 6f;
    private float m_ySpeed;
    public float m_yAccelerationSpeed = 20f;
    private bool m_isJumping = true;
    private bool m_xMoveFinished = false;
    private Vector3 m_targetPos;
    private Vector3 m_startPos;
    void Start()
    {
        m_startPos = this.transform.position;
        m_targetPos = target.transform.position;
        float halfJumpTime = (Vector3.Distance(m_startPos, m_targetPos) / m_xSpeed) / 2;
        m_ySpeed = m_yAccelerationSpeed * halfJumpTime;
    }
    private void Update()
    {
        float secondDelta = Time.deltaTime;
        if (m_isJumping)
        {
            float deltaX = m_xSpeed * secondDelta;
            float deltaY = m_ySpeed * secondDelta;
            var currentPos = transform.position;
            var currentPosFix = new Vector3(currentPos.x, m_targetPos.y, currentPos.z);
            var currentOffsetX = m_targetPos - currentPosFix;
            float totalDist = Vector3.Distance(m_startPos, m_targetPos);
            float currentDist = Vector3.Distance(currentPosFix, m_startPos);
            // 水平位移不能超过最大位移
            if (m_xMoveFinished)
            {
                deltaX = 0;
            }
            else if (deltaX > currentOffsetX.magnitude)
            {
                deltaX = currentOffsetX.magnitude;
                m_xMoveFinished = true;
            }
            float currentDistRate = currentDist / totalDist;
            // 是否继续向上飞
            int upFlag = (currentDistRate <= 0.5f) ? 1 : -1;
            // 向上飞,垂直方向开始在减速,之后在加速
            m_ySpeed -= m_yAccelerationSpeed * secondDelta * upFlag;
            if (m_ySpeed < 0)
            {
                m_ySpeed = 0;
            }
            var flyDirectionX = m_targetPos - m_startPos;
            flyDirectionX.Normalize();
            // 水平位移
            var moveX =  deltaX * flyDirectionX;
            // 垂直位移
            var moveY =  upFlag * deltaY * Vector3.up;
            // 位移之和
            var flyToPos = currentPos + moveX + moveY;
            //检测与地面的高度
            bool jumpFinished = false;
            if (flyToPos.y < m_targetPos.y)
            {
                flyToPos.y = m_targetPos.y;
                jumpFinished = true;
            }
            //更新位置
            transform.position = flyToPos;
            if (jumpFinished)
            {
                m_isJumping = false;
            }
        }
    }
}
 |