(Unity tutorial) OnValidate is my babe. How to use Unitys functions and gizmos to make your designlife easier. Same thing.


Disclaimer or whatever: This is not this cycles devlog, this is a tutorial about using some useful functionalities.

 Use the OnValidate function to automate tedious actions and use Gizmos to make your editor more readable.

This is the situation. You have a gameobject that has a script on it, and a particle system. On the script is a variable, and when it changes you want to change the emission rate on the particle system. You could do this manually, if you  are going to create one of these things. But when you know that you will place way more than one (up to 50 or 100), then doing it manually begins to sound like a real pain. And this was the way I used to do things! I would do all of this tedious work myself, without even thinking about making it more efficient. Then I did some research and suddenly I could create, and most importantly, iterate upon my levels waaaay faster.


Part 1: the example script

So for this tutoral I will use a script I made to create wind that moves a Rigidbody2D when it comes within a specific area.

Here is the code for it:

public class Wind : MonoBehaviour
{
    public Vector2 size; //how big is the windzone
    public LayerMask affectedLayers; //the layers that gets effected by the wind
    public float strenght; //how strong the wind is
    private void FixedUpdate()
    {
        //get the colliders in the chosen area
        Collider2D[] InArea = Physics2D.OverlapBoxAll(transform.position, size, transform.rotation.eulerAngles.z, affectedLayers);
        //loop through the colliders. If the collider has a rigidbody add a force in the direction of the wind.
        foreach(Collider2D col in InArea)
        {
            if (col.GetComponent<rigidbody2d>())
            {
                Rigidbody2D colRb = col.GetComponent<rigidbody2d>();
                Vector2 forceToAdd = Quaternion.Euler(0, 0, transform.rotation.eulerAngles.z) * Vector2.right * strenght; //don't look at this one. It is very ugly code to convert from degrees to a Vector2
                colRb.AddForce(forceToAdd);
            }
        }
    }
}

The code works, but it looks like this in the scene view:


It tells you jack shit about how big the effected area is, which direction the wind goes. It tells you NOTHING. So let's make it tell something.


Part 2: gizmos

Let's fix not knowing what is going on by placing out some Gizmos. (I will get to the OnValidate function, I promise you)

The first thing that should be shown is the area that the wind covers. So let's display a box around the area!

private void OnDrawGizmos()
    {
        Gizmos.DrawWireCube(transform.position, size); 
    }

Problem solved, right? Not really. I want to rotate the gizmo, because the object can be rotated and that will change the direction of wind, and the rotation of the OverlapBox that gets all of the gameobjects to move.

So how do you rotate a gizmo? Simple answer, you can't. Less simple answer, there is a workaround:

