Unity开发技巧

2015年03月22日 15:36 0 点赞 0 评论 更新于 2025-11-21 18:09

和备忘录篇一样,这篇文章旨在总结Unity开发中的一些设计技巧。当然,这里的内容是我通过所见所闻总结而来,若有不对之处,欢迎指出。

技巧1:将全局常量存于单独脚本

在开发过程中,我们常常需要用到一些常量,例如是否输出Log、正式服务器和测试服务器的IP等。我们可以把这些常量写在同一个脚本里,并将属性设置为public static,这样在其他脚本里就能直接访问该变量。当代码量逐渐增大时,这种做法能显著减少查找常量的时间。而且,更改常量也十分方便。例如,发布新版本时,只需将该脚本中的log开关设为false;若公司更改了服务器地址,简单修改一个字符串即可。

以下是在名为Const.cs的脚本中添加的示例代码:

public class Const {
public static bool IsWriteMsg = true;
public static bool IsDebugBuild = true;
}

其中,IsWriteMsg用于表明是否需要将文本写到本地以供查看,IsDebugBuild用于表明是否为Debug模式(一般用于控制是否输出Log)。

技巧2:把全局函数置于单独脚本

与技巧1类似,有时我们需要一些常用且与对象无关的函数,例如解析系统某些特定含义的字符串、获取角色在场景中的位置等。我们可以把这些函数写在同一个脚本里,并将函数属性设置为public static

技巧3:保存字符串和JSON信息

在开发中,我们经常要和字符串打交道,比如获取服务器传来的消息等,并且可能需要保存这些信息。手动复制粘贴过于繁琐,作为程序员,应避免重复劳动。

以下是在名为GlobalFunc.cs(即技巧2中提到的全局函数脚本)的脚本中添加的代码示例(关于Json部分,需要LitJson插件,可参见这篇博文)。注意,代码中用到了技巧1中Const.cs脚本里的变量:

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);

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 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来暂停游戏,可参考以下两种方法:

  1. 把所有的移动都放到FixedUpdate中,但这种做法不太现实。
  2. Update中,所有的移动都使用Time.deltaTime控制。

此外,还有一种扩展性较强但相对麻烦的方法。如果一个物体需要暂停动作,例如停止动画等,可以让它的脚本实现OnPauseGame()函数,在重启时实现OnResumeGame()函数。暂停游戏时,可通过调用所有对象上的OnPauseGame()函数来实现:

Object[] objects = FindObjectsOfType (typeof(GameObject));
foreach (GameObject go in objects) {
go.SendMessage ("OnPauseGame", SendMessageOptions.DontRequireReceiver);
}

然后再调用OnResumeGame()进行重启。

以下是一个基本的脚本示例:

protected bool paused;

void OnPauseGame () {
paused = true;
}

void OnResumeGame () {
paused = false;
}

void Update () {
if (!paused) {
// do movement
}
}

这种方法的好处是可以自定义所有物体在暂停和重启时的行为,例如存储和加载数据等。

技巧6:使用Vector3.Lerp移动物体

我们可以使用Lerp函数在两个点(startto)之间进行插值,其中t是插值比率。

transform.position = Vector3.Lerp(start, to, t);

t <= 0时,Lerp函数返回start;当t >= 1时,Lerp函数返回to。因此,若要在某个时间内将物体从start移动到to位置,可以通过不断增加t(通常每帧增加的值为Time.deltaTime / NumberOfSecondsToComplete)来实现。示例代码如下:

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

void Update() {
transform.position = Vector3.Lerp(transform.position, target.position, Time.deltaTime);
}

需要注意的是,有些例子使用Time.time作为插值比率,但这种方法可能会出现一些莫名其妙的错误,导致移动可能只在游戏开始的几秒钟内发生。

作者信息

feifeila

feifeila

共发布了 3994 篇文章