Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

2D Platformer Friendly AI [SOLVED]

Discussion in '2D' started by LucaBGT, Mar 12, 2015.

  1. LucaBGT

    LucaBGT

    Joined:
    Feb 20, 2015
    Posts:
    22
    Hi!
    I have a little problem with my 2D Platformer. I have nearly finished everything (I'm pretty proud, because I'm pretty new to Unity). Now there is one last thing I want to add into the game. It's a friendly AI that should jump and run through the level with the player, but I have no Idea hoe to do so. It would be very nice if I could get some help. I don't want the AI to be very clever, he also might die, but he should get over some bottomless pits. I had an Idea, wich was to give every pit a trigger collider and then tell him to jump whenever he collides with this trigger. But again, I have no idea where to start. I would really appreciate help!
    (Sorry if my English is bad, it's not my native language...)
     
  2. GarBenjamin

    GarBenjamin

    Joined:
    Dec 26, 2013
    Posts:
    7,441
    In general... break it down. Identify specifically what you want to happen in each case. You seem to already have a good idea of the general behavior.

    Get the AI player running across your terrain.
    Get the AI player leaping over pits.
    Get the AI falling into a pit.

    Now you can code each of these cases focusing on one at a time.
    First just get the AI into an Idle state which is probably what you need at the start of the level.
    Then get the AI to respond to some kind of event/trigger and go into Running state.
    Test and tweak until it is moving the way you want.
    Next get the AI to respond to a different event/trigger and change to LeapingOverObstacle state.
    Test and tweak until you are happy with the result.
    Next get the AI to respond to another trigger and change to FallingToDeath state.

    You can build this AI in isolation. Meaning just set up a scene with your terrain and AI object and expose public variables in the inspector to simulate the triggers. Just use boolean values say bRunTrigger, bLeapTrigger and bFallToDeath. Alternately you can use keys to simulate the triggers. Maybe R for Run, L for leap and D for death.

    When you have it all working you just replace the simulated triggers with the real trigger such as collisions, time or whatever.

    Just trying to give you a starting point. Hope it helps.
     
    theANMATOR2b likes this.
  3. LucaBGT

    LucaBGT

    Joined:
    Feb 20, 2015
    Posts:
    22
    Thanks, I will try to do something like this!
     
    GarBenjamin likes this.
  4. LucaBGT

    LucaBGT

    Joined:
    Feb 20, 2015
    Posts:
    22
    Okay, I have tried to do something and these are my results:
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class AiZ : MonoBehaviour {
    5.    
    6.     public Transform target;
    7.     public float speed = 3f;
    8.    
    9.    
    10.     void Start () {
    11.        
    12.     }
    13.    
    14.     void Update(){
    15.        
    16.  
    17.         transform.LookAt(target.position);
    18.         transform.Rotate(new Vector3(0,-90,0),Space.Self);
    19.        
    20.        
    21.         //move towards the player
    22.         if (Vector3.Distance(transform.position,target.position)>6f){//move if distance from target is greater than 6
    23.             transform.Translate(new Vector3(speed* Time.deltaTime,0,0) );
    24.         }
    25.  
    26.         if (Vector3.Distance(transform.position,target.position)<50f){//stop if distance from target is smaller than 5
    27.             transform.Translate(new Vector3(0,0,0));
    28.         }
    29.  
    30.    
    31.     }
    32.  
    33.    
    34. }
    35.  
    I'm fine with the moving to the left and right side, but the AI is rotating towards the player, but what I want is that the AI only flips if the player goes to the other side of his sprite, like when the player moves right and you press left. Here's a picture so you can see what I mean:
    UnityForum 5.png
     
  5. GarBenjamin

    GarBenjamin

    Joined:
    Dec 26, 2013
    Posts:
    7,441
    Check to see if AI is already facing player

    Break it down:
    If player is to right of AI and AI is not facing right, rotate to face right
    If player is to left of AI and AI is not facing left, rotate to face left.

    However, I think with Unity you only need to use the LookAt method as you are doing. I have never used it but I thought I remembered reading it actually rotated the object so it faces the target object. If so you shouldn't need to use the Rotate method.

    Hopefully you'll get someone who is really into Unity to provide some help. I develop the same way no matter what I am using so do not have much Unity specific knowledge.
     
    Last edited: Mar 13, 2015
  6. LucaBGT

    LucaBGT

    Joined:
    Feb 20, 2015
    Posts:
    22
    Thanks for your reply!
    I have one question: Is there a code that sees on which side the player is?
     
  7. GarBenjamin

    GarBenjamin

    Joined:
    Dec 26, 2013
    Posts:
    7,441
    In your code above transform.position seems to be the position of your AI character and target.position seems to be the position of the player.

    If that is correct then target.position.x < transform.position.x means the player is to the left of your character.
     
  8. LucaBGT

    LucaBGT

    Joined:
    Feb 20, 2015
    Posts:
    22
    Okay, I have changed the code and now it works like this:

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4.  
    5. public class AiZ : MonoBehaviour {
    6.  
    7.  
    8.  
    9.     public Transform target;//set target from inspector instead of looking in Update
    10.     public float speed = 3f;
    11.     private bool leftRight = true;
    12.    
    13.     void Start () {
    14.        
    15.     }
    16.    
    17.     void Update(){
    18.  
    19.         /*transform.LookAt(target.position);
    20.         transform.Rotate(new Vector3(0,-90,0),Space.Self);
    21.         */
    22.  
    23.                         if (target.position.x < transform.position.x) {
    24.                
    25.                                 transform.Rotate (new Vector3 (0, 180, 0));
    26.                 //leftRight = false;
    27.                         }
    28.  
    29.  
    30.         //move towards the player
    31.         if (Vector3.Distance(transform.position,target.position)>6f){//move if distance from target is greater than 1
    32.             transform.Translate(new Vector3(speed* Time.deltaTime,0,0) );
    33.  
    34.         }
    35.  
    36.         if (Vector3.Distance(transform.position,target.position)<=5f){//stop if distance from target is smaller than 50
    37.             transform.Translate(new Vector3(0,0,0));
    38.         }
    39.  
    40.         if (Vector3.Distance(transform.position,target.position)<4f){//stop if distance from target is smaller than 50
    41.             transform.Translate(new Vector3(0,0,0));
    42.         }
    43.        
    44.     }
    45.  
    46.     void OnTriggerEnter2D(Collider2D other)
    47.     {
    48.         if (other.gameObject.tag == "GameObject")
    49.         {
    50.             rigidbody2D.AddForce(new Vector2(0f, 2000f));
    51.        
    52.         }
    53.     }
    54.  
    55.     void RotateLeft() {
    56.  
    57.         Quaternion theRotation = transform.localRotation;
    58.         theRotation.z *= 270;
    59.         transform.localRotation = theRotation;
    60.  
    61.     }
    62.  
    63. }
    It works relatively good, but when I go to the left side of the AI, it turns towards me and follows me. The only problem: It turns back to his original position every frame, and also does this while standing and following the player.
    Is there something in my code that causes this?
    I have tried to create a boolean variable called "leftRight". Then, in the "Update" part of the section, I checked:
    1. If leftRight is true (which it is from the beginning), check if the target.position.x < transform.position.x. If this is true, turn 180°, then set leftRight to false.
    2. If LeftRight is false, check if the target.position.x > transform.position.x then turn the AI and set leftRight to false.
    This did not work either...
     
  9. GarBenjamin

    GarBenjamin

    Joined:
    Dec 26, 2013
    Posts:
    7,441
    What I do for this kind of thing is have an enumeration that is used by various game objects:
    Code (CSharp):
    1. public enum FacingType
    2. {
    3.    Left = -1,
    4.    Right = 1
    5. }
    This enumeration is found (along with all others) in my enumerations.cs code file. But for your project since you will likely only be using it for this one AI character you may just want to include it at the top of your script... if you decide to follow a similar approach to what I am describing here.

    Each object has a variable declared at the top of their script:
    Code (CSharp):
    1. private FacingType eCurrentlyFacing;
    For 2D, this helps out in multiple ways.

    First, the -1 for left and 1 for right correspond to the direction of horizontal movement based on the way an object is currently facing. It simply needs to be turned into a float with a simple (float)eCurrentlyFacing and then can be multiplied by the speed at which the object is to move which ends up (float)eCurrentlyFacing * fDistanceToMoveThisFrame.

    It also works out very well that an x-scale of -1 flips a sprite around horizontally so it faces the other direction.
    So, if my sprite images all face to the right this corresponds to x-scale of 1 (FacingType.Right). To flip them the x-scale needs to be -1 (FacingType.Left).

    To achieve what you are after I would do something like this:
    Code (CSharp):
    1. // keep the AI character facing the player
    2. if (target.position.x < transform.position.x - 3F)
    3. {
    4.     eCurrentlyFacing = FacingType.Left;
    5. }
    6. else if (target.position.x > transform.position.x + 3F)
    7. {
    8.   eCurrentlyFacing = FacingType.Right;
    9. }
    10.  
    11. v2Scale.x = (float)eFacing;
    12. this.gameObject.transform.localScale = v2Scale;
    Note that I added an offset of 3 pixels movement on either side of (presumably the center of) the AI character before the change of direction takes place. You may need to play around with that value or remove it entirely to get your desired behavior.

    The v2Scale is a Vector2 variable declared at the top of the script.
    Code (CSharp):
    1. private Vector2 v2Scale;
    And initialized during the Awake() method
    Code (CSharp):
    1. v2Scale.x = 1F;
    2. v2Scale.y = 1F;
    Like I mentioned previously, I develop in Unity using the same approach I have used in every other API/engine so this may not be the way most people accomplish this sort of thing in Unity. But I stick with what has been proven to work over many years in numerous languages and APIs whether it is the popular way for Unity or not. ;)

    Sorry about any typos or other mistakes in the code. I am just writing this here in the editor off the top of my head. It should be fine and at the very least provide insight into what I mean.
     
    Last edited: Mar 14, 2015
  10. LucaBGT

    LucaBGT

    Joined:
    Feb 20, 2015
    Posts:
    22
    Hello and a huge thanks for your reply!
    I understand what you meant, but like I just said, I'm a real beginner. I understand what you mean and the bits of code you wrote, but I have no idea how to compose them. I tried to, but there were too many errors:
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. Vector2 v2Scale;
    5.  
    6.  
    7.  
    8. public enum FacingType
    9. {
    10.     Left = -1,
    11.     Right = 1
    12. }
    13.  
    14. private FacingType eCurrentlyFacing;
    15.  
    16.  
    17. void Awake() {
    18.  
    19.     v2Scale.x = 1F;
    20.     v2Scale.y = 1F;
    21.  
    22. }
    23.  
    24.  
    25.  
    26. void update() {
    27.  
    28. // keep the AI character facing the player
    29. if (target.position.x < transform.position.x - 3F)
    30. {
    31.     eCurrentlyFacing = FacingType.Left;
    32. }
    33. else if (target.position.x > transform.position.x + 3F)
    34. {
    35.     eCurrentlyFacing = FacingType.Right;
    36. }
    37.  
    38. v2Scale.x = (float)eFacing;
    39. this.gameObject.transform.localScale = v2Scale;
    40.  
    41. }
    42.  
    I hope that you could find the mistakes I made...
    But really, thank you!
     
  11. GarBenjamin

    GarBenjamin

    Joined:
    Dec 26, 2013
    Posts:
    7,441
    It looks like you have all of this inside a code file and not inside your AiZ class.
     
  12. LucaBGT

    LucaBGT

    Joined:
    Feb 20, 2015
    Posts:
    22
    Okay, thanks and hooray! It nearly worked!
    The AI turned, but still searches for the player on the right side.
    This is my code:
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4.  
    5. public class Ai2 : MonoBehaviour {
    6.  
    7.     private Vector2 v2Scale;
    8.     public enum FacingType
    9.     {
    10.         Left = -9,
    11.         Right = 9
    12.     }
    13.     private FacingType eCurrentlyFacing;
    14.     public Transform target;
    15.     public float speed = 3f;
    16.  
    17.    
    18.     void Awake() {
    19.  
    20.         v2Scale.x = 9F;
    21.         v2Scale.y = 9F;
    22.    
    23.     }
    24.  
    25.     void Start () {
    26.        
    27.     }
    28.    
    29.     void Update(){
    30.  
    31.         // keep the AI character facing the player
    32.         if (target.position.x < transform.position.x)// - 3F)
    33.         {
    34.             eCurrentlyFacing = FacingType.Left;
    35.         }
    36.         else if (target.position.x > transform.position.x)// + 3F)
    37.         {
    38.             eCurrentlyFacing = FacingType.Right;
    39.         }
    40.        
    41.         v2Scale.x = (float)eCurrentlyFacing;
    42.         this.gameObject.transform.localScale = v2Scale;
    43.  
    44.  
    45.         //move towards the player
    46.         if (Vector3.Distance(transform.position,target.position)>6f){//move if distance from target is greater than 1
    47.             transform.Translate(new Vector3(speed* Time.deltaTime,0,0) );
    48.  
    49.         }
    50.  
    51.         if (Vector3.Distance(transform.position,target.position)<=5f){//stop if distance from target is smaller than 50
    52.             transform.Translate(new Vector3(0,0,0));
    53.         }
    54.  
    55.         if (Vector3.Distance(transform.position,target.position)<4f){//stop if distance from target is smaller than 50
    56.             transform.Translate(new Vector3(0,0,0));
    57.         }
    58.        
    59.     }
    60.  
    61.     void OnTriggerEnter2D(Collider2D other)
    62.     {
    63.         if (other.gameObject.tag == "GameObject")
    64.         {
    65.             GetComponent<Rigidbody2D>().AddForce(new Vector2(0f, 2000f));
    66.        
    67.         }
    68.     }
    69.  
    70.     void RotateLeft() {
    71.  
    72.         Quaternion theRotation = transform.localRotation;
    73.         theRotation.z *= 270;
    74.         transform.localRotation = theRotation;
    75.  
    76.     }
    77.  
    78. }
    Where is my mistake?
    Thanks in advance!
     
  13. GarBenjamin

    GarBenjamin

    Joined:
    Dec 26, 2013
    Posts:
    7,441
    On cell phone so cannot get into too much code...

    Basically just take eCurrentlyFacing into account in your movement code.

    transform.Translate(new Vector3((float)eCurrentlyFacing*speed*Time.deltaTime,0,0));

    I wouldn't use new Vector3 though. I'd set up a Vector3 v3Velocity variable at the top of the script. Update it as needed when changing direction then just use that in the translate method.
     
  14. GarBenjamin

    GarBenjamin

    Joined:
    Dec 26, 2013
    Posts:
    7,441
    Off work now so revisited this.

    Basically you can do something like this...

    At the top of your class declare a new variable below your declaration for float speed;
    Code (CSharp):
    1. public Vector3 v3CurVelocity;
    Change your Update() method to something like this:
    Code (CSharp):
    1. void Update()
    2. {
    3.    // keep the AI character facing the target
    4.    if (target.position.x < transform.position.x)// - 3F)
    5.       eCurrentlyFacing = FacingType.Left;
    6.    else if (target.position.x > transform.position.x)// + 3F)
    7.       eCurrentlyFacing = FacingType.Right;
    8.  
    9.    v2Scale.x = (float)eCurrentlyFacing;
    10.    this.gameObject.transform.localScale = v2Scale;
    11.    // end of keeping the AI character facing the target
    12.  
    13.    //move the AI character horizontally towards the target if distance is greater than 6 something on either side
    14.    float xDist = target.position.x - transform.position.x;
    15.    float xAbsDist = (xDist >= 0f) ? xDist : -xDist;
    16.  
    17.    if (xAbsDist > 6f)
    18.       v3CurVelocity.x = (float)eCurrentlyFacing * speed * Time.deltaTime;
    19.    else
    20.       v3CurVelocity.x = 0f;
    21.  
    22.    transform.Translate(v3CurVelocity);
    23.    // end of moving the AI character horizontally toward the target
    24. }
    I am just typing this into the editor so there may be some typos in this but that is the general idea. And it is possibly more involved than you may actually need but you will be in a fairly decent position to update it over time as needed.

    Just trying to work in the same style as what you had.
     
    Last edited: Mar 19, 2015
  15. LucaBGT

    LucaBGT

    Joined:
    Feb 20, 2015
    Posts:
    22
    :eek: It worked.
     
    GarBenjamin likes this.