private void OnDrawGizmos()
    {         Gizmos.matrix = transform.localToWorldMatrix; //sets the world matrix to the matrix of this transform         Gizmos.DrawWireCube(Vector2.zero, size); //change the position from the transforms center to 0, 0     }

Question is; what does this actually do? To be completely honest, what I figured out was that it sets the middle of the world, from the gizmos perspective, to be transform. I hope that explains it, if you want to understand it better, I recommend you to use a programmers number one skill, to google.

So now it looks like this in the scene when the size is set to 10 by 10 (and you can rotate it now):

Look, a beatiful gizmo!
You can also rotate it!

That's great and all, but we still have no clue where the wind is going. So let's make an arrow that points to the right (that's the direction that the wind blows).

Now how in the world do you create an arrow gizmo that:

  • Moves along with the transform
  • Is rotatable
  • Doesn't look like shit

You can't rotate a gizmo icon, so I had to do something different. So up with a great solution. I can draw an arrow from a bunch of lines! So here is how I did it:

private void OnDrawGizmos()
    {
        //draw the box that visualizes the size of the windzone
        Gizmos.matrix = transform.localToWorldMatrix;
        Gizmos.DrawWireCube(Vector2.zero, size);
        //draw an arrow to indicate the direction of the wind
        //a vector array containing all of the points the line should go through
        Vector2[] positions = new Vector2[6]
        {
            new Vector2(-2, 0),
            new Vector2(1, 0),
            new Vector2(1, 1),
            new Vector2(2, 0),
            new Vector2(1, -1),
            new Vector2(1, 0)
        };
        //Loop through the points and draw a line from that point to the next one. 
        //Don't do one for the last item though, because that would result in a indexoutsidearray error.
        for (int i = 0; i < positions.Length - 1; i++)
        {
            Gizmos.DrawLine(positions[i], positions[i + 1]);
        }
    }

Here is how it looks:


Beatiful! That's the gizmos that are useful for this example, so I'm stopping there.

Part 3: How to use OnValidate.

OnValidate is called when you change something about the script in the editor. So it is very useful for automating tedious things, like making a particlesystems shape change depending on the size of the wind. So let's do exactly that.

private void OnValidate()
    {
        ParticleSystem ps = GetComponent<particlesystem>(); //get the particle system on this gameobject
        ParticleSystem.ShapeModule editableShape = ps.shape; //set the shape module as its own variable because the variables in it can't be be acessed otherwise
        editableShape.position = new Vector2(-size.x / 2, 0); //set the shapes position to be at the left edge of the windzone
        editableShape.radius = size.y / 2; //since the emission shape is a line changing the radius will make it longer
    }

And here it is in action:

It has some issues:

  • The amount of particles that are emitted stays the same, so when the are gets bigger the density of particles gets smaller
  • The particles dies sometimes before, sometimes after the end of the zone, depending on the width of the zone

Fixing these are preeeetty easy:

private void OnValidate()
    {
        ParticleSystem ps = GetComponentInChildren<particlesystem>(); //get the particle system on this gameobject
        ParticleSystem.ShapeModule editableShape = ps.shape; //set the shape module as its own variable because the variables in it can't be be acessed otherwise
        ParticleSystem.MainModule editableMain = ps.main; //set the main module, same reason as the shape
        ParticleSystem.EmissionModule editableEmission = ps.emission; //same thing here
        editableShape.position = new Vector2(-size.x / 2, 0); //set the shapes position to be at the left edge of the windzone
        editableShape.radius = size.y / 2; //since the emission shape is a line changing the radius will make it longer
        editableEmission.rateOverTime = 4 * size.y; //set the rate over time(this is the normal emission rate) to be 4 * the height of the zone. Why 4? Because it looks good. You can turn the four into a variable for easier editing from the inspector
        editableMain.startSpeed = new ParticleSystem.MinMaxCurve(strenght * 0.8f, strenght * 1.2f); //Create a new minmax curve and then assign it to the startspeed
        editableMain.startLifetime = 1 / strenght * size.x; //the start lifetime should be 1 / strenght(the strenght is how quickly the particles will move) * size.x(the length that the particles should move)
    }

And how it finally looks in action:



And that's it!  You have now learnt how to help yourself to make your life easier. I learned all of this when developing my game, so if you would check it out it would be very sweet of you!

Get Nordic Niefel

Comments

Log in with itch.io to leave a comment.

I was able to move the pivot point, the rectangle rotates behind the cursor, but does not match the real raycast :(

RaycastHit2D[] hits = Physics2D.BoxCastAll(transform.position, new Vector2(attackRangeX, attackRangeY), 0f, transform.right, whatIsEnemies);

private void OnDrawGizmosSelected() { Gizmos.color = Color.green; Gizmos.matrix = Matrix4x4.TRS(transform.position, transform.rotation, Vector3.one); Gizmos.DrawWireCube(Vector2.right * 2f, new Vector2(attackRangeX, attackRangeY)); }

and the usual raycast works correctly, the line goes from the center of the character to a right behind the cursor

Debug.DrawRay(transform.position, transform.right * max_distance, Color.green); RaycastHit2D[] hitCenter = Physics2D.RaycastAll(transform.position, transform.right * max_distance, 4f, whatIsEnemies);

I understand that I don’t understand anything at all

It’s a bit too vague for me to understand, do you think you can post the full code and describe more exactly what it is supposed to do?

Actually it might just be the box cast that’s “wrong”. Right now it’s position is in the middle of the character, (the rotation aligns) so you need to make it reposition to work with the pivot. What this will do is make it so the boxcast the same as the gizmo (which I guess is what you want to do). Let me sketch it up.
That is how it looks right now. The arrow is the mouse, the dotted lines as the gizmos, the full lines are the rat cast and the point is the player. 

This is how we want to move the boxcast. In the same angle as the gizmo, half the length of the boxcast (that might be the width or height I’m not sure). 

So we want to take the angle of the cast, convert it to a vector, set that vectors magnitude to be half the length of the boxcast and then offset the boxcast by that vector. In code it would be something like:

Vector2 offset = new Vector2(cos(angle), sin(angle));
offset = offset * length / 2;
Vector2 positionOfCast = transform.position + offset;
(10 edits)

Hi! Here is my code http://p.tuduf.ru/UOtRtRc

If the enemy gets into the raycast, then it is clearly painted green, and with the cube otherwise, if I point the cursor at the enemy, it turns blue without entering the gizmo

public static Vector2 GetDirectionTowardsCursor(Transform baseTransform) {

var mouseWorld = Camera.main.ScreenToWorldPoint(Input.mousePosition);

return new Vector2(mouseWorld.x, mouseWorld.y) -

new Vector2(baseTransform.position.x, baseTransform.position.y);

}

public static Transform GetCharacterTransform()

{

return GetCharacter.transform;

}

(+1)

Sorry for the delay. Anyways I think the boxcast is still centered like I told you in the response above. If it makes the enemy blue when you have the mouse the opposite way from it then that is probably the issue. If it turns blue when you have the mouse 90deg / 270deg from it then the cast is rotated incorrectly. 

To solve the issue with the boxcast being in the center of the character instead of being aligned to the pivot, change the:

void FixedUpdate() { 
        RaycastHit2D[] hits = Physics2D.BoxCastAll(transform.position, new Vector2(attackRangeX, attackRangeY), 0f, transform.right, 4f, whatIsEnemies);
into something like:

void FixedUpdate() {
        Vector2 offset = transform.right * attackRangeY / 2;
        RaycastHit2D[] hits = Physics2D.BoxCastAll(transform.position + offset, new Vector2(attackRangeX, attackRangeY), 0f, transform.right, 4f, whatIsEnemies);

That will (as I reaponded with before) offset the boxcast so that it is in front of the character instead of in the center of it. Hope that works :D

(1 edit)

Hey! Thank you very much for your help! The problem was in the wrong angle

RaycastHit2D[] hits = Physics2D.BoxCastAll(transform.position, new Vector2(attackRangeX, attackRangeY), 0f, transform.right, 2f, whatIsEnemies);
private void OnDrawGizmosSelected() {
		float angle = Util.GetAngleTowardsCursor(transform.position);
        Quaternion rotation = Quaternion.Euler(new Vector3(0f, 0f, angle - 90f));
		Gizmos.matrix = Matrix4x4.TRS(transform.position, transform.rotation, Vector3.one);
        Gizmos.DrawWireCube(Vector3.right * 2f, new Vector2(attackRangeX, attackRangeY));
    }
public static float GetAngleTowardsCursor(Vector3 pos) {
	Vector3 mousePos = Input.mousePosition;
	mousePos.z = 5.23f;
	Vector3 objectPos = Camera.main.WorldToScreenPoint(pos);
		mousePos.x = mousePos.x - objectPos.x;
		mousePos.y = mousePos.y - objectPos.y;

float angle = Mathf.Atan2(mousePos.y, mousePos.x) * Mathf.Rad2Deg;
	return angle;
		}

Happy to help!

as planned it will be a staff of wind :)

Hi! Thanks for the tutorial! How can you make a rotation not in the center of the cube, but at another point?

Uh yea so the is really old and I have honestly kind of moved on from unity since. But I’ll do my best to help you. If you just want to move the center point of the gizmo then do the matrix multiplication and then add an offset. The term for it is called a pivot, and that is the point where it will rotate around.

How I would do it with pure vertices is: take all the vertices and move them with -pivotpoint, then do your rotational math and then you would have them (hopefully) placed correctly. 

If that’s a bit unclear then tell me and I’ll draw it up tomorrow on some paper.