📜 ⬆️ ⬇️

Tiling in 2D games on Unity, scaling material

When developing 2D games on Unity, it is often necessary to make a multitude of elements of different sizes from the same material. The simplest example is the tiles of earth, grass, stone, and other elements in all kinds of platformers. As a rule, the same tiles by default use the same material (otherwise the number of materials created in advance would be too large). Often, it may be inconvenient to make a level frame from elementary tiles because of too many objects at a level, so instead of elementary tiles, large tiles are used - the same tiles, only on a larger scale. Suppose we need to “plant” two patches of grass above the ground:

image


')
In this picture, there are 9 separate grass tiles, 1x1 in size, on the ground. With Ctrl + C, Ctrl + V, the eight tiles on the right were successfully placed, but if the level should be on the order of a couple hundred grass tiles and the levels themselves are a few dozen, then the placement of single tiles will take too much time. The optimal solution for such tasks is scaling a single tile. Those. in this case, you will need to copy a piece of grass and stretch the resulting object along the X axis eight times (for example, in the editor's inspector), you get this thing:

image

It is hardly possible to call the result obtained acceptable. For normal scaling, in addition to the object itself, you need to change the scale of the material of this object (again in the editor, in the Inspector tab), namely the Tiling parameter:

image

We get the following result:

image

As you can see, a block of eight tiles looks as it was intended, but a block of one tile to the left has shrunk eight times. The thing is that these two objects use the same material, so changes in the material of one object entail changes in the material of another object.
This problem can be solved in the following way: each object has a transform.sharedMaterial component - this is a common material, as well as a transfrom.material component - when this component changes, a copy of the material used by the object is created, which the object will use in the future, and can be changed without affecting other objects. Create a simple script:

TexTilingScript.js

scaleMaterial(); function scaleMaterial() { //       renderer.material.mainTextureScale.x = transform.localScale.x; renderer.material.mainTextureScale.y = transform.localScale.y; } 


This script creates and scales new material for an object during object initialization. Add this script to both objects. If you click Play, we get the following result:

image

It would seem that life is beautiful, but there is one "but": this result is obtained only during the "game", while editing is still sad:

image

It is an expected result, because the script is executed only during the application launch. In order for the material to be correctly scaled during editing, you must add a handler for this script to the editor. To do this, create another script in the Assets / Editor folder:

TexTilingEditorScript.js

 @CustomEditor(TexTilingScript) class TexTilingEditorScript extends Editor { function OnSceneGUI () { //    TexTilingScript     ,     var script = target as TexTilingScript; //        script.scaleMaterial(); } } 


This script will call the scaleMaterial function for an object that has a TexTilingScript component every time such an object is selected on the screen (for example, it is selected with the mouse), and saves the resulting changes:

image

Everything is fine, except for one thing - an error appears in the Log-e:
Instantiating material due to calling renderer.material during edit mode. This will leak materials into the scene. You most want to use renderer.sharedMaterial instead.
The fact is that by changing the material of the object during editing - we create a new temporary material, which after launching the application will be deleted. I sincerely do not understand why the Unity developers designed this case as an error, and not as a warning, since Such an alignment is quite acceptable and does not lead to any conflicts. But if anyone bothers red - you can follow their advice - for scaling, you can use not renderer.material, but renderer.sharedMaterial - in this case, while editing the selected object, its proportions will be correct, but other objects with the same material will be distorted nevertheless, this option can be considered acceptable and convenient. Add a new scaleSharedMaterial () function to TexTilingScript:

TexTilingScript.js

 scaleMaterial(); function scaleMaterial() { //       renderer.material.mainTextureScale.x = transform.localScale.x; renderer.material.mainTextureScale.y = transform.localScale.y; } function scaleSharedMaterial() { //   ,      renderer.sharedMaterial.mainTextureScale.x = transform.localScale.x; renderer.sharedMaterial.mainTextureScale.y = transform.localScale.y; } 


Now in the TexTilingEditorScript script, we will change the called function:


TexTilingEditorScript.js

 @CustomEditor(TexTilingScript) class TexTilingEditorScript extends Editor { function OnSceneGUI () { //    TexTilingScript     ,     var script = target as TexTilingScript; //     script.scaleSharedMaterial(); } } 


Now the “error” does not appear, but in order for the material of the object to scale correctly during editing, you must first select it. Also quite acceptable option.
Well, another solution to this problem is to create in advance for each object its own material. But not everyone will like to have in the source code of the project a hundred pre-created materials, when you can do with just one, so the use of scripts is one of the best solutions.

All scripts are written in JavaScript. Unity 3.3, Pro.

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


All Articles