📜 ⬆️ ⬇️

Correctly programmed. We use polymorphism. General logic of game characters

Unity3d WE USE THE ADVANTAGES OF PLO. POLYMORPHISM


ATTENTION!
I just want to warn you, read the comments on the article, there KumoKairo suggested a more perfect approach to solving the tasks set in the article.


Good day. In this lesson we will look at several important programming techniques on the Unity 3D engine. I think the article will help both beginners and experienced igrodelyam.

The aim of the article is to realize the power and unlimited convenience of using all the charms of the PLO. In particular, POLYMORPHISM. In addition, we will touch on several other important issues.
')
We will also talk a little about the concept of creating your own engines, the general logic of game characters and how to interact with each other and with the world around them. We will talk how to make it simple and logical, easily editable (modernized). How to make it so that when adding new elements to the game, you do not have to rewrite (supplement) what has been written.

I think in the future there will be a continuation of the article. There we will examine in detail the writing of our own Motors, talk about vectors and the operations we need on them, and create a complete inventory model. Very good inventory in which you can easily add new items without changing old ones.

This article is intended for people with Unity3D experience. If you do not have this presentation, I highly recommend reading these articles:

1) Very good articles: habrahabr.ru/post/112287 , habrahabr.ru/post/113859
In the second article, examples of scripts in JS. I think that there will be no problems transferring to C #, even if you are new to both JS and C #. The only thing I will say is that the line is on JS:
bull_clone = Instantiate(bullet, transform.position, transform.rotation); 

On C # it will look like this:
 bull_clone = (GameObject)Instantiate(bullet, transform.position, transform.rotation); 

And such a line on JS:
 var ml = GameObject.Find("Player").GetComponent(MouseLook); 

On C # it will look like this:
 GameObject ml = GameObject.Find("Player").GetComponent<MouseLook>(); 


2) Article: habrahabr.ru/post/141362 There is also a continuation of this article on the site. They are called “Unity3d. Lessons from Unity 3D Student (B00-B16) »Total 4 articles. At the moment, at least. There at the top right there is a search engine on the site. I think you can handle it.

3) habrahabr.ru/post/148410 It is also necessary to read and understand. There is the second part of this article. Again the search engine on the site will help you.

4) This is something like the habrahabr.ru/post/128711 directory, habrahabr.ru/post/128948 .

5) There are many interesting articles on this forum. Look for yourself if you want.
So. You have learned everything that you have read, experimented, you may have already made a little game. Perhaps not small. It's bad if you just read these articles, but did not write anything yourself. Practice and practice again

But first, consider a couple of minor issues. (Just in case)

The first half of the article is for demonstration purposes. No need to rewrite the code.
I use the following words as synonyms: script class

CLASSES, CLASS COPIES


We are engaged in object-oriented programming (OOP). There are classes and instances of classes. A class is like a mold or a drawing of an object. And the molded shape of the mold is a copy of the class. All buses are instances of class Bus. Bus Vasya, your neighbor, is a copy of the class Bus. Vasin's bus has a sticker on the left side, scratches all over the body, the right front wheel is lowered, the floor of the gasoline tank, one glass is broken, one of the doors does not work. This bus has many individual properties. Your other neighbor also has a bus. He also has scratches, stickers, but there are no broken windows, the wheels are not lowered, and there are still many individual features of this particular bus. Both buses are instances of class Bus. The class bus is a drawing. A drawing is a bus that has not yet been created and you cannot ride on it. He has no scratches and lowered wheels. Without a drawing you can not create a single bus. Without a class description, we cannot create any instance of the class. The class serves not only to create instances, it is possible, for example, to know the length of the bus. But you can not ride a drawing. In Unity, class names are capitalized (GameObject, Transform, Animation, etc.), and class names are small.

It makes sense to start all variable names with a small letter in order to distinguish them from class names. So dictates us Unity.

CASHING


From Wikipedia: Caching work results -
Many programs write somewhere intermediate or auxiliary results of the work in order not to calculate them every time they are needed. This speeds up the work, but requires additional memory (operational or disk).


