Unity 2D - Attack system only works while moving - c#

I've recently started coding on Unity, trying to make a game. So long it's been fine, but I faced a problem.
I've implemented a script for the Attack System:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class AttackDamage : MonoBehaviour
{
private float attackDamage = 20;
private void OnTriggerEnter2D(Collider2D other)
{
if (other.GetComponent<Health>() != null)
{
Health health = other.GetComponent<Health>();
health.TakeDamage(attackDamage);
}
}
}
And I also implemented one for the Health System:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
public class Health : MonoBehaviour
{
public Image healthBar;
public float healthAmount = 100;
private void Update()
{
if (healthAmount <= 0)
{
SceneManager.LoadScene(0);
}
}
public void TakeDamage(float Damage)
{
healthAmount -= Damage;
healthBar.fillAmount = healthAmount / 100;
}
public void Healing(float healPoints)
{
healthAmount += healPoints;
healthAmount = Mathf.Clamp(healthAmount, 0, 100);
healthBar.fillAmount = healthAmount / 100;
}
}
And it works prety well.
But as you read on the title, the attack only actually works right after I move. If I try to attack while I'm not moving, the attackArea appears on the scene, but doesn't deal damage. And i can't figure out why.
Do you have any idea on what could be the problem?
Here there's also a video of what actually happens, in the game:
https://drive.google.com/drive/folders/1BTYTNz_yzus-eRLnjsgB0hsYLU5sQm2k?usp=sharing
I have no idea on how to solve this problem, since I've copied the code from a source online, which actually works prorperly.
The code is exactly the same, apart form the script for the Health System, which is not shown on the video, but which also shouldn't make that much of a difference.
So i really don't know how to handle this.
Thanks for the help :)

If you are looking to deal continuous damage while any health-character is in the trigger, use OnTriggerStay2d instead.
Otherwise, if you are looking to deal damage when the user presses a button, you can do the following:
Have a hit-box collider that stores all enemy in range. (By adding enemy to a list when it enters the hit-box, and removing them from list when they exit.)
When the attack is triggered, fetch all enemy in the list from 1. and deal damage to all.
Code-wise, looks something like this:
public class AttackDamage : MonoBehaviour
{
private HashSet<Health> inRange = new HashSet<Health>();
private float attackDamage = 20;
private void OnTriggerEnter2D(Collider2D other)
{
if (other.GetComponent<Health>() != null)
{
// Add to set of characters that are in range of attack
inRange.Add(other.GetComponent<Health>());
}
}
private void OnTriggerExit2D(Collider2D other)
{
var charHealth = other.GetComponent<Health>();
if (charHealth != null) {
// Remove, since it exit the range of the attacking character.
inRange.Remove(charHealth);
}
}
// Call this function whenever you want to do damage to all characters in range.
public void Attack(){
foreach(var character in inRange){
character.TakeDamage(attackDamage);
}
}
}
Then somewhere else... (example, in your player.)
public class YourPlayer {
// ...
// Inspector reference to the hitbox
[SerializeField]
private AttackDamage attackHitbox;
private void Update(){
// Attack when button is pressed (or something).
if (Input.GetKeyDown(KeyCode.A)) {
attackHitbox.Attack();
}
}
// ...
}
Finally, if you are looking to deal damage at specific points in an animation, the term you should search for is Key-Frame. Look for a tutorial on Animation, then Key-Frame.
Once you learn about it, you can use the above mentioned method, but call the damage script on your desired key-frames.

Related

How can I make it so that the player loses health when an enemy collides with a different object?

