Unity or Unity3D Best Practices and Tips by Toptal Developers

Share

此资源包含由我们的网络成员提供的Unity或Unity3D最佳实践和技巧的集合.

此资源包含由我们的网络成员提供的Unity或Unity3D最佳实践和技巧的集合, 并将定期更新其他信息和新兴的Unity技术. This is a community-driven project, so we encourage you to contribute as well, and we are counting on your feedback.

Unity is a cross-platform game engine, 在这里,我们将拥抱Unity和Unity3D的最佳实践,这将使你成为一个更好的游戏开发者.

Check out the Toptal resource pages for additional information on Unity or Unity3D interview questions.

Efficient Ray Collision Detection Against a Single Object

光线在Unity中对于实现碰撞和UI交互非常有用. A typical way to use them is provided by the official documentation:

    RaycastHit hit;
    float distanceToGround = 0;

    if (Physics.Raycast(transform.position, -Vector3.up, out hit, 100.0F)) {
	    // Determine which object is using hit.collider and do something with it
    }

The equivalent for detecting all objects hit is:

    RaycastHit[] hits;
    hits = Physics.RaycastAll(transform.position, transform.forward, 100.0F);

However, 如果我们只是想看看一个特定的物体是否挡住了光线呢, without going through a list of them, and we are not sure (or don’t care) if it’s a hit? 我们可以通过从它的边界开始而不是从射线开始来检查单个物体, using the Bounds.IntersectsRay method:

        Ray ray = ... // our ray
	GameObject go = ... // our game object
	Collider collider = go.GetComponent();
	if (collider.bounds.IntersectsRay(ray)) {
		// object hit!
	}

This can also be used with the Bounds object provided by a MeshRenderer.

Get the latest developer updates from Toptal.

Subscription implies consent to our privacy policy

如何使用“深度优先搜索”算法访问层次结构中的所有元素?

Sometimes, 开发人员需要找到或测试由复杂转换关系组成的复杂结构中的元素. To find or test the desired element, it is necessary to visit all the nodes of the mentioned structure.

Usually, transforms are organized as a complex tree data structure,访问所有树节点的最常用算法之一是 Depth-first search. 该算法从左到右递归地访问最内层节点优先级的所有节点.

using System;

