Day 1: Unity, Neural networks and C#

in #neural-networks6 years ago (edited)

This is the beginning... Well, it has already started actually.

I instantly fell in love with neural networks when I found out about them, but at that time I didn't have enough knowledge/skills for it.
time has passed and I believe I should try it now.

My friend and I make Unity games, but right now I thought... Why not make a game and then we both make neural networks for the Bots(players), which we will train and then make the fight against each other.
That would be fun, wouldn't it...

A small disclaimer though: this is my journey into learning the neural networks and I just hope I can help those who are trying to learn as well and those who want to see how learning programming is.

By the end of these posts, I will give the link for the repository so that any of you can clone it and use it if you want to.

The game itself

The first thing I started to make the game itself. I started with a basic scene and the "Arena" so to speak.
It came out pretty simple but appealing (To me at least :P).

Scripts And Controllers

Note: In this post, we won't talk about the Network itself for now, but it will be covered in the next post ;)

After setting up the scene I proceeded with the botController script.
This script will be used to control the bot by the neural network itself.

The first thing that I usually do (And you should too) is to set up your variables beforehand, you can, of course, add more and change them later but it's best to manage them at the beginning.

For the Neural Network, we will need the outputs of the 3 ray casts and inputs of the 2 buttons. Ray casts will be used for the inputs of the neural network and buttons will be used for the outputs of the network.

(Don't worry if you don't understand now, I will talk more about it later.)

First lets setup the raycast objects and their distance values.

[SerializeField]
    private RaycastHit2D RaycastForward, RaycastLeft, RaycastRight;

#region Neural Inputs
    [Header("Neural Inputs")]
    [SerializeField]
    private float _raycastForwardDistance;
    [SerializeField]
    private float _raycastLeftDistance;
    [SerializeField]
    private float _raycastRightDistance;
    #endregion

If you're wondering why they have "_" in front of them, that is because they are private variables but they have Properties which we're about to make.

#region Neural Inputs
    public float RaycastForwardDistance { get { return this._raycastForwardDistance; } }
    public float RaycastLeftDistance { get { return this._raycastLeftDistance; } }
    public float RaycastRightDistance { get { return this._raycastRightDistance; } }
    #endregion

If you don't know what #region and endregion do, they create a foldable folder of code, which you can name what you like organize your code with it.

Properties are not really necessary here, however, I wanted to limit access to them so that the players won't be able to cheat by changing the values itself.

If you'd like me to make a post about Properties, comment below and I'll go at it some time later ;)

Now that we've set up the Inputs, let's do the same with Outputs.

#region Neural Outputs
    [Header("Neural Outputs")]
    [SerializeField]
    private float _rotationButton;
    [SerializeField]
    private int _dashButton;
    #endregion

#region Neural Outputs
    public int RotationButton { set { this._rotationButton = value; } }
    public int DashButton { set { this._dashButton = value; } }
    #endregion

Now that we have the needed variables let's start to make the raycast and the 2 other functions that we will need.

Raycasts Are quite straightforward and I won't be explaining them right now as well.
(Although if you want me to comment about it ;) )

        Debug.DrawRay(transform.position, transform.right * distance, Color.white);
        Debug.DrawRay(transform.position, transform.up * distance, Color.white);
        Debug.DrawRay(transform.position, -transform.up * distance, Color.white);


        RaycastForward = Physics2D.Raycast(transform.position, transform.right, distance);
        RaycastLeft = Physics2D.Raycast(transform.position, transform.up, distance);
        RaycastRight = Physics2D.Raycast(transform.position, -transform.up, distance);

        _raycastForwardDistance = RaycastForward.distance;
        _raycastLeftDistance = RaycastLeft.distance;
        _raycastLeftDistance = RaycastRight.distance;


        Debug.Log("Raycast: " + gameObject.name + "\n Distance " + RaycastForward.distance );
        Debug.Log("Raycast: " + gameObject.name + "\n Distance " + RaycastLeft.distance);
        Debug.Log("Raycast: " + gameObject.name + "\n Distance " + RaycastRight.distance);

Debug.DrawRay Are used to see the rays in the scene as such:

We then initialize the rays and then apply the distances of the collisions so that the neural network will be able to use them.

I used Debug.Log to later test if our raycasts are working properly, after starting the game again we can see that the outputs are correct:

Until now I used an IgnoreRaycast on the object to test the raycasts, however, i can't use this because we want the other bot to also detect this bot and vice-versa. To solve this, we need to go into the project settings and change something in physic2D, more specifically, "Queries Start In Colliders" Which is here:

Now we need Rotation and the Dash Attack

Rotation is self-explanatory,
A or D(left/right arrows also work) which will give -1 to 1 value and that would rotate the GameObject.

This will be called in the update

        //temporary input
        _rotationButton = Input.GetAxis("Horizontal");
        RotateBot(_rotationButton);

And this is the function itself

