Unity或Unity3D最佳实践和技巧

Share

This resource contains a collection of Unity or Unity3D best practices and tips provided by our Toptal network members.

This resource contains a collection of Unity or Unity3D best practices and tips provided by our Toptal network members, and will be updated regularly with additional information and emerging Unity techniques. 这是一个社区驱动的项目, 因此,我们也鼓励您做出贡献, 我们期待着你们的反馈.

Unity是一个跨平台游戏引擎, and here we will embrace Unity and Unity3D best practices that will make you a better game developer.

Check out the Toptal resource pages 有关Unity或Unity3D的更多信息 interview questions.

针对单个对象的有效光线碰撞检测

Rays are very useful in Unity for implementing collisions and UI interaction, among other things. 官方文档提供了使用它们的典型方法:

    RaycastHit hit;
    float distanceToGround = 0;

    if (Physics.Raycast(transform.position, -Vector3.up, out hit, 100.0F)) {
	    //确定哪个对象正在使用hit.对撞机,对它做点什么
    }

检测所有击中物体的等效方法是:

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

However, what if we just want to see if a specific object is in the way of the ray, 而不是一一列举, 我们不确定(或者不在乎)它是否会大受欢迎? We can check specifically for a single object by starting from its bounds instead of from the Ray, 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!
	}

这也可以与 Bounds object provided by a MeshRenderer.

从Toptal获取最新的开发人员更新.

订阅意味着同意我们的 privacy policy

How can I access all Elements of a hierarchy using The “Depth-first search” algorithm?

Sometimes, developers need to find or test the elements that are inside of a complex structure made of intricate transforms relationships. 要找到或测试所需的元素,必须访问 all 上述结构的节点.

通常,转换被组织为一个复合体 tree data structure, and one of the most common algorithms for visiting all tree nodes is the Depth-first search. This algorithm recursively visits all nodes prioritizing the innermost ones, from left to right.

using System;

//Visits all nodes using the DepthFirstSearch algorithm calling ‘p_callback’ on each visit.
public bool TraverseDFS(Transform p_root,Predicate p_callback)
{

  //’Predicate’ is a C# delegate that accepts one parameter and returns a ‘bool’
  //We can use this ‘bool’ it to check if the user wants to keep searching the tree.
  if(!p_callback(p_root))
  {
    //找到所需的查询,我们可以停止搜索.
    return false;
  }

  for(int i=0;i

Contributors

Eduardo Dias da Costa

自由Unity或Unity3D开发人员
Brazil

Eduardo is a developer with over a decade of experience focused on client and front-end applications. He is always open to learn and take up new challenges that can make him handle new languages and/or technologies. 他专攻计算机图形学, image processing, game development, tools development (CLI, desktop, etc.),以及UI/UX/前端开发.

Show More

如何从场景中正确地摧毁物品?

Somewhere in the gameplay, your player eliminated a monster or picked an item. 现在,您的代码必须从场景中删除这些实例.

新开发人员通常会弄错 gameObject’s components, such as the Transform and the attached MonoBehaviours 作为场景中的主要实例.

//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. 
}

Unity API中的每个组件都有一个对其 gameObject,它是包含的元素 all 与游戏元素相关的脚本和组件.

//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.
}

区分a的知识 gameObject and its components is crucial to avoid unwanted behaviour in the key parts of your gameplay.

Yet, sometimes, the objective is to actually kill a given script to open a slot for another one. AI行为之间的变化就是一个例子.

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()
{
    aisscriptattack ai =怪物.GetComponent(); //Gets the current AI instance
    if(ai) Destroy(ai);//如果存在,则销毁.
    monster.AddComponent(); //Adds the Idle AI script.
}

Contributors

Eduardo Dias da Costa

自由Unity或Unity3D开发人员
Brazil

Eduardo is a developer with over a decade of experience focused on client and front-end applications. He is always open to learn and take up new challenges that can make him handle new languages and/or technologies. 他专攻计算机图形学, image processing, game development, tools development (CLI, desktop, etc.),以及UI/UX/前端开发.

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.

