Unity 2D Enemy Behavior (Couple of Questions) - unity3d

I need help with making my Enemy stop moving once the Player is in range. When a Player enters the range of the Enemy, the Enemy will start moving towards the Player, and won't stop until the Player is pushed off the cliff. Also the Enemy doesn't seem to play the Attack animation once it reaches the Player. I will put my code down below:
#region Public Variables
public Transform rayCast;
public LayerMask raycastMask;
public float rayCastLength;
public float attackDistance; //Minimum distance for attack
public float moveSpeed = 4f;
public float timer; //Timer for cooldown between attacks
#endregion
#region Private Variables
private RaycastHit2D hit;
private GameObject target;
private Animator anim;
private float distance;//Store the distance between enemy and player
private bool attackMode;
private bool inRange; //Check if Player is in range
private bool cooling; //Check if Enemy is cooling after attack
private float intTimer;
#endregion
void Awake ()
{
intTimer = timer; //Store the initial value of timer
anim = GetComponent<Animator>();
}
void Update()
{
if (inRange)
{
hit = Physics2D.Raycast(rayCast.position, Vector2.left,rayCastLength, raycastMask);
RaycastDebugger();
}
//When Player is detected
if(hit.collider != null)
{
EnemyLogic();
}
else if(hit.collider == null)
{
inRange = false;
}
if (inRange == false)
{
anim.SetBool("canWalk", false);
StopAttack();
}
}
void OnTriggerEnter2D(Collider2D trig)
{
if(trig.gameObject.tag == "Player")
{
target = trig.gameObject;
inRange = true;
}
}
void EnemyLogic ()
{
distance = Vector2.Distance(transform.position, target.transform.position);
if(distance > attackDistance)
{
Move();
StopAttack();
}
else if(attackDistance >= distance && cooling == false)
{
Attack();
}
if(cooling)
{
Cooldown();
anim.SetBool("Attack", false);
}
}
void Move()
{
anim.SetBool("canWalk", true);
if(!anim.GetCurrentAnimatorStateInfo(0).IsName("Goblin Attack"))
{
Vector2 targetPosition = new Vector2(target.transform.position.x, transform.position.y);
transform.position = Vector2.MoveTowards(transform.position, targetPosition, moveSpeed * Time.deltaTime);
}
}
void Attack()
{
timer = intTimer; //Reset Timer when Player enter Attack Range
attackMode = true; //To check if Enemy can still attack or not
anim.SetBool("canWalk", false);
anim.SetBool("Attack", true);
}
void Cooldown ()
{
timer -= Time.deltaTime;
if(timer <= 0 && cooling && attackMode)
{
cooling = false;
timer = intTimer;
}
}
void StopAttack()
{
cooling = false;
attackMode = false;
anim.SetBool("Attack", false);
}
void RaycastDebugger()
{
if(distance > attackDistance)
{
Debug.DrawRay(rayCast.position, Vector2.left * rayCastLength, Color.red);
}
else if(attackDistance > distance)
{
Debug.DrawRay(rayCast.position, Vector2.left * rayCastLength, Color.green);
}
}
public void TriggerCooling ()
{
cooling = true;
}

it seems that you are not setting
private RaycastHit2D hit;
to null, yet you are checking if it is null,
else if(hit.collider == null)
which could explain why the enemy keeps chasing the player.
Also in the raycast there is no way to differentiate for the player.
//When Player is detected
if(hit.collider != null)
You are checking if player is detected, but hit could be anything other than the player.
hit = Physics2D.Raycast(rayCast.position, Vector2.left,rayCastLength, raycastMask);
So the ray could collide with the floor or something and not see the player, but in the enemies eyes they saw the player. Or maybe you are passing in a player layermask? In that case renaming the "raycastMask" to something like playerMask would make it more readable.

Related

Sticky Projectile and Explosive Projectile problem c#