private void RotateBot(float rotationButton)
    {
        //We take the direction and the magnitude of rotation
        float rotAmount = degreesPerSec * Time.deltaTime * rotationButton;
    
         // Then we find current z rotation (because of unity stuff...)
        float currentRot = transform.localRotation.eulerAngles.z;

         //Now we just assign the z rotation value.
        transform.localRotation = Quaternion.Euler(new Vector3(0, 0, currentRot + rotAmount));
    }

Now for the Dash Function

This on itself is quite simple, all we need to do is to apply force towards the right ("front of the sprite")
And then we will need to check if the Bot has collided with other bot and if the other bot is also dashing and if not we destroy it.

This may sound complicated for some people but it's quite simple. Let's do it step by step.

First, we just take the input from the update

       //this is only temporary input
        if (Input.GetKeyDown(KeyCode.Space))
        {
            dash = true;
        }

We do this because we have to do run our function in FixedUpdate() otherwise it gives weird bugs.(This took me quite a while to figure out and i am still not sure why this weird bug is happening.)

Our dash function is like this.

private void Dash(float dashForce)
    {
        rb.velocity = Vector2.zero; // set the velocity at zero
        rb.AddForce(transform.right * dashForce, ForceMode2D.Impulse); 
//Add force to the right of the transform (it changes depending on the rotation of the GameObject)
    }

Now that we've our function ready, if we call it using a normal if, this is how it would work:
example.gif

Now before we go into collision I just wanted to add a Cooldown effect, just sometime after the dash is used there will be a delay before you can use it again.

All we have to do is make an if check and when we use the dash we start the timer

if (dash & isCooldownFinished)
        {
            dash = false;
            Dash(dashForce);
            
            timer = 1;
            isCooldownFinished = false;
            
        }
        else if (!isCooldownFinished)
        {
            timer -= Time.deltaTime;
            if (timer <= 0)
            {
                isCooldownFinished = true;
                timer = 0;
            }
        }

Now, we need to add the collision checks.

First, let's make a isDashing boolean to check if we're currently dashing.
I think to check if the velocity(it's magnitude) is less the 3 (I found this value by using the Debug.Log, you should probably avoid magical numbers though ) would be the easiest approach.

So I am just going to do this:

else if (!isCooldownFinished)
        {
            if (rb.velocity.magnitude <= 3f) <---------------
            {                                                                <---------------
                isDashing = false;                             <----------------
            }                                                               <----------------
            timer -= Time.deltaTime;
            if (timer <= 0)
            {
                isCooldownFinished = true;
                timer = 0;
            }
        }

And now we are ready to check collisions.

private void OnCollisionEnter2D(Collision2D collision)
    {
        var gameObj = collision.gameObject; //Cashe the gameObj 
        if (isDashing && gameObj.CompareTag("Bot") && !gameObj.GetComponent<BotController>().isDashing)
        {
            Destroy(gameObj); //Destroy object
        }
    }

What we're doing here is, when the object collides with something, we check if we're dashing, if other object is another bot, and if that bot is NOT dashing.
If all those conditions pass, we destroy that object.

Now for testing, i changed the conditions so that if the other object is dashing it would be destroyed.
This is what we get:

example.gif

Works as Planned!

Everything works now and that means we're done with this part.

Next post I will be starting to make my neural network and training it.
I might also fix some things around and add some features or other rules e.t.c

If you liked this post please leave a like, And if you have any questions or suggestions i will definetly listen.
Cheers!

And check out my second part!
https://steemit.com/programming/@reborninferno/day-2-or-part-2-neural-networks-and-what-you-eat-them-with

Sort:  

Congratulations @reborninferno! You have completed the following achievement on Steemit and have been rewarded with new badge(s) :

Award for the number of upvotes

Click on the badge to view your Board of Honor.
If you no longer want to receive notifications, reply to this comment with the word STOP

Do not miss the last post from @steemitboard:
SteemitBoard and the Veterans on Steemit - The First Community Badge.

Do you like SteemitBoard's project? Then Vote for its witness and get one more award!

Congratulations @reborninferno! You have completed the following achievement on Steemit and have been rewarded with new badge(s) :

You published your First Post
You got a First Vote
Award for the number of upvotes received

Click on the badge to view your Board of Honor.
If you no longer want to receive notifications, reply to this comment with the word STOP

Do not miss the last post from @steemitboard:
SteemitBoard and the Veterans on Steemit - The First Community Badge.

Do you like SteemitBoard's project? Then Vote for its witness and get one more award!

Congratulations @reborninferno! You have completed the following achievement on Steemit and have been rewarded with new badge(s) :

You got a First Reply

Click on the badge to view your Board of Honor.
If you no longer want to receive notifications, reply to this comment with the word STOP

Do not miss the last post from @steemitboard:
SteemitBoard and the Veterans on Steemit - The First Community Badge.

Do you like SteemitBoard's project? Then Vote for its witness and get one more award!

Notifications have been disabled. Sorry if I bothered you.
To reactivate notifications, drop me a comment with the word NOTIFY