Consider this script. What's wrong with it? It seems all is well. An object moves in Left at a constant speed of 10 units per second. It is thanks to Time.deltaTime that uniform motion occurs, independent of the current performance on a specific computer.

The bad thing about this script is that each frame is called transform.position, or rather badly, which is called transform. In fact, transform written in our Update () means gameObject.transform. This in turn is the same as gameObject.GetComponent (). And this already means that each frame your script searches for among all that is hung on the gameObject transform. And why every time to look for when you can find once and remember (cache) (note the gameObject is written with a small letter. That is, it is an instance of the GameObject class)

It would seem that nothing has changed. But in fact, it is much more rational use of machine resources.
Do not believe me. Open the Unity documentation and it tells you to cache it !!!

Consider a couple of examples of what should be cached:

Carefully read the comments in the code

Of course, it makes sense to cache only if what we are going to cache will be used many times. (Often it is more than once). In addition, it is worth understanding if we remove the components from our gameObject that were previously cached in the variable “A” and then added again, there will be nothing in “A”. Therefore, there is no point in caching something that is constantly deleted and added again. Although you can cache immediately after the next addition. You can cache only Instances of a class (maybe something else I won't lie), but you don’t need to cache data types and structures.
For example, it can be cached



It says on the right that this is a class (class), and yes even the icon on the left is also special. (Circled in red)
But this can not be cached

Since position is not an instance of a class. This is a Vector3 structure.

And again, Visual Studio shows us that Vector3 is a structure. And her badge is also different.
So. Cache only classes. Variables are all types of float, byte, vector3, etc. do not cache! Well, you know what:
 Public Vector3 aaa; aaa = transform.position; //    transform.position.      aaa    transform.position.    ( )     , .         . 

You need to cache not only scripts hung on objects, but also the objects themselves.

In the following examples, everything will be cached, so you will figure it out with examples.

GENERAL CONCEPT OF MOBA (CHARACTER)


If you analyze the standard projects that come with Unity and spread your brains a little, then it becomes clear that the correct concept of the character is this: There is a main script that enables and disables behavior scripts. For example, behavioral scripts may be:

Of course, you can complicate the scheme. For example, adding Behavior in the "see the goal, I can not kill, run away" mode (Running away from the target)
The implementation of each behavior in its own script. The main script has no idea how a particular behavior is implemented. Its task is to turn it on at the right time. For example, the script Behavior in the “see goal, can kill, approach” mode can be turned on when a player approached a mob at a certain distance or attacked the mob first. If we need two almost identical mobs, then we do not redo the whole script, but change one of the Behavior scripts. This can be done not only at the design stage, but also at run time. (this requires polymorphism)

All these scripts can control the movement of the character (mob). Manage not themselves, but through an intermediate script called the MOTOR. Motor-in-law has several methods needed to control a character. For example:

Why is this necessary? There are two main reasons. Firstly, it’s just logical and if you want to change something in the Motor you don’t have to change the code in different scripts that move the character. They all do it through a single script. Change the code in the motor and that's it. In addition, the launch of the animation while driving is also carried out from the Motor. This is especially important if the project is developed by a team. The person who makes the motor has no idea where the motor methods will be called from. And the person making the script has no idea how the MobMotor.MoveLeft () method is implemented. The main thing is that he moves the character to the left. That's all he needs. Thus, for coordinated work, they need to know only two simple facts. Mob can walk left and right. Everything! Nothing extra. Secondly ....... the most important thing is second. But we still do not know what polymorphism is. We return to this advantage later.

We continue. We have a main script that monitors the situation and includes the necessary scripts when necessary. And they move the character only through the motor. There is a script that is responsible for the health of the character. For example:



By the way, let me remind you Public means that the declared variable (method, etc.) will be available to all scripts in general. Private-available only inside the script.

Consider the MobHp script in detail. The character appears and immediately has a health of 100 units. When a character takes damage (getting damage, it tracks the main script and not the MobHp itself), the main script calls the MobHp.SetDamage (float damage) method. This method returns either true or false. If true is returned, then the mob has experienced getting the damage. And the main script continues its operation. If it returns false, the mob is dead. What happens to a mob after death doesn't matter to the main script. He doesn't know that. In our case, a sound file will be played at death (the sound of a character dying) and in 5 seconds the character will disappear (see picture). The role of the main script is simple. If false, it disables all Behavior scripts. Turns off the motor of the character. He will turn off everything. That is, the mob will freeze, the sound of the mob dying will be played, and then it will disappear. Of course, you can add the generation of fragments to the MobHp script, play the animation of the character falling to the ground. Whatever you want. The beauty is that the MobHp script is completely independent. If the main script and MobHp are programmed by different people, then all they need to know for coordinated work is that the character gets the damage through the MobHp.SetDamage method (float dfamage). And if the Method returns false, then the mob is dead. Everything! No unnecessary information. And if the creator of MobHp wants to change the contents of the script, it does not concern the main script either!

His Majesty POLYMORPHISM


So we got. Those who did not quit reading congratulations. I will start the story of polymorphism with a few simple life examples, and then we will extend them to our common concept of a mob (character).

Imagine a switch. Normal switch. Which turns the light on in your room. Or maybe he turns on the fan? Or maybe he starts the lathe. Can a conventional switch start a nuclear bomb? Perhaps it is obvious that the switch does not care what to turn on (turn off). He simply opens the contact. He lets the energy go somewhere. He does not care where.

Now imagine a tank shell. It flies, falls and explodes, causing damage to everything around. He doesn't care what's around. He doesn’t just care. He has no idea what's around him but, nevertheless, he does damage.

Let's return to our scripts. We have a method MobHp.SetDamage (float damage). The hypothetical tank projectile, which fell next to the character, should call this method to cause damage to the character. However, according to our concept, the MobHp.SetDamage (float damage) method should be called from the Main Script. Thus, the projectile of the tank must interact with the main script. Suppose we create a global method in the main script. And the projectile during the fall will check whether the character (mob) is in the blast radius and call this method if necessary. Suppose we have a lot of absolutely identical mobs (characters) on the map. The projectile during the fall must go through them all, select the ones that are in the radius of destruction and inflict damage to them (call the appropriate method in the main script of each of the mobs). In Unity, this can be implemented like this: Assign Mob tag to all mobs

You can get an array of all objects with a specific tag.
 GameObject[] allMobs= GameObject.FindGameObjectsWithTag ("Mob" ); //    //     

For definiteness, let our main script be called MyBehaviour

And now we iterate over all the mobs and damage them (Note. We have submitted that the main script (MyBehaviour) has the MyBehaviour.SetDamage (float damage) method, which in turn calls the MobHp.SetDamage (float damage) method on the MobHp script. Both script hang on the character (mobe))
The code in the script on the projectile.
 foreach ( GameObject mob in allMob ) //      { mob.GetComponent<MyBehaviour>().SetDamage(50); //     SetDamage } 

In this example, the Projectile deals 50 damage.
Everything is fine! It will work. But if we want a lot of mobs of TWO types? In the second type, the Main script can be implemented differently. For example, a robot and a gun. They have completely different behaviors. They have differently implemented Behavior in the mode “I see the goal, I can kill, I get closer” (Run to the new goal). I don't have a gun at all. Thus, at the tower, the main script will be called MyBehaviour2 (different scripts must be called differently), and all the scripts of the Behavior are called differently. And even MobHp cannon is different. MobHp2, for example, the “dying of a cannon” is implemented in its own way (it may not be limited only to replacing the sound and animation of dying). How now should a tank shell behave itself in order to cause damage to all mobs and towers?

It is necessary to introduce a new tag for the tower "tower". And the script of the shell itself will look like:
 //   GameObject[] allMobs= GameObject.FindGameObjectsWithTag ("Mob" ); //    //     foreach ( GameObject mob in allMob )//     { mob.GetComponent<MyBehaviour>().SetDamage(50); //     } //   GameObject[] allTower= GameObject.FindGameObjectsWithTag ("Tower" ); foreach ( GameObject tower in allTower )//     { tower.GetComponent<MyBehaviour2>().SetDamage(50); //     } 

What are the disadvantages of this approach. Obviously, the shell script depends on the number of types of mobs. Thus, if you want to add one type of mob, you will have to climb into the shell script and upgrade it. Now imagine that the game has 500 types of mobs. The projectile script will have about 1500 lines of code. It is worth considering that there will probably be several dozen types of shells in the game. Count yourself. If the Projectile script is done by one person, and the main scripts of mobs and guns and other characters (MyBehaviour, MyBehaviour2, MyBehaviour3, etc.) are other people, then for coordinated work they will need to know the number of mobs and the exact names (and maybe content) their main scripts (imagine that the names will not be of the form MyBehaviourN, but RobotBehaviour, TowerBehaviour, MonsterBehaviour, TigerBehaviour, etc.)

Let's return to the switch, which doesn't care what it turns on. If you have 5 types of doors in the game, then everyone will need their own switch (button). And if you want to add one type of door, then you have to add a button, or upgrade existing ones. And if the buttons and doors are programmed by different people, they will have to know all the names (and maybe the content) of all the scripts of all the doors and buttons.

Similarly, the situation with the arming of characters, armor (if their influence on the game is not limited to changes in strength, speed, etc.), in any complex way interacting objects.

Of course, there are ways to solve this problem. But the most elegant and easy to understand, in my opinion, is POLYMORPHISM. I will not write strict definitions, you can find them yourself.

Inheritance, encapsulation and polymorphism - three OOP elephants.
Next come no definitions! Rather approximate explanations
Encapsulation is the ability of a class to hide its guts from an external code. We cannot refer to variables (methods, procedures, etc.) declared with the Private keyword, Protected from external, with respect to the code class, and this is good.
Inheritance is the ability of a class to inherit (copy) methods, variables, etc. from its parent.

You see! MobHp - MonoBehaviour's heir. MonoBehaviour is the parent of MobHp. A colon is written through the colon.
Now focus. Create a new empty script, call it NewMobHp and instead of MonoBehaviour (in the script itself) write MobHp (That is, we indicated that the parent of this new NewMobHp will not be MonoBehaviour, but MobHp) (See the picture below).
Let's declare in the NewMobHp script a variable of the NewMobHp type with the name newMobHp (with a small letter). Do not worry that in the NewMobHp script (“script” and “class” are synonyms for us) we declared a variable of the same type as the class itself. Yes, a class may contain variables of the same type as the class itself. Let's write “newMobHp” in Start () and click a point. If you have a normal code editor, then it will show all the methods, properties, etc. that are available to you in this place of the code. Let's start writing SetDa ... Magic

And magic !!! Our NewMobHp class has a NewMobHp .SetDamage (float damage) method just like the MobHp class. Although the script has only been created and there is no SetDamage (float damage) method declaration in it, but this method is declared for the parent class. This is the INHERITANCE. The NewMobHp class inherited the SetDamage (float damage) method as it was declared in MobHp with the keyword Public.

By the way, what is the difference between Protected and Private? All that Private is not inherited. All that is protected is inherited. So, if we want to inherit a globally declared (Public) method or a variable, etc., then nothing needs to be done. It is inherited anyway. If we want to inherit a local variable for this class, then instead of Private, we need to write Protected in the parent class.
So from this moment I give the command, CODE! All that will be further written by me, must be written by you. Experiment, sort it out, stuff the pens.

NewMobHp and other scripts will be useful to us, we will remove them (if we did). By the way, the NewMobHp class is not only MobHp’s successor, but also MonoBehaviour. Since MobHp is a successor to the MonoBehaviour class.

Like this: MonoBehaviour -> MobHp -> NewMobHp.

MobHp is the immediate (middle) parent for NewMobHp.
Open Unity, we start coding! Perhaps something will not be clear, but when we get to create a small game, everything will become clear.

Create MyScript Daddy. In it we will create the MyBehaviour script. This will be the parent for all the main scripts of all mobs (in general, all that can get damage). We can create a SetDamage method in it. And in the future, the new main scripts will inherit it. And all the mobs will have this method. But for each type of mob, the process of obtaining damage can be implemented in different ways. How to make the method implemented in its own way, for each script?

POLYMORPHISM - I do not know how to define it. Maybe you yourself then give it. We will take a better look at the example. Write in MyBehaviour this:

Empty method SetDamage. Why is it needed? Yes, we do not need it. Pay attention to the Virtual keyword (see picture).This means that this method can be overridden in other scripts that will be inherited from the MyBehaviour script. That is, they inherit it, but implement it in their own way. Now we will make the MobBehaviour script:

And so let 's analyze what is happening here:
1- We explicitly indicated that MobBehaviour is a successor of the MyBehaviour class
2- I am writing the word Override, put a space and Visual Studio itself suggests that we can OVERLAP. As you can see there is SetDamage (float damage), we need it. All other methods are methods declared in Parent scripts more distant than MyBehaviour. For example ToString (). We can override ToString () and program this method for our MobBehaviour class according to our work. But we do not need it. Select SetDamage (float damage) and see (figure 3 in the figure) that this method was originally defined in MyBehaviour (Ie, it inherits from the class MyBehaviou). You can see where ToString () and others are defined. Now select the SetDamage (float damage) line, press Enter and this is what Visual Studio itself drew:

Base.SetDamage (damage); - here Base is like the parent (class) in which the SetDamage (float damage) method was defined. Or, to put it differently, the reference to the class from which this method was inherited. We have it defined in MyBehaviour. It is empty if you have not forgotten. That is, you can add anything there. And then Base. SetDamage (damage); will call this code, and then you can add something for a specific successor. This is necessary if the milestones of the successors of MyBehaviour have something in common in the SetDamage method (float damage). But we do not need it. For now, remove all the excess from MobBehaviour. Leave it so

divert from MobBehaviour. Create a new script MyHp

This is a script from which all Scripts responsible for the health of the character will be inherited
Create another script. Let's call it MobHp. We considered it above.

Everything, just like last time, only has the word Override in the re-declaration of the SetDamage method (float damage). And it is indicated that the class is an inheritor of the MyHp class. Mob's dying is slightly modified. There are no sounds, the mob will simply increase at death by 3 times.

Again, we remember MobBehaviour. Let's make it like this:

We redeclared the SetDamfge (float damage) method. Please note that thisHp is declared as MyHp, although the Script responsible for health is called MobHp. Here it is POLYMORPHISM. We can work with MobHp as well as with MyHp. We can assign an instance of the class MobHp to a variable of the type MyHp, since it is a successor of MyHp. Thus, the MobBehaviour script does not care which heir of the MyHp class lies in the variable thisHp. The main thing is that thisHp has a method SetDamfge (float damage). And you can use it! We can change the MobHp script to another (also a successor of MyHp). And everything will work.

Now we will make our mob in the form of a ball (we will double its size from the initial one). Let's make the earth (I make a flat rectangle), and some light. I did it like this:

This ball will be our first mob. Let's hang MobBehaviour and MobHp scripts on it.

Notice this hp in the MobBehaviour script (see picture). Now there is nothing assigned (None). However, at the start (in the Start () procedure in MobBehaviour) MyHp (or its successor) will be found
thisHp = GetComponent ();
So, we have a mob that can be killed. True, he called "Sphere", but it does not matter. It would be necessary to create a player who will shoot balls at the mobs. Let's do it.
Remove the existing camera. Let's create the First Person Controller (he is there, in his daddies, remember the lessons to which there are references at the beginning of the article). Create a ball (small), attach it to Rigibody. Call it Bullet and drag it into a new folder called MyPrefab

This is our future cartridge. By the way so that it does not fly through walls at high speeds, we will do so.

Now the physics engine will interpolate the movement of the cartridge. This is probably a rather complicated algorithm, so you should not put Interpolate on everything. Only to fast moving objects (Very fast. Those that can fly from half a meter or more in one frame). Read on Wikipedia what is interpolation and extrapolation.
Create a new script BulletScript. This is a cartridge script.

When hitting something, this script gets the MyBehaviour component (or any of its descendants) from it. And it calls (if it has something MyBehaviour) the method SetDamfge (float damage) with damage = 30. This means that one cartridge will inflict 30 damage. Thanks to POLYMORPHISM, the patron still has exactly what, and which particular heir of MyBehaviour he wants to attack. Even if after writing this script, we add a new type of mob to the game, with its successor MyBehaviour (for example, TowerBehaviour), the cartridge will still damage if it hits this new mob. Remember the example of a tank shell. Do not forget to drag this script to the cartridge prefab.

It remains only to make the player shoot these cartridges. Create a new script PlayerShoot

A similar script was created and described in detail in one of the articles, the link to which is at the beginning of the article. Only there he was on JS. Let's dwell only on the main points:
1-prefab of the cartridge, which we will shoot,
2-initial speed of the cartridge,
3-pause between shots,
4-transform of the camera, after all, it turns when we move the mouse,
5-time of the last shot,
6- Cache Transform Camera. Notice how we did it. This script will hang on our FirstPersonController. Therefore, under transform (if you write this word in a script) is meant the transform of FirstPersonController, not the camera.

Pay attention to the scale. First Person Controller I have a scale of 1.1.1

If you have 100,100,100 there, then you may not notice either the mob or its future movement.

We will search among under transforms (we can say daughter transforms. But not in the sense of heirs as with classes) transform the camera using the transform.FindChild (“Main Camera”) method. Test this method will find among the subobjects FirstPersonController ʻa object with the name "Main Camera" and get out of it Transform. And we are caching this Transform into the variable cameraTransform.
7- if more than shootPause has passed since the last shot, then you can shoot.
8-Create a copy of the cartridge.
9-Get rigiBody cartridge.
10-Add speed to patron.
11-remember the time of the shot.
We throw this script on the player. Drag the cartridge prefab where necessary

Run the game. We find the mob. We shoot at it, using the left mouse button. We count the shots. He needs to swell with 4 hits. In the console appeared "Mob died !!!". Mob swells and disappears after 5 seconds.

The mob is swollen since we wrote thisTransform.localScale = new Vector3 (3, 3, 3) in the MobHp script. If your mob is not swollen enough, change triples for larger values. Well, here we did what you probably could have done. What is the beauty of polymorphism? But in what. Create another mob. Also a ball. Hang on it a new MonsterHp script:

As you can see, it is almost the same as the MobHp script. All differences are underlined in red. We cached the material, and change its color depending on health. If you do not know how to create the color you want, look on the Internet for the RGB color model.

Let's make the “Player” tag for our main character.

Now let's create a MonsterBehaviour script:



This is an analogue of the script MobBehaviour. But the mob possessing this script can also run after the player, if he is close enough. In general, the methods of movement of the mob should be in the Motor (we have already discussed why) but in order not to clutter up the example and not to blow up your brain, we will do this in MonsterBehaviour itself. Please note that the SetDamage method (float damage) is not implemented as in MobHp. It added IsLive = false; and the text has changed in Debud. Log () ;. Of course, you can add some more code. I want to draw your attention to the fact that the FindGameObjectWithTag (“Player”) method returns only one first gameObject with the “Player” tag. But we have one and so. If we need to get all the objects, we need the FindGameObjectsWithTag method.
 FindGameObjectWithTag("Tag") //  gameObject FindGameObjectsWithTag("Tag")//   gameObject[]    .    . 

Note that we have cached the transform of the player, not the entire gameObject. We’ll refer to the transformation, and through gameObject it would look like this: player.transform, which is equivalent to player.getComponent (). And why do this each time when you can once and immediately cache. Of course, from a couple of dozen extra getComponent, nothing much will change. But if the project is very big. And each programmer will poke where getComponent and GameObject.Find (...) and GameObject.FindGameObjectWithTag (...), etc., get. This together can slow down the execution of a program. Lower FPS. Do you need it?

Let's hang up the MonsterBehaviour and MonsterHp scripts on our new mob (New Ball). I got this scene:

The little ball is the bullet. You can delete it, anyway, it is already in prefabs (MyPrefab folder). Please note that my both mobs are called the same (Shape). You can change. Let's call the new mob Monster, and the old Tower.

Run the game. We are getting closer to the new mob. He begins to move in our direction. We run away away, he stops. We shoot at the mob, it changes color. We shoot more, he blushes and no longer runs after us. After 5 seconds disappears. You can shoot the old mob and make sure that he is still able to take damage and die.

So, what is the beauty of polymorphism. We added a mob and wrote him a script. But we did not write anything in the player’s script or in the cartridge’s script.

Again, remember our tank shell. What would our script look like in our case? Very simple.But first, add our “Mob” tag to our mobs to

both mobs! Now we will make Tank explosive projectile out of our patron. We will not make fragments, explosion, etc. this is not related to our lesson. Remove the hanging cartridge from the scene (if not already removed). It should remain only in the MyPrefab folder. Create a new script BigBulletScript



This is an analogue of the BulletScript script. This is the script of the Tank cartridge. When colliding with something, he using GameObject.FindGameObjectsWithTag ("Mob") gets an array of all game objects with the tag "Mob". Checks whether this object has a component MyBehaviour (or its successor), and if so, it checks the distance to this object. And if this distance is less than atacDistanse, then this object, through the interface (SetDamage method) MyBehaviourʻa (or any of its descendants) is damaged.

Now remove the BulletScript script from the cartridge's prefab, and replace the BigBulletScript script instead.

Make your mobs on the stage not far from each other. Run the game. We shoot so that the cartridge falls between the mobs. Four shots and both mobs are ready. We completely replaced the script on the cartridge, but we didn’t touch the mobs and player scripts in any way. The script BigBullrtScript does not depend on the mob in which it falls. If the mobs will be done by one person, and the cartridges by the other, then all they need to know for coordinated work is that all mobs (in general, everything that can get damage) carry some kind of (we don’t know what, that is, any ) heir to the class MyBehaviour. And that this successor has a method SetDamage (float damage). It is very convenient. Thus, 100500 types of mobs can be riveted without thinking about the rest of the project code.

SUMMING UP


In this simple project, we used Polymorphism in two places.

1) You can remove MyHp from any mob and hang a new one (another heir to MyHp). This is especially convenient for collective development. Suppose a certain head of the project selects the optimal script of behavior (or the script responsible for health in our case). He changes the options offered to him. And they immediately work without rework with the main script. The head of the project does not know how these scripts work, and he will not be able to quickly find in them the declaration of the desired variable and change its type. But if we use polymorphism, this is not necessary. You can edit and add new heirs to the MyHp script and they will be perfectly integrated into the project. Most importantly, these scripts can be changed not only at the development stage, but also at run time.