I cannot figure out what the problem is, suppose to be, when both explodeOnTouch and isSticky is true, it will call the parameter below, first stop the projectile movement with isKinematic then call the coroutine, but what happens is that only isKinematic is being called. Also extra questions, why is that the first projectile I instantiate is very fast, faster than the normal force I applied, and also why is that after the projectile lifeSpan is done and the projectile gameObject is destroyed, the force is still being applied making the for example enemy gameObject flying.
[Header("==BULLET SETTINGS==")]
[SerializeField] bool useGravity;
[SerializeField] float lifeSpan = 3.2f;
[SerializeField] List<string> tagName;
[Header("==BOUNCING==")]
[Range(0, 1)]
[SerializeField] float bounciness;
[SerializeField] bool isBouncing;
[Header("==EXPLOSIVE==")]
[SerializeField] bool explodeOnTouch = true;
[SerializeField] bool isSticky = true;
[SerializeField] float explosionRange;
[SerializeField] float explosionForce;
[SerializeField] float triggerForce;
[Header("==FREEZE==")]
[SerializeField] bool isFrozen;
[Header("==REFERENCE==")]
[SerializeField] LayerMask layer;
[SerializeField] Rigidbody rb;
PhysicMaterial physics_mat;
private void Start()
{
Setup();
}
private void Update()
{
BulletLifeSpan();
}
private void OnCollisionEnter(Collision collision)
{
Rigidbody enemyRb = collision.gameObject.GetComponent<Rigidbody>();
var collided = collision.gameObject.CompareTag(tagName[0]) || collision.gameObject.CompareTag(tagName[1]);
var explode = collision.relativeVelocity.magnitude >= triggerForce && explodeOnTouch;
if (explode && !isSticky)
{
Explode();
}
if (collided && isSticky && !explodeOnTouch)
{
rb.isKinematic = true;
}
if (explode && isSticky)
{
rb.isKinematic = true;
StartCoroutine(Delay());
}
if (collided && isFrozen)
{
enemyRb.isKinematic = true;
}
else if(collided && !isBouncing && !isSticky && !explodeOnTouch)
{
Destroy(gameObject);
}
Debug.Log("Player collided with: " + collision.gameObject.name + " with powerup set to none");
//Destroy(gameObject);
}
void Explode()
{
var surroundingObjects = Physics.OverlapSphere(transform.position, explosionRange);
foreach (var obj in surroundingObjects)
{
var rb = obj.GetComponent<Rigidbody>();
if (rb == null) continue;
rb.AddExplosionForce(explosionForce, transform.position, explosionRange);
}
Destroy(gameObject);
}
IEnumerator Delay()
{
yield return new WaitForSeconds(lifeSpan);
Explode();
}
void BulletLifeSpan()
{
//Bullet lifespan
lifeSpan -= Time.deltaTime;
if (lifeSpan <= 0)
{
Destroy(gameObject);
}
}
void Setup()
{
//Create a new Physic material
physics_mat = new PhysicMaterial();
physics_mat.bounciness = bounciness;
physics_mat.frictionCombine = PhysicMaterialCombine.Minimum;
physics_mat.bounceCombine = PhysicMaterialCombine.Maximum;
//Assign material to collider
GetComponent<SphereCollider>().material = physics_mat;
//Set gravity
rb.useGravity = useGravity;
}
private void OnDrawGizmosSelected()
{
Gizmos.color = Color.red;
Gizmos.DrawWireSphere(transform.position, explosionRange);
}

Unity - How to jump using a NavMeshAgent and click to move logic

I am building a game where the player can be controlled using the mouse input, using a click to move logic via a navmesh agent.
In order to let the player jump, I started using a CharacterController as well which should help managing the player. My issue is that I can't figure out where to put the jump logic. All references I found are related using the character controller without the navmesh agent.
I can get rid of the CharacterController if needed, but the NavMeshAgent has to stay.
Here it is a working code which allows to walk. Can you please help me with the jumping logic?
private NavMeshAgent _agent;
private CharacterController _characterController;
private Vector3 _desVelocity;
private void Update()
{
if (Input.GetMouseButtonDown(0))
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray.origin, ray.direction, out RaycastHit hitInfo))
{
_agent.destination = hitInfo.point;
}
}
var currMovementDirection = _desVelocity.normalized * currentSpeed;
if (_agent.remainingDistance > _agent.stoppingDistance)
{
_desVelocity = _agent.desiredVelocity;
_characterController.Move(currMovementDirection * Time.deltaTime);
}
}
You can achieve this using a Rigidbody instead of a CharacterController. The trick is that you need to disable the NavMeshAgent in order to jump.
Optionally, you set the destination to where you are at the time of the jump, so that the agent doesn't continue the simulation while the jump is happening.
Using collision detection, you turn the NavMeshAgent back on again once you land.
public class PlayerMovement : MonoBehaviour
{
private Camera cam;
private NavMeshAgent agent;
private Rigidbody rigidbody;
public bool grounded = true;
void Start()
{
cam = Camera.main;
agent = GetComponent<NavMeshAgent>();
rigidbody = GetComponent<Rigidbody>();
}
void Update()
{
// clicking on the nav mesh, sets the destination of the agent and off he goes
if (Input.GetMouseButtonDown(0) && (!agent.isStopped))
{
Ray ray = cam.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out RaycastHit hit))
{
agent.SetDestination(hit.point);
}
}
// when you want to jump
if (Input.GetKeyDown(KeyCode.Space) && grounded)
{
grounded = false;
if (agent.enabled)
{
// set the agents target to where you are before the jump
// this stops her before she jumps. Alternatively, you could
// cache this value, and set it again once the jump is complete
// to continue the original move
agent.SetDestination(transform.position);
// disable the agent
agent.updatePosition = false;
agent.updateRotation = false;
agent.isStopped = true;
}
// make the jump
rigidbody.isKinematic = false;
rigidbody.useGravity = true;
rigidbody.AddRelativeForce(new Vector3(0, 5f, 0), ForceMode.Impulse);
}
}
/// <summary>
/// Check for collision back to the ground, and re-enable the NavMeshAgent
/// </summary>
private void OnCollisionEnter(Collision collision)
{
if (collision.collider != null && collision.collider.tag == "Ground")
{
if (!grounded)
{
if (agent.enabled)
{
agent.updatePosition = true;
agent.updateRotation = true;
agent.isStopped = false;
}
rigidbody.isKinematic = true;
rigidbody.useGravity = false;
grounded = true;
}
}
}
}
The jump logic should be inside the Update() method since we want the height to be calculated every frame.
void Update()
{
if (Input.GetMouseButtonDown(0))
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray.origin, ray.direction, out RaycastHit hitInfo))
{
_agent.destination = hitInfo.point;
}
}
var currMovementDirection = _desVelocity.normalized * currentSpeed;
groundedPlayer = _characterController.isGrounded;
if (groundedPlayer && currMovementDirection.y < 0)
{
currMovementDirection.y = 0f;
}
// Changes the height position of the player..
if (Input.GetButtonDown("Jump") && groundedPlayer)
{
currMovementDirection.y += Mathf.Sqrt(jumpHeight * -3.0f * gravityValue);
}
currMovementDirection.y += gravityValue * Time.deltaTime;
if (_agent.remainingDistance > _agent.stoppingDistance)
{
_desVelocity = _agent.desiredVelocity;
_characterController.Move(currMovementDirection * Time.deltaTime);
}
}
Please see the official docs here

Can't figure out how to stop zombie from dealing damage when no longer colliding with player

I am creating a top down zombie shooter and I have made the zombie do damage to the player when it is touching the player. However when the player backs away from the zombie after taking damage, the players health will continue to drop. Any ideas on how to fix this would be appreciated.
public float moveSpeed= 5f;
public Rigidbody2D rb;
public Camera cam;
public float playerHealth = 100;
public float enemyDamage = 25;
public GameObject gameOverScreen;
Vector2 movement;
Vector2 mousePos;
// Update is called once per frame
void Update()
{
movement.x = Input.GetAxisRaw("Horizontal");
movement.y = Input.GetAxisRaw("Vertical");
mousePos = cam.ScreenToWorldPoint(Input.mousePosition);
if(playerHealth == 0)
{
gameOverScreen.SetActive(true);
}
}
void FixedUpdate()
{
rb.MovePosition(rb.position + movement * moveSpeed * Time.fixedDeltaTime);
Vector2 lookDir = mousePos - rb.position;
float angle = Mathf.Atan2(lookDir.y, lookDir.x) * Mathf.Rad2Deg - 90f;
rb.rotation = angle;
}
void OnCollisionEnter2D(Collision2D collision)
{
if (collision.gameObject.CompareTag("Enemy"))
{
StartCoroutine(DamagePlayer());
}
else
{
StopCoroutine(DamagePlayer());
}
}
IEnumerator DamagePlayer()
{
while(true)
{
yield return new WaitForSeconds(1);
playerHealth -= enemyDamage;
}
}
First of all for doing something if it is not colliding anymore you should use OnCollisionExit2D
Then you can either use it the way you did using
StopCoroutine(DamagePlayer());
Or if want to be sure you could either store a reference to your routine
private Coroutine routine;
void OnCollisionEnter2D(Collision2D collision)
{
if (collision.gameObject.CompareTag("Enemy"))
{
if(routine == null)
{
routine = StartCoroutine(DamagePlayer());
}
}
else
{
if(routine != null) StopCoroutine(routine);
}
}
private void OnCollisionEnter2D(Collision2D collision)
{
if(routine != null) StopCoroutine (routine);
}
or use a bool flag in order to terminate it
private bool cancelRoutine = true;
void OnCollisionEnter2D(Collision2D collision)
{
if (collision.gameObject.CompareTag("Enemy"))
{
if(cancelRoutine) routine = StartCoroutine(DamagePlayer());
}
else
{
cancelRoutine = true;
}
}
private void OnCollisionEnter2D(Collision2D collision)
{
cancelRoutine = true;
}
IEnumerator DamagePlayer()
{
cancelRoutine = false;
while(! cancelRoutine)
{
yield return new WaitForSeconds(1);
playerHealth -= enemyDamage;
}
}
In general you could solve this without a routine by directly using OnCollisionStay2D like e.g.
void OnCollisionEnter2D(Collision2D collision)
{
if (collision.gameObject.CompareTag("Enemy"))
{
timer = 1;
}
}
void OnCollisionStay2D(Collision2D collision)
{
if (collision.gameObject.CompareTag("Enemy"))
{
timer -= Time.deltaTime;
if(timer <= 0)
{
timer = 1;
playerHealth -= enemyDamage;
}
}
}

Trying to create a nice character controller?

I'm kind of new in the 2D environment of Unity.
I'm trying to create a platformer. For now, I have a simple map and my player.
My simple map and my player
My player have one script attached :
public class Player : MonoBehaviour
{
public float speed;
public float jump;
public GameObject raycastPoint; // Positioned at 0.01 pixel below the player
private SpriteRenderer spriteRenderer;
private Rigidbody2D body; // Gravity Scale of the Rigidbody2D = 50
private Animator animator;
private void Start()
{
spriteRenderer = GetComponent<SpriteRenderer>();
body = GetComponent<Rigidbody2D>();
animator = GetComponent<Animator>();
}
private void Update()
{
float horizontal = Input.GetAxisRaw("Horizontal");
if (horizontal == 1 && spriteRenderer.flipX)
{
spriteRenderer.flipX = false;
}
else if (horizontal == -1 && !spriteRenderer.flipX)
{
spriteRenderer.flipX = true;
}
body.velocity = new Vector2(horizontal * speed, body.velocity.y);
animator.SetFloat("Speed", Mathf.Abs(horizontal));
float vertical = Input.GetAxisRaw("Vertical");
if (vertical == 1)
{
RaycastHit2D hit = Physics2D.Raycast(raycastPoint.transform.position, Vector2.down, 0.01f);
if (hit.collider != null)
{
body.AddForce(new Vector2(0f, jump));
}
}
}
}
For now I have achieved the right and left movements.
For the jump, I have a child gameobject just under the player and I'm firing a raycast to the bottom so I can know if my player is grounded or not.
I have two problems.
PROBLEM NUMBER ONE.
Sometimes I feel like my "AddForce" line is executed multiple times my player is jumping really high
Problem number one image
PROBLEM NUMBER TWO.
When I'm jumping to the left or right wall, if I keep pressing the left or right key my player is not falling anymore and stay against the wall.
Problem number two image
I tried to put my code into the FixedUpdate method (I know it's better) but I had the same results.
And I tried to set the Collision Detection on Continuous but I had the same results.
Try this code for your first problem :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[RequireComponent(typeof(BoxCollider2D))]
[RequireComponent(typeof(Rigidbody2D))]
[RequireComponent(typeof(Animator))]
public class Player_Controller : MonoBehaviour {
private Rigidbody2D body;
private bool canJump, facingRight;
private Animator anim;
[SerializeField]
private float moveSpeed, jumpForce;
void Start ()
{
SetStartValues();
}
void FixedUpdate ()
{
float horizontal = Input.GetAxis("Horizontal");
animator.SetFloat("Speed", Mathf.Abs(horizontal));
Flip(horizontal);
Move(horizontal);
Jump();
}
private void SetStartValues()
{
body = GetComponent<Rigidbody2D>();
anim = GetComponent<Animator>();
canJump = true;
facingRight = true;
}
private void Jump()
{
if (Input.GetKeyDown(KeyCode.Space) && canJump)
{
body.AddForce(new Vector2(0, jumpForce));
canJump = false;
}
}
private void Move(float x)
{
body.velocity = new Vector2(x * moveSpeed * Time.deltaTime, body.velocity.y);
}
private void Flip(float x)
{
if (x > 0 && !facingRight|| x < 0 && facingRight)
{
facingRight = !facingRight;
transform.localScale = new Vector2(transform.localScale.x * -1, transform.localScale.y) ;
}
}
private void OnCollisionEnter2D(Collision2D collision)
{
if (collision.gameObject.tag == "Ground")
{
canJump = true;
}
}
}
And don't forget to put the "Ground" tag on your ground object.

Ball moves too far when it jumps. It can still be controlled while its in air. Unity3d player control script

I am working on unity ball game. My player is a ball and it uses a player control script. When the ball jumps in air, it can still be controlled and mo move to any direction while its in air. I do not want that as it fails the purpose of heaving a maze since it can fly above obstacles.
I am using a player control script that came with a free unity game kit. I have tried to fix it, but I am only capable of either removing the jump function or reducing its height, and could not fix the issue.
using UnityEngine;
using System.Collections;
public class PlayerControl : MonoBehaviour
{
private GameObject moveJoy;
private GameObject _GameManager;
public Vector3 movement;
public float moveSpeed = 6.0f;
public float jumpSpeed = 5.0f;
public float drag = 2;
private bool canJump = true;
void Start()
{
moveJoy = GameObject.Find("LeftJoystick");
_GameManager = GameObject.Find("_GameManager");
}
void Update ()
{
Vector3 forward = Camera.main.transform.TransformDirection(Vector3.forward);
forward.y = 0;
forward = forward.normalized;
Vector3 forwardForce = new Vector3();
if (Application.platform == RuntimePlatform.Android)
{
float tmpSpeed = moveJoy.GetComponent<Joystick>().position.y;
forwardForce = forward * tmpSpeed * 1f * moveSpeed;
}
else
{
forwardForce = forward * Input.GetAxis("Vertical") * moveSpeed;
}
rigidbody.AddForce(forwardForce);
Vector3 right= Camera.main.transform.TransformDirection(Vector3.right);
right.y = 0;
right = right.normalized;
Vector3 rightForce = new Vector3();
if (Application.platform == RuntimePlatform.Android)
{
float tmpSpeed = moveJoy.GetComponent<Joystick>().position.x;
rightForce = right * tmpSpeed * 0.8f * moveSpeed;
}
else
{
rightForce= right * Input.GetAxis("Horizontal") * moveSpeed;
}
rigidbody.AddForce(rightForce);
if (canJump && Input.GetKeyDown(KeyCode.Space))
{
rigidbody.AddForce(Vector3.up * jumpSpeed * 100);
canJump = false;
_GameManager.GetComponent<GameManager>().BallJump();
}
}
void OnTriggerEnter(Collider other)
{
if (other.tag == "Destroy")
{
_GameManager.GetComponent<GameManager>().Death();
Destroy(gameObject);
}
else if (other.tag == "Coin")
{
Destroy(other.gameObject);
_GameManager.GetComponent<GameManager>().FoundCoin();
}
else if (other.tag == "SpeedBooster")
{
movement = new Vector3(0,0,0);
_GameManager.GetComponent<GameManager>().SpeedBooster();
}
else if (other.tag == "JumpBooster")
{
movement = new Vector3(0,0,0);
_GameManager.GetComponent<GameManager>().JumpBooster();
}
else if (other.tag == "Teleporter")
{
movement = new Vector3(0,0,0);
_GameManager.GetComponent<GameManager>().Teleporter();
}
}
void OnCollisionEnter(Collision collision)
{
if (!canJump)
{
canJump = true;
_GameManager.GetComponent<GameManager>().BallHitGround();
}
}
void OnGUI()
{
GUI.Label(new Rect(300,10,100,100),"X: " + moveJoy.GetComponent<Joystick>().position.x.ToString());
GUI.Label(new Rect(300,30,100,100),"Y: " + moveJoy.GetComponent<Joystick>().position.y.ToString());
}
}
The question has been answered. Now how to use this script -> Create a sphere and give it "Sphere Collider", "Mesh Renderer", "Rigidbody", "Player Control(Script)" Under player control script put this script and your done. Now you have a ball that can be controlled in ios,android and pc i guess and can jump.
I think canJump flag says "Player on the ground". So, if you "can't jump", that means you shouldn't allow gamer to control the character as it is flying. Check it in very start of Update() and call return; if canJump == false