Unity Mirror, Client is not executing damage Command - c#

I have a script on the Player that gets its target by clicking on them. I check if the players auto attack cooldown is 0 and if the player is in range. After that it should run a Command and damage the enemy mob.
This only happens as it is intended on the host and not on client.
And if I remove the enemy != null check in the CmdDamage function the client just disconnects.
public class PlayerAttacker : NetworkBehaviour
public EnemyScript enemy;
public float timer = 0;
public float timerMax;
private void Start()
{
timer = timerMax;
}
private void Update()
{
if (!isLocalPlayer)
return;
if (Input.GetMouseButtonDown(0))
{
Vector3 mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
Vector2 mousePos2d = new Vector2(mousePos.x, mousePos.y);
RaycastHit2D hit = Physics2D.Raycast(mousePos2d, Vector2.zero);
if(hit.collider != null && hit.collider.GetComponent<EnemyScript>() != null)
{
enemy = hit.collider.GetComponent<EnemyScript>();
enemy.target.SetActive(true);
}
}
if (timer <= 0)
{
timer = 0;
BasicAttack();
}
else if(timer > 0)
{
timer -= Time.deltaTime;
}
}
private void BasicAttack()
{
float dist = Vector3.Distance(enemy.transform.position, transform.position);
if(dist < 2.5f)
{
GetComponent<NetworkAnimator>().SetTrigger(Animator.StringToHash("sword slash")); ///SEND TRIGGERS OVER NETWORK
CmdDamage();
timer = timerMax;
}
}
[Command]
private void CmdDamage()
{
if(enemy != null)
enemy.TakeDamage(5);
}
public class EnemyScript : NetworkBehaviour
[SyncVar(hook = "OnHealthChanged")] public float currentHealth; //ADD HP BARS INGAME
public float maxHealth;
[SerializeField] public HealthBar healthBar;
public override void OnStartServer()
{
currentHealth = maxHealth;
}
public void TakeDamage(float amount)
{
if (!isServer)
return;
currentHealth -= amount;
}

Where is your OnHealthChanged -hook implemented? With SyncVars that are hooked, you have to assign changed variable inside hook method. In your case, you have to assign a new value for currentHealth inside OnHealthChanged.
Now your currentHealth is updated only on server.

Related

How can I remove health from individual instantiated objects in unity?

So my current code removes 5 from the curhealth variable in my enemys once the bullet collides with it. But the issue is, if there is no matter which enemy i shoot and hit, it removes from every single instantiated enemy. I only want to remove health from the enemy who gets hit, how can i fix this?
Enemy.cs
using System;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.AI;
using UnityStandardAssets.Characters.FirstPerson;
public interface IDamagable
{
void TakeDamage(int damage);
}
public class Enemy : MonoBehaviour, IDamagable
{
//Health
public float maxHealth = 100f;
static public float curHealth = 100f;
//Health Bar
public Image healthBar;
public Transform goal;
// AI Pathfinding Variables
NavMeshAgent agent;
// Enemy Collision Variables
public float damageCooldown = 1f;
// Start is called before the first frame update
void Start()
{
curHealth = maxHealth;
}
// Update is called once per frame
void Update()
{
// Pathfinding code(?)
agent = GetComponent<NavMeshAgent>();
agent.destination = goal.position;
//Health Bar Image
healthBar.fillAmount = curHealth / maxHealth;
//Death Function
if (curHealth < 1)
{
Destroy(gameObject);
ZombieSpawner.zombieCount--;
}
}
private void OnTriggerEnter(Collider other)
{
curHealth -= Weapon.damage;
}
private void OnCollisionStay(Collision collision)
{
if (damageCooldown > 0f)
{
damageCooldown = damageCooldown - 0.1f;
}
if (collision.rigidbody.name == "Player" && damageCooldown <= 0f)
{
RigidbodyFirstPersonController.playerCurHealth = RigidbodyFirstPersonController.playerCurHealth - 10;
damageCooldown = 1f;
}
}
public void TakeDamage(int damage)
{
curHealth -= damage;
}
private void Awake()
{
//agent = GetComponent<NavMeshAgent>();
}
}
Weapon.cs
using UnityEngine;
using UnityEngine.UI;
public class GunHit
{
public float damage;
public RaycastHit raycastHit;
}
public class Weapon : MonoBehaviour
{
//Weapon Model
public GameObject weaponModel;
public LayerMask mask;
//UI Variables
public Text ammoUI;
// Weapon Variables
static public int damage = 5;
public int ammo = 12;
public int MaxAmmo = 12;
public int reserveAmmo = 90;
public AudioSource fire;
public AudioSource empty;
public bool isAiming = false;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
ammoUI.text = ammo + "/" + reserveAmmo;
weaponFiring();
weaponReload();
}
void weaponAiming()
{
if (Input.GetKeyDown(KeyCode.Mouse1))
{
//weaponModel.transform.position = 0, 0, 0;
}
}
// Default Position = x0, y0, z0.5
public GameObject projectilePrefab;
public GameObject gunBarrel;
void weaponFiring()
{
if (Input.GetKeyDown(KeyCode.Mouse0) && ammo > 0)
{
fire.Play();
Instantiate(projectilePrefab, gunBarrel.transform.position, gunBarrel.transform.rotation);
ammo--;
}
if (Input.GetKeyDown(KeyCode.Mouse0) && ammo == 0)
{
empty.Play();
}
}
void weaponReload()
{
if (Input.GetKeyDown(KeyCode.R))
{
if (ammo == 0)
{
if (reserveAmmo >= MaxAmmo)
{
ammo = MaxAmmo;
reserveAmmo = reserveAmmo - ammo;
}
else
{
ammo = reserveAmmo;
reserveAmmo = 0;
}
}
else if (ammo < MaxAmmo)
{
//Tiddies code
reserveAmmo += ammo;
ammo = 0;
reserveAmmo -= ammo = (reserveAmmo < MaxAmmo ? (reserveAmmo) : (MaxAmmo));
/*
* My code
* ammo = MaxAmmo - ammo;
reserveAmmo = reserveAmmo - ammo;
if (reserveAmmo > MaxAmmo)
{
ammo = MaxAmmo;
}
else if (reserveAmmo < MaxAmmo)
{
ammo = reserveAmmo;
reserveAmmo = 0;
}*/
}
}
}
}
The issue is that your current health variable is static, that means that each enemy is using the same variable for health.
Static makes the varaible global to everybody.
remove the static modifier and the variable becomes personal to each enemy ;)

