I have two objects: a player and an enemy. The main problem is the enemy, which should start shooting at the player when it approaches him at a certain distance (in short, just start the animation).
At first I tried to implement this through Vector 3, as with other ordinary opponents. But he is as stupid and crutch as possible, however, you yourself can see everything in the pinned.
I started to implement it more allegedly correctly, through the trigger and the collider (box collider) of the enemy itself. And everything works right as it should, but there is a nuance. The enemy also has boxing implemented through the box collider, the player, hitting which, causes damage to him. There is only one box collider for these two tasks, and since I had to increase this collider so that the enemy could stop in front of the player at a certain distance. Because of this, the player can hit at a great distance (at which the enemy can shoot) from the enemy and the enemy takes damage anyway.
I tried to make a separate object the size of the enemy himself and use it as a box to receive damage. Then he already transmits data about receiving damage to the enemy object. But this does not work, all links between scripts and objects are made, but he does not want to transfer data. That is, simply making two colliders for different tasks does not work.
In general, here my powers are all. I searched the entire Internet, but I did not find how to implement this mechanic in a different way so that it does not conflict with others. Therefore, I ask the help of the local experts, where I screwed up.
private Animator ch_animator;
// Start is called before the first frame update
void Start()
{
myAgent = GetComponent<NavMeshAgent>();
myAnim = GetComponent<Animator>();
EnemyH = GetComponent<GDHealth>();
}
// Update is called once per frame
void Update()
{
dist = Vector3.Distance(/*checker.*/transform.position, target.transform.position);
if (dist > range)
{
myAgent.enabled = false;
myAnim.SetBool("Idle", true);
myAnim.SetBool("Move", false);
myAnim.SetBool("Attack", false);
}
if (dist <= range & dist > atRange)
{
myAgent.enabled = true;
myAgent.SetDestination(target.position);
myAnim.SetBool("Idle", false);
myAnim.SetBool("Move", true);
myAnim.SetBool("Attack", false);
}
if (dist <= atRange)
{
StartCoroutine(Attack());
}
if (PlayerH._health <= 0)
{
atRange = 0;
}
if (EnemyH._health < 0)
{
myAgent.enabled = false;
}
}
public IEnumerator Attack()
{
yield return new WaitForSeconds(0.5f);
myAgent.enabled = false;
myAnim.SetBool("Idle", false);
myAnim.SetBool("Move", false);
myAnim.SetBool("Attack", true);
}
void OnTriggerStay(Collider col)
{
if (col.tag == "Player")
{
//gameObject.GetComponent<Animator>().SetBool("Attack", true);
StartCoroutine(Attack());
transform.LookAt(col.transform.position);
transform.eulerAngles = new Vector3(0, transform.eulerAngles.y, 0);
}
}
void OnTriggerExit(Collider col)
{
if (col.tag == "Player")
{
myAgent.enabled = true;
myAgent.SetDestination(target.position);
myAnim.SetBool("Idle", false);
myAnim.SetBool("Move", true);
myAnim.SetBool("Attack", false);
//gameObject.GetComponent<Animator>().SetBool("Attack", false);
}
}
But this does not work, all links between scripts and objects are made, but he does not want to transfer data.
From this description it could be anything: wrong layers, no rigidbody on either of objects, misstyped tags in OnTriggerStay method.
In my project, I successfully created 2 colliders for 2 separate tasks, so this is how I would see it in your problem:
Use two colliders
Attach one collider with one script to the enemy object - this collider should have a size of the enemy. The OnTriggerStay method here should deal damage to the enemy, check for death, etc.
Create child object to the enemy. Attach new collider to it with the size of enemy's attack range. Attach a script with OnTriggerStay method that will stop enemy and begin ranged attack (or whatever you want to do).
If this doesn't work: check collision matrix or try adding a kinematic rigidbody to either of objects.
Measure distance between player and the enemy in update (which you are already doing) and apply necessary code based on distance (stop or attack) thus replacing one of the colliders.
Hope that helps!
Related
I am a starter in Unity and developing a soccer game. I have a problem ,my IF statements conflict each other. Let me explain it in detail.
In order for a ball to stick to player, I have used IF operator, so whenever the distance between the player and the ball is less than < 0.5 , the ball sticks to player and move together with it. Now when I try to set up shooting the ball (I try with "addforce") it doesnt let me, cause the ball is still attached to player and distance is <0.5.
This one is the balls script.
public bool sticktoplayer;
public transform player;
//gameobject Player is attached
float distancetoplayer;
Rigidbody rb;
//balls rigidbody
void Awake ()
{
rb = getComponent<Rigidbody>();
}
void Update()
{
If (!sticktoplayer)
{
float distancetoplayer = Vector3.Distance (player.position, transform.position);
if(distancetoplayer < 0.5f)
{
sticktoplayer = true;
}
}
else
{
transform.position = player.position;
}
if(Input.GetKeyDown(KeyCode.Space))
{
rb.addforce(20, 0, 0, ForceMode.Impulse);
sticktoplayer = false;
}
When the player is not controlling the ball the force is succesfully applied to the ball, but when the ball is attached (distancetoplayer<0.5) then the other IF statements blocks it from shooting.
Maybe there are some work arounds ? Thanks.
I tried to make another if statement.
why dont you try sticking the ball to the player by collision? instead of checking the distance, create a collider with the radius or scale you desire and whenever the ball is inside the collider (when it triggers with the collider) stick it to the player. Because you will not be able to let the ball go since the ball will always be less then 0.5f away from the player once it sticks
have you tried it this way?
if(Input.GetKeyDown(KeyCode.Space))
{
sticktoplayer = false;
rb.addforce(20, 0, 0, ForceMode.Impulse);
}
So I'm trying to make a little pushback effect in my tests arena, I've got a sphere collider and here is my script:
// PushBack Class Script
if (Input.GetKeyDown(KeyCode.Q))
{
explosion_ball.transform.position = transform.position;
StartCoroutine(WaitAndPrint());
}
IEnumerator WaitAndPrint()
{
float i = 0;
while (i < 1)
{
i += 0.01f;
explosion_ball.radius = curve.Evaluate(i) * 10;
yield return new WaitForSeconds(0.01f);
}
}
//__________//
Sphere collider is set and stuff, but it doesn't push things back like I thought it would.
Thanks!
Edit:
explosion_ball is a sphere collider, I'm changing it with the point on the animation curve and * it by 10
EDIT:Unity Rigid bodies go to sleep so[Also Change interpolation to continuous if collider changes size too quickly]
A.check if obstacle rigid bodies are Sleeping with onTriggerEnter and Wake Up
void OnTriggerEnter(collider){ if(rb.IsSleeping()){rb.WakeUp();}
or
B.Attach this forceWakeUp Script to all Objects you want to be obstacles.
using UnityEngine;
public class forceWakeUp : MonoBehaviour
{
private Rigidbody rb;
void Start()
{
rb = gameObject.GetComponent<Rigidbody>();
}
void Update()
{
if(rb.IsSleeping()){rb.WakeUp();}
}
}
Forcibly Keeping Many objects awake Will impact performance.So ,your decision.
You need to scale up the collider on the object by radius and all the objects it is supposed to wobble need to have Rigid body component attached to them
2.if you ARE doing above things and its not adding any force you could just add a force on all the overlapping objects using the "OnCollisionEnterTrigger" and AddForceMethod radially away from the Sphere
Obstacle.position - Sphere.position is the vector Radially away from Sphere I think.
You don't need coroutines for this I think.
While searching through the Unity scripting API, found a method in there called
Rigidbody.AddExplosionForce(explosionForce, explosionPosition, explosionRadius, upwardsModifier, mode)
In order to get the rigid bodies that are to be affected by the explosion I would need to get them using Physics.OverlapSphere(position, radius) this gets all of the objects within the radius variable, then get the component.
Combining these two would also look like:
Collider[] colliders = Physics.OverlapSphere(transform.position, radius);
foreach (Collider hit in colliders)
{
if (hit.transform.tag == "Interactable")
{
if (hit.gameObject != hitgameObject)
{
Rigidbody rb = hit.GetComponent<Rigidbody>();
if (rb != null)
rb.AddExplosionForce(power, transform.position, radius, upForce, ForceMode.Impulse);
}
}
}
If there is any explaining that you would like me to do about my variables mentioned I will reply :)
Thanks for the help guys.
I don't know your curve and what values that evaluation produces, but you can check your code visually with Window/Analysis/Physics Debugger, or write a gizmo:
private void OnDrawGizmos()
{
Gizmos.color = new(1.0f, 0.0f, 0.0f, 0.5f);
Gizmos.DrawSphere(explosion_ball.transform.position, explosion_ball.radius);
}
Or simply just use https://docs.unity3d.com/ScriptReference/Rigidbody.AddExplosionForce.html
Recently I have started my first 2D game project on unity and everything has been going well. My only problem so far is with my enemy. My enemy, when attacking, jumps in the air and then falls and hits the ground. I want my script to detect when it falls and how hard it falls then create a force that pushes the player back as if its an "explosion."
So my questions are how do I detect the enemy hitting the ground, after a fall, and then add a force?
I tried using onCollisionEnter2D on Unity but it does not work since technically even when the enemy is moving it's still "falling."
Here is my attempt at checking if the enemy fell then searching for the player and calling the explosion force function.
private void OnCollisionEnter2D(Collision2D collision)
{
if(collision.gameObject.tag == "Ground")
{
foreach (Collider2D Obj in Physics2D.OverlapCircleAll(transform.position, radius))
{
if (Obj.GetComponent<Rigidbody2D>() != null && Obj.gameObject != gameObject)
{
Debug.Log("Calling Function");
Rigidbody2D rb = Obj.GetComponent<Rigidbody2D>();
ExplosionForce2D forceScript = GetComponent<ExplosionForce2D>();
forceScript.AddExplosionForce(rb, force, transform.position, radius);
}
}
}
}
This is my code for adding a force to the object.
public void AddExplosionForce (Rigidbody2D body, float expForce, Vector3 expPosition, float expRadius)
{
var dir = (body.transform.position - expPosition);
float calc = 1 - (dir.magnitude / expRadius);
if (calc <= 0) {
calc = 0;
}
body.AddForce (dir.normalized * expForce * calc);
}
I expect that, if the player is in the enemy radius, and the enemy jumps, falls, and hits the floor it will push the player back as if it was an explosion.
you can use a flag that can check that if the enemy is falling or not, so when your enemy is actually falling set this as true, if not (means moving) then set as false.
Checking for Velocity is a good solution.
So you can do like,
body.velocity.magnitude
// magnitude to remove the direction issue.
// now apply all this as
body.AddForce(dir.normalized * expForce * calc * body.velocity.magnitude);
I'm trying to make a BTD game. Since different balloons (enemies) have different speeds, they collide with each other when traveling on the path. I'm currently using this code:
void OnCollisionEnter (Collision coll)
{
if (coll.gameObject.tag == "Enemy")
{
Physics.IgnoreCollision(coll.collider, gameObject.GetComponent<SphereCollider>());
}
}
However, it doesn't appear to be working at all. The enemies still collide with each other. On the otherhand, the collision with the enemies and bullets from towers is working.
void OnTriggerEnter (Collider col)
{
if (col.tag == "Bullet")
{
CurrentHP -= col.GetComponent<TackShooterBullet>().Damage;
}
I've tried layer-collision (enemies to same layer & unchecking of the same layer collision in the layer collision matrix, but that doesn't work either. The enemy contains a sphere mesh filter, sphere collider, mesh renderer, rigidbody, material, and 2 scripts. Is there a better way to avoid collisions between the enemies. I'm asking this question since I've seen duplicates, but their solutions aren't working at all. I can provide more of my code if needed.
Edit for Clarity: Again, what I'm trying to accomplish is have the enemies be able to go through each other.
Edit (Fixed Problem): I found out to avoid Enemy Collisions, I could also remove rigidbody. However, removing the ridigbody would mess up the bullet --> enemy trigger in the enemy class. Therefore, I just wrote the collision between bullet & enemy in the bullet class instead.
using UnityEngine;
public class TackShooterBullet : MonoBehaviour {
private GameObject target;
public float Damage;
// Use this for initialization
void Start () {
target = transform.parent.GetComponent<TackShooterRange>().Target; // Target = Enemy[0] (First Enemy To Enter Range - Enemy is Removed from JList when exiting Range)
}
// Update is called once per frame
void Update()
{
Damage = gameObject.transform.parent.transform.parent.GetComponent<TackShooterLimitingRange1>().level * 20; // Upgrade Level * 20 = Damage Done
if (target == null) // If Enemy Exits Range
{
Destroy(gameObject); // Destroy Bullet
}
if (target != null) // Enemy Exists In Range
{
transform.position = Vector3.MoveTowards(transform.position, target.transform.position, 20 * Time.deltaTime); // Bullet Follows Enemy
Destroy(gameObject); // Destroy Bullet Upon Contact With Enemy
target.GetComponent<HealthOfEnemy>().CurrentHP -= Damage; // Enemy Loses Health
}
}
This allowed me to remove the OnTriggerEnter & OnCollisionEnter methods as well as the RigidBody from the Enemy Class as stated before, so these properties no longer affect the collisions between Enemies.
Unity has a built in function for easier collision detection called layer-based collision detection:
https://docs.unity3d.com/Manual/LayerBasedCollision.html
The documentation is really good. Just comment if you need further clarification.
In my game for pong, the ball is supposed to bounce and never become slower. However, the ball is steadily slowing down over time. I will put an image of the ball object and the scripts.
Here is the ball properties on the left
Here is the ball script
using UnityEngine;
using System.Collections;
public class Ball : MonoBehaviour
{
public float ballVelocity = 3000;
Rigidbody rb;
bool isPlay;
int randInt;
void Awake()
{
rb = GetComponent<Rigidbody>();
randInt = Random.Range(1,3);
}
void Update()
{
if (Input.GetMouseButton(0) && isPlay == false)
{
transform.parent = null;
isPlay = true;
rb.isKinematic = false;
if (randInt == 1)
{
rb.AddForce(new Vector3(ballVelocity, ballVelocity, 0));
}
if (randInt == 2)
{
rb.AddForce(new Vector3(-ballVelocity, -ballVelocity, 0));
}
}
}
}
and here is the bounce physics image
and since I have no idea why it won't work, here is my physics project settings
The reason why this is happening is because of the randomness being added. All it takes is it to hit harder in one direction one than the opposite direction. Eventually, idk after how long of a time, but it will finally settle at a velocity of 0. To fix this, you need to remove the drag coefficient if you haven't already. Next, you need to clear whatever the current velocity is from the ball. "rigidbody.velocity = Vector3.zero;" should do it for you. After that, you can either generate a new velocity directly using some maths that I do not know off of my head, or add a new force that is no longer dependent on the previous condition of the ball. I hope this helps, and if not, leave a comment and let's see if we can't find a better solution :)