How to use events in Unity? - c#

I've watched bunch of videos about events in unity, but still cant figure out how to use them.
I have 2 scripts, in first i detect collision, second script should teleport an object with the first script attached.
First script
using UnityEngine;
public class PlayerShip : MonoBehaviour
{
private Rigidbody2D rb;
private float angle;
public delegate void TeleportHandler(GameObject Border);
public event TeleportHandler OnShipCollidedEvent;
[SerializeField] private float speedMoving;
[SerializeField] private float speedRotating;
void Start()
{
rb = GetComponent<Rigidbody2D>();
}
void Update()
{
if (Input.GetAxis("Horizontal") != 0)
{
angle = -Input.GetAxis("Horizontal") * Time.deltaTime * speedRotating;
transform.Rotate(transform.rotation.x, transform.rotation.y, angle);
}
if (Input.GetKey(KeyCode.W))
rb.AddRelativeForce(Vector2.up * speedMoving);
}
private void OnTriggerEnter2D(Collider2D other)
{
this.OnShipCollidedEvent?.Invoke(other.gameObject);
}
}
Second script - OnShipCollided doesn't output Test
using UnityEngine;
public class BordersCommands : MonoBehaviour
{
private PlayerShip _playerShip;
[SerializeField] private GameObject LeftBorder;
[SerializeField] private GameObject RightBorder;
[SerializeField] private GameObject BotBorder;
[SerializeField] private GameObject TopBorder;
public BordersCommands(PlayerShip _playerShip)
{
this._playerShip = _playerShip;
this._playerShip.OnShipCollidedEvent += OnShipCollided;
}
private void OnShipCollided(GameObject border)
{
Debug.Log("Test");//Here will be teleportation
}
}

Solved by adding Borders = new BordersCommands(this); in first script Start

Related

Unity 2D Enemies don't take damage

I'm a newbie in unity and game development. I'm trying to build a combat 2d game, but I can't damage enemies and I'm banging my head against a wall for 1 week, because I can't see where is the error.
This is my Player script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerAttack : MonoBehaviour
{
//[SerializeField] private float attackCooldown;
[SerializeField] private float range;
[SerializeField] private int damage;
[SerializeField] private LayerMask enemyLayer;
public Transform AttackPoint;
//private float cooldownTimer = Mathf.Infinity;
private Animator anim;
private Enemy enemyHealth;
private void Awake()
{
anim = GetComponent<Animator>();
enemyHealth = GetComponent<Enemy>();
}
private void Update()
{
if (Input.GetButtonDown("Fire1"))
{
anim.SetTrigger("Attack");
Attack();
Debug.Log("attacking");
}
}
private void OnDrawGizmos()
{
if (AttackPoint == null)
return;
Gizmos.color = Color.red;
Gizmos.DrawWireSphere(AttackPoint.position, range);
}
public void Attack()
{
Collider2D[] hitEnemies = Physics2D.OverlapCircleAll(AttackPoint.position, range, enemyLayer);
foreach (Collider2D Enemy in hitEnemies)
{
if (Enemy.CompareTag("Enemy"))
Enemy.transform.GetComponent<Enemy>().TakeDamage(damage);
Debug.Log("Enemy Hit!");
}
}
}
And this is the Enemy script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Enemy : MonoBehaviour
{
[SerializeField] private float startingHealth;
public float currentHealth;
public Animator anim;
private bool dead;
void Start()
{
currentHealth = startingHealth;
}
public void TakeDamage(float _damage)
{
//currentHealth = Mathf.Clamp(currentHealth - _damage, 0, startingHealth);
if (currentHealth > 0)
{
//hurt anim
Debug.Log("damage taken");
}
if (!dead)
{
//dead anim
GetComponent<EnemyPatrol>().enabled = false; //enemy cannot move while he is dead
GetComponent<EnemyMelee>().enabled = false; //enemy cannot attack
dead = true;
}
}
private void Update() //for testing
{
if (Input.GetKeyDown(KeyCode.P))
TakeDamage(1);
}
}
I tried testing it with the debug, and unity tells me that I'm hitting the enemy but without damaging him.
I tried also to damage the enemy pressing P for testing, and this works.
Thank you guys in advance for your help,
Federico.
Built Player and Enemy scripts. The player can be damaged by enemies but the player cannot damage the enemies.
In your attack function, here:
if (Enemy.CompareTag("Enemy"))
Enemy.transform.GetComponent<Enemy>().TakeDamage(damage);
Debug.Log("Enemy Hit!");
Only the TakeDamage line is inside the if statement, the log is not so it always executes. YOu need to change it look like this:
if (Enemy.CompareTag("Enemy"))
{
Enemy.transform.GetComponent<Enemy>().TakeDamage(damage);
Debug.Log("Enemy Hit!");
}
So the issue is therefore that CompareTag is returning false, if you're seeing the enemy hit log and not the take damage log. The enemy does not have the correct tag.