Why is my function runs twice in OnTriggerStay?

I have a simple game in Unity.
The situation is like if I kill an enemy, it drops a pickupable Health.
In the OnTriggerEnter, I handle the player collision with the health, and if the player misses health, it heals a certain amount. (70 exactly)
When player is standing on the health while at maximum health, it does nothing. But if player got damaged while standing on the health, the health should also be picked up, that is where OnTriggerStay comes in.
There I constantly check if the player needs heeling, if do, do the healing, and destroy the heal object.
My problem is that it runs twice. It heals the amount twice to the player. No matter what I do, the function I call in OnTriggerStay is gonna run twice. Why is that? Anybody knows the solution?
Here is the part of my Player.cs file, and my Heal.cs file:
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class Player : MonoBehaviour
{
//HP and MANA system
[SerializeField]
private int playerHealth;
[SerializeField]
private int playerMaxHealth = 100;
[SerializeField]
private Transform Healthbar;
private void OnTriggerStay(Collider other)
{
if (other.tag == "Enemy")
{
timer += Time.deltaTime;
if (timer > attackSpeed)
{
Enemy enemy = other.GetComponent<Enemy>();
if (enemy != null)
{
enemy.DamageEnemy(playerDamage);
}
timer = 0.0f;
}
}
if (other.tag == "PatrollingEnemy")
{
timer += Time.deltaTime;
if (timer > attackSpeed)
{
PatrollingEnemy enemy = other.GetComponent<PatrollingEnemy>();
if (enemy != null)
{
enemy.DamageEnemy(playerDamage);
}
timer = 0.0f;
}
}
if (other.tag == "Heal")
{
heal(other, false);
}
}
private void OnTriggerEnter(Collider other)
{
if (other.tag == "Heal")
{
heal(other, false);
}
else if (other.tag == "HealthPotion")
{
heal(other, true);
}
}
private void heal(Collider other, bool isPotion)
{
dynamic heal;
if (isPotion)
{
heal = other.GetComponent<HealthPotion>();
}
else
{
heal = other.GetComponent<Heal>();
}
if (playerHealth < playerMaxHealth && heal != null)
{
addHealthToPlayer(heal.HealAmount);
Destroy(other.gameObject);
}
}
private void addHealthToPlayer(int amount)
{
playerHealth += amount;
if (playerHealth > playerMaxHealth)
{
playerHealth = playerMaxHealth;
}
rescaleHealthBar();
}
private void rescaleHealthBar()
{
Healthbar.transform.localScale = new Vector3((float)playerHealth / (float)playerMaxHealth, 1.0f, 1.0f);
}
}
public class Heal : MonoBehaviour
{
float timer = 0.0f;
float destroyTimer = 0.0f;
float timeWhenDestroy = 15f;
Vector3 rotation = new Vector3(0, 45, 0);
private int healAmount = 70;
public int HealAmount
{
get
{
return healAmount;
}
}
void Update()
{
timer += Time.deltaTime;
destroyTimer += Time.deltaTime;
if (destroyTimer > timeWhenDestroy)
{
Destroy(this.gameObject);
}
transform.Rotate(rotation * Time.deltaTime);
if (timer < 1.0f)
{
transform.Translate(Vector3.up * Time.deltaTime);
}
if (timer > 1.0f)
{
transform.Translate(-Vector3.up * Time.deltaTime);
}
if (timer > 2.0f)
{
timer = 0.0f;
}
}
}
https://docs.unity3d.com/ScriptReference/Object.Destroy.html
Maybe both your events (Enter & Stay) are fired during that frame ?
https://docs.unity3d.com/ScriptReference/Collider.OnTriggerStay.html
OnTriggerStay is called almost all the frames for every Collider other that is touching the trigger. The function is on the physics timer so it won't necessarily run every frame.