通常,一个人会为每个人创建一个材料. However, 如果在某一点基础材料需要它的着色器, 纹理或参数改变, 您需要更新所有以前创建的.

One way to avoid that is to have one material for all characters and store the parameters and textures in the character script.

//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. 为了简化工作流程,我们只有一种材质 all characters. So, if someone changes the material attributes, all characters would be affected.

To avoid this, you must duplicate the material instance as soon as the game starts and make it exclusive to that character.

//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

自由Unity或Unity3D开发人员
Brazil

Eduardo is a developer with over a decade of experience focused on client and front-end applications. He is always open to learn and take up new challenges that can make him handle new languages and/or technologies. 他专攻计算机图形学, image processing, game development, tools development (CLI, desktop, etc.),以及UI/UX/前端开发.

Show More

How to move objects towards the desired position with constant and/or variable rates in a defined time frame?

Things in games must move. 这只是一个速度、加速度和时间的问题.

The most common methods for moving things outside the physics loop of Unity is using MoveTowards and Lerp.

如果你想以恒定的速度移动物体, MoveTowards 每帧以恒定速率增加你的位置.

MoveTowards

//Constant Speed
Vector3 position;
Vector3 target;
float speed;
void Update()
{
  position = Vector3.致力于(位置、目标、时间.deltaTime * speed);
}

要使物体有加速的感觉,必须使用 Lerp. The effect we get is caused because the next position is a percentage of the remaining distance. So, the first steps are bigger than the last ones because the remaining distance keeps getting shorter.

Lerp

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

有趣的是这些方程都是用数字表示的, 考虑到四元数(旋转), colors, 矩形和其他数学结构具有相同的组成, it can be seen that everything can be interpolated using this technique. For instance, fading or sliding screens and rotating objects are other use cases of it.

Contributors

Eduardo Dias da Costa

自由Unity或Unity3D开发人员
Brazil

Eduardo is a developer with over a decade of experience focused on client and front-end applications. He is always open to learn and take up new challenges that can make him handle new languages and/or technologies. 他专攻计算机图形学, image processing, game development, tools development (CLI, desktop, etc.),以及UI/UX/前端开发.

Show More

使用材质池避免过多实例化

In another tip here, Eduardo Dias da Costa showed us how to customize materials for different game objects. This is a great practice that can save a lot of work in creating the materials. However, in cases where the objects will frequently be generated and later destroyed, 这种技术可能导致泄漏, as Unity generates a new instance of the material every time it’s modified and assigned to a Mesh Renderer. In this case, 如果您将重复使用具有相同参数的材料, 使用一个材料库是很有用的. I usually start with this generic class, and later customize if needed:

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

        // The definition of a function that given a T will return a new material.
        // This is used when the material is inserted in the pool for the first time.
        public delegate Material MaterialGenerator(T);

        private Dictionary pool;
        private MaterialGenerator生成器;

        公共MaterialPool(MaterialGenerator生成器){
            this.pool = new Dictionary();
            this.generator = generator;
        }

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

It requires two additional, customized, 元素:一个材料生成器函数, 以及提供定制材料参数的类型:

/// be careful when overriding both Equals and GetHashCode methods so that it will work correctly
///作为字典中的键
struct MaterialDef {
...
}

///从定义中生成一个新的材质
private Material MaterialGenerator(MaterialDef matDef) {
...
}

///在Start或Awake实例中设置材质池
    matPool = new MaterialPool(MaterialGenerator);
/// later in the code, when needing a new material, just request it from the pool
gameObject.GetComponent().material = matPool.GetMaterial(新MaterialDef ( ... ));

A big benefit of this approach is that it enables you to separate different concerns:

  1. The pooling.
  2. The identification of what makes one material different from the other.
  3. 物质生成本身.

You can replace each one of these without worrying (much) about the others. Another advantage of this approach is that it allows you just to query for a material from the pool without caring whether it will generate a new one or reuse an existing one.

Submit a tip

提交的问题和答案将被审查和编辑, 并可能会或可能不会选择张贴, 由Toptal全权决定, LLC.

* All fields are required

Toptal Connects the Top 3% 世界各地的自由职业人才.

Join the Toptal community.