2) The cartridge can attack any mob. Even created after the creation of the cartridge itself. You can create a new cartridge, and do not change the scripts of mobs.

Here is a link to a polymorphism lesson from the creators of Unity unity3d.com/learn/tutorials/modules/intermediate/scripting/polymorphism . In my opinion, it is disgusting and not understandable. I'm not talking about the fact that he is in English. For more information about polymorphism, you can read here sharp-generation.narod.ru/C_Sharp/methods.html . But first, read the first couple of chapters of this tutorial (to which the link is given), otherwise it will not be clear.

Rethink team approach to the creation of large projects.

Design can begin at different ends. But it is better to start from above. That is, first decide what will be in the game. What mobs, what weapons will be available to the player, etc. We break everything that we made into classes and subclasses. For example:

In the figure, ovals do not mean the objects themselves, but the scripts hung on them. for example

The player is a kind of script that will hang on the player. So, in the picture there is MyHp, familiar to us, and several of his heirs. As you can see, destructible items also have their own heir MyHp, since they react to damage inflicted. There is a class “Dynamic” in the scheme (they participate in one way or another). If you find all the heirs of this class in the scene (except those that are attached to the player) and disable them (the disconnect method will be publicly announced in Dynamic (participate in one way or another)), then everything in the game will freeze except for the player. So you can just realize the effect of stopping time. Although you can do it differently and not create a class "Dynamic" - start with the lower classes. “Able to take damage” is a script (class) that will hang on everything that can get damage. We have already implemented it and called MyBehaviour,but here, MyBehaviour is MonoBehaviour's heir, not “Dynamic” because we do not need it ("Dynamic"). Our scheme looks like this:

We have all the scripts (classes) are the heirs of MonoBehaviour. So dictates us Unity.

Let's return to the big scheme. “Action” is a class whose heirs receive several methods necessary for the realization of events. For example, it can be StartAction (), Open (), Close () methods. The heirs of the class "Action" are all plot scripts, scripts for doors, hatches, etc. This approach allows you to create a universal trigger (button, etc.) that can call any of these methods (or several methods at once) on the Action array of scripts. It is very convenient.Unity even has a standard script for such a trigger. Only he is a little improved. Due to polymorphism, each successor of the class “Action” implements in its own way the StartAction (), Open (), Close () methods. Different doors open in different ways, different plot scripts implement StartAction () in different ways. An example with a door: a player enters a universal trigger, a door is tied to a trigger (more precisely, an heir to the Action class from this door). The trigger calls the Open () method. Setting up a trigger is to put a tick, which method exactly should be called from the specified heir to the class "Action". That is, it is not necessary to make your own button class for each class of the door. The button is universal. And it declares a public variable:
 Public ActionScript action; //     ActionScript Public bool callStartAction=true; // ,     Public bool callOpen =false; Public bool callClose =false; 

Further, in the code of a button (a trigger, etc.) under certain circumstances (pressing a button or entering the trigger of the desired object) the desired method of the action object is called
 If(callStartAction) { action. StartAction(); } If(callOpen) { action. Open(); } If(callClose) { action. Close (); } 