How to make damage client sided in unity?

I'm converting a simple platformer game into a multiplayer game(my first ever attempt at a multiplayer game), and i have these objects that if the player touches them, the player gets damaged. I'm having slight problems with server registering damage that shouldn't be, so to avoid all future problems i wanted to make damage client-sided.
I want to make it so all damage is client sided and the clients health gets sent to the server.
Edit: Made question less confusing.
public class EnemyTrigger : MonoBehaviour
{
private PlayerStats player;
private CharacterController2D controller;
public int Damage;
public float horizontal;
public float vertical;
private void Start()
{
}
private void OnTriggerStay2D(Collider2D collider)
{
if (collider.tag == "Player" && collider.GetComponent<PlayerStats>().DamageTimer <= 0 && collider.GetComponent<PlayerStats>().isDashing == false)
{
player = collider.GetComponent<PlayerStats>();
controller = player.GetComponent<CharacterController2D>();
player.TakeDamage(Damage);
if (this.transform.position.x - player.transform.position.x > 0)
{
controller.KnockBackRight(horizontal);
}
else
{
controller.KnockBackLeft(horizontal);
}
controller.KnockBackDown(vertical);
}
}
}
public class PlayerStats : NetworkBehaviour {
[SyncVar] public int Health;
public GameObject BloodEffect;
public Transform HealthBar;
public float DefDamageTimer;
public float DamageTimer;
public bool isDashing;
public int facingDirection;
private Rigidbody2D m_Rigidbody2D;
private CharacterController2D controller;
public AnimationPlayer anim;
private void Awake()
{
m_Rigidbody2D = GetComponent<Rigidbody2D>();
controller = GetComponent<CharacterController2D>();
}
private void Update()
{
HealthBar.localScale = new Vector3(Health / 10f, 1f);
if (DamageTimer > 0)
{
DamageTimer -= Time.deltaTime;
}
CmdSendDashing(isDashing);
}
public void TakeDamage(int Damage)
{
Health -= Damage;
Instantiate(BloodEffect, transform.position, Quaternion.identity);
anim.Damaged();
Debug.Log("Player Has Taken " + Damage + " Damage");
DamageTimer = DefDamageTimer;
if (Health <= 0)
{
Destroy(gameObject);
}
CmdSendHealth(Health);
}
void CmdSendHealth(int health)
{
Health = health;
}
void CmdSendDashing(bool isdashing)
{
isDashing = isdashing;
}
}

Cause player to take damage in Unity 2D

