1.7 Learning Unity game development from 0--the composition of objects

In the previous article, we briefly introduced how to use an editor to edit a game scene (Scene). This is an important workflow for our game content creation, but so far, the smallest unit we can operate is only an object, and we have not gone deep into it. As for the object itself, in this article we will use the Inspector panel and code to explain what the objects that make up the game world are, and how we can manipulate them.

Inspector panel

We also briefly mentioned this panel in the previous article, and here we will learn more about it.

When selecting an object on any editor, whether it is selected from the Hierarchy or directly from the Scene window, its content will be displayed, and the panel displayed by the object content is the Inspector panel.

For a built-in 3D cube, it is like this. I clicked the arrow on the left and put away the details of each part, so that I can have an intuitive feeling.

Build game objects from the code level

Assuming that we need to use code to design such a general concept as game objects, those who have learned object-oriented programming should immediately think of that classic example:

// 基类是Person,代表所有子类都是人类
class Person {};
// Student继承自Person,可以带有Person的能力,又能有自己的特殊功能
class Student : public Person {};
// Doctor也是类似
class Doctor : public Person {};

The advantage of this design is that it reuses the ability of subclasses to reuse the parent class, and can also use polymorphism to make different responses to the same interface, so as to achieve some unified encapsulation logic.

For some directions, it is really easy to use. For example, the UI framework is a very typical scene that can be inherited layer by layer.

But for games, we can imagine this:

// 基类是GameObject,代表所有物体都是GameObject
class GameObject{};
// 立方体Cube继承自GameObject
class Cube : public GameObject {};
// 球形Sphere继承自GameObject
class Sphere : public GameObject {};

It seems that there is no problem with this design, but what if there is such a situation:

  • All objects need to support physical collisions, so is it necessary to add a PhysicalGameObject as an abstraction?
  • When adding an object player, should it inherit from Cube or Sphere or write another one that inherits from GameObject? If it is re-inherited from GameObject, can the rendering functions required by Cube, Sphere and players only be placed in GameObject?

etc.

These problems actually reflect that the game world is actually very flexible. The base class designed at the beginning may expand rapidly due to more and more functions required by the subclasses. The final result will be a very large GameObject Classes lead to unmaintainable or inefficient.

Therefore, in Unity and UE4, the functional composition of game objects is designed in the form of components, similar to the following:

// 仍然有一个基类来通用的代表所有游戏物体,但是不再被继承
class GameObject
{
public:
    const GameObjectComponent* GetComonent(size_t componentIndex) const
    {
        // 返回对应的组件指针
    }

    // 不要纠结这里返回下标会被后面的增删所影响,这里只是一个示例,实际实现肯定还是遍历查找
    size_t AddComponent(GameObjectComponent* component)
    {
        mComponents.push_back(component);
        return mComponents.size() - 1;
    }
private:
    // 有一个数组成员,保存这个物体有哪些功能组件
    std::vector<GameObjectComponent*> mComponents;
};

Functional components can be inherited by various

// 游戏物体功能组件的基类
class GameObjectComponent 
{
public:
    // 组件创建的时候需要将自己设置给GameObject,以便后续功能访问GameObject的资源
    GameObjectComponent(GameObject* owner)
    {
        owner->AddComponent(this);
        mGameObject = owner;
    }

protected:
    GameObject* mGameObject;
};

// 渲染相关的功能组件
class RenderComponent : public GameObjectComponent
{
public:
    virtual void Render();
}

// 移动相关的功能组件
class MoveComponent : public GameObjectComponent
{
public:
    virtual void Move();
}

// 玩家的移动功能组件可以在基础移动组件上进行扩展或者改写
class PlayerMoveComponent : public MoveComponent
{
public:
    virtual void Move() override;
}

It can be seen that functional components provide functions to GameObject by adding members under GameObject.

There are several advantages to doing this:

  1. GameObject will be kept in a relatively small size, which will help both development efficiency and operating efficiency, especially avoiding the problem of difficult maintenance caused by huge GameObject classes.
  2. Each component does not need to be so dead, even if the functions of component A and component B overlap.
  3. Components can also use inheritance, and because the scope of functions that need to be designed is much smaller after the functions are divided, it is more likely to think clearly about the general development direction in the future without causing the expansion of the base class.

In any case, the two design methods of inheritance and combination are not silver bullets, so no one is necessarily better than the other, but different projects choose one of them to achieve functions according to their own situations, and both Unity and UE4 use Combining to realize the function of game objects also has its own background.

Reflection on the editor

