I'm programming a top-down survival type game, and I added slimes that have a simple AI, which just follows the player when in range. I want to make it so when the slimes touch the player, you will take damage, and the slime will get knocked back a little. This is the code I have to add force to the slime. I've tried reversing the kbDirection, the player and slime are both not kinematic, the "KB" debug does show up when they collide, and the player still loses health.
public void AddKnockback()
{
Vector2 kbDirection = transform.position - player.transform.position;
kbDirection.Normalize();
slimeRb.AddForce(kbDirection * 100, ForceMode2D.Impulse);
Debug.Log("KB");
}
void OnCollisionEnter2D(Collision2D collision)
{
if(collision.gameObject.tag == "Player")
{
player.GetComponent<PlayerActions>().playerHealth--;
AddKnockback();
}
}
I suspect that it might have something to do with the way the slime's movement is calculated. This is what I have for the movement:
public void AddKnockback()
{
Vector2 kbDirection = transform.position - player.transform.position;
kbDirection.Normalize();
slimeRb.AddForce(kbDirection * 100, ForceMode2D.Impulse);
Debug.Log("KB");
}
void OnCollisionEnter2D(Collision2D collision)
{
if(collision.gameObject.tag == "Player")
{
player.GetComponent<PlayerActions>().playerHealth--;
AddKnockback();
}
}
Most prob your slime follows the target by overriding its position. like slime.transform.position = targetPosition; That is why the force effect can't change the position of the slime. That's only an assumption since I don't know how you move them. But sounds possible to me that is why I shared it with you.
Related
I am new to Unity2D. I am trying to make castle defense game. When the spawners start to inheritance the enemies overlap (they should), but when the archer arrow collides whit the enemies it kills them all. I searched everywhere for the answer of this but nothing...
My questions is:
Is there a way to only hit one target at time?
Arrow script:
void Start()
{
target = GameObject.FindGameObjectWithTag("Enemy").GetComponent<Transform>();
}
// Update is called once per frame
void Update()
{
transform.position = Vector2.MoveTowards(transform.position, target.position, speedProjectile * Time.deltaTime);
}
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.gameObject.CompareTag("Enemy"))
{
Destroy(gameObject);
}
}
Enemy script:
void Start()
{
target = GameObject.FindGameObjectWithTag("target3").GetComponent<Transform>();
}
// Update is called once per frame
void Update()
{
transform.position = Vector2.MoveTowards(transform.position, target.position, speed * Time.deltaTime);
}
private void OnTriggerEnter2D(Collider2D col)
{
if (col.gameObject.CompareTag("arrow"))
{
EHealth -= HDamage;
}
if (EHealth <= 0)
{
Destroy(gameObject);
}
Your enemies are all taking damage because destroying a GameObject isn't something that happens immediately (for good reason). Because of this, in a single frame, any number of enemies can be hit by the same arrow.
If you'd like to rely on these collision methods, I'd suggest controlling the damage from the arrow instead of from the enemy, so you can be sure it only hits something once:
private bool dealtDamage = false;
private void OnTriggerEnter2D(Collider2D col) {
// Only deal damage once
if (dealtDamage) {
return;
}
// Does the thing I hit have this "EnemyScript" ?
var enemy = col.gameObject.GetComponentInChildren<EnemyScript>();
if (enemy == null) {
return;
}
// Make this "DealDamage()" method public on your EnemyScript
enemy.DealDamage();
dealtDamage = true;
Destroy(gameObject);
}
Then get rid of the Enemy Script's OnTriggerEnter2D because the arrow is handling it all. I don't know what the name of the script is on your enemies so I just called it EnemyScript. This is also calling a DealDamage() method that you'd have to make (which would probably look a lot like the one you current have listed in your "Enemy Script.")
its hard to say with the information given, based on what you have said it seems that all the entity's are just 1 entity(so when you kill 1 enemy you kill the only enemy which is all of them). you can have them run independently from each other.
Maybe you can use collision enter function to check bullet is hitting enemy's body.
You can use this code below...
private bool isEntered = false;
void OnCollisionEnter(Collision collision)
{
if(isEntered) return;
if(collision.gameObject.tag == "enemy") isEntered = true;
....
....
}
I hope it will work for you.
Finally figured it out. First of all the script is dealing damage so I should hit the target specific number of times, so I can't stop the collision of the arrow without turning it back on. So I made a method, that is being Invoked (InvokeRepeating()) after the arrow collides, that turns a bool back to false. Which method (function) of course should be in Update();
I am not sure if I am saying it right bc I am a beginner but I hope this helps somebody. :D
And here is the code:
Arrow Script:
private void DealDamage()
{
if (hit == true)
return;
eHealth.EHealth -= hDamage.HDamage;
hit = true;
}
void OnTriggerEnter2D(Collider2D collision)
{
if (collision.gameObject.CompareTag("enemy"))
{
DealDamage();
Destroy(gameObject); //Destroying arrow on collision
}
}
private void HitSetter()
{
hit = false;
}
void Update()
{
InvokeRepeating("HitSetter", 0f, 1.1f);
}
Enemy Script:
void Update()
{
if (EHealth <= 0)
{
anim.SetBool("EnemyDie", true);
Destroy(gameObject, 0.833f);
}
Also thanks to #Foggzie
Whenever my character collides with an object, i add a rigidbody to that object; and i also would like to reapply the same force it should have received with the collision on the same point, so it reacts with physics.
This is my attempt so far, but force is way too much as all objects fly from the scene:
void OnCollisionEnter(Collision collision) {
if (collision.gameObject.GetComponent<Rigidbody>() == null) {
collision.gameObject.AddComponent<Rigidbody>().AddForceAtPosition(collision.impulse, collision.contacts[0].point);
}
}
I also tried with:
void OnCollisionEnter(Collision collision) {
if (collision.gameObject.GetComponent<Rigidbody>() == null) {
collision.gameObject.AddComponent<Rigidbody>().AddForceAtPosition(collision.impulse / Time.fixedDeltaTime, collision.contacts[0].point);
}
}
What i'm doing wrong/missing?
Your point of collision might be to far from the origin of the object.
Check:
if the collision box is proportionnal to the object.
the colision point makes sense with the actual position of the object.
the mass of the rigidbody is adequate.
Note that when position is far away from the center of the rigidbody
the applied torque will be unrealistically large.
from unity doc page
as for the colision interpretation in the code, i found this piece of code in this article describing How to get the impact force of a collision in Unity
public class Character : MonoBehaviour {
private void OnCollisionEnter (Collision collision) {
float collisionForce = collision.impulse.magnitude / Time.fixedDeltaTime;
if (collisionForce < 100.0F) {
// This collision has not damaged anyone...
}
else if (collisionForce < 200.0F) {
// Auch! This will take some damage.
}
else {
// This collision killed me!
}
}
}
I have a character (moved with the keyboard's arrows). I have several walls on my house.
The thing is, I wanted to detect collisions when I go against a wall (so it won't go through). It's ok. But now, when my character goes against a wall, he does weird things, he moves alone like he was in a gravity mode (I don't know if understandable).
So, I'd like that, when I go against a wall, character stops moving ? I've tried a lot of things and I'm kinda lost atm so if you have any idea I'll take it and try ! Just for the record,started using unity a few months ago, so there might errors in my script ( I mean, it compiles but maybe its not written the best way).
Here's my script :
public class ScriptCharacter : MonoBehaviour
{
private Animator m_animator;
private Rigidbody m_rigidBody;
private void Start()
{
m_animator = gameObject.GetComponent<Animator>();
m_animator.SetFloat("Speed", 1);
}
// Update is called once per frame
private void FixedUpdate()
{
float v = Input.GetAxis("Vertical");
float h = Input.GetAxis("Horizontal");
transform.Translate(transform.forward * v * Time.deltaTime, Space.World);
transform.Rotate(0, h * Time.deltaTime * 30, 0);
m_animator.SetFloat("Speed", v);
}
void OnCollisionEnter(Collision collision)
{
Debug.Log("here");
if (collision.gameObject.name == "Wall")
{
m_rigidBody.velocity = Vector3.zero;
m_rigidBody.angularVelocity = Vector3.zero;
m_animator.SetFloat("Speed", 0);
}
}
}
As Chestera mentioned in the comments:
Set the rigidbody to kinematic in onCollisionEnter() and set it back to dynamic in onCollisionExit().
From the Unity docs, "If isKinematic is enabled, Forces, collisions or joints will not affect the rigidbody anymore."
https://docs.unity3d.com/ScriptReference/Rigidbody-isKinematic.html
I have been try for a while to stop the enemy rotate wen my player collide with it. I have insert a rigid body, and I have try to freeze position and also the freeze rotation but he keep doing the same, I can't find any were any info to learn about this problem.
I have google it a few times and could not find any thing, close to my problem.
This is the screenshot of the problem
https://imgur.com/fyryuqY
Also I have tried to set Angular drag to 0 but no success it keep doing the same.
It sounds like I am missing something, but I can't find any solution for it yet.
I have also tried to see all answers on unity forum but I can't find anywhere a solution or the way to learn about this problem.
I have edit to insert the enemy script
This is my enemy script
using UnityEngine;
using System.Collections;
public class target : MonoBehaviour {
public float health = 100f;
public Animator animx2;
public AudioSource audiovfx2;
public void TakeDamage (float amount)
{
health -= amount;
if (health <= 0f)
{
Die();
}
}
void Die()
{
animx2.SetBool("isdie",true);
audiovfx2.Play();
healthcontroller.score += 10;
health -= 10;
Destroy (gameObject, 1.5f);
}
void Update()
{
animx2 = GetComponent<Animator>();
GetComponent<AudioSource>().Play();
}
void OnTriggerEnter(Collider other) {
if (other.tag == "Player") {
Rigidbody rb = GetComponent<Rigidbody>();
rb.angularVelocity = Vector3.zero
//Stop Moving/Translating
//rbdy.velocity = Vector3.zero;
//Stop rotating
//rbdy.angularVelocity = Vector3.zero;
}
}
void OnTriggerExit(Collider other) {
if (other.tag == "Player") {
//here i want to return to normal
}
}
}
Thank you in advance
Did you try to nullify angularVelocity of the Rigidbody component, for example
rb.angularVelocity = Vector3.zero;
I have a ball standing on a platform and i have written code so that every time i swipe up the ball jumps from one platform to another depending on the force of the swipe. At the moment my platforms are just placed in position by myself and i dont have a script for their random generation. The only script i have is on the player for swiping and moving forward.
Currently im making this motion by adding force in two directions, up and forward to create the projectile motion. Its working like its supposed too, but the motion is too slow. I want it to sort of move faster. Iwe tried playing with the forces as well as the mass of the ball. They do make a difference, but I still want the ball to move some what faster.
Is adding force the best way to do this? Or would you recommend a different way?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SwipeScript : MonoBehaviour {
public float maxTime;
public float minSwipeDist;
float startTime;
float endTime;
Vector3 startPos;
Vector3 endPos;
float swipeDistance;
float swipeTime;
// Use this for initialization
void Start()
{
}
// Update is called once per frame
void Update()
{
if (Input.touchCount > 0)
{
Touch touch = Input.GetTouch(0);
if (touch.phase == TouchPhase.Began)
{
startTime = Time.time;
startPos = touch.position;
}
else if (touch.phase == TouchPhase.Ended)
{
endTime = Time.time;
endPos = touch.position;
swipeDistance = (endPos - startPos).magnitude;
swipeTime = endTime - startTime;
if (swipeTime < maxTime && swipeDistance > minSwipeDist)
{
swipe();
}
}
}
}
public void swipe()
{
Vector2 distance = endPos - startPos;
if (Mathf.Abs(distance.y) > Mathf.Abs(distance.x))
{
Debug.Log("Swipe up detected");
jump();
}
}
private void jump()
{
Vector2 distance = endPos - startPos;
GetComponent<Rigidbody>().AddForce(new Vector3(0, Mathf.Abs(distance.y/5), Mathf.Abs(distance.y/5)));
}
private void OnCollisionEnter(Collision collision)
{
if (collision.gameObject.name == "Cube (1)") {
Debug.Log("collision!");
GetComponent<Rigidbody>().velocity = Vector3.zero;
GetComponent<Rigidbody>().angularVelocity = Vector3.zero;
}
}
}
I was writing my answer when I noticed #Fredrik comment: pretty much all he said was in what I wrote so I'll just skip it ! (I also do not recommend increasing Time.timeScale)
The other way you could move your ball would be using ballistic equations and setting your RigidBody to Kinematic. This way you will be able to control the ball speed through code using RigidBody.MovePosition() and still get OnCollision[...] events.
Also as a side note I do not recommend using collision.gameObject.name for collision object checking but rather tagging your cubes or even setting their layer to a specific one (but I guess this may be temporary code of yours ;) ).
Hope this helps,
Either pass ForceMode.VelocityChange as a second parameter to AddForce, or make sure you divide your vector by Time.fixedDeltaTime (which has the effect of multiplying it because Time.fixedDeltaTime will be less than 1).