Unity2D Melee Combat

I'm new to programming and C#. I'm trying to build a melee system for my platform game following tutorials on yt.
This is my PlayerAttack script:
using System.Collections; using System.Collections.Generic; using UnityEngine;
public class PlayerAttack : MonoBehaviour {
//[SerializeField] private float attackCooldown;
[SerializeField] private float range;
[SerializeField] private int damage;
[SerializeField] private LayerMask enemyLayer;
public Transform AttackPoint;
//private float cooldownTimer = Mathf.Infinity;
private Animator anim;
private Enemy enemyHealth;
private void Awake()
{
anim = GetComponent<Animator>();
enemyHealth = GetComponent<Enemy>();
}
private void Update()
{
if (Input.GetButtonDown("Fire1"))
{
anim.SetTrigger("Attack");
Attack();
Debug.Log("attacking");
}
}
private void OnDrawGizmos()
{
if (AttackPoint == null)
return;
Gizmos.color = Color.red;
Gizmos.DrawWireSphere(AttackPoint.position, range);
}
void Attack()
{
Collider2D[] hitEnemies = Physics2D.OverlapCircleAll(AttackPoint.position, range, enemyLayer);
foreach (Collider2D Enemy in hitEnemies)
{
Enemy.transform.GetComponent<Enemy>().TakeDamage(damage);
}
}
}
and this one is the EnemyHealth script
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Enemy : MonoBehaviour
{
[SerializeField] private int startingHealth;
public int currentHealth;
public Animator anim;
void Start()
{
currentHealth = startingHealth;
}
public void TakeDamage(int _damage)
{
currentHealth = Mathf.Clamp(currentHealth - _damage, 0, startingHealth);
if (currentHealth > 0)
{
//hurt animation
//invulnerability
}
else
{
//die animation
GetComponentInParent<EnemyPatrol>().enabled = false;
GetComponent<EnemyMelee>().enabled = false;
}
}
void Die()
{
if (currentHealth <= 0)
{
Debug.Log("Enemy Dead!");
Destroy(gameObject);
//die animation
}
}
}
I'm getting an error from the Unity editor "object reference not set to an instance of an object" on line 61 of the PlayerAttack:
enter code here
Enemy.transform.GetComponent().TakeDamage(damage);
I checked the scripts names and they are fine, and I checked also if I missed something in the editor , I don't know what's wrong.
When I hit the enemy the game crashes and i get this error.
Thanks
You are trying to call Enemy.transform.GetComponent<Enemy>().TakeDamage(damage); on everything that has collider but some of those objects don't have the Enemy component so you can't call TakeDamage on them. That is where the error comes from. To fix it you have to find only the enemy game objects. To do this you can assign tags to your enemy and check the ones with Enemy tag through the script like this:
foreach (Collider2D Enemy in hitEnemies)
{
if (Enemy.tag == "Enemy")
Enemy.transform.GetComponent<Enemy>().TakeDamage(damage);
}
There is another way to do it without tags which I don't recommend:
foreach (Collider2D Enemy in hitEnemies)
{
if (Enemy.transform.GetComponent<Enemy>() != null)
Enemy.transform.GetComponent<Enemy>().TakeDamage(damage);
}
Check if the player has hit the enemy or not:
foreach (Collider2D hitEnemy in hitEnemies) {
if(hitEnemy.TryGetComponent(out Enemy enemy)) {
enemy.TakeDamage(damage);
}
}
Other than the answer:
In your PlayerAttack.cs script, you have a field enemyHealth which you are trying to get a reference to it using enemyHealth = GetComponent<Enemy>(); in your Awake() method. This doesn't get a reference to it because of Enemy.cs and PlayerAttack.cs scripts are attached to different gameobjects.

