Unity3D开发技巧
2015-11-11 20:04
429 查看
转载自:http://blog.csdn.net/candycat1992/article/details/24884667
和备忘录篇一样,这篇文章旨在总结Unity开发中的一些设计技巧,当然这里只是我通过所见所闻总结的东西,如果有不对之处欢迎指出。
很多时候我们需要一些常量,例如是否输出Log,正式服务器和测试服务器的IP等等,我们可以把这些常量写在同一个脚本里,并设置属性为public static,然后在其他脚本里直接访问该变量即可。当代码量越来越大时,你会发现这样会减少很多查找常量的时间。而且,这样更改时也非常方便,例如当需要发布新版本时,你只要把该脚本中的log开关设为false就可以了。又或者公司更改了服务器地址,一个简单字符串的更改就可以了。
例如,我们在名为Const.cs的脚本中添加如下代码:
[csharp] view
plaincopyprint?
![](https://code.csdn.net/assets/CODE_ico.png)
![](https://code.csdn.net/assets/ico_fork.svg)
public class Const {
public static bool IsWriteMsg = true;
public static bool IsDebugBuild = true;
}
其中IsWriteMsg表明是否需要将文本写到本地以供查看,IsDebugBuild表明是不是Debug模式(一般用于控制是否输出Log)。
和上一条类似,有时我们需要一些经常使用的、且无关对象的函数,例如解析系统某些特定含义的字符串、得到角色在场景中的位置等。我们可以把这些函数写在同一个脚本里,并设置函数属性为public static即可。
我们经常需要和字符串打交道,例如得到服务器传来的消息等等,而且想要保存它们,复制粘贴又太麻烦了,作为一个很懒的程序员,怎么能一直重复做一件事情呢!
例如,我们在名为GlobalFunc.cs(即为技巧2中提到的全局函数脚本)的脚本中,添加如下代码(关于Json部分,需要LitJson插件,可参见这篇博文)(注意:其中有用到技巧1中的Const.cs脚本中的变量):
[csharp] view
plaincopyprint?
![](https://code.csdn.net/assets/CODE_ico.png)
![](https://code.csdn.net/assets/ico_fork.svg)
using UnityEngine;
using System;
using System.Text;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using LitJson;
public class GlobalFunc {
static public void SaveJson(object obj, string filepathandname)
{
Debug.LogWarning("========> SaveJson: " + filepathandname);
// ELIMINATE WARNING
//string levelnameLower = Application.loadedLevelName.ToLower();
if(Application.isEditor)
{
string file = "./" + filepathandname;
if (File.Exists(file) )
{
File.Delete (file);
}
System.IO.TextWriter writer = new System.IO.StreamWriter(file, false);
LitJson.JsonWriter jw = new JsonWriter( writer as System.IO.TextWriter );
jw.PrettyPrint = true;
try
{
LitJson.JsonMapper.ToJson( obj, jw );
}
catch(Exception e)
{
UnityEngine.Debug.LogError(e);
}
writer.Close();
}
}
static public void SaveText(string tex, string filepathandname)
{
if(Const.IsWriteMsg || !Const.IsDebugBuild)
{
return;
}
Debug.LogWarning("========> SaveJson: " + filepathandname);
//string levelnameLower = Application.loadedLevelName.ToLower();
string file = "";
if(Application.platform == RuntimePlatform.Android )
{
file = Application.persistentDataPath+"/"+filepathandname;
}
else if(Application.isEditor)
{
file = "./" + filepathandname;
}
if(file == "")
{
return ;
}
if (File.Exists(file) )
{
File.Delete (file);
}
System.IO.TextWriter writer = new System.IO.StreamWriter(file, false);
writer.Write(tex);
writer.Close();
}
static public string LoadText(string filepathandname)
{
Debug.LogWarning("========> LoadJson: " + filepathandname);
System.IO.TextReader r = new System.IO.StreamReader("./" + filepathandname);
string tmp = r.ReadToEnd();
r.Close();
return tmp;
}
}
弹出框,类似于Windows编程中常见的各种MessageBox,它们有固定的界面格式,程序员一般只要指定style、title和内容即可。
详见这篇博文。
关于Time.timeScale来暂停游戏的细节,请见《Unity备忘录篇》。
如果使用Time.timeScale = 0来暂停游戏,那么下面两种方法可能可以帮助你:
把所有的移动都放到FixedUpdate中(不太可能)
Update中,所有的移动都使用Time.deltaTime控制
当然,还有另一种比较麻烦但扩展性很强的方法。如果一个物体需要一个暂停动作,例如停止动画等等,可以让它的脚本实现OnPauseGame()函数,而在重启时实现OnResumeGame() 函数。那么暂停游戏可以通过调用所有对象上的OnPauseGame()函数:
[csharp] view
plaincopyprint?
![](https://code.csdn.net/assets/CODE_ico.png)
![](https://code.csdn.net/assets/ico_fork.svg)
Object[] objects = FindObjectsOfType (typeof(GameObject));
foreach (GameObject go in objects) {
go.SendMessage ("OnPauseGame", SendMessageOptions.DontRequireReceiver);
}
然后再调用OnResumeGame() 进行重启。
一个基本的脚本类似下面这样:
[csharp] view
plaincopyprint?
![](https://code.csdn.net/assets/CODE_ico.png)
![](https://code.csdn.net/assets/ico_fork.svg)
protected bool paused;
void OnPauseGame ()
{
paused = true;
}
void OnResumeGame ()
{
paused = false;
}
void Update ()
{
if (!paused) {
// do movement
}
}
这样方法有一个非常大的好处就是,你可以自己定制所有物体在暂停和重启时的行为,例如存储和加载数据等等。
我们可以使用Lerp函数实现在两个点——start和to,进行插值,其中t是插值比率。
[csharp] view
plaincopyprint?
![](https://code.csdn.net/assets/CODE_ico.png)
![](https://code.csdn.net/assets/ico_fork.svg)
transform.position = Vector3.Lerp(start, to, t);
当t<=0时,Lerp函数返回start;当t>=1时,Lerp函数返回to。因此,如果你想要在某个时间内把物体从start移动到to位置,你可以通过不断增加t(通常每帧增加的值为Time.deltaTime/NumberOfSecondsToComplete)来实现。像下面这样:
[csharp] view
plaincopyprint?
![](https://code.csdn.net/assets/CODE_ico.png)
![](https://code.csdn.net/assets/ico_fork.svg)
Vector3 _start;
Vector3 _target;
float _t;
void Update()
{
transform.position = Vector3.Lerp(_start, _target, _t);
_t += Time.deltaTime/2; //Take 2 seconds
}
public void SetTargetPosition(Vector3 newTargetPosition)
{
_start = transform.position;
_target = newTargetPosition;
_t = 0;
}
还有一种情况就是,你想要从物体的当前位置开始进行一个平滑的移动。这时,我们需要把start替换成物体本身的位置,transform.position。
[csharp] view
plaincopyprint?
![](https://code.csdn.net/assets/CODE_ico.png)
![](https://code.csdn.net/assets/ico_fork.svg)
void Update(){
transform.position = Vector3.Lerp(transform.position, target.position, Time.deltaTime);
}
一些例子使用Time.time作为插值比率。但这种方法会发生一些莫名其妙的错误,因此你的移动可能只会发生在游戏的一开始几秒钟。
可以使用lerp和step函数来代替使用If语句。例如:
[cpp] view
plaincopyprint?
![](https://code.csdn.net/assets/CODE_ico.png)
![](https://code.csdn.net/assets/ico_fork.svg)
/* y1, y2, b1, b2 */
float4 constants = float4(5, 6, 2, 3);
float2 tmp = 10 * constants.xy + constants.zw;
x = lerp(tmp[1], tmp[0], step(x, 0.5));
参考:
http://gamedev.stackexchange.com/questions/45398/avoid-if-statements-in-directx-10-shaders
http://gamedev.stackexchange.com/questions/59476/using-two-shaders-instead-of-one-with-if-statements
写在前面
和备忘录篇一样,这篇文章旨在总结Unity开发中的一些设计技巧,当然这里只是我通过所见所闻总结的东西,如果有不对之处欢迎指出。
技巧1:把全局常量放到一个单独的脚本中
很多时候我们需要一些常量,例如是否输出Log,正式服务器和测试服务器的IP等等,我们可以把这些常量写在同一个脚本里,并设置属性为public static,然后在其他脚本里直接访问该变量即可。当代码量越来越大时,你会发现这样会减少很多查找常量的时间。而且,这样更改时也非常方便,例如当需要发布新版本时,你只要把该脚本中的log开关设为false就可以了。又或者公司更改了服务器地址,一个简单字符串的更改就可以了。例如,我们在名为Const.cs的脚本中添加如下代码:
[csharp] view
plaincopyprint?
![](https://code.csdn.net/assets/CODE_ico.png)
public class Const {
public static bool IsWriteMsg = true;
public static bool IsDebugBuild = true;
}
其中IsWriteMsg表明是否需要将文本写到本地以供查看,IsDebugBuild表明是不是Debug模式(一般用于控制是否输出Log)。
技巧2:把全局函数放到一个单独的脚本中
和上一条类似,有时我们需要一些经常使用的、且无关对象的函数,例如解析系统某些特定含义的字符串、得到角色在场景中的位置等。我们可以把这些函数写在同一个脚本里,并设置函数属性为public static即可。
技巧3:保存字符串和JSON信息
我们经常需要和字符串打交道,例如得到服务器传来的消息等等,而且想要保存它们,复制粘贴又太麻烦了,作为一个很懒的程序员,怎么能一直重复做一件事情呢!例如,我们在名为GlobalFunc.cs(即为技巧2中提到的全局函数脚本)的脚本中,添加如下代码(关于Json部分,需要LitJson插件,可参见这篇博文)(注意:其中有用到技巧1中的Const.cs脚本中的变量):
[csharp] view
plaincopyprint?
![](https://code.csdn.net/assets/CODE_ico.png)
using UnityEngine;
using System;
using System.Text;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using LitJson;
public class GlobalFunc {
static public void SaveJson(object obj, string filepathandname)
{
Debug.LogWarning("========> SaveJson: " + filepathandname);
// ELIMINATE WARNING
//string levelnameLower = Application.loadedLevelName.ToLower();
if(Application.isEditor)
{
string file = "./" + filepathandname;
if (File.Exists(file) )
{
File.Delete (file);
}
System.IO.TextWriter writer = new System.IO.StreamWriter(file, false);
LitJson.JsonWriter jw = new JsonWriter( writer as System.IO.TextWriter );
jw.PrettyPrint = true;
try
{
LitJson.JsonMapper.ToJson( obj, jw );
}
catch(Exception e)
{
UnityEngine.Debug.LogError(e);
}
writer.Close();
}
}
static public void SaveText(string tex, string filepathandname)
{
if(Const.IsWriteMsg || !Const.IsDebugBuild)
{
return;
}
Debug.LogWarning("========> SaveJson: " + filepathandname);
//string levelnameLower = Application.loadedLevelName.ToLower();
string file = "";
if(Application.platform == RuntimePlatform.Android )
{
file = Application.persistentDataPath+"/"+filepathandname;
}
else if(Application.isEditor)
{
file = "./" + filepathandname;
}
if(file == "")
{
return ;
}
if (File.Exists(file) )
{
File.Delete (file);
}
System.IO.TextWriter writer = new System.IO.StreamWriter(file, false);
writer.Write(tex);
writer.Close();
}
static public string LoadText(string filepathandname)
{
Debug.LogWarning("========> LoadJson: " + filepathandname);
System.IO.TextReader r = new System.IO.StreamReader("./" + filepathandname);
string tmp = r.ReadToEnd();
r.Close();
return tmp;
}
}
技巧4:自定义弹出框
弹出框,类似于Windows编程中常见的各种MessageBox,它们有固定的界面格式,程序员一般只要指定style、title和内容即可。详见这篇博文。
技巧5:暂停游戏
关于Time.timeScale来暂停游戏的细节,请见《Unity备忘录篇》。如果使用Time.timeScale = 0来暂停游戏,那么下面两种方法可能可以帮助你:
把所有的移动都放到FixedUpdate中(不太可能)
Update中,所有的移动都使用Time.deltaTime控制
当然,还有另一种比较麻烦但扩展性很强的方法。如果一个物体需要一个暂停动作,例如停止动画等等,可以让它的脚本实现OnPauseGame()函数,而在重启时实现OnResumeGame() 函数。那么暂停游戏可以通过调用所有对象上的OnPauseGame()函数:
[csharp] view
plaincopyprint?
![](https://code.csdn.net/assets/CODE_ico.png)
Object[] objects = FindObjectsOfType (typeof(GameObject));
foreach (GameObject go in objects) {
go.SendMessage ("OnPauseGame", SendMessageOptions.DontRequireReceiver);
}
然后再调用OnResumeGame() 进行重启。
一个基本的脚本类似下面这样:
[csharp] view
plaincopyprint?
![](https://code.csdn.net/assets/CODE_ico.png)
protected bool paused;
void OnPauseGame ()
{
paused = true;
}
void OnResumeGame ()
{
paused = false;
}
void Update ()
{
if (!paused) {
// do movement
}
}
这样方法有一个非常大的好处就是,你可以自己定制所有物体在暂停和重启时的行为,例如存储和加载数据等等。
技巧6:使用Vector3.Lerp移动物体
我们可以使用Lerp函数实现在两个点——start和to,进行插值,其中t是插值比率。[csharp] view
plaincopyprint?
![](https://code.csdn.net/assets/CODE_ico.png)
transform.position = Vector3.Lerp(start, to, t);
当t<=0时,Lerp函数返回start;当t>=1时,Lerp函数返回to。因此,如果你想要在某个时间内把物体从start移动到to位置,你可以通过不断增加t(通常每帧增加的值为Time.deltaTime/NumberOfSecondsToComplete)来实现。像下面这样:
[csharp] view
plaincopyprint?
![](https://code.csdn.net/assets/CODE_ico.png)
Vector3 _start;
Vector3 _target;
float _t;
void Update()
{
transform.position = Vector3.Lerp(_start, _target, _t);
_t += Time.deltaTime/2; //Take 2 seconds
}
public void SetTargetPosition(Vector3 newTargetPosition)
{
_start = transform.position;
_target = newTargetPosition;
_t = 0;
}
还有一种情况就是,你想要从物体的当前位置开始进行一个平滑的移动。这时,我们需要把start替换成物体本身的位置,transform.position。
[csharp] view
plaincopyprint?
![](https://code.csdn.net/assets/CODE_ico.png)
void Update(){
transform.position = Vector3.Lerp(transform.position, target.position, Time.deltaTime);
}
一些例子使用Time.time作为插值比率。但这种方法会发生一些莫名其妙的错误,因此你的移动可能只会发生在游戏的一开始几秒钟。
Shader中避免If语句
可以使用lerp和step函数来代替使用If语句。例如:[cpp] view
plaincopyprint?
![](https://code.csdn.net/assets/CODE_ico.png)
/* y1, y2, b1, b2 */
float4 constants = float4(5, 6, 2, 3);
float2 tmp = 10 * constants.xy + constants.zw;
x = lerp(tmp[1], tmp[0], step(x, 0.5));
参考:
http://gamedev.stackexchange.com/questions/45398/avoid-if-statements-in-directx-10-shaders
http://gamedev.stackexchange.com/questions/59476/using-two-shaders-instead-of-one-with-if-statements
相关文章推荐
- Kinect结合Unity3D引擎开发体感游戏(一)
- 一步一步跟我学易语言之第二个易程序菜单设计
- 基于逻辑运算的简单权限系统(原理,设计,实现) VBS 版
- C#中设计、使用Fluent API
- 基于逻辑运算的简单权限系统(原理,设计,实现) VBS 版
- JavaScript 组件之旅(一)分析和设计
- C# 事件的设计与使用深入理解
- 大型网站设计注意事项大全
- Android中的脑残设计总结
- Unity3D中脚本的执行顺序和编译顺序
- Unity3D动态对象优化代码分享
- Unity3D获取当前键盘按键及Unity3D鼠标、键盘的基本操作
- Unity3d获取系统时间
- unity3d发布apk在android虚拟机中运行的详细步骤(unity3d导出android apk)
- Unity3D游戏引擎实现在Android中打开WebView的实例
- 用户权限管理设计[图文说明]
- unity3d调用手机或电脑摄像头
- Unity3d发布IOS9应用时出现中文乱码的解决方法
- 分享一个开源的网络游戏服务器架构—HouHai