Here an image of what my game looks like so far so that you can get a better idea but I need it so that when one of the outer capsules touches the red box the player (middle capsule) loses health.
I have tried creating a new script which checks for collisions but I couldn't get it to work and am unsure where to go from here. Below is my code for how the health bar works and pressing the space bar reduces health for demonstration purposes.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class PlayerHealth : MonoBehaviour
{
public float MaxHealth;
public Slider _slide;
private float currentHealth;
void Start()
{
currentHealth = MaxHealth;
_slide.maxValue = MaxHealth;
_slide.value = MaxHealth;
}
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
TakeDamage(25f);
if(currentHealth <=0)
{
//move to game over
}
}
void TakeDamage(float Damage)
{
currentHealth = currentHealth - Damage;
_slide.value = currentHealth;
}
}
At one point I tried using
void Update()
{
(collision.collider.name == "Barrier");
TakeDamage(25f);
if(currentHealth <=0)
{
//move to game over
}
}
but realised this is completely wrong, as well as trying to add a box collider to the red "barrier" to aid this but it didn't fix anything.
Update 2:
Also tried changing it so that the enemies move towards the barrier and not the player and added the code:
private void OnTriggerEnter(Collider other) {
TakeDamage(25f);
if(currentHealth <=0)
{
//move to game over
}
}
which doesn't present any errors but rather just doesn't do anything. The enemy capsules just go through it and to the centre, with no health being lost.
You need to check for collisions using OnCollisionEnter3D and then use the TakeDamage function inside of it. Here is an example:
public class PlayerHealth: MonoBehaviour
{
void OnCollisionEnter3D(Collision3D col) //Check for collision
{
TakeDamage(25f);
}
}
Note that for this function to work, both objects need a 3d collider and a rigid body. If you don't want your character to fall, just set gravity to 0.

Trying to get death and respawn working in my game

I can't get my respawning in my game to work, I have followed numerous tutorials, but have been failing to change one small aspect of them. I have a health variable in my game, and all the tutorials have the player die and respawn right upon touching the enemy. I want it to be so you take damage when touching an enemy, and when your health reaches 0 you are brought to a game over scene. I just can't seem to figure it out. I am new to game dev so I have tried my absolute hardest to solve the problem on my own, but to no avail. This is pretty much my last resort. I appreciate any help I can get.
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class PlayerHealth : MonoBehaviour
{
public static PlayerHealth instance;
public int maxHealth;
public int health;
public int GameOver;
public event Action DamageTaken;
public int Health
{
get
{
return health;
}
}
// Start is called before the first frame update
void Awake()
{
if(instance == null)
{
instance = this;
}
}
private void Start()
{
health = maxHealth;
}
// Update is called once per frame
public void TakeDamage()
{
if(health <= 0)
{
return;
}
health -= 1;
if(DamageTaken != null)
{
DamageTaken();
}
}
public void heal()
{
if (health >= maxHealth)
{
return;
}
health += 1;
if (DamageTaken != null)
{
DamageTaken();
}
}
void OnCollisionEnter(Collision other)
{
if(other.gameObject.tag == "Enemy")
{
TakeDamage();
}
if(health <= 0)
{
SceneManager.LoadScene(GameOver);
}
}
}
I don't see anything wrong with your code specifically. It could be a different issue in the editor, which is usually the case. To debug try one of the following:
Check that the OnCollisionEnter is actually being triggered
Check that the Enemey has the Enemy Tag.
You can check 1 & 2 with the following code:
void OnCollisionEnter(Collision other)
{
// Check what the tag is
print(other.gameObject.tag);
...
}
There should now be messages when you run the program. Other things to check:
Make sure the player object has the PlayerHealth script attached to it.
Make sure that the player has a RigidBody
Make sure the enemy has a collider
Make sure the enemy collider is not set to Trigger

Unity3D playing sound when Player collides with an object with a specific tag