//使用DepthFirstSearch算法访问所有节点,每次访问时调用' p_callback '.
public bool TraverseDFS(Transform p_root,Predicate p_callback)
{

  //‘Predicate’是一个c#委托,它接受一个参数并返回一个bool值
  //我们可以使用这个' bool '来检查用户是否想继续搜索树.
  if(!p_callback(p_root))
  {
    //The desired query was found and we can stop searching.
    return false;
  }

  for(int i=0;i

Contributors

Eduardo Dias da Costa

Freelance Unity or Unity3D Developer
Brazil

Eduardo是一名拥有超过十年客户端和前端应用开发经验的开发者. 他总是乐于学习和接受新的挑战,这可以使他掌握新的语言和/或技术. He specializes in computer graphics, image processing, game development, tools development (CLI, desktop, etc.), and UI/UX/front-end development.

Show More

How to correctly destroy an item from the scene?

在游戏的某个地方,你的玩家消灭了一个怪物或挑选了一个道具. Now your code must remove those instances from the scene.

New developers usually mistake the gameObject’s components, such as the Transform and the attached MonoBehaviours as the main instance in the scene.

//Reference to the scripts
MonsterScript monster;
ItemScript item;

void OnPlayerWin()
{
    //Process score.
    Destroy(monster); //Will destroy the monster’s script only and the monster will be on scene. 
}

void OnPlayerGetItem()
{
    //Process item.
    Destroy(item); //Will destroy the item’s script only and the item will be on scene. 
}

Every component in the Unity API has a reference to its gameObject, which is the element containing all scripts and components related to a game element.

//Reference to the scripts
MonsterScript monster;
ItemScript item;

void OnPlayerWin()
{
    //Process score.
    Destroy(monster.gameObject); //Will destroy the monster’s entire instance.
}

void OnPlayerGetItem()
{
    //Process item.
    Destroy(item.gameObject); //Will destroy the item’s entire instance.
}

The knowledge to differentiate between a gameObject 它的组成部分对于避免游戏玩法关键部分的不良行为至关重要.

然而,有时,目标实际上是终止给定的脚本,为另一个脚本打开一个位置. One example is changing between AI behaviours.

GameObject monster;

void OnPlayerClose()
{
    AIScriptIdle ai = monster.GetComponent(); //Gets the current AI instance
    if(ai) Destroy(ai); //If it exists, destroy.
    monster.AddComponent(); //Adds the Attack AI Script.
}

void OnPlayerFar()
{
    AIScriptAttack ai = monster.GetComponent(); //Gets the current AI instance
    if(ai) Destroy(ai);//If it exists, destroy.
    monster.AddComponent(); //Adds the Idle AI script.
}

Contributors

Eduardo Dias da Costa

Freelance Unity or Unity3D Developer
Brazil

Eduardo是一名拥有超过十年客户端和前端应用开发经验的开发者. 他总是乐于学习和接受新的挑战,这可以使他掌握新的语言和/或技术. He specializes in computer graphics, image processing, game development, tools development (CLI, desktop, etc.), and UI/UX/front-end development.

Show More

How to customize a material on runtime exclusively for its GameObject?

Sometimes you have one material that is configured to render your character with the correct shader and parameters; but your game could have a great number of characters with different textures and parameters for each.

Usually, one would create one material for each. However, if at some point the base material needs its shader, textures or parameters changed, you would need to update all previously created ones.

避免这种情况的一种方法是为所有角色使用一种材质,并将参数和纹理存储在角色脚本中.

//Character.cs

Texture2D skin; //Reference to the character skin texture.
Color tint;     //Some tint parameter for the shader.

void Start()
{
    Material m = GetComponent().sharedMaterial; //Get the renderer material reference.
    m.color = tint;       //Change the shader color parameter to the character’s.
    m.mainTexture = skin; //Change the skin texture to the character’s.
}

Pretty easy? However, there is a catch. To simplify the workflow, we had only one material for all characters. 所以,如果有人改变了材质属性,所有的角色都会受到影响.

To avoid this, 你必须在游戏开始时复制材质实例,并使其专属于该角色.

//Character.cs

Texture2D skin; //Reference to the character skin texture.
Color tint;     //Some tint parameter for the shader.

void Start()
{
    Material m = GetComponent().sharedMaterial; //Get the renderer material reference.
    m = Instantiate(m);                         //Duplicate the original
    m.color = tint;       //Change the shader color parameter to the character’s.
    m.mainTexture = skin; //Change the skin texture to the character’s.
    GetComponent().sharedMaterial = m; //Assign the new material only for this character.
}

Contributors

Eduardo Dias da Costa

Freelance Unity or Unity3D Developer
Brazil

Eduardo是一名拥有超过十年客户端和前端应用开发经验的开发者. 他总是乐于学习和接受新的挑战,这可以使他掌握新的语言和/或技术. He specializes in computer graphics, image processing, game development, tools development (CLI, desktop, etc.), and UI/UX/front-end development.

Show More

如何在规定的时间框架内以恒定和/或可变速率将对象移动到所需位置?

Things in games must move. It is just a matter of speed, acceleration, and time.

将物体移出Unity物理循环的最常见方法是使用 MoveTowards and Lerp.

If you want to move things with constant speed, MoveTowards increments your position with a constant rate for each frame.

MoveTowards

//Constant Speed
Vector3 position;
Vector3 target;
float speed;
void Update()
{
  position = Vector3.MoveTowards(position,target,Time.deltaTime * speed);
}

To move things with the feel of acceleration one must use Lerp. 我们得到的效果是因为下一个位置是剩余距离的一个百分比. So, 第一步比最后一步大,因为剩下的距离越来越短.

Lerp

//Variable Speed
Vector3 position;
Vector3 target;
float speed;
void Update()
{
  position = Vector3.Lerp(position,target,Time.deltaTime * speed);
}

The interesting part here is that these equations operate in numbers, and considering that quaternions (rotations), colors, rectangles and other Math structures have the same composition, 可以看到,一切都可以使用这种技术进行插值. 例如,褪色或滑动屏幕和旋转对象是它的其他用例.

Contributors

Eduardo Dias da Costa

Freelance Unity or Unity3D Developer
Brazil

Eduardo是一名拥有超过十年客户端和前端应用开发经验的开发者. 他总是乐于学习和接受新的挑战,这可以使他掌握新的语言和/或技术. He specializes in computer graphics, image processing, game development, tools development (CLI, desktop, etc.), and UI/UX/front-end development.

Show More

Using a Material Pool to Avoid Excessive Instancing

In another tip here, Eduardo Dias da Costa向我们展示了如何为不同的游戏对象定制材料. 这是一个很好的实践,可以节省很多创建材料的工作. However, 在对象将频繁生成和稍后销毁的情况下, this technique could lead to leaking, 因为Unity每次修改并分配给Mesh Renderer时都会生成一个新的材质实例. In this case, if you will reuse materials with the same parameters, it’s useful to use a Pool of Materials. 我通常从这个泛型类开始,然后根据需要进行定制:

    /// 
    /// Generic material pool
    /// 
    /// A class or struct that contains the parameters for identifying and constructing a new material
    public class MaterialPool {

        //函数定义给定一个T将返回一个新材料.
        //当材料第一次插入到池中时使用.
        public delegate Material MaterialGenerator(T t);

        private Dictionary pool;
        private MaterialGenerator generator;

        public MaterialPool(MaterialGenerator generator) {
            this.pool = new Dictionary();
            this.generator = generator;
        }

        public Material GetMaterial(T t) {
            Material mat;
            if (!pool.TryGetValue(t, out mat)) {
                mat = generator(t);
                pool[t] = mat;
            }
            return mat;
        }
    }

It requires two additional, customized, elements: a material generator function, and a type that provides the parameters for customizing the material:

///重写Equals和GetHashCode方法时要小心,这样才能正常工作
/// as a key in the dictionary
struct MaterialDef {
...
}

/// generate a new material from the definition
private Material MaterialGenerator(MaterialDef matDef) {
...
}

/// in Start or Awake instance the material pool
    matPool = new MaterialPool(MaterialGenerator);
///稍后在代码中,当需要一个新材料时,只需从池中请求它
gameObject.GetComponent().material = matPool.GetMaterial(new MaterialDef( ... ));

这种方法的一大好处是,它使您能够分离不同的关注点:

  1. The pooling.
  2. 鉴别一种物质与另一种物质的区别.
  3. The material generation itself.

您可以替换其中的每一个,而不必担心(太多)其他的. 这种方法的另一个优点是,它允许您从池中查询材料,而不必关心它是否会生成新的材料或重用现有的材料.

Submit a tip

Submitted questions and answers are subject to review and editing, and may or may not be selected for posting, at the sole discretion of Toptal, LLC.

* All fields are required

Toptal Connects the Top 3% of Freelance Talent All Over The World.

Join the Toptal community.