using System.Collections;
public class TowerAttack : MonoBehaviour {
public float timeBetweenAttacks = 0.5f;
public int attackDamage = 10;
GameObject player;
PlayerHealth playerHealth;
bool playerInRange;
float timer;
void Awake()
{
player = GameObject.FindGameObjectWithTag ("Player");
playerHealth = player.GetComponent<PlayerHealth> ();
}
void OnTriggerEnter(Collider other)
{
print (other);
if (other.gameObject == player)
playerInRange = true;
}
void OnTriggerExit(Collider other)
{
if (other.gameObject == player)
playerInRange = false;
}
// Update is called once per frame
void Update () {
timer += Time.deltaTime;
if (timer >= timeBetweenAttacks && playerInRange)
Attack ();
if (playerHealth.currentHealth <= 0)
print ("Player's dead");
}
void Attack()
{
timer = 0f;
print (playerHealth);
if (playerHealth.currentHealth > 0)
playerHealth.TakeDamage (attackDamage);
}
}
I am using this code on an object called tower and it has box collider with istrigger active. There are multiple objects spawn on the field with "PlayerHealth" script attached and tagged as "Player". However only the first "Player" which enter the box collider get its health remove and the rest of the "Player" objects stay healthy.
There are multiple objects spawn on the field with "PlayerHealth"
script attached and tagged as "Player". However only the first
"Player" which enter the box collider get its health remove and the
rest of the "Player" objects stay healthy.
The playerHealth = player.GetComponent<PlayerHealth> (); line of code will only get the PlayerHealth from one object only.
To remove health from multiple enemies, you need to perform other.GetComponent<PlayerHealth> () each time OnTriggerEnter is called.
void OnTriggerEnter(Collider other)
{
print(other);
//Remove health only if the Object is tagged as Player
if (other.CompareTag("Player"))
{
other.GetComponent<PlayerHealth>().TakeDamage(attackDamage);
playerInRange = true;
}
}
player = GameObject.FindGameObjectWithTag ("Player");
This is for setting one player. If there are multiple player, one of players is set to the variable. playerHealth = player.GetComponent<PlayerHealth> (); is also only the player. You should do like below.
using System.Collections;
public class TowerAttack : MonoBehaviour
{
public float timeBetweenAttacks = 0.5f;
public int attackDamage = 10;
GameObject[] players;
bool playerInRange;
float timer;
void Awake()
{
players = GameObject.FindGameObjectsWithTag ("Player");
// FindGameObjectWithTag => FindGameObjectsWithTag
}
void OnTriggerEnter(Collider other)
{
print (other);
if (other.tag == "Player")
playerInRange = true;
}
void OnTriggerExit(Collider other)
{
if (other.tag == "Player")
playerInRange = false;
}
void Update ()
{
timer += Time.deltaTime;
if (timer >= timeBetweenAttacks && playerInRange)
Attack ();
for (int i = 0; i < players.Length; i++)
{
if (players [i].GetComponent<PlayerHealth> ().currentHealth <= 0)
print ("Player's dead")
}
}
void Attack()
{
timer = 0f;
print (playerHealth);
if (playerHealth.currentHealth > 0)
playerHealth.TakeDamage (attackDamage);
}
}
If you want to make the TowerAttack Attack() all players in range at the same time, you can apply the code above. But, if you want to attack players one after another, move the function in Update() to OnTrigger* methods. Then you may need this method OnTriggerStay.
Related
I am trying to create a multiplayer TPS where I instantiate a bullet locally (in my editor) and apply force to it. Once it hits other player I would want to call that players TakeDamage RPC.
Sadly I am encountering this weird error message that I can't fix (already taking me days).
my projectile script:
using Photon.Pun;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Arrow : MonoBehaviour
{
[SerializeField] float timeToDestroy;
[SerializeField] ParticleSystem hitEffect;
[SerializeField] TrailRenderer trailRenderer;
float timer;
private Rigidbody rb;
private Collider col;
private void Awake()
{
rb = GetComponent<Rigidbody>();
col = GetComponent<Collider>();
}
private void Update()
{
timer += Time.deltaTime;
if (timer > timeToDestroy) Destroy(gameObject);
}
private void OnCollisionEnter(Collision collision)
{
rb.isKinematic = true;
rb.velocity = Vector3.zero;
trailRenderer.enabled = false;
ShowHitEffect(collision.GetContact(0));
col.enabled = false;
if (collision.gameObject.CompareTag("Player"))
{
rb.isKinematic = true;
rb.velocity = Vector3.zero;
trailRenderer.enabled = false;
PhotonView pv = collision.gameObject.GetPhotonView();
Debug.Log("hit " + pv.Controller.NickName);
pv.RPC("DealDamage", RpcTarget.All, PhotonNetwork.LocalPlayer.NickName, .5f, PhotonNetwork.LocalPlayer.ActorNumber);
}
Destroy(gameObject, 2f);
}
private void ShowHitEffect(ContactPoint cp)
{
hitEffect.transform.position = cp.point;
hitEffect.transform.forward = cp.normal;
hitEffect.Emit(1);
}
}
weapon controller script
using Photon.Pun;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class WeaponController : MonoBehaviourPunCallbacks
{
[Header("Fire Rate")]
[SerializeField] float fireRate;
float fireRateTimer;
[SerializeField] bool semiAuto;
[Header("Bullet Property")]
[SerializeField] GameObject bullet;
[SerializeField] Transform barrelPos;
[SerializeField] float bulletVelocity;
[SerializeField] byte bulletPerShot;
public AimController aim;
[SerializeField] AudioClip gunshot;
public AudioSource audioSource;
public WeaponAmmo ammo;
public ParticleSystem muzzleFlash;
public ParticleSystem hitEffect;
// Start is called before the first frame update
void Start()
{
fireRateTimer = fireRate;
}
private void Update()
{
if (!photonView.IsMine) return;
if (ShouldFire()) Fire();
}
bool ShouldFire()
{
fireRateTimer += Time.deltaTime;
if (fireRateTimer < fireRate) return false;
if (ammo.currentAmmo == 0) return false;
if (semiAuto && Input.GetKeyDown(KeyCode.Mouse0)) return true;
if (!semiAuto && Input.GetKey(KeyCode.Mouse0)) return true;
return false;
}
[PunRPC]
private void EmitMuzzleFlash()
{
muzzleFlash.Emit(1);
}
void Fire()
{
fireRateTimer = 0;
barrelPos.LookAt(aim.actualAimPos);
audioSource.PlayOneShot(gunshot);
EmitMuzzleFlash();
//photonView.RPC("EmitMuzzleFlash", RpcTarget.All);
ammo.currentAmmo--;
for (int i = 0; i < bulletPerShot; i++)
{
GameObject currentBullet = Instantiate(bullet, barrelPos.position, barrelPos.rotation);
Rigidbody rigidbody = currentBullet.GetComponent<Rigidbody>();
rigidbody.AddForce(barrelPos.forward * bulletVelocity, ForceMode.Impulse);
// Projectile Instantiate
/*{
GameObject currentProjectile = (GameObject)PhotonNetwork.Instantiate(bullet.name, barrelPos.position, barrelPos.rotation);
Rigidbody rb = currentProjectile.GetComponent<Rigidbody>();
rb.AddForce(barrelPos.forward * bulletVelocity, ForceMode.Impulse);
}*/
}
}
}
player controller script (where rpc is located)
using Photon.Pun;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerController : MonoBehaviourPunCallbacks
{
private PlayerStats stats = new PlayerStats();
void Start()
{
if (photonView.IsMine)
{
UIController.instance.healthSlider.maxValue = stats.maxHealth;
UIController.instance.healthSlider.value = stats.GetCurrentHealth();
}
Cursor.lockState = CursorLockMode.Locked;
}
// Update is called once per frame
void Update()
{
FocusWindows();
}
void FocusWindows()
{
if (Input.GetKeyDown(KeyCode.Escape))
{
Cursor.lockState = CursorLockMode.None;
}
else if (Cursor.lockState == CursorLockMode.None)
{
if (Input.GetMouseButton(0))
{
Cursor.lockState = CursorLockMode.Locked;
}
}
}
#region
[PunRPC]
public void DealDamage(string damager, float damageAmount, int actor)
{
Debug.Log("called rpc deal damage");
Debug.Log("damager " + damager + " damageAmount " + damageAmount + " actor " + actor);
TakeDamage(damager, damageAmount, actor);
}
public void TakeDamage(string damager, float damageAmount, int actor)
{
if (photonView.IsMine)
{
stats.ReduceHealth(damageAmount);
if (stats.GetCurrentHealth() == 0)
{
PlayerSpawner.Instance.Die(damager);
MatchManager.instance.UpdateStatSend(actor, 0, 1);
}
UIController.instance.healthSlider.value = stats.GetCurrentHealth();
}
}
#endregion
}
Projectile Prefab(resource)
enter image description here
Player prefab
enter image description here
Weird thing is if I go with "Instantiate Arrow in the network" the rpc is getting called. But this is not ideal since it will because it will send the transform of the projectile every time it is flying. So I just want to check in the local (arrow with no photonView) if it hits an enemy then call the rpc of that enemy to make him take damage and possibly display a fake project coming from the damager. If my approach is naive / wrong please tell me a better approach please.
I transfered the rpc call on the same script where the photonView is located as mentioned by #derHugo. I think the main problem was my understanding of RPC. I thought rpc is like a public method where you can call it outside. I think I need to reread the documentations about rpc and raise events.
I've created a "game" where you can attack enemies with a sword. All damages applies to only one enemy, even if i attack the other one. Also when I destroy the first I get this message
MissingReferenceException: The object of type 'RectTransform' has been destroyed but you are still trying to access it.
How do I fix it?
Here's my code:
Attached to the enemy
public class EnemyHealth : MonoBehaviour
{
private Transform healthBar;
private Transform canvas;
public float maxHealth = 100f;
public float damage = 10f;
float currentHealth;
float barLength;
private void Start()
{
currentHealth = maxHealth;
GameObject gfx = gameObject.transform.Find("GFX").gameObject;
canvas = gfx.transform.Find("Health Bar");
GameObject canv = canvas.gameObject;
healthBar = canv.transform.Find("Green Bar");
barLength = canvas.GetComponent<RectTransform>().rect.width * canvas.localScale.x;
}
public void GetStruck()
{
currentHealth -= damage;
if (currentHealth < 0) currentHealth = 0;
healthBar.localScale = new Vector3(currentHealth / maxHealth, 1f, 1f);
Vector3 p = healthBar.position;
p -= damage / (maxHealth * 2) * barLength * transform.right * -1;
healthBar.position = p;
if(currentHealth == 0)
{
Destroy(gameObject);
}
}
}
And the sword attack
using UnityEngine;
public class SwordAttack : MonoBehaviour
{
public Animator animator;
private EnemyHealth enemyHealth;
private bool isAttacking;
private void Start()
{
enemyHealth = GameObject.FindGameObjectWithTag("Enemy").GetComponent<EnemyHealth>();
}
void Update()
{
if(Input.GetMouseButtonDown(0))
{
isAttacking = true;
animator.Play("swordSwing", -1, 0f);
}
else if(animator.GetCurrentAnimatorStateInfo(0).normalizedTime > 1)
{
isAttacking = false;
}
animator.SetBool("isAttacking", isAttacking);
}
private void OnTriggerEnter(Collider other)
{
if(isAttacking && other.CompareTag("Enemy"))
{
enemyHealth.GetStruck();
}
}
}
private void Start()
{
enemyHealth = GameObject.FindGameObjectWithTag("Enemy").GetComponent<EnemyHealth>();
}
This is your mistake. You are only getting 1 enemy's health at your start method and you are always decreasing from that enemy's health. Instead you can get the hitted enemy's health in your OnTriggerEnter method and apply damage to that enemy.
My problem is that I'm making a game with unity, and what I want to do is when the enemy in my game hits the obstacle, it does x damage every one second.
There's a collider and health script on the "desk" obstacle, and a collider and script for movement and attacking on the enemy.
The enemy stops when colliding with the desk object, and does damage! but, the damage is continuous... I've tried coroutines and Invoke's but all the results are the same; 10 damage per trigger detection, not per second.
Here are the scripts:
Enemy movement and attack:
private Animator anim;
public GameObject Desk;
private DeskHealth deskHealth;
//private bool inQueue = false;
private bool collidingWithDesk;
public float timeAfterStart;
[Range(0,10)]
public float attackSpeedEnemy = 1f;
[Range(10,100)]
public float walkSpeed;
[SerializeField] float damageDealtToDesk = 10f;
void Start () {
anim = GetComponent<Animator>();
deskHealth = GetComponent<DeskHealth>();
collidingWithDesk = false;
}
void Update()
{
if (collidingWithDesk == false)
{
enemyIsWalking();
}
}
void enemyIsWalking()
{
transform.Translate(0, 0, (-walkSpeed * Time.deltaTime));
}
IEnumerator enemyIsAttacking()
{
var deskHealth = Desk.GetComponent<DeskHealth>();
deskHealth.DealDamageToDesk(damageDealtToDesk);
yield return new WaitForSeconds(5f);
}
void OnTriggerStay(Collider otherCollider)
{
if (otherCollider.tag == "Desk")
{
collidingWithDesk = true;
transform.Translate(0, 0, 0);
anim.Play("enemyHit");
StartCoroutine(enemyIsAttacking());
}
}
desk Health:
[SerializeField] float deskHealth;
Animator anim;
public void DealDamageToDesk(float deskDamage)
{
deskHealth -= deskDamage;
if (deskHealth <= 0)
{
print("am ded");
//anim.Play("deskDestroyed");
}
}
For what the doc says "OnTriggerStay is called once per physics update for every Collider other that is touching the trigger." Which mean that your code :
if (otherCollider.tag == "Desk")
{
collidingWithDesk = true;
transform.Translate(0, 0, 0);
anim.Play("enemyHit");
StartCoroutine(enemyIsAttacking());
}
will be called every update then pause for 5 seconds because of yield return new WaitForSeconds(5f); You should use OnColliderEnter or it might be even better to use OnTriggerEnter instead, because this one will be called once. Then change the value inside the WaitForSeconds call to 1 if you want it once per second.
Also as specified by Draco18s your yield wait for nothing.
You should do something like that :
IEnumerator enemyIsAttacking()
{
var deskHealth = Desk.GetComponent<DeskHealth>();
while(collidingWithDesk)
{
deskHealth.DealDamageToDesk(damageDealtToDesk);
yield return new WaitForSeconds(1f);
}
}
void OnTriggerExit(Collider otherCollider)
{
if (otherCollider.tag == "Desk")
{
collidingWithDesk = false;
// rest of your code
}
}
The problem is that I what to create an openable door. This door should open when the player enter the Box Collider which is connected to the door. But the problem is when the door begins to open and to rotate, Collider starts to rotate too which brings me a lot of problems with usind such an idea. I try to create EmptyObject with its Collider but I can't connect this Collider with script and OnTriggerEnter function itself. Maybe I don't understand something, who knows, I'm just a begginer. How knows how to help, please write an answer.
My code if somebody needs it:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class openDoor : MonoBehaviour {
public Vector3 Rotation_;
private int i;
public float speed;
bool opentheDoor;
bool closetheDoor;
// Use this for initialization
void Start () {
opentheDoor = false;
closetheDoor = false;
}
// Update is called once per frame
void Update () {
if (opentheDoor == true) {
this.transform.Rotate (Rotation_ * Time.deltaTime * speed);
i += 1;
if (i == 70) {
opentheDoor = false;
i = 0;
}
}
if (closetheDoor == true) {
this.transform.Rotate (-Rotation_ * Time.deltaTime * speed);
i += 1;
if (i == 70) {
closetheDoor = false;
i = 0;
}
}
}
void OnTriggerEnter (Collider other) {
if (other.gameObject.tag == "Player") { {
opentheDoor = true;
}
}
}
void OnTriggerExit (Collider other) {
if (other.gameObject.tag == "Player") {
closetheDoor = true;
}
}
}
This is how i would handle the scenerio
Take
DoorHandler.cs
public class DoorHandler : MonoBehaviour {
public Door door;
private void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Player"))
{
door.OpenDoor();
}
}
}
This should be attached to Parent of the door.
Next Take
Door.cs
public class Door : MonoBehaviour {
public bool isOpened = false;
public void OpenDoor()
{
if (!isOpened)
{
isOpened = true;
Debug.Log("OPEN");
//OPEN DOOR CODE!
}
}
}
Attach this to the Door GameObject
NOTE
The hierarchy would be like DoorHandler->Door->DoorModel (where Door is just an empty gameobject pivot of the Door)
In DoorHandler GameObject attach BoxCollider and Check Mark IsTrigger.
Also Player SHOULD HAVE A RIGIDBODY (preferably Kinametic) and obviously a collider
So When Player enters the DoorHandler's Collider -> The DoorHandler's OnTriggerEnter will be triggered and finally Call the Door to OpenDoor()
Add another check in OnTriggerEnter that checks if the door is currently opening or not.
void OnTriggerEnter (Collider other) {
if (other.gameObject.tag == "Player" && !opentheDoor) {
opentheDoor = true;
}
}
attach the door to an empty object. put the trigger on the empty object. then make the ontrigger entry rotate the door, not the paent object, and the collider will remain in place.
Parent
-child(door)
-child(collider)
I am developing a 2D game. There are different enemies in the game, but I am being stuck with one of them. The enemy is a monster with a hammer in the hand. When the player enters in it's range, it runs towards the player and attacks him with the hammer. Everything worked fine. I made a prefab of it and used it in the rest of my game. But then I noticed that enemy is attacking and even the script is working as I can see the hammer collider at the time of enemy attack. But that collider was not damaging the player. I checked everything from script to tags and colliders, but nothing worked. Then I created a separate scene to sort out the issue. I just dragged my player and the enemy from prefab folder to the scene and guess what it was working there. Which mean that if there was only one enemy (one instance of it), everything worked but not when I created the second instance of the same enemy with the same scripts and everything else. Just can't sort out the issue. Hammer Enemy
Monster script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Monster : MonoBehaviour {
private static Monster instance;
public static Monster Instance
{
get {
if (instance == null) {
instance = GameObject.FindObjectOfType<Monster> ();
}
return instance;
}
}
//Movement Variables
public float movementSpeed;
private IMonsterState currentState;
public Animator MyAnimator{ get; set;}
public GameObject Target{ get; set;}
bool facingRight;
public bool Attack{ get; set;}
public bool TakingDamage{ get; set;}
public float meleeRange;
public Transform leftEdge;
public Transform rightEdge;
public float pushBackForce;
public EdgeCollider2D attackCollider{ get; set;}
//public EdgeCollider2D monsterhammer;
public bool InMeleeRange
{
get{
if (Target != null) {
return Vector2.Distance (transform.position, Target.transform.position) <= meleeRange;
}
return false;
}
}
// Use this for initialization
void Start () {
ChangeState(new IdleState());
MyAnimator = GetComponent<Animator> ();
attackCollider = GetComponentInChildren<EdgeCollider2D> ();
//attackCollider=monsterhammer;
facingRight = true;
}
// Update is called once per frame
void FixedUpdate () {
if (!Attack) {
attackCollider.enabled = false;
}
if (!TakingDamage) {
currentState.Execute ();
}
LookAtTarget ();
}
private void LookAtTarget()
{
if (Target != null) {
float xDir = Target.transform.position.x - transform.position.x;
if (xDir < 0 && facingRight || xDir > 0 && !facingRight) {
ChangeDirection ();
}
}
}
public void ChangeState(IMonsterState newState)
{
if (currentState != null) {
currentState.Exit ();
}
currentState = newState;
currentState.Enter (this);
}
public void Move()
{
if (!Attack) {
if ((GetDirection ().x > 0 && transform.position.x < rightEdge.position.x) || (GetDirection ().x < 0 && transform.position.x > leftEdge.position.x)) {
MyAnimator.SetFloat ("speed", 1);
transform.Translate (GetDirection () * (movementSpeed * Time.deltaTime));
}
else if (currentState is MonsterPatrol)
{
ChangeDirection ();
}
}
}
public Vector2 GetDirection()
{
return facingRight ? Vector2.right : Vector2.left;
}
public void ChangeDirection()
{
facingRight = !facingRight;
Vector3 theScale = transform.localScale;
theScale.x *= -1;
transform.localScale = theScale;
}
public void MoveLeft()
{
facingRight = false;
Vector3 theScale = transform.localScale;
theScale.x *= -1;
transform.localScale = theScale;
}
public void MoveRight()
{
facingRight = true;
Vector3 theScale = transform.localScale;
theScale.x *= -1;
transform.localScale = theScale;
}
void OnTriggerEnter2D(Collider2D other)
{
currentState.OnTriggerEnter (other);
}
void OnCollisionStay2D(Collision2D other)
{
if (other.gameObject.tag == "Player") {
playerHealth thePlayerHealth = other.gameObject.GetComponent<playerHealth> ();
thePlayerHealth.addDamage (2);
//if (playerHealth.damaged) {
pushBack (other.transform);
//}
}
}
void pushBack(Transform pushedObject)
{
//Vector2 pushDirection = new Vector2 (0, (pushedObject.position.y - transform.position.y)).normalized;
//pushDirection *= pushBackForce;
Rigidbody2D pushRB = pushedObject.gameObject.GetComponent<Rigidbody2D> ();
pushRB.velocity = Vector2.zero;
if (pushedObject.position.x > transform.position.x) {
pushRB.AddRelativeForce (Vector3.right * pushBackForce);
} else {
pushRB.AddRelativeForce (Vector3.left * pushBackForce);
}
}
public void MeleeAttack()
{
attackCollider.enabled = true;
}
}
The problem most likely lies with:
get {
if (instance == null) {
instance = GameObject.FindObjectOfType<Monster> ();
}
return instance;
}
GameObject.FindObjectOfType<Monster>(); Will always return the first object found of this type as stated in the docs under description.
This means that when you add multiple Monsters into your scene your variable instance will get filled with the same Monster for all instances (the first one it finds in your hierarchy)
Now since you havn't posted your Player script I'll have to do some assuming now:
You are probably checking your <Monster> instance somewhere in your player script to see if it is near enough to the player to attack and hurt it. This will not be the case for all monsters except the single monster found by your FindObjectOfType<Monster>() . You could test this by manually placing each monster right next to your player, and most likely if for example you have 5 monsters in your scene 1 will attack, and 4 won't.
To fix this you can:
Assuming you want the current instance of the Monster script in instance simply apply this to it (instance = this)
store all your monsters in an array using FindObjectsOfType<Monster>() (notice the s after object) which will return all instances of the type monster. as found in the docs