I made two scripts. One that'll keep track of the player's health, health bar and cause the screen to flash when the player is damaged. The other script is meant to be placed on any object I wish to do damage to the player, on contact. My problem is, Nothing seems to be doing any damage to the player.
PlayerHealth.cs:
using UnityEngine;
using UnityEngine.UI;
public class PlayerHealth : MonoBehaviour
{
public int currentHealth;
public float flashSpeed = 5;
public Slider healthSlider;
public Color flashColour = new Color(1, 0, 0, 0.1f);
bool isDead;
bool damaged;
private void Awake()
{
currentHealth = 100;
}
private void Update()
{
damaged = false;
}
public void TakeDamage(int amount)
{
damaged = true;
currentHealth -= amount;
healthSlider.value = currentHealth;
}
}
AttackPlayer.cs:
using UnityEngine;
public class AttackPlayer : MonoBehaviour
{
public float timeBetweenAttacks = 0.5f;
public int attackDamage = 10;
GameObject player;
PlayerHealth playerHealth;
float timer;
private void Awake()
{
player = GameObject.FindGameObjectWithTag("Player");
playerHealth = player.GetComponent<PlayerHealth>();
}
private void OnTriggerEnter2D(Collider2D col)
{
if (col.gameObject == player)
{
Attack();
}
}
private void Update()
{
timer += Time.deltaTime;
if(playerHealth.currentHealth <=0)
{
// TODO: add death script here.
}
}
void Attack()
{
timer = 0f;
if(playerHealth.currentHealth > 0)
{
playerHealth.TakeDamage(attackDamage);
}
}
}
The player has a rigidbody2D. The player and damaging objects have Box Collider 2D's on them.
Make sure that the players Collider has isTrigger enabled.
attackDamage is public -> set in the inspector. Make sure it is not 0.
You could use
[Range(1,100)] public int attackDamage = 10;
to automatically clamp the value in the inspector.
A guess but I'ld say your Collider might not be on the GameObject player but probably on one of its children => the condition col.gameObject == player is not true.
Instead of GameObject references rather compare the PlayerHealth (since there is only one) reference like
private void OnTriggerEnter2D(Collider2D col)
{
// gets PlayerHealth component on this or any parent object
var health = col.GetComponentInParent<PlayerHealth>();
if (health == playerHealth)
{
Attack();
}
}
You have
private void Update()
{
damaged = false;
}
public void TakeDamage(int amount)
{
damaged = true;
currentHealth -= amount;
healthSlider.value = currentHealth;
}
I don't know what else should happen on TakeDamage but the value of damaged is resetted in Update so right after it was set by the Trigger because Physics events like OnTriggerEnter are executed before Update (see execution Order).
Hint: Instead of
player = GameObject.FindGameObjectWithTag("Player");
playerHealth = player.GetComponent<PlayerHealth>();
you could also use
playerHealth = FindObjectOfType<PlayerHealth>();
if that component exists only once in your scene.
Or to be more flexible (having multiple Players) all you have to do is change your OnTriggerEnter2D and Attack method to
private void OnTriggerEnter2D(Collider2D col)
{
// gets PlayerHealth component on this or any parent object
var health = col.GetComponentInParent<PlayerHealth>();
if (health != null)
{
Attack(health);
}
}
void Attack(PlayerHealth health)
{
timer = 0f;
if(health.currentHealth > 0)
{
health.TakeDamage(attackDamage);
}
}
So you wouldn't need to get the reference before.

Unity2D: How to make spawn object gradually go faster after the player collects 10 points?