I using Unity 2019.2.14f1 to create a simple 3D game.
In that game, I want to play a sound anytime my Player collides with a gameObject with a specific tag.
The MainCamera has an Audio Listener and I am using Cinemachine Free Look, that is following my avatar, inside the ThridPersonController (I am using the one that comes on Standard Assets - but I have hidden Ethan and added my own character/avatar).
The gameObject with the tag that I want to destroy has an Audio Source:
In order to make the sound playing on the collision, I started by creating an empty gameObject to serve as the AudioManager, and added a new component (C# script) to it:
using UnityEngine.Audio;
using System;
using UnityEngine;
public class AudioManager : MonoBehaviour
{
public Sound[] sounds;
// Start is called before the first frame update
void Awake()
{
foreach (Sound s in sounds)
{
s.source = gameObject.AddComponent<AudioSource>();
s.source.clip = s.clip;
s.source.volume = s.volume;
s.source.pitch = s.pitch;
}
}
// Update is called once per frame
public void Play (string name)
{
Sound s = Array.Find(sounds, sound => sound.name == name);
s.source.Play();
}
}
And created the script Sound.cs:
using UnityEngine.Audio;
using UnityEngine;
[System.Serializable]
public class Sound
{
public string name;
public AudioClip clip;
[Range(0f, 1f)]
public float volume;
[Range(.1f, 3f)]
public float pitch;
[HideInInspector]
public AudioSource source;
}
After that, in the Unity UI, I went to the Inspector in the gameObject AudioManager, and added a new element in the script that I named: CatchingPresent.
On the Third Person Character script, in order to destroy a gameObject (with a specific tag) when colliding with it, I have added the following:
void OnCollisionEnter(Collision other)
{
if (other.gameObject.CompareTag("Present"))
{
Destroy(other.gameObject);
count = count - 1;
SetCountText();
}
}
It is working properly as that specific object is disappearing on collision. Now, in order to play the sound "CatchingPresent" anytime the Player collides with the object with the tag, in this case, Present, I have tried adding the following to the if in the OnCollisionEnter:
FindObjectOfType<AudioManager>().Play("CatchingPresent");
But I get the error:
The type or namespace name 'AudioManager' could not be found (are you
missing a using directive or an assembly reference?)
AudioManager.instance.Play("CatchingPresent");
But I get the error:
The name 'AudioManager' does not exist in the current context
As all the compiler errors need to be fixed before entering the Playmode, any guidance on how to make the sound playing after a collision between the player and the gameObject with the tag Present is appreciated.
Edit 1: Assuming that it is helpful, here it goes the full ThirdPersonUserControl.cs:
using System;
using UnityEngine;
using UnityEngine.UI;
using UnityStandardAssets.CrossPlatformInput;
namespace UnityStandardAssets.Characters.ThirdPerson
{
[RequireComponent(typeof (ThirdPersonCharacter))]
public class ThirdPersonUserControl : MonoBehaviour
{
public Text countText;
public Text winText;
private int count;
private ThirdPersonCharacter m_Character; // A reference to the ThirdPersonCharacter on the object
private Transform m_Cam; // A reference to the main camera in the scenes transform
private Vector3 m_CamForward; // The current forward direction of the camera
private Vector3 m_Move;
private bool m_Jump; // the world-relative desired move direction, calculated from the camForward and user input.
private void Start()
{
count = 20;
SetCountText();
winText.text = "";
// get the transform of the main camera
if (Camera.main != null)
{
m_Cam = Camera.main.transform;
}
else
{
Debug.LogWarning(
"Warning: no main camera found. Third person character needs a Camera tagged \"MainCamera\", for camera-relative controls.", gameObject);
// we use self-relative controls in this case, which probably isn't what the user wants, but hey, we warned them!
}
// get the third person character ( this should never be null due to require component )
m_Character = GetComponent<ThirdPersonCharacter>();
}
private void Update()
{
if (!m_Jump)
{
m_Jump = CrossPlatformInputManager.GetButtonDown("Jump");
}
}
// Fixed update is called in sync with physics
private void FixedUpdate()
{
// read inputs
float h = CrossPlatformInputManager.GetAxis("Horizontal");
float v = CrossPlatformInputManager.GetAxis("Vertical");
bool crouch = Input.GetKey(KeyCode.C);
// calculate move direction to pass to character
if (m_Cam != null)
{
// calculate camera relative direction to move:
m_CamForward = Vector3.Scale(m_Cam.forward, new Vector3(1, 0, 1)).normalized;
m_Move = v*m_CamForward + h*m_Cam.right;
}
else
{
// we use world-relative directions in the case of no main camera
m_Move = v*Vector3.forward + h*Vector3.right;
}
#if !MOBILE_INPUT
// walk speed multiplier
if (Input.GetKey(KeyCode.LeftShift)) m_Move *= 0.5f;
#endif
// pass all parameters to the character control script
m_Character.Move(m_Move, crouch, m_Jump);
m_Jump = false;
}
void OnCollisionEnter(Collision other)
{
if (other.gameObject.CompareTag("Present"))
{
Destroy(other.gameObject);
count = count - 1;
SetCountText();
//FindObjectOfType<AudioManager>().Play("CatchingPresent");
AudioManager.instance.Play("CatchingPresent");
}
}
void SetCountText()
{
countText.text = "Missing: " + count.ToString();
if (count == 0)
{
winText.text = "You saved Christmas!";
}
}
}
}
Edit 2: Hierarchy in Unity:
Reformulated the approach that I was following and solved the problem by simply adding an Audio Source to the ThirdPersonController (with the AudioClip that I wanted to call) and added GetComponent<AudioSource>().Play(); to the if statement as it follows:
void OnCollisionEnter(Collision other)
{
if (other.gameObject.CompareTag("Present"))
{
Destroy(other.gameObject);
count = count - 1;
SetCountText();
GetComponent<AudioSource>().Play();
}
}
Importing your scripts myself works without any issues when using FindObjectOfType<AudioManager>().Play("CatchingPresent");. Try reimporting your scripts from the editor (right click in the project folder > reimport all. this might take a while depending on the size of your project)
to use AudioManager.instance.Play("CatchingPresent"); you would first need to create a static variable that holds instance like this (this only works as a singleton, and will break if multiple AudioManager's are in the scene):
public class AudioManager : MonoBehaviour
{
//Create a static AudioManager that will hold the reference to this instance of AudioManager
public static AudioManager Instance;
public Sound[] sounds;
//Assign Instance to the instance of this AudioManager in the constructor
AudioManager()
{
Instance = this;
}
// Rest of the AudioManager code
}
Doing it like this, and using the rest of your code also works for me.

Next Scene not loading when requested to

I have created a game where when the user breaks all the blocks he is taken to the next scene but this is not happening despite adding all of the scenes I have in the build settings. I have no errors whatsoever and the scene is written correctly. Can someone help me resolve this, please?
This is the build settings
Bricks script : (where the scene is called)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Bricks : MonoBehaviour {
public LevelManager myLevelManager;
public static int brickCount = 0;
public int maxNumberOfHits = 0;
int timesHit;
public AudioClip BlockBreaking;
// Use this for initialization
void Start () {
timesHit = 0;
if(this.gameObject.tag == "BrickHit")
{
brickCount++;
}
if(this.gameObject.tag == "BrickHitTwice")
{
brickCount++;
}
}
void OnCollisionEnter2D()
{
timesHit++;
if (timesHit == maxNumberOfHits)
{
brickCount--;
Destroy(this.gameObject);
}
if(brickCount == 0)
{
myLevelManager.LoadLevel("Level1.2"); //THIS SCENE IS NOT LOADING
}
if(this.gameObject.tag == "BrickHit") //If the gameObject (Block One Point) with the tag "BrickHit" is hit
{
Scores.scoreValue += 1;//The user will be rewarded 1 point
AudioSource.PlayClipAtPoint(BlockBreaking, transform.position);
}
if(this.gameObject.tag == "BrickHitTwice") //If the gameObject (Block Two Points) with the tag "BrickHitTwice" is hit
{
Scores.scoreValue += 2; //The user will be rewarded 2 points
AudioSource.PlayClipAtPoint(BlockBreaking, transform.position);
}
}
LevelManager Script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class LevelManager : MonoBehaviour {
public void LoadLevel(string name)
{
print("Level loading requested for" + name);
SceneManager.LoadScene(name);
}
I suspect that your bug may lie in the fact that you're Destroy()ing the gameObject before it can load the next scene; you get a race condition on what will finish first; LoadScene or Destroy() - which would explain why it sometimes work. You should never assume it is a bug in the framework before understanding your problem.
Try putting the Destroy() after the LoadScene() or with a delay to understand if this is your issue.
Also, your LevelManager can be made static and doesn't need to inherit from MonoBehaviour since it doesn't use gameObject functionality.
public static class LevelManager {
public static void LoadLevel(string name)
{
print("Level loading requested for" + name);
SceneManager.LoadScene(name);
}
}
Used by doing LevelManager.LoadLevel("MyLevel");, but then you may question what is more effective, doing LevelManager.LoadLevel or SceneManager.LoadLevel, as they will do the exact same thing.
The main issue that you're having is not having a single source to check brickCount instead each individual brick is maintaining its own count. I would recommend moving the brick counting logic into a separate class. It would seem like LevelManager would a good place for it. So in LevelManager add:
private int brickCount = 0;
public void AddBrick()
{
brickCount++;
}
public void RemoveBrick()
{
brickCount--;
// Check if all bricks are destroyed
if (brickCount == 0)
{
LoadLevel("Level1.2");
}
}
And then in your brick script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Bricks : MonoBehaviour {
public LevelManager myLevelManager;
public int maxNumberOfHits = 0;
int timesHit;
public AudioClip BlockBreaking;
// Use this for initialization
void Start () {
timesHit = 0;
myLevelManager.AddBrick();
// I'm not sure why you were checking the tag here, since the result was the same
}
void OnCollisionEnter2D()
{
timesHit++;
if (timesHit == maxNumberOfHits)
{
myLevelManager.RemoveBrick();
Destroy(this.gameObject);
}
/* This looks like the player is getting score whether the brick is destroyed or not. Also, it would appear the player won't get scored on the final brick */
if(this.gameObject.tag == "BrickHit") //If the gameObject (Block One Point) with the tag "BrickHit" is hit
{
Scores.scoreValue += 1;//The user will be rewarded 1 point
AudioSource.PlayClipAtPoint(BlockBreaking, transform.position);
}
if(this.gameObject.tag == "BrickHitTwice") //If the gameObject (Block Two Points) with the tag "BrickHitTwice" is hit
{
Scores.scoreValue += 2; //The user will be rewarded 2 points
AudioSource.PlayClipAtPoint(BlockBreaking, transform.position);
}
}
}

Attacking closest target C#

I'm very new to the programming / scripting scene and I'm following a tutorial for making a basic controller and combat system (Birds view RPG). Thing is the tutorial will be making a "click to attack" (targeting) when me myself want to make a "attack closest enemy" type of combat.
What I have managed to do so far is having 3 opponents but I'm only able to attack 1 of them, thats where the instructor starts to add a targeting script which I wanna avoid. I tried finding someone else with a somewhat similar problem on answers.unity3d.com and tried to incorporate the code into my own project. The result seems to be slightly lacking and need help figuring out what it might be!
I did see many similar problems and tried to copy/paste code but there always seem to be minor issues I'm not familiar solving. (For example getting Tags to work?)
///Player code:
using UnityEngine;
using System.Collections;
public class Player : MonoBehaviour {
public string name;
public int health;
public int damage;
public float range;
public Transform opponent;
void Start ()
{
}
void Update ()
{
Player. ();
}
void Player.Attack()
{
if(Input.GetKeyUp (KeyCode.Space))
{
if(Vector3.Distance(opponent.transform.position, transform.position);
{
if(opponent != null && Vector3.Distance (opponent.position, Transform.position) < Range)
{
opponent.GetComponent<Enemy>().GetHit(damage);
}
}
///Enemy code:
using UnityEngine;
using System.Collections;
public class Enemy : MonoBehaviour
{
public string name = "Monster";
public int health;
public int damage;
void Start ()
{
}
void Update ()
{
}
public void GetHit(int playerDamage)
{
health = health - playerDamage;
}
void OnMouseOver()
{
Player.opponent = Transform;
}
}

Categories