Probably this is the effect, that is to say, every node in the Hierarchy window, except the root node, is a GameObject, and the GameObject itself has some attributes:

And each GameObject can have one or more components, the first to bear the brunt is the Transform component, which is the information indicating the position, rotation and size of the object. Obviously, this is very important, so that Unity uses this as the default component, and all GameObjects are created. Mandatory component and cannot be removed.

Other components can be deleted. Click the three dots on the right to select Remove Component to delete this component:

However, it should be noted that sometimes there is a dependency relationship between components. If the component that depends on this component is not deleted first, the dependent component cannot be deleted. Of course, we have not encountered Cube here.

Since there are deletions, there are also new components. Obviously, the big Add Component button is the new component:

After clicking, a menu will be displayed, and then you can quickly select the component you want according to the function classification, and if we know the name of the component, we can also directly enter the name in the input box to search, for example, we need to add a Box Collider component , enter Box and you can actually search:

The actual code for Unity

I have learned how to add and delete components to a GameObject in the editor, so if there is no function we want, how to develop it by myself?

In Unity, C# code is used instead of C++, and it doesn't make sense to not discuss which language is better. Based on many historical reasons and comprehensive considerations, Unity chose C#, which has a lower threshold, as the development language, so we must first learn the C# grammar, and I will definitely not talk about it here. Just search the grammar online and you will be done.

So how do we start writing code in Unity?

Just like creating a new scene resource file, the code we write must create a new code file, so the operation is exactly the same, we need to right click on the directory you like in the Project window, Create->C# Script.

Then you will get a new .cs suffix file, which is similar to .cpp .c. After the creation is completed, double-click to open it.

The default configuration of Unity is Visual Studio, but if you like JetBrains series IDE, you can configure it as Rider as the IDE for code development. The configuration entry is Edit->Preferences in the upper left corner of the Unity window. Open the following window to switch IDEs

I use Rider myself, because it is really smoother, and it supports the display of resource reference codes in the code (by default, only the code reference codes are displayed), which is very practical for game development.

Of course, in our novice tutorial, it doesn’t matter what IDE you use, even if you use Notepad. Here I use rider as an example, because there is no need to touch compilation or compilation configuration during the development process, so if you only write code files, the difference between different IDEs There is no significant usage difference between them.

Here I created a new DemoTest.cs file. After the creation is complete, double-click to open it and you can see the code like this:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class DemoTest : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

This is the template code that Unity pre-creates according to the file name you input, and this template is actually the template of the GameObject component, which means that the MonoBehaviour base class here is the subclass of the GameObjectComponent we just mentioned.

And there are two functions automatically written for us here, Start and Update, which are actually the life cycle callbacks designed by Unity for components. That is to say, when the corresponding time comes, the Unity engine will use C# reflection to magically call you. code, without actually writing a code to call your function. As for the reflection principle, we will talk about it in a separate article later.

Here you only need to know that the Start and Update functions are the same as the automatically generated comments. Before the game runs the first frame, Start will be called, and every frame will call Update.

You may ask, will it be called directly after writing in this way? Does it seem to have nothing to do with GameObject?

That's right, because we are still missing a step, which is to add our components to the GameObject, or the method mentioned above, click Add Component in the Inspector panel of the GameObject, and then enter DemoTest, you will find the class you created Already miraculously found:

Click DemoTest, then this component will be added to this GameObject:

Then you will be confused about the relationship between GameObject and the life cycle callback of component code? In fact, it is the GameObject that has a life cycle, and the component actually calls the life cycle callback along with the GameObject.

Now we add a little function to our component and print a sentence in Start:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class DemoTest : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        Debug.Log("Hello World!");
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

The Debug class here comes from UnityEngine, and the general IDE will automatically add using, which is very similar to Java. Debug.Log is similar to printf, std::cout, qDebug, Console.WriteLine, System.out.println, Unity is Debug.Log, and this output will be output to the Console window:

Ok, the code is written. After returning to Unity, Unity will automatically detect the code change and compile it. After the progress bar is finished, we can click the play button to start the game:

At this time, you can see that the content we wrote has been output in the Console window:

next chapter

This chapter spent a lot of space explaining how Unity designs GameObject, and then initially used C# code to create a component, and learned how to add your own code in Unity. Of course, writing code in Unity must be far more than GameObject Add new components, but this is a very important base.

In the next chapter, we will continue to write components. We will learn more about how the modifiable parameters of each component on the Inspector are created, and how Unity can call our newly added code functions without writing calling code. .

Guess you like

Origin blog.csdn.net/z175269158/article/details/129832270