We look at the big scheme again. We find there player Motors, zombies, and guns. We have already discussed the concept of the motor. But now there is the Main Motor, from which all others inherit methods. It will be very good if all engines have the same methods. For example, there can be such methods Jump (), MoveDirection (Vector3 direction), Run (bool run), Stop (), LookTo (Vector3 direction), GoToPoint (Vector3 point). All methods except the last player need. And here is the last one for mobs. It may contain a pathfinding algorithm, etc. Due to polymorphism, each heir to the main motor will be able to implement these methods in its own way, and it will also be possible to change the motor from one to another. They are completely interchangeable.

Now consider the behavior scripts. “Zombies are not busy”, “Zombies Attack”, etc. They are also the heirs of a single script. You can select a common method. For example, SetTarget (Transform target). Through this method, you can transfer the current goal of the mob to the behavior script. After all, the heir to the MyBehaviour class is engaged in analyzing the situation, therefore, he must, when he turns on the necessary behavior script, pass on the necessary information to him. In a real game, these methods may be more.

3) After drawing up a similar scheme, proceed to its implementation. First we make the "parents" scripts. All others will be inherited from them. For example, the script “parent” of the motor can be done like this:

The red underlined keyword that will allow the heirs of this class to redefine these methods in their own way. The ad will look like this:

Then in MonsterBehaviour we will create a variable of the MyMotor type, in which any successor of the MyMotor script, hung on this mob, will be stored. Of course, the mob is not jumping in our game. This is just an example. For mobs, you can come up with another method for the class MyMotor and call it SetTarget (Transform target). And when you need to move for the player (or other mob) use this method. A motor mob itself will decide on the direction of movement of jumps, etc.
The last example clearly shows: all that is needed to implement polymorphism is the keywords Virtual and Override, as well as explicit indication of the parent of the class.

That's all.Several “words” in the code will make our scripts independent and interchangeable. In general, all the scripts should be made independent of each other.

Let's continue to use polymorphism and other delights of OOP in the next article.

Source: https://habr.com/ru/post/195970/


All Articles