Forum begins after the advertisement:


[Part 3] Dashing and Enemy Collisions

Home Forums Video Game Tutorial Series Creating a Metroidvania in Unity [Part 3] Dashing and Enemy Collisions

Viewing 20 posts - 1 through 20 (of 24 total)
  • Author
    Posts
  • #18982
    Nirvik Rajbhandari
    Level 4
    Participant
    Helpful?
    Up
    0
    ::

    Whenever, I dash into an enemy it sort of glitches out and will also stop time for a really long time. Should I also add my code so that it is easier find the issue? If so, which scripts?

    #18983
    Terence
    Level 31
    Keymaster
    Helpful?
    Up
    0
    ::

    You can show the PlayerController script and explain the issue in a bit more detail, and post a video of the issue as well.

    I’ll see if the AI helper is able to detect any issue with your code with a quick scan. If not, I’ll look at your video and help you further with it.

    #18995
    Nirvik Rajbhandari
    Level 4
    Participant
    Helpful?
    Up
    0
    ::

    Ok, Here’s the Player Controller Script

    
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class Player_Controller : MonoBehaviour
    {
        [Header("Horizontal Movement Settings")]   
        [SerializeField] private float WalkSpeed = 1;
        [Space(5)]
    
        [Header("Vertical Movement Settings")]
        [SerializeField] private float JumpForce = 45;
        private float Jump_Buffer_Counter;
        [SerializeField] private float Jump_Buffer_Frames = 0;
        private float Coyote_Time_Counter;
        [SerializeField] private float Coyote_Time;
        private int Air_Jump_Counter = 0;
        [SerializeField] private int Max_Air_Jumps;
        [Space(5)]
    
        [Header("Ground Check Settings")]
        [SerializeField] Transform GroundCheckPoint;
        [SerializeField] private float GroundCheckY = 0.2f;
        [SerializeField] private float GroundCheckX = 0.5f;
        [SerializeField] private LayerMask WhatIsGround;
        [Space(5)]
    
        [Header("Dash Settings")]
        [SerializeField] private float Dash_Speed;
        [SerializeField] private float Dash_Time;
        [SerializeField] private float Dash_Cooldown;
        private bool Can_Dash = true;
        private bool Dashed;
        [SerializeField] GameObject Dash_Effect;
        [Space(5)]
    
        [Header("Attack Settings")]
        [SerializeField] float Time_Between_Attack;
        float Attack_Time_Counter;
        [SerializeField] private Transform Side_Attack_Transform;
        [SerializeField] private Transform Up_Attack_Transform;
        [SerializeField] private Transform Down_Attack_Transform;
        [SerializeField] private Vector2 Side_Attack_Area;
        [SerializeField] private Vector2 Up_Attack_Area;
        [SerializeField] private Vector2 Down_Attack_Area;
        [SerializeField] private float Up_Attack_Parameter;
        [SerializeField] private float Down_Attack_Parameter;
        [SerializeField] private LayerMask Attackable_Layer;
        [SerializeField] private float Player_Damage;
        [SerializeField] GameObject Slash_Effect_Object;
        bool Attack_Bool = false;
        bool Restore_Time;
        float Restore_Time_Speed;
        [Space(5)]
    
        [Header("Recoil Settings")]
        [SerializeField] int Recoil_X_Steps = 5;
        [SerializeField] int Recoil_Y_Steps = 5;
        [SerializeField] int Recoil_X_Speed = 100;
        [SerializeField] int Recoil_Y_Speed = 100;
        int Steps_X_Recoiled;
        int Steps_Y_Recoiled;
    
        [Header("Health Settings")]
        public int Player_Health;
        public int Player_Max_Health;
        [SerializeField] GameObject Blood_Spurt;
        [SerializeField] float Hit_Flash_Speed;
        public delegate void On_Health_Changed_Delegate();
        [HideInInspector] public On_Health_Changed_Delegate On_Health_Changed_Callback;
        [Space(5)]
    
        private Vector3 Mouse_Position;
        [HideInInspector] public Player_State_List Player_State;
        private Rigidbody2D Rigidbody;
        private SpriteRenderer Sprite_Renderer;
        private float Gravity;
        private float XAxis;
        Animator Animator;
    
        public static Player_Controller Instance;
    
        private void Awake()
        {
            if (Instance != null && Instance != this)
            {
                Destroy(gameObject);
            }
            else
            {
                Instance = this;
            }
            Player_Health = Player_Max_Health; 
        }
    
        // Start is called before the first frame update
        void Start()
        {
            Player_State = GetComponent<Player_State_List>();
            Rigidbody = GetComponent<Rigidbody2D>();
            Sprite_Renderer = GetComponent<SpriteRenderer>();
            Animator = GetComponent<Animator>();
            Gravity = Rigidbody.gravityScale;
        }
    
        private void OnDrawGizmos()
        {
            Gizmos.color = Color.red;
            Gizmos.DrawWireCube(Side_Attack_Transform.position, Side_Attack_Area);
            Gizmos.DrawWireCube(Up_Attack_Transform.position, Up_Attack_Area);
            Gizmos.DrawWireCube(Down_Attack_Transform.position, Down_Attack_Area);
        }
    
        // Update is called once per frame
        void Update()
        {
            GetInputs();
            UpdateJumpVariables();
            if (Player_State.Dashing)
            {
                return;
            }
            Flip();
            Move();
            Jump();
            Attack();
            Start_Dash();
            Restore_Time_Scale();
            Flash_While_Invincible();
        }
    
        private void FixedUpdate()
        {
            if (Player_State.Dashing)
            {
                return;
            }
            Recoil();
        }
    
        void GetInputs()
        {
            XAxis = Input.GetAxisRaw("Horizontal");
            Attack_Bool = Input.GetButtonDown("Attack");
            Mouse_Position = Camera.main.ScreenToWorldPoint(Input.mousePosition);
            Mouse_Position.x -= gameObject.transform.position.x;
            Mouse_Position.y -= gameObject.transform.position.y;
            Mouse_Position.z = 0f;
        }
    
        void Flip()
        {
            if(XAxis < 0)
            {
                transform.localScale = new Vector2(Mathf.Abs(transform.localScale.x), transform.localScale.y);
                Player_State.Looking_Right = false;
            }
            else if(XAxis > 0)
            {
                transform.localScale = new Vector2(-Mathf.Abs(transform.localScale.x), transform.localScale.y);
                Player_State.Looking_Right = true;
            }
        }
    
        private void Move()
        {
            Rigidbody.velocity = new Vector2(WalkSpeed * XAxis, Rigidbody.velocity.y);
            Animator.SetBool("Walking", Rigidbody.velocity.x != 0 && Grounded());
        }
    
        void Start_Dash()
        {
            if(Input.GetButtonDown("Dash") && Can_Dash && !Dashed)
            {
                StartCoroutine(Dash());
                Dashed = true;
            }
            if(Grounded())
            {
                Dashed = false;
            }
        }
    
        IEnumerator Dash()
        {
            Player_State.Dashing = true;
            Player_State.Invincible = true;
            Can_Dash = false;
            Animator.SetTrigger("Dashing");
            Rigidbody.gravityScale = 0;
            Rigidbody.velocity = new Vector2(-transform.localScale.x * Dash_Speed, 0);
            if(Grounded())
            {
                Instantiate(Dash_Effect, transform);
            }
            yield return new WaitForSeconds(Dash_Time);
            Rigidbody.gravityScale = Gravity;
            Player_State.Dashing = false;
            Player_State.Invincible = false;
            yield return new WaitForSeconds(0.2f);
            yield return new WaitForSeconds(Dash_Cooldown);
            Can_Dash = true;
        }
    
        void Attack()
        {
            Attack_Time_Counter += Time.deltaTime;
            if(Attack_Bool && Attack_Time_Counter >= Time_Between_Attack)
            {
                Attack_Time_Counter = 0;
                Animator.SetTrigger("Attacking");
                if(Mouse_Position.y < Up_Attack_Parameter && Mouse_Position.y > Down_Attack_Parameter || Mouse_Position.y <= Down_Attack_Parameter && Grounded())
                {
                    Hit(Side_Attack_Transform, Side_Attack_Area, ref Player_State.Recoiling_X, Recoil_X_Speed);
                    Slash_Effect_Angle(Slash_Effect_Object, 0, Side_Attack_Transform);
                }
    
                else if(Mouse_Position.y > Up_Attack_Parameter)
                {
                    Hit(Up_Attack_Transform, Up_Attack_Area, ref Player_State.Recoiling_Y, Recoil_Y_Speed);
                    Slash_Effect_Angle(Slash_Effect_Object, 80, Up_Attack_Transform);
                }
                else if(Mouse_Position.y < Down_Attack_Parameter && !Grounded())
                {
                    Hit(Down_Attack_Transform, Down_Attack_Area, ref Player_State.Recoiling_Y, Recoil_Y_Speed);
                    Slash_Effect_Angle(Slash_Effect_Object, -90, Down_Attack_Transform);
                }
            }
        }
    
        void Hit(Transform Attack_Transform, Vector2 Attack_Area, ref bool Recoil_Directory, float Recoil_Strength)
        {
            Collider2D[] Objects_To_Hit = Physics2D.OverlapBoxAll(Attack_Transform.position, Attack_Area, 0, Attackable_Layer);
            List<Enemy> Hit_Enemies = new List<Enemy>();
    
            if(Objects_To_Hit.Length > 0)
            {
                Recoil_Directory = true;
            }
            for(int i = 0; i < Objects_To_Hit.Length; i++)
            {
                Enemy e = Objects_To_Hit[i].GetComponent<Enemy>();
                if(e && !Hit_Enemies.Contains(e))
                {
                    e.Enemy_Hit(Player_Damage, (transform.position - Objects_To_Hit[i].transform.position).normalized, Recoil_Strength);
                    Hit_Enemies.Add(e);
                }
            }
        }
    
        void Slash_Effect_Angle(GameObject Slash_Effect, int Effect_Angle, Transform Attack_Transform)
        {
            Slash_Effect = Instantiate(Slash_Effect, Attack_Transform);
            Slash_Effect.transform.eulerAngles = new Vector3(0, 0, Effect_Angle);
            Slash_Effect.transform.localScale = new Vector2(transform.localScale.x, transform.localScale.y);
        }
    
        void Recoil()
        {
            if(Player_State.Recoiling_X)
            {
                if(Player_State.Looking_Right)
                {
                    Rigidbody.velocity = new Vector2(-Recoil_X_Speed, 0);
                }
                else if(!Player_State.Looking_Right)
                    Rigidbody.velocity = new Vector2(Recoil_X_Speed, 0);
            }
            if(Player_State.Recoiling_Y)
            {
                if(Mouse_Position.y < Down_Attack_Parameter)
                {
                    Rigidbody.gravityScale = 0;
                    Rigidbody.velocity = new Vector2(Rigidbody.velocity.x, Recoil_Y_Speed);
                }
                else if(Mouse_Position.y > Up_Attack_Parameter)
                {
                    Rigidbody.gravityScale = 0;
                    Rigidbody.velocity = new Vector2(Rigidbody.velocity.x, -Recoil_Y_Speed);
                }
                Air_Jump_Counter = 0;
            }
            else
            {
                Rigidbody.gravityScale = Gravity;
            }
    
            if(Player_State.Recoiling_X && Steps_X_Recoiled < Recoil_X_Steps)
            {
                Steps_X_Recoiled++;
            }
            else
            {
                Stop_Recoil_X();
            }
            if(Player_State.Recoiling_Y && Steps_Y_Recoiled < Recoil_Y_Steps)
            {
                Steps_Y_Recoiled++;
            }
            else
            {
                Stop_Recoil_Y();
            }
    
            if(Grounded())
            {
                Stop_Recoil_Y();
            }
        }
    
        void Stop_Recoil_X()
        {
            Steps_X_Recoiled = 0;
            Player_State.Recoiling_X = false;
        }
    
        void Stop_Recoil_Y()
        {
            Steps_Y_Recoiled = 0;
            Player_State.Recoiling_Y = false;
        }
    
        public void Take_Damage(float Damage_Taken)
        {
            Player_Health_Int -= Mathf.RoundToInt(Damage_Taken);
            StartCoroutine(Stop_Taking_Damage());
        }
    
        public IEnumerator Stop_Taking_Damage()
        {
            Player_State.Invincible = true;
            GameObject Blood_Spurt_Particles = Instantiate(Blood_Spurt, transform.position, Quaternion.identity);
            Destroy(Blood_Spurt_Particles, 1.5f);
            Animator.SetTrigger("Take Damage");
            yield return new WaitForSeconds(1f);
            Player_State.Invincible = false;
        }
    
        void Flash_While_Invincible()
        {
            Sprite_Renderer.material.color = Player_State.Invincible ? Color.Lerp(Color.white, Color.black, Mathf.PingPong(Time.time * Hit_Flash_Speed, 1.0f)) : Color.white;
        }
    
        void Restore_Time_Scale()
        {
            if(Restore_Time)
            {
                if (Time.timeScale < 1)
                {
                    Time.timeScale += Time.deltaTime * Restore_Time_Speed;
                }
                else
                {
                    Time.timeScale = 1;
                    Restore_Time = false;
                }
            }
        }
    
        public void Hit_Stop_Time(float New_Time_Scale, int Restore_Speed, float Delay)
        {
            Restore_Time_Speed = Restore_Speed;
            Time.timeScale = New_Time_Scale;
            if (Delay > 0)
            {
                StopCoroutine(Start_Time_Again(Delay));
                StartCoroutine(Start_Time_Again(Delay));
            }
        }
    
        IEnumerator Start_Time_Again(float Delay)
        {
            Restore_Time = true;
            yield return new WaitForSeconds(Delay);
        }
    
        public int Player_Health_Int
        {
            get { return Player_Health; }
            set
            {
                if (Player_Health != value)
                {
                    Player_Health = Mathf.Clamp(value, 0, Player_Max_Health);
    
                    if(On_Health_Changed_Callback != null)
                    {
                        On_Health_Changed_Callback.Invoke();
                    }
                }
            }
        }
    
        public bool Grounded()
        {
            if (Physics2D.Raycast(GroundCheckPoint.position, Vector2.down, GroundCheckY, WhatIsGround)
            || Physics2D.Raycast(GroundCheckPoint.position + new Vector3(GroundCheckX, 0, 0), Vector2.down, GroundCheckY, WhatIsGround)
            || Physics2D.Raycast(GroundCheckPoint.position + new Vector3(-GroundCheckX, 0, 0), Vector2.down, GroundCheckY, WhatIsGround))
            {
                return true;
            }
            else
            {
                return false;
            }
        }
    
        void Jump()
        {
            if(Input.GetButtonUp("Jump") && Rigidbody.velocity.y > 0)
            {
                Rigidbody.velocity = new Vector2(Rigidbody.velocity.x, 0);
                Player_State.Jumping = false;
            }
            if(!Player_State.Jumping)
            {
                if(Jump_Buffer_Counter > 0 && Coyote_Time_Counter > 0)
                {
                    Rigidbody.velocity = new Vector3(Rigidbody.velocity.x, JumpForce);
                    Player_State.Jumping = true;
                }
                else if(!Grounded() && Air_Jump_Counter < Max_Air_Jumps && Input.GetButtonDown("Jump"))
                {
                    Player_State.Jumping = true;
                    Air_Jump_Counter ++;
                    Rigidbody.velocity = new Vector3(Rigidbody.velocity.x, JumpForce);
                }
            }
            Animator.SetBool("Jumping", Rigidbody.velocity.y > 0 && !Grounded());
            Animator.SetBool("Falling", Rigidbody.velocity.y < 0 && !Grounded());
    
        }
    
        void UpdateJumpVariables()
        {
            if(Grounded())
            {
                Player_State.Jumping = false;
                Coyote_Time_Counter = Coyote_Time;
                Air_Jump_Counter = 0;
            }
            else
            {
                Coyote_Time_Counter -= Time.deltaTime;
            }
    
            if(Input.GetButtonDown("Jump"))
            {
                Jump_Buffer_Counter = Jump_Buffer_Frames;
            }
            else
            {
                Jump_Buffer_Counter -= Time.deltaTime * 10;
            }
        }
    }```
    
    and here's the video.
    https://imgur.com/a/c3jaDSj<ol></li>
    #18996
    Ser Apustaja
    Level 6
    AI Assistant
    (BETA)
    Helpful?
    Up
    0
    ::

    It looks like you’ve provided a comprehensive player controller script for a Unity game, along with a link to a video. However, it seems you’re looking for some assistance or feedback on this script.

    Here are a few points that might help you improve or debug your script:

    1. Syntax Issues:

      • In your script, you’re using HTML entities like <, >, and & instead of the actual symbols `, and&`. Make sure to replace these with the correct symbols to avoid syntax errors.
    2. Jump Logic:

      • Ensure that your jump logic is working correctly, especially the coyote time and jump buffering. You might want to add debug logs to see if the conditions for jumping are being met.
    3. Dash Functionality:

      • Make sure that the dash feature is working as intended. You could test the dash in various scenarios to ensure it doesn’t interfere with other actions like jumping or attacking.
    4. Animation Triggers:

      • Check if the animation triggers (Dashing, Attacking, etc.) are set properly in your Animator. Sometimes issues arise from not having the correct transitions in the Animator setup.
    5. Health System:

      • In your Take_Damage method, ensure that you are limiting the health correctly and that the invincibility works as expected after taking damage.
    6. Input Handling:

      • Consider using Unity’s Input System for better input handling, especially if you plan to expand the controls later.
    7. Debugging:

      • Use Debug.Log statements in your code to see if certain blocks of code are being executed as expected. This can help identify any logical errors.
    8. Ground Check:

      • Ensure your ground check is robust. You may want to visualize the ground check rays in the scene view to confirm they are hitting the ground as expected.

    If you have a specific issue or error message you’re encountering, please share that, and I can help troubleshoot further!

    #18997
    Terence
    Level 31
    Keymaster
    Helpful?
    Up
    0
    ::

    @Alp is there any issue with the dash script?

    #18998
    Ser Apustaja
    Level 6
    AI Assistant
    (BETA)
    Helpful?
    Up
    0
    ::

    To help you determine if there are any issues with your dash script in the Player_Controller, let’s take a closer look at the key parts of the dash functionality:

    1. Dash Trigger: Ensure that the dash is only triggered when the player is able to dash. The check you have in Start_Dash() looks correct:

      if(Input.GetButtonDown("Dash") && Can_Dash && !Dashed)
    2. Coroutine for Dashing: In the Dash() coroutine, you’re setting Player_State.Dashing to true and modifying the velocity:

      Player_State.Dashing = true;
      Rigidbody.gravityScale = 0;
      Rigidbody.velocity = new Vector2(-transform.localScale.x * Dash_Speed, 0);

      This part should work correctly as long as Dash_Speed and transform.localScale are set up properly.

    3. Resetting States: After the dash, you reset the gravity and the dashing state. Make sure that this logic is working properly:

      yield return new WaitForSeconds(Dash_Time);
      Rigidbody.gravityScale = Gravity;
      Player_State.Dashing = false;
    4. Dash Cooldown: After the dash, you have a cooldown period:

      yield return new WaitForSeconds(0.2f);
      yield return new WaitForSeconds(Dash_Cooldown);
      Can_Dash = true;

      Ensure that the cooldown is actually giving you the expected behavior. You might want to log when the dash can be initiated again.

    5. Ground Check: You have a check for whether the player is on the ground after the dash, which resets the Dashed state:

      if(Grounded())
      {
          Dashed = false;
      }

      This is good, but make sure that the Grounded() function is accurately detecting whether the player is on the ground.

    Debugging Steps

    • Add Debug Logs: Add Debug.Log statements in your Start_Dash() and Dash() methods to track when the dash starts and ends. For example:

      Debug.Log("Dash started");
      Debug.Log("Dash ended");
    • Test Input: Check if the input for the dash is being registered properly. You can test this by logging the input state.

    • Visualize Dash: Ensure that the player visibly dashes by checking if the velocity changes as expected during the dash.

    If you encounter any specific issues or unexpected behaviors, please provide details on that, and I can help troubleshoot further!

    #18999
    Terence
    Level 31
    Keymaster
    Helpful?
    Up
    0
    ::

    @nirvikrajbhandari let me know if this helps. If not, zip up your project and share a link to your project on Google Drive.

    You can follow this guide if you’re not sure how to do that: https://blog.terresquall.com/2023/07/how-to-package-and-transfer-your-unity-project-from-one-device-to-another/#zipping-project-files

    #19000
    Nirvik Rajbhandari
    Level 4
    Participant
    Helpful?
    Up
    0
    ::

    Ok, Here’s the Player Controller Script using System.Collections; using System.Collections.Generic; using UnityEngine;

    public class Player_Controller : MonoBehaviour
    {
        [Header("Horizontal Movement Settings")]   
        [SerializeField] private float WalkSpeed = 1;
        [Space(5)]
    
        [Header("Vertical Movement Settings")]
        [SerializeField] private float JumpForce = 45;
        private float Jump_Buffer_Counter;
        [SerializeField] private float Jump_Buffer_Frames = 0;
        private float Coyote_Time_Counter;
        [SerializeField] private float Coyote_Time;
        private int Air_Jump_Counter = 0;
        [SerializeField] private int Max_Air_Jumps;
        [Space(5)]
    
        [Header("Ground Check Settings")]
        [SerializeField] Transform GroundCheckPoint;
        [SerializeField] private float GroundCheckY = 0.2f;
        [SerializeField] private float GroundCheckX = 0.5f;
        [SerializeField] private LayerMask WhatIsGround;
        [Space(5)]
    
        [Header("Dash Settings")]
        [SerializeField] private float Dash_Speed;
        [SerializeField] private float Dash_Time;
        [SerializeField] private float Dash_Cooldown;
        private bool Can_Dash = true;
        private bool Dashed;
        [SerializeField] GameObject Dash_Effect;
        [Space(5)]
    
        [Header("Attack Settings")]
        [SerializeField] float Time_Between_Attack;
        float Attack_Time_Counter;
        [SerializeField] private Transform Side_Attack_Transform;
        [SerializeField] private Transform Up_Attack_Transform;
        [SerializeField] private Transform Down_Attack_Transform;
        [SerializeField] private Vector2 Side_Attack_Area;
        [SerializeField] private Vector2 Up_Attack_Area;
        [SerializeField] private Vector2 Down_Attack_Area;
        [SerializeField] private float Up_Attack_Parameter;
        [SerializeField] private float Down_Attack_Parameter;
        [SerializeField] private LayerMask Attackable_Layer;
        [SerializeField] private float Player_Damage;
        [SerializeField] GameObject Slash_Effect_Object;
        bool Attack_Bool = false;
        bool Restore_Time;
        float Restore_Time_Speed;
        [Space(5)]
    
        [Header("Recoil Settings")]
        [SerializeField] int Recoil_X_Steps = 5;
        [SerializeField] int Recoil_Y_Steps = 5;
        [SerializeField] int Recoil_X_Speed = 100;
        [SerializeField] int Recoil_Y_Speed = 100;
        int Steps_X_Recoiled;
        int Steps_Y_Recoiled;
    
        [Header("Health Settings")]
        public int Player_Health;
        public int Player_Max_Health;
        [SerializeField] GameObject Blood_Spurt;
        [SerializeField] float Hit_Flash_Speed;
        public delegate void On_Health_Changed_Delegate();
        [HideInInspector] public On_Health_Changed_Delegate On_Health_Changed_Callback;
        [Space(5)]
    
        private Vector3 Mouse_Position;
        [HideInInspector] public Player_State_List Player_State;
        private Rigidbody2D Rigidbody;
        private SpriteRenderer Sprite_Renderer;
        private float Gravity;
        private float XAxis;
        Animator Animator;
    
        public static Player_Controller Instance;
    
        private void Awake()
        {
            if (Instance != null && Instance != this)
            {
                Destroy(gameObject);
            }
            else
            {
                Instance = this;
            }
            Player_Health = Player_Max_Health; 
        }
    
        // Start is called before the first frame update
        void Start()
        {
            Player_State = GetComponent<Player_State_List>();
            Rigidbody = GetComponent<Rigidbody2D>();
            Sprite_Renderer = GetComponent<SpriteRenderer>();
            Animator = GetComponent<Animator>();
            Gravity = Rigidbody.gravityScale;
        }
    
        private void OnDrawGizmos()
        {
            Gizmos.color = Color.red;
            Gizmos.DrawWireCube(Side_Attack_Transform.position, Side_Attack_Area);
            Gizmos.DrawWireCube(Up_Attack_Transform.position, Up_Attack_Area);
            Gizmos.DrawWireCube(Down_Attack_Transform.position, Down_Attack_Area);
        }
    
        // Update is called once per frame
        void Update()
        {
            GetInputs();
            UpdateJumpVariables();
            if (Player_State.Dashing)
            {
                return;
            }
            Flip();
            Move();
            Jump();
            Attack();
            Start_Dash();
            Restore_Time_Scale();
            Flash_While_Invincible();
        }
    
        private void FixedUpdate()
        {
            if (Player_State.Dashing)
            {
                return;
            }
            Recoil();
        }
    
        void GetInputs()
        {
            XAxis = Input.GetAxisRaw("Horizontal");
            Attack_Bool = Input.GetButtonDown("Attack");
            Mouse_Position = Camera.main.ScreenToWorldPoint(Input.mousePosition);
            Mouse_Position.x -= gameObject.transform.position.x;
            Mouse_Position.y -= gameObject.transform.position.y;
            Mouse_Position.z = 0f;
        }
    
        void Flip()
        {
            if(XAxis < 0)
            {
                transform.localScale = new Vector2(Mathf.Abs(transform.localScale.x), transform.localScale.y);
                Player_State.Looking_Right = false;
            }
            else if(XAxis > 0)
            {
                transform.localScale = new Vector2(-Mathf.Abs(transform.localScale.x), transform.localScale.y);
                Player_State.Looking_Right = true;
            }
        }
    
        private void Move()
        {
            Rigidbody.velocity = new Vector2(WalkSpeed * XAxis, Rigidbody.velocity.y);
            Animator.SetBool("Walking", Rigidbody.velocity.x != 0 && Grounded());
        }
    
        void Start_Dash()
        {
            if(Input.GetButtonDown("Dash") && Can_Dash && !Dashed)
            {
                StartCoroutine(Dash());
                Dashed = true;
            }
            if(Grounded())
            {
                Dashed = false;
            }
        }
    
        IEnumerator Dash()
        {
            Player_State.Dashing = true;
            Player_State.Invincible = true;
            Can_Dash = false;
            Animator.SetTrigger("Dashing");
            Rigidbody.gravityScale = 0;
            Rigidbody.velocity = new Vector2(-transform.localScale.x * Dash_Speed, 0);
            if(Grounded())
            {
                Instantiate(Dash_Effect, transform);
            }
            yield return new WaitForSeconds(Dash_Time);
            Rigidbody.gravityScale = Gravity;
            Player_State.Dashing = false;
            Player_State.Invincible = false;
            yield return new WaitForSeconds(0.2f);
            yield return new WaitForSeconds(Dash_Cooldown);
            Can_Dash = true;
        }
    
        void Attack()
        {
            Attack_Time_Counter += Time.deltaTime;
            if(Attack_Bool && Attack_Time_Counter >= Time_Between_Attack)
            {
                Attack_Time_Counter = 0;
                Animator.SetTrigger("Attacking");
                if(Mouse_Position.y < Up_Attack_Parameter && Mouse_Position.y > Down_Attack_Parameter || Mouse_Position.y <= Down_Attack_Parameter && Grounded())
                {
                    Hit(Side_Attack_Transform, Side_Attack_Area, ref Player_State.Recoiling_X, Recoil_X_Speed);
                    Slash_Effect_Angle(Slash_Effect_Object, 0, Side_Attack_Transform);
                }
    
                else if(Mouse_Position.y > Up_Attack_Parameter)
                {
                    Hit(Up_Attack_Transform, Up_Attack_Area, ref Player_State.Recoiling_Y, Recoil_Y_Speed);
                    Slash_Effect_Angle(Slash_Effect_Object, 80, Up_Attack_Transform);
                }
                else if(Mouse_Position.y < Down_Attack_Parameter && !Grounded())
                {
                    Hit(Down_Attack_Transform, Down_Attack_Area, ref Player_State.Recoiling_Y, Recoil_Y_Speed);
                    Slash_Effect_Angle(Slash_Effect_Object, -90, Down_Attack_Transform);
                }
            }
        }
    
        void Hit(Transform Attack_Transform, Vector2 Attack_Area, ref bool Recoil_Directory, float Recoil_Strength)
        {
            Collider2D[] Objects_To_Hit = Physics2D.OverlapBoxAll(Attack_Transform.position, Attack_Area, 0, Attackable_Layer);
            List<Enemy> Hit_Enemies = new List<Enemy>();
    
            if(Objects_To_Hit.Length > 0)
            {
                Recoil_Directory = true;
            }
            for(int i = 0; i < Objects_To_Hit.Length; i++)
            {
                Enemy e = Objects_To_Hit[i].GetComponent<Enemy>();
                if(e && !Hit_Enemies.Contains(e))
                {
                    e.Enemy_Hit(Player_Damage, (transform.position - Objects_To_Hit[i].transform.position).normalized, Recoil_Strength);
                    Hit_Enemies.Add(e);
                }
            }
        }
    
        void Slash_Effect_Angle(GameObject Slash_Effect, int Effect_Angle, Transform Attack_Transform)
        {
            Slash_Effect = Instantiate(Slash_Effect, Attack_Transform);
            Slash_Effect.transform.eulerAngles = new Vector3(0, 0, Effect_Angle);
            Slash_Effect.transform.localScale = new Vector2(transform.localScale.x, transform.localScale.y);
        }
    
        void Recoil()
        {
            if(Player_State.Recoiling_X)
            {
                if(Player_State.Looking_Right)
                {
                    Rigidbody.velocity = new Vector2(-Recoil_X_Speed, 0);
                }
                else if(!Player_State.Looking_Right)
                    Rigidbody.velocity = new Vector2(Recoil_X_Speed, 0);
            }
            if(Player_State.Recoiling_Y)
            {
                if(Mouse_Position.y < Down_Attack_Parameter)
                {
                    Rigidbody.gravityScale = 0;
                    Rigidbody.velocity = new Vector2(Rigidbody.velocity.x, Recoil_Y_Speed);
                }
                else if(Mouse_Position.y > Up_Attack_Parameter)
                {
                    Rigidbody.gravityScale = 0;
                    Rigidbody.velocity = new Vector2(Rigidbody.velocity.x, -Recoil_Y_Speed);
                }
                Air_Jump_Counter = 0;
            }
            else
            {
                Rigidbody.gravityScale = Gravity;
            }
    
            if(Player_State.Recoiling_X && Steps_X_Recoiled < Recoil_X_Steps)
            {
                Steps_X_Recoiled++;
            }
            else
            {
                Stop_Recoil_X();
            }
            if(Player_State.Recoiling_Y && Steps_Y_Recoiled < Recoil_Y_Steps)
            {
                Steps_Y_Recoiled++;
            }
            else
            {
                Stop_Recoil_Y();
            }
    
            if(Grounded())
            {
                Stop_Recoil_Y();
            }
        }
    
        void Stop_Recoil_X()
        {
            Steps_X_Recoiled = 0;
            Player_State.Recoiling_X = false;
        }
    
        void Stop_Recoil_Y()
        {
            Steps_Y_Recoiled = 0;
            Player_State.Recoiling_Y = false;
        }
    
        public void Take_Damage(float Damage_Taken)
        {
            Player_Health_Int -= Mathf.RoundToInt(Damage_Taken);
            StartCoroutine(Stop_Taking_Damage());
        }
    
        public IEnumerator Stop_Taking_Damage()
        {
            Player_State.Invincible = true;
            GameObject Blood_Spurt_Particles = Instantiate(Blood_Spurt, transform.position, Quaternion.identity);
            Destroy(Blood_Spurt_Particles, 1.5f);
            Animator.SetTrigger("Take Damage");
            yield return new WaitForSeconds(1f);
            Player_State.Invincible = false;
        }
    
        void Flash_While_Invincible()
        {
            Sprite_Renderer.material.color = Player_State.Invincible ? Color.Lerp(Color.white, Color.black, Mathf.PingPong(Time.time * Hit_Flash_Speed, 1.0f)) : Color.white;
        }
    
        void Restore_Time_Scale()
        {
            if(Restore_Time)
            {
                if (Time.timeScale < 1)
                {
                    Time.timeScale += Time.deltaTime * Restore_Time_Speed;
                }
                else
                {
                    Time.timeScale = 1;
                    Restore_Time = false;
                }
            }
        }
    
        public void Hit_Stop_Time(float New_Time_Scale, int Restore_Speed, float Delay)
        {
            Restore_Time_Speed = Restore_Speed;
            Time.timeScale = New_Time_Scale;
            if (Delay > 0)
            {
                StopCoroutine(Start_Time_Again(Delay));
                StartCoroutine(Start_Time_Again(Delay));
            }
        }
    
        IEnumerator Start_Time_Again(float Delay)
        {
            Restore_Time = true;
            yield return new WaitForSeconds(Delay);
        }
    
        public int Player_Health_Int
        {
            get { return Player_Health; }
            set
            {
                if (Player_Health != value)
                {
                    Player_Health = Mathf.Clamp(value, 0, Player_Max_Health);
    
                    if(On_Health_Changed_Callback != null)
                    {
                        On_Health_Changed_Callback.Invoke();
                    }
                }
            }
        }
    
        public bool Grounded()
        {
            if (Physics2D.Raycast(GroundCheckPoint.position, Vector2.down, GroundCheckY, WhatIsGround)
            || Physics2D.Raycast(GroundCheckPoint.position + new Vector3(GroundCheckX, 0, 0), Vector2.down, GroundCheckY, WhatIsGround)
            || Physics2D.Raycast(GroundCheckPoint.position + new Vector3(-GroundCheckX, 0, 0), Vector2.down, GroundCheckY, WhatIsGround))
            {
                return true;
            }
            else
            {
                return false;
            }
        }
    
        void Jump()
        {
            if(Input.GetButtonUp("Jump") && Rigidbody.velocity.y > 0)
            {
                Rigidbody.velocity = new Vector2(Rigidbody.velocity.x, 0);
                Player_State.Jumping = false;
            }
            if(!Player_State.Jumping)
            {
                if(Jump_Buffer_Counter > 0 && Coyote_Time_Counter > 0)
                {
                    Rigidbody.velocity = new Vector3(Rigidbody.velocity.x, JumpForce);
                    Player_State.Jumping = true;
                }
                else if(!Grounded() && Air_Jump_Counter < Max_Air_Jumps && Input.GetButtonDown("Jump"))
                {
                    Player_State.Jumping = true;
                    Air_Jump_Counter ++;
                    Rigidbody.velocity = new Vector3(Rigidbody.velocity.x, JumpForce);
                }
            }
            Animator.SetBool("Jumping", Rigidbody.velocity.y > 0 && !Grounded());
            Animator.SetBool("Falling", Rigidbody.velocity.y < 0 && !Grounded());
    
        }
    
        void UpdateJumpVariables()
        {
            if(Grounded())
            {
                Player_State.Jumping = false;
                Coyote_Time_Counter = Coyote_Time;
                Air_Jump_Counter = 0;
            }
            else
            {
                Coyote_Time_Counter -= Time.deltaTime;
            }
    
            if(Input.GetButtonDown("Jump"))
            {
                Jump_Buffer_Counter = Jump_Buffer_Frames;
            }
            else
            {
                Jump_Buffer_Counter -= Time.deltaTime * 10;
            }
        }
    }

    and here’s the video. https://imgur.com/a/c3jaDSj

      #19001
      Ser Apustaja
      Level 6
      AI Assistant
      (BETA)
      Helpful?
      Up
      0
      ::

      It seems you’ve shared the player controller script again, and there are still HTML entities present instead of correct symbols, which could lead to syntax errors in Unity. Let’s address that first and then discuss the script further.

      Fixing HTML Entities

      Please replace all occurrences of HTML entities with their corresponding characters in your script. Here are the replacements you need to make:

      • < ➔ “
      • &&

      After making these replacements, your script should compile correctly.

      Review of the Player Controller Script

      Your script looks quite comprehensive, handling various aspects of player movement, jumping, dashing, attacking, and health management. Here are some suggestions and checks you can perform:

      1. Dash Functionality:

        • Ensure the Dash() coroutine is performing as expected. Print debug logs in the dash coroutine to confirm it gets triggered and completes successfully.
      2. Ground Check:

        • The Grounded() method should effectively detect if the player is on the ground. You can visualize the raycasts in the Unity editor using OnDrawGizmos() to ensure they are positioned correctly and detecting the ground.
      3. Jump Logic:

        • Review the jump conditions to ensure they work as intended, particularly the coyote time and jump buffer. Debug logs can help verify if the player can jump when expected.
      4. Animation States:

        • Ensure that the animations are set up correctly in the Animator. Check transitions between states (e.g., from idle to walking to jumping) to ensure they reflect the player’s current state accurately.
      5. Health Management:

        • In your Take_Damage method, ensure that the health system properly triggers events when health changes. You can use Debug.Log to track health values during gameplay.
      6. Testing:

        • Playtest various scenarios to ensure movement, jumping, dashing, and attacking all work smoothly together. Look for any bugs or unexpected behaviors, especially when transitioning between states.
      7. Code Optimization:

        • Consider breaking down larger methods into smaller ones for clarity, especially if you plan to expand the functionality. This can make it easier to manage and debug.

      If you have a specific issue you’re facing or a particular aspect of the script you’d like feedback on, please let me know!

      #19003
      Nirvik Rajbhandari
      Level 4
      Participant
      Helpful?
      Up
      0
      ::

      I tried the solutions given to me, but it didn’t work which leads me to believe the main problem has something to do with the enemies collision or the invincibility. For some reason whenever I uploaded the ZIP folder to google drive it deleted the packages and project settings folders. IF those are not necessary, here’s the link to the folder. https://drive.google.com/file/d/1h9_iWmOzcW02T8LnPJsOZbwFePjRP8Px/view?usp=sharing

      #19004
      Ser Apustaja
      Level 6
      AI Assistant
      (BETA)
      Helpful?
      Up
      0
      ::

      It seems you’ve narrowed down the problem to either enemy collision or the invincibility logic, and you’re having issues with uploading your project files correctly. Here are some steps to help you troubleshoot the collision and invincibility issues in your game:

      1. Check Enemy Collision Logic

      • Collider Setup: Ensure that your enemies have the correct colliders set up. They should have a Collider2D component (like BoxCollider2D or CircleCollider2D) that matches their sprites.
      • Layer Settings: Ensure that the enemy objects are on the correct layer that your player can interact with. Check the Layer Collision Matrix in the Physics2D settings to ensure that collisions between the player and enemies are enabled.
      • Collision Detection: Make sure that you are using the appropriate collision detection method in your scripts. If you are using OnTriggerEnter2D, ensure that the colliders are set as triggers.

      2. Debugging Invincibility

      • Log Invincibility State: Place Debug.Log statements in your Take_Damage and Stop_Taking_Damage methods to confirm when invincibility is activated and deactivated.
      • Check Timing: Ensure that the timing for invincibility is correct. You might want to test with a longer invincibility duration to see if the problem persists.
      • Visual Indication: Consider adding a visual feedback (like a shader effect or flashing) when the player is invincible to confirm that the logic is being executed.

      3. Testing with Simplified Conditions

      • Remove Complex Logic: Temporarily disable complex logic (like dashing or jumping) to isolate the problem. See if the basic collision detection works when the player is just moving.
      • Use Debug Colliders: Create a simple debug object with a collider to test collision interactions without the complexity of the player character.

      4. Project Settings and Packages

      • Missing Packages: If uploading your project is deleting necessary folders, ensure that you are compressing the project correctly. Only the Assets and ProjectSettings folders are typically needed to share a project.
      • Reimport Packages: If you find that packages are missing, you may need to reimport them. Open the Package Manager in Unity and confirm that all necessary packages are installed.

      5. Reproduce the Issue

      • Testing in a New Scene: Create a new scene where you can test your player against a single enemy to see if the collision and invincibility issues persist. This can help eliminate scene-specific problems.

      Conclusion

      If none of these steps resolve your issue, please provide more specifics about the behavior you’re experiencing (e.g., player not taking damage when it should, invincibility not working at all, etc.). If you have specific error messages or behaviors in the Unity console, those would also be helpful for diagnosing the problem.

      Let me know how it goes!

      #19008
      Nirvik Rajbhandari
      Level 4
      Participant
      Helpful?
      Up
      0
      ::

      I tried the Debug.Log(Player_Controller.Instance.Player_State.Invincible); and whenever the bug happens it is saying that invincible is false. Also sorry for not being the most help right now.

      #19009
      Ser Apustaja
      Level 6
      AI Assistant
      (BETA)
      Helpful?
      Up
      0
      ::

      It seems that the Invincible state is not being set to true when it should be, which is likely causing your player to take damage even when they are supposed to be invincible. Here are some steps to help you troubleshoot this issue:

      1. Check Invincibility Logic:

        • Review the logic in your Take_Damage and Stop_Taking_Damage methods. Ensure that Player_State.Invincible is set to true at the start of the damage-taking process and that it is being reset correctly after the invincibility duration.
      2. Timing of Invincibility:

        • Make sure that the duration for which the player is invincible is long enough. If the invincibility is set to false too quickly, it might not be effective.
      3. Collision Detection:

        • Confirm that the collision detection is not happening during the invincibility period. You might want to add additional checks in your enemy scripts to prevent them from damaging the player if Player_State.Invincible is true.
      4. Debugging:

        • Add more Debug.Log statements in the Take_Damage method to track when damage is being taken and when invincibility is activated and deactivated. This can help you pinpoint where the logic might be failing.
      5. Check for Multiple Damage Sources:

        • Ensure that there are no other scripts or components that might be affecting the Invincible state or causing damage to the player unexpectedly.
      6. Test in Isolation:

        • Create a simple test scene where you can isolate the player and an enemy to see if the invincibility logic works as expected without other game mechanics interfering.

      Clarifying Question

      Can you provide more details about how the Take_Damage method is being called and what conditions lead to the player taking damage? This information could help identify where the issue lies.

      #19010
      Nirvik Rajbhandari
      Level 4
      Participant
      Helpful?
      Up
      0
      ::

      Also, is there a way to sort of turn off collision with the enemy so that you can be inside it I guess?

      #19011
      Ser Apustaja
      Level 6
      AI Assistant
      (BETA)
      Helpful?
      Up
      0
      ::

      Yes, you can achieve a “no collision” effect with enemies in Unity by using layers and the Layer Collision Matrix. Here’s how you can do it:

      1. Create a New Layer:

        • Go to the top right of the Unity editor and click on the “Layer” dropdown. Create a new layer, for example, “IgnoreEnemies”.
      2. Assign the Layer to Enemies:

        • Select your enemy GameObjects and assign them to the “IgnoreEnemies” layer.
      3. Modify the Layer Collision Matrix:

        • Go to Edit > Project Settings > Physics2D.
        • In the Layer Collision Matrix, uncheck the box where the “IgnoreEnemies” layer intersects with the layer your player is on (usually “Default”). This will prevent collisions between the player and enemies.
      4. Handling Interactions:

        • If you still want the player to interact with the enemy (like taking damage or triggering events), you can implement custom logic in your scripts to handle those interactions separately, such as using triggers instead of colliders.

      This setup allows the player to move through the enemy objects without any physical collision while still being able to interact with them through scripts.

      Clarifying Question

      Are you looking to completely ignore collisions with enemies, or do you want to maintain some interactions (like taking damage) while passing through them?

      #19013
      giselle
      Level 2
      Moderator
      Helpful?
      Up
      0
      ::

      @nirvikrajbhandari hello! here are some steps to fix the issue of the glitching that happens whenever you dash into an enemy:

      1. Changing Collision Detection for Player

      Under Rigidbody2D for player, change the Collision Detection from Discrete to Continuous.

      Discrete collision detection checks for collisions only at each physics update, which works fine for slow-moving objects but can miss fast-moving ones in this instance – Dashing, causing them to pass through other colliders. While Continuous collision detection checks along the object’s entire movement path between physics steps, ensuring that even high-speed objects, like a dashing player, detect and respond to collisions properly. This makes continuous mode more reliable for fast movement, preventing overlap or glitches.

      2. Changing 2D Collider of your player

      Would reccommend you to change the 2D collider of your player from Polygon Collider 2D to Box Collider 2D instead as well. As a 2D Polygon Collider has many vertices and complex shapes, which can make dashing, prone to glitches or getting stuck in other colliders.

      3. Using Rigidbody.MovePosition to follow player

      Change the Walker’s movement towards the player’s position using Rigidbody.MovePosition() instead of directly setting transform.position. While both approaches move the Walker horizontally toward the player, using transform.positionbypasses physics and can cause the Walker to phase through colliders or glitch. Rigidbody.MovePosition() respects physics and collisions, allowing the Walker to chase the player smoothly without causing overlap or other physics conflicts.

      You can make the following changes to the Walker script to solve this issue:

      protected override void Update()
      {
          base.Update();
      
      }
      
      private void FixedUpdate()
      {
          if (!Is_Recoiling)
          {
              Vector2 targetPosition = new Vector2(Player_Controller.Instance.transform.position.x, transform.position.y);
              Vector2 newPosition = Vector2.MoveTowards(Rigidbody.position, targetPosition, Speed * Time.fixedDeltaTime);
              Rigidbody.MovePosition(newPosition);
          }
      }

      4. Using Partial Time Stop Instead of Freezing Time

      The original Hit_Stop_Time(0, 5, 0.1f) sets Time.timeScale to 0, which completely freezes the game’s time. This may cause problems with coroutines, physics, and animations that rely on time progression. To avoid these issues, it’s better to use a small nonzero value, such as 0.2f. This slows down time instead of stopping it completely, preserving the hit effect while allowing coroutines and animations to continue functioning normally.

      You can make the following adjustments to your Enemy script to solve this issue:

      protected void OnTriggerStay2D(Collider2D Other)
      {
          if(Other.CompareTag("Player") & !Player_Controller.Instance.Player_State.Invincible)
          {
              Attack();
              Player_Controller.Instance.Hit_Stop_Time(0.2f, 5, 0.1f);
          }
      }
      #19014
      Nirvik Rajbhandari
      Level 4
      Participant
      Helpful?
      Up
      1
      ::

      Thank you so much for responding, but I think you misunderstood what I meant. Instead of having the player and enemy being able to collide, if its possible I sort of want it to be able to go through. I already tried the method with Physics2D and making the layers not interact but that also turns off damage.

        1 anonymous person
      has upvoted this post.
      #19015
      Ser Apustaja
      Level 6
      AI Assistant
      (BETA)
      Helpful?
      Up
      0
      ::

      To allow the player to pass through enemies while still being able to take damage from them, you can use triggers instead of colliders. Here’s how to set it up:

      1. Change Enemy Colliders to Triggers:

        • Select your enemy GameObjects and change their Collider2D components (like BoxCollider2D or CircleCollider2D) to be triggers. You can do this by checking the “Is Trigger” option in the Collider2D component.
      2. Modify the Enemy Script:

        • In your enemy script, implement the OnTriggerEnter2D method to handle damage when the player enters the trigger. For example:
          private void OnTriggerEnter2D(Collider2D other)
          {
            if (other.CompareTag("Player"))
            {
                // Call the method to deal damage to the player
                PlayerController.Instance.TakeDamage(damage);
            }
          }
      3. Ensure Player Collider is Not a Trigger:

        • Make sure that the player’s Collider2D is not set as a trigger, so it can still interact with the environment and other game mechanics.
      4. Testing:

        • Test the setup to ensure that the player can move through the enemies while still receiving damage when colliding with them.

      This way, the player will be able to pass through the enemies without any physical collision, while still being able to take damage when they enter the enemy’s trigger area.

      Clarifying Question

      Does this solution meet your needs, or do you have specific interactions in mind that you want to achieve while passing through the enemies?

      #19016
      Nirvik Rajbhandari
      Level 4
      Participant
      Helpful?
      Up
      1
      ::

      Also I forgot to state this but the advice you gave earlier fixed the original problem thank you!

        1 anonymous person
      has upvoted this post.
      #19017
      Ser Apustaja
      Level 6
      AI Assistant
      (BETA)
      Helpful?
      Up
      0
      ::

      I’m glad to hear that the previous advice helped resolve your original problem!

      To achieve the effect of allowing the player to pass through enemies while still being able to take damage from them, you can use a combination of triggers and collision detection. Here’s how you can set it up:

      1. Use Triggers:

        • Set the enemy colliders to be triggers instead of regular colliders. This allows the player to pass through the enemy without physical collision.
      2. Modify the Enemy Script:

        • In your enemy script, implement the OnTriggerEnter2D method to detect when the player enters the trigger area. This is where you can handle damage logic.
        private void OnTriggerEnter2D(Collider2D other)
        {
            if (other.CompareTag("Player"))
            {
                // Call the method to deal damage to the player
                other.GetComponent().Take_Damage(damageAmount);
            }
        }
      3. Ensure Player Collider is Not a Trigger:

        • Make sure the player’s collider is not set as a trigger so that it can still interact with other game mechanics (like ground checks).
      4. Layer Collision Matrix:

        • You can still use the Layer Collision Matrix to prevent other types of collisions if necessary, but for the player to take damage, the enemy’s collider must be a trigger.

      This way, the player can move through the enemies while still being able to take damage when colliding with them.

      Clarifying Question

      Does this approach align with what you were looking to achieve, or do you have specific interactions in mind that need to be addressed differently?

    Viewing 20 posts - 1 through 20 (of 24 total)
    • You must be logged in to reply to this topic.

    Go to Login Page →


    Advertisement below: