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);
}
Related
I'm working on a platformer game, and I instantiate my character after he gets hit by an enemy object. The problem is, my character is instantiating twice. Here's my code.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class PlayerMove : MonoBehaviour
{
public GameObject player;
private Rigidbody2D rb;
private SpriteRenderer sprite;
private BoxCollider2D coll;
private Animator anim;
public float Speed;
public float JumpForce;
private float dirX = 0f;
private float fJumpPressedRemember = 0f;
private float fJumpPressedRememberTime = 0.2f;
[SerializeField] private LayerMask jumpableGround;
// Start is called before the first frame update
void Start()
{
rb = GetComponent<Rigidbody2D>();
anim = GetComponent<Animator>();
sprite = GetComponent<SpriteRenderer>();
coll = GetComponent<BoxCollider2D>();
}
// Update is called once per frame
void Update()
{
dirX = Input.GetAxisRaw("Horizontal");
rb.velocity = new Vector2(dirX * Speed, rb.velocity.y);
fJumpPressedRemember -= Time.deltaTime;
if (Input.GetButtonDown("Jump"))
{
fJumpPressedRemember = fJumpPressedRememberTime;
}
if ((fJumpPressedRemember > 0) && isGrounded())
{
fJumpPressedRemember = 0;
rb.velocity = new Vector2(rb.velocity.x, JumpForce);
}
UpdateAnimation();
}
private bool isGrounded()
{
return Physics2D.BoxCast(coll.bounds.center, coll.bounds.size, 0f, Vector2.down, .1f, jumpableGround);
}
void OnCollisionEnter2D(Collision2D spikeCol)
{
if (spikeCol.gameObject.tag.Equals("DamageDealer") == true)
{
Instantiate(player, new Vector3(-10, 1, 0), Quaternion.identity);
Destroy(player);
}
if (spikeCol.gameObject.tag.Equals("End") == true)
{
SceneManager.LoadScene("Level2", LoadSceneMode.Single);
}
if (spikeCol.gameObject.tag.Equals("End2") == true)
{
SceneManager.LoadScene("Level1", LoadSceneMode.Single);
}
}
private void UpdateAnimation()
{
if (dirX > 0f)
{
anim.SetBool("running", true);
sprite.flipX = false;
}
else if (dirX < 0f)
{
anim.SetBool("running", true);
sprite.flipX = true;
}
else
{
anim.SetBool("running", false);
}
}
}
It's supposed to instantiate once after dying. I don't know what's happening, but it only started after I added animation code, so I think that's the issue. Thanks to anyone who can help!
OnCollisionEnter2D is called twice.
Reasons why this can be described here: Why is OnCollisionEnter getting called twice?
You can add instantiate method double call check, like this:
private bool isInited;
void OnCollisionEnter2D(Collision2D spikeCol)
{
if (spikeCol.gameObject.tag.Equals("DamageDealer") == true)
{
if (isInited)
{
return;
}
isInited = true;
Instantiate(player, new Vector3(-10, 1, 0), Quaternion.identity);
Destroy(player);
}
...
}
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.
I'm trying to pick up objects in unity. I have a gameObject called LookObject. Whenever the camera points to an object, the name of that object will be stored in LookObject, then when I press Space the object gets picked up. it is working but not completely. The issue I'm facing is that when I look at an object then look at another direction, the lookObject still shows the name of the object I was looking at (it doesn't update).
please see this image:
as shown in the image, the reticle is not pointing to the object. but it is still showing Cube as the Look Object.
Here is PlayerInteractions class:
GameObject[] targetObjects;
List<GameObject> targetObjectsList;
[Header("InteractableInfo")]
public float sphereCastRadius = 0.5f;
public int interactableLayerIndex;
private Vector3 raycastPos;
public GameObject lookObject;
private PhysicsObjects physicsObject;
private Camera mainCamera;
public GameObject winUI;
private InteractiveObjects interactiveObjects;
[Header("Pickup")]
[SerializeField] private Transform pickupParent;
public GameObject currentlyPickedUpObject;
private Rigidbody pickupRB;
[Header("ObjectFollow")]
[SerializeField] private float minSpeed = 0;
[SerializeField] private float maxSpeed = 300f;
[SerializeField] private float maxDistance = 8f;
private float currentSpeed = 0f;
private float currentDist = 0f;
[Header("Rotation")]
public float rotationSpeed = 100f;
// Quaternion lookRot;
[SerializeField] GameObject TargetsCanvas;
static bool strikeThrough = false;
private void Start()
{
mainCamera = Camera.main;
targetObjects = GameObject.FindGameObjectsWithTag("TargetObj");
targetObjectsList = new List<GameObject>();
foreach (var obj in targetObjects)
{
var mytext = CreateText(TargetsCanvas.transform);
mytext.text = "• Find The " + obj.name;
Debug.Log(""+ obj.name);
}
}
//A simple visualization of the point we're following in the scene view
private void OnDrawGizmos()
{
Gizmos.color = Color.yellow;
Gizmos.DrawSphere(pickupParent.position, 0.5f);
}
void Update()
{
//Here we check if we're currently looking at an interactable object
raycastPos = mainCamera.ScreenToWorldPoint(new Vector3(Screen.width / 2, Screen.height / 2, 0));
RaycastHit hit;
if (Physics.SphereCast(raycastPos, sphereCastRadius, mainCamera.transform.forward, out hit, maxDistance, 1 << interactableLayerIndex))
{
lookObject = hit.collider.transform.gameObject;
}
//if we press the button of choice
if (Input.GetKeyDown(KeyCode.Space))
{
//and we're not holding anything
if (currentlyPickedUpObject == null)
{
//and we are looking an interactable object
if (lookObject != null )
{
PickUpObject();
if (!targetObjectsList.Contains(lookObject.gameObject))
{
targetObjectsList.Add(lookObject.gameObject);
if (targetObjectsList.Count == targetObjects.Length)
{
// Time.timeScale = 0f;
// winUI.SetActive(true);
// SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);
// Time.timeScale = 1f;
}
}
}
}
//if we press the pickup button and have something, we drop it
else
{
BreakConnection();
}
}
}
private void FixedUpdate()
{
if (currentlyPickedUpObject != null)
{
currentDist = Vector3.Distance(pickupParent.position, pickupRB.position);
currentSpeed = Mathf.SmoothStep(minSpeed, maxSpeed, currentDist / maxDistance);
currentSpeed *= Time.fixedDeltaTime;
Vector3 direction = pickupParent.position - pickupRB.position;
pickupRB.velocity = direction.normalized * currentSpeed;
//Rotation//
// lookRot = Quaternion.LookRotation(mainCamera.transform.position - pickupRB.position);
// lookRot = Quaternion.Slerp(mainCamera.transform.rotation, lookRot, rotationSpeed * Time.fixedDeltaTime);
// pickupRB.MoveRotation(lookRot);
}
}
//Release the object
public void BreakConnection()
{
pickupRB.constraints = RigidbodyConstraints.None;
currentlyPickedUpObject = null;
lookObject = null;
physicsObject.pickedUp = false;
currentDist = 0;
}
public void PickUpObject()
{
physicsObject = lookObject.GetComponentInChildren<PhysicsObjects>();
currentlyPickedUpObject = lookObject;
pickupRB = currentlyPickedUpObject.GetComponent<Rigidbody>();
pickupRB.constraints = RigidbodyConstraints.FreezeRotation;
physicsObject.playerInteractions = this;
}
Here is the code attached to objects:
public float waitOnPickup = 0.2f;
public float breakForce = 35f;
[HideInInspector] public bool pickedUp = false;
[HideInInspector] public PlayerInteractions playerInteractions;
private void OnCollisionEnter(Collision collision)
{
if (pickedUp)
{
if (collision.relativeVelocity.magnitude > breakForce)
{
playerInteractions.BreakConnection();
}
}
}
//this is used to prevent the connection from breaking when you just picked up the object as it sometimes fires a collision with the ground or whatever it is touching
public IEnumerator PickUp()
{
yield return new WaitForSecondsRealtime(waitOnPickup);
pickedUp = true;
}
Here is an image of the object inspector:
how can I make it accurately showing the objects I'm looking at?
A simple fix for this would be to set lookObject to null if the SphereCast returns false since that would indicate you are no longer looking at a valid object. Simply adding else lookObject = null; to the end of the first if statement in your Update() method should do the trick.
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;
}
}
}
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.