so I was wondering if there was a way to make a spawned object gradually moves/go faster after the player (you the user) collects 10 points. And faster when the player collects another 10 points and so on and so on?
This is my movement script attach to my objects that get spawned in:
public class Movement : MonoBehaviour
{
public static int movespeed = 20;
public Vector3 userDirection = Vector3.right;
public void Update()
{
transform.Translate(userDirection * movespeed * Time.deltaTime);
}
}
This is my score script attach to my player
public int Score;
public Text ScoreText;
void Start ()
{
Score = 0;
SetScoreText ();
}
void OnTriggerEnter2D(Collider2D other)
{
if (other.gameObject.CompareTag ("Pick Up"))
{
other.gameObject.SetActive (false);
Score = Score + 1;
SetScoreText ();
}
}
void SetScoreText ()
{
ScoreText.text = "Score: " + Score.ToString ();
}
And this is my generateEnemy script:
public GameOverManager gameOverManager = null;
[HideInInspector]
public float minBlobSpawnTime = 2;
[HideInInspector]
public float maxBlobSpawnTime = 5;
[HideInInspector]
public bool generateBlobs = true;
[HideInInspector]
public GameObject blobPrefab = null;
[HideInInspector]
public GameObject blobRoot = null;
[HideInInspector]
public float minimumYPosition = -0.425f;
[HideInInspector]
public float maximumYPosition = 0.35f;
[HideInInspector]
public float minDaggerSpawnTime = 2;
[HideInInspector]
public float maxDaggerSpawnTime = 5;
[HideInInspector]
public bool generateDaggers = true;
[HideInInspector]
public GameObject daggerPrefab = null;
[HideInInspector]
public GameObject daggerRoot;
[HideInInspector]
public float minimumXPosition = -11.5f;
[HideInInspector]
public float maximumXPosition = 11.5f;
public Camera camera = null;
// Use this for initialization
void Start ()
{
generateBlobs = ((generateBlobs) && (blobPrefab != null) && (blobRoot != null));
generateDaggers = ((generateDaggers) && (daggerPrefab != null) && (daggerRoot != null));
if (camera == null)
{
Debug.LogError("GenerateEnemy: camera is not set in the inspector. Please set and try again.");
camera = Camera.main;
}
if (gameOverManager == null)
{
Debug.LogError("GenerateEnemy: gameOverManager not set in the inspector. Please set and try again.");
}
if (generateBlobs)
{
StartCoroutine(GenerateRandomEnemy(true, blobPrefab, blobRoot, minBlobSpawnTime, maxBlobSpawnTime));
}
if (generateDaggers)
{
StartCoroutine(GenerateRandomEnemy(false, daggerPrefab, daggerRoot, minDaggerSpawnTime, maxDaggerSpawnTime));
}
}
// Update is called once per frame
void Update ()
{
DestroyOffScreenEnemies();
}
// Spawn an enemy
IEnumerator GenerateRandomEnemy(bool generateOnYAxis, GameObject prefab, GameObject root, float minSpawnTime, float maxSpawnTime)
{
if ((prefab != null) && (gameOverManager != null))
{
if (!gameOverManager.GameIsPaused())
{
GameObject newEnemy = (GameObject) Instantiate(prefab);
newEnemy.transform.SetParent(root.transform, true);
// set this in the prefab instead
// newEnemy.transform.position = new Vector3(newEnemy.transform.parent.position.x, 0.5f, newEnemy.transform.parent.position.z);
// or if you want the y position to be random you need to do something like this
if (generateOnYAxis)
{
newEnemy.transform.position = new Vector3(newEnemy.transform.parent.position.x, Random.Range(minimumYPosition, maximumYPosition), newEnemy.transform.parent.position.z);
}
else
{
newEnemy.transform.position = new Vector3(Random.Range(minimumXPosition, maximumXPosition), newEnemy.transform.parent.position.y, newEnemy.transform.parent.position.z);
}
}
}
yield return new WaitForSeconds(Random.Range(minSpawnTime, maxSpawnTime));
StartCoroutine(GenerateRandomEnemy(generateOnYAxis, prefab, root, minSpawnTime, maxSpawnTime));
}
public void DestroyOffScreenEnemies()
{
GameObject[] enemies = GameObject.FindGameObjectsWithTag("enemy");
if ((enemies.Length > 0) && (camera != null))
{
for (int i = (enemies.Length - 1); i >= 0; i--)
{
// just a precaution
if ((enemies[i] != null) && ((camera.WorldToViewportPoint(enemies[i].transform.position).x > 1.03f) ||
(enemies[i].transform.position.y < -6f)))
{
Destroy(enemies[i]);
}
}
}
}
}
(This script has an generateEnemyCustomEditor script that references it, to show in the inspector)
Thank you :)
First of all, remove the static keyword from public static int movespeed = 20; and use GameObject.Find("ObjectMovementIsAttachedTo").GetComponent<Movement>(); to get the script instance if you want to modify movespeed variable from another script.
And faster when the player collects another 10 points and so on and so
on?
The solution is straight on. Use
if (Score % 10 == 0){
//Increement by number (4) movespeed from Movement script
movement.movespeed += 4;
}
to check if the Score is increased by 10 then increment movespeed by any value you want if that condition is true. It makes sense to put that in the OnTriggerEnter2D function after Score is incremented by 1.
Your new score script:
public class score : MonoBehaviour
{
public int Score;
public Text ScoreText;
private int moveSpeed;
void Start()
{
Score = 0;
SetScoreText();
}
void OnTriggerEnter2D(Collider2D other)
{
if (other.gameObject.CompareTag("Pick Up"))
{
other.gameObject.SetActive(false);
Score = Score + 1;
if (Score % 10 == 0)
{
//Increement by number (4) movespeed from Movement script
moveSpeed += 4;
}
SetScoreText();
}
}
public int getMoveSpeed()
{
return moveSpeed;
}
void SetScoreText()
{
ScoreText.text = "Score: " + Score.ToString();
}
}
Since your Movement script will be instantiated, when you instantiate it, you send its reference to the Player script.
public class Movement : MonoBehaviour
{
public int movespeed = 20;
public Vector3 userDirection = Vector3.right;
score mySpeed;
void Start()
{
//Send Movement instance to the score script
GameObject scoreGameObject = GameObject.Find("GameObjectScoreIsAttachedTo");
mySpeed = scoreGameObject.GetComponent<score>();
}
public void Update()
{
transform.Translate(userDirection * mySpeed.getMoveSpeed() * Time.deltaTime);
}
}

Categories