How to make damage client sided in unity? - c#

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;
}
}

Related

Formula to deal damage - armor

I'm just starting to learn c# and got stuck in this exercise.
I need to create a formula to deal damage - armor. The armor is reduced with each hit. If I write
float damage = hp+armor-(physicalDamage-damageStrength)it doesn't damage the enemy. The task says I should change only `float damage = ...' for this code to work. So, the mistake can be only in this line float damage =
[RequireComponent(typeof(Animator))]
public class Goblin : MonoBehaviour, IDamagable {
static private string hitTriggerName = "Hit";
[SerializeField] private float hp = 800; // health
[SerializeField] private float armor = 100; // armor
[SerializeField] private float armorStrength = 5; // armor decrease each time
[SerializeField] private int n = 0; // number of hits
private Animator selfAnimator;
private void Awake() {
selfAnimator = GetComponent<Animator>();
}
public void ApplyDamage(float physicalDamage, float damageStrength) {
float damage = hp+armor-physicalDamage-damageStrength;
if (damage < 0) {
hp += damage;
}
n += 1;
armor -= armorStrength;
selfAnimator.SetTrigger(hitTriggerName);
}
public void onHitAnimationEnd() {
if (hp <= 0) {
Kill();
}
}
private void Kill() {
Destroy(gameObject);
}
}```
***Corersponding part of a "player" object***
```using System;
using UnityEngine;
[RequireComponent(typeof(Animator))]
public class Player : MonoBehaviour {
static private string attackTriggerName = "Attack";
[SerializeField] private float attackCooldown = 0.1f;
[SerializeField] private Goblin aim;
private float timeToNextAttack = 0f;
private Animator selfAnimator;
private void Awake() {
selfAnimator = GetComponent<Animator>();
}
[Serializable] private class Staff {
[SerializeField] private float physicalDamage = 100; // staff damage
[SerializeField] private float damageStrength = 5; // damage reduction each time
public void Use(IDamagable aim) {
if (aim != null) {
aim.ApplyDamage(physicalDamage, damageStrength);
}
}
}
[SerializeField] private Staff weapon;
private void Attack() {
selfAnimator.SetTrigger(attackTriggerName);
weapon.Use(aim);
timeToNextAttack = attackCooldown;
}
private void Update() {
if (Input.GetKeyDown(KeyCode.Space) && timeToNextAttack <= 0) {
Attack();
}
timeToNextAttack -= Time.deltaTime;
}
}```
I may be wrong or didn't understand, but by quickly looking at the code, I'd suggest you declare the damage variable above the Awake function. And give it a 0.0f value by default.
I think you made two unrelated mistakes:
damage sould be only: float damage = armor-physicalDamage
damageStrength should not be a parameter of the method because it should be applied to the staff physicalDamage after the hit, like you did to the armor.

Unity Mirror, Client is not executing damage Command

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.

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 ;)

"Cannot reuse the bullets in obectpool."

When bulletpool is being instantiated, I can shoot bullets. But when all the bullets are done instantiated, I can't shoot the bullet. IDK where is the porblem..
BulletPoolScript:
public class scriptBulletPool : MonoBehaviour
{
private static scriptBulletPool myInstance;
public static scriptBulletPool MyInstance
public GameObject bulletPrefab;
public List<GameObject> bulletPool;
public int poolSize;
{
get
{
return myInstance;
}
}
private void Awake()
{
if (myInstance == null)
{
myInstance = this;
} else if(myInstance != this)
{
Debug.LogError("Obj 1:", gameObject);
Debug.LogError("Obj 2:", myInstance.gameObject);
}
instantiatePool();
}
private void instantiatePool()
{
bulletPool = new List<GameObject>();
for (int i=0; i<poolSize; i++)
{
GameObject newBullet = Instantiate(bulletPrefab);
bulletPool.Add(newBullet);
newBullet.SetActive(false);
}
}
public GameObject getBullet(Vector3 targetPos, Quaternion targetRot)
{
GameObject newBullet = bulletPool[bulletPool.Count - 1];
newBullet.transform.position = targetPos;
newBullet.transform.rotation = targetRot;
newBullet.SetActive(true);
bulletPool.Remove(newBullet);
return newBullet;
}
public void returnBullet(GameObject bullet)
{
bulletPool.Add(bullet);
bullet.SetActive(false);
}
}
ShootScript:
public class scriptShoot : MonoBehaviour
{
public GameObject bulletSpawnPoint;
void Update()
{
shoot();
}
private void shoot()
{
if (Input.GetMouseButtonDown(0))
{
scriptBulletPool.MyInstance.getBullet(bulletSpawnPoint.transform.position, bulletSpawnPoint.transform.rotation);
}
}
}
BulletScript:
public class scriptBullet : MonoBehaviour
{
public GameObject bullet;
public float maxDistance;
void Update()
{
transform.Translate(Vector3.forward * 7 * Time.deltaTime);
maxDistance += 1 * Time.deltaTime;
if (maxDistance >= 5)
{
scriptBulletPool.MyInstance.returnBullet(bullet);
}
}
}
The problem is probably the scriptBullet.maxDistance.
You increment it in every update, and eventually pool the bullet.
When you need a bullet, you unpool it, but the maxDistance is never reset, resulting in the bullet being pooled again immediately.

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.

Categories