Twin Stick Controller and KBM not responding (Unity C#)

The problem is that when I import the code to the character it does not add the Player Input automatically and also it doesnt move at all when I press neither the controller nor the keyboard, Ive checked the code so many times but ill paste it here, it doesnt work if I add the player input manually either :(
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
[RequireComponent(typeof(CharacterController))]
[RequireComponent(typeof(PlayerInput))]
public class TwinStickMovement : MonoBehaviour
{
[SerializeField] private float playerSpeed = 5f;
[SerializeField] private float gravityValue = -9.81f;
[SerializeField] private float controllerDeadzone = 0.1f;
[SerializeField] private float gamepadRotateSmoothing = 1000f;
[SerializeField] private bool isGamepad;
private CharacterController controller;
private Vector2 movement;
private Vector2 aim;
private Vector3 playerVelocity;
private PlayerControls playerControls;
private PlayerInput playerInput;
private void Awake()
{
controller = GetComponent<CharacterController>();
playerControls = new PlayerControls();
playerInput = GetComponent<PlayerInput>();
}
private void OnEnable()
{
playerControls.Enable();
}
private void OnDisable()
{
playerControls.Disable();
}
void Update()
{
HandleInput();
HandleMovement();
HandleRotation();
}
void HandleInput()
{
movement = playerControls.Controls.Movement.ReadValue<Vector2>();
aim = playerControls.Controls.Aim.ReadValue<Vector2>();
}
void HandleMovement()
{
Vector3 move = new Vector3(movement.x, 0, movement.y);
controller.Move(move * Time.deltaTime * playerSpeed);
playerVelocity.y += gravityValue * Time.deltaTime;
controller.Move(playerVelocity * Time.deltaTime);
}
void HandleRotation()
{
}
}```

GameObject not being removed from List?

I've tried 2/3 different ways of removing my gameObject from my List but none are working. When I debug the method the debug log is showing up as it should yet the gameobject still isn't removed from the list.
When my teammates kill an enemy I want the enemy to be removed from the list and then destroyed so I can continue to iterate through the List to find the closest enemy to begin attacking. Because the gameObject's are not being removed I get a null reference and i can loop through my for loop to check.
1st Script: List is created and used in a for loop, removing and destroying the enemy also occurs in here.
public class FriendlyManager : MonoBehaviour
{
public NavMeshAgent navMeshAgent;
public Transform player;
public static FriendlyManager singleton;
public float health;
public float minimumDistance;
public int damage;
public List<GameObject> enemies;
private GameObject enemy;
private GameObject enemyObj;
// animations
[SerializeField] Animator animator;
bool isAttacking;
bool isPatroling;
// attacking
[SerializeField] Transform attackPoint;
[SerializeField] public GameObject projectile;
public float timeBetweenAttacks;
bool alreadyAttacked;
private void Awake()
{
navMeshAgent = GetComponent<NavMeshAgent>();
animator = GetComponent<Animator>();
enemyObj = new GameObject();
}
private void Start()
{
singleton = this;
isAttacking = false;
isPatroling = true;
animator.SetBool("isPatroling", true);
}
private void Update()
{
for(int i = 0; i < enemies.Count; i++)
{
if(Vector3.Distance(player.transform.position, enemies[i].transform.position) <= minimumDistance)
{
enemy = enemies[i];
Attacking(enemy);
}
}
}
private void Attacking(GameObject enemy)
{
// stop enemy movement.
navMeshAgent.SetDestination(transform.position);
enemyObj.transform.position = enemy.transform.position;
transform.LookAt(enemyObj.transform);
if (!alreadyAttacked)
{
isAttacking = true;
animator.SetBool("isAttacking", true);
animator.SetBool("isPatroling", false);
Rigidbody rb = Instantiate(projectile, attackPoint.position, Quaternion.identity).GetComponent<Rigidbody>();
rb.AddForce(transform.forward * 32f, ForceMode.Impulse);
alreadyAttacked = true;
Invoke(nameof(ResetAttack), timeBetweenAttacks);
}
}
private void ResetAttack()
{
alreadyAttacked = false;
animator.SetBool("isAttacking", false);
}
public void DestroyEnemy(GameObject enemy)
{
enemies.Remove(enemy);
Debug.Log("AHHHHHHH M GOING CRAZY");
Destroy(gameObject);
}
}
}
2nd Script: Deals with the damage and checks enemy's currentHealth. (I have to post it as an image because for Stack Overflow is being annoying.) ._.
Shouldn't it just be
Destroy(enemy);
and not
Destroy(gameObject);

I'm not sure why but my add force isn't working i have a rigidbody2d, and the code looks correct but it still won't work?

I'm not sure why but my .addforce on my rigidbody isn't working.
I have tried following the official unity addforce tutorial.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ArrowController : MonoBehaviour
{
public Rigidbody2D rb;
public float speed = 5.0f;
public Vector2 pos;
void Start()
{
rb = GetComponent<Rigidbody2D>();
}
// Update is called once per frame
void Update()
{
faceMouse();
testForClick();
}
void faceMouse()
{
Vector3 mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
Vector3 differance = GameObject.Find("gunArm").transform.position - mousePos;
float gunAngle = Mathf.Atan2(differance.y, differance.x) * Mathf.Rad2Deg;
GameObject.Find("gunArm").transform.rotation = Quaternion.Euler(0, 0, gunAngle);
}
void testForClick()
{
if (Input.GetMouseButtonDown(0))
{
print("click");
rb.AddForce(transform.forward);
}
}
}
I expect arrow to have force added to it in the forwards direction but it just prints out "click" (The message I added to ensure the mouse-click was working).
I'm not sure why but I created a test script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Test : MonoBehaviour
{
public Rigidbody2D rb;
public float speed = 20.0f;
// Start is called before the first frame update
void Start()
{
rb = GetComponent<Rigidbody2D>();
}
// Update is called once per frame
void Update()
{
if (Input.GetMouseButtonDown(0))
{
print("click");
rb.AddForce(transform.right * speed, ForceMode2D.Impulse);
}
rotate();
}
private void rotate()
{
}
}
I also edited my old script to this:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ArrowController : MonoBehaviour
{
public Rigidbody2D rb;
public float speed = 50.0f;
public Vector2 pos;
private void Start()
{
rb = GetComponent<Rigidbody2D>();
}
private void Update()
{
faceMouse();
testForClick();
}
void FixedUpdate()
{
if (doForce == true)
{
doForce = false;
rb.AddForce(transform.forward * speed, ForceMode2D.Impulse);
}
}
private bool doForce;
private GameObject gunArm;
private Camera cam;
private void faceMouse()
{
// try to reuse the reference
if (!cam) cam = Camera.main;
var mousePos = cam.ScreenToWorldPoint(Input.mousePosition);
// try to re-use the reference
if (!gunArm) gunArm = GameObject.Find("gunArm");
var difference = rb.transform.position - mousePos;
var gunAngle = Mathf.Atan2(difference.y, difference.x) * Mathf.Rad2Deg;
rb.transform.rotation = Quaternion.Euler(0, 0, gunAngle);
}
void testForClick()
{
if (Input.GetMouseButtonDown(0))
{
print("click");
// only set the flag
doForce = true;
}
}
void place()
{
}
}
and the test worked by itself with no rotation and on the main script only the rotation worked so i tried having both scripts active at the same time and it started working, thanks for all the help on this issue.
Despite the fact that isn't working is a quite weak description:
First of all you should do it in FixedUpdate but get the input in Update.
Second reduce the Find calls in Update .. very inefficient. It would be better to reference them via the Inspector if possible. Otherwise maybe in Start .. the way I show here is the last resort with lazy initialization assuming your script might be spawned later on runtime
Additionally (thanks to EricOverflow) you might want to rather pass ForceMode.Impulse to AddForce since you add the force only once and not continuesly.
public class ArrowController : MonoBehaviour
{
public Rigidbody2D rb;
public float speed = 5.0f;
public Vector2 pos;
// store and re-use references!
// would be better to already reference them via drag&drop
// in the Inspector
[SerializeField] private GameObject gunArm;
[SerializeField] private Camera cam;
private void Start()
{
rb = GetComponent<Rigidbody2D>();
}
private void Update()
{
testForClick();
}
private void FixedUpdate()
{
// also do this here
faceMouse();
if (doForce)
{
doForce = false;
rb.AddForce(transform.forward, ForceMode.Impulse);
}
}
private bool doForce;
private void faceMouse()
{
// try to reuse the reference
if(!cam) cam = Camera.main;
var mousePos = cam.ScreenToWorldPoint(Input.mousePosition);
// try to re-use the reference
if (!gunArm) gunArm = GameObject.Find("gunArm");
var difference = gunArm.transform.position - mousePos;
var gunAngle = Mathf.Atan2(difference.y, difference.x) * Mathf.Rad2Deg;
gunArm.rotation = Quaternion.Euler(0, 0, gunAngle);
}
private void testForClick()
{
if (Input.GetMouseButtonDown(0))
{
print("click");
// only set the flag
doForce = true;
}
}
}
The reason your code doesn't do anything is not because it doesn't work, but instead because transform.forward is a vector with magnitude 1. Adding a force of magnitude 1 will not do much to most objects, and friction will likely slow down the object again.
Try adding a force with a higher strength and ForceMode.Impulse instead:
float strength = 50f;
rb.AddForce(transform.forward * strength, ForceMode.Impulse);
Update:
It looks like you want the gun to face your mouse position and that's where your problem might be:
Let's try using Quaternion.LookRotation to get that working instead of doing to math manually.
Maybe:
GameObject gunArm;
void Awake()
{
gunArm = GameObject.Find("gunArm");
}
void faceMouse()
{
Vector3 difference = mousePos - gunArm.transform.position;
difference.z = 0;
gunArm.transform.rotation = Quaternion.LookRotation(difference);
}

Categories

Resources