I have multiple enemies set up in a level, all using the same behaviour and animator scripts. When I hit or kill one of them, all of them get hit or killed. I need them to function as separate instances.
I have tried referencing only an instance of the script:
private GoblinBehaviour goblin;
goblin = GetComponent<GoblinBehaviour>();
goblin.IncrementHits(1);
But that doesn't work. An error arises that says that script can't be accessed with an instance and needs a type instead.
code for hit detection script:
public class RangedDetection : MonoBehaviour
{
private GoblinBehaviour goblin;
void OnTriggerEnter(Collider other)
{
//on colliding destroy rocks after its life time
Destroy(gameObject, rockLifeTime);
if (other.gameObject.tag.Equals("Enemy"))
//if (other.tag == "Enemy")
{
Destroy(other.gameObject);
}
else if (other.gameObject.tag.Equals("Goblin"))//goblin should play
death animation
{
goblin = GetComponent<GoblinBehaviour>();
goblin.IncrementHits(1);
GetComponent<BoxCollider>().enabled = false; //Removing hit
collider so it only hits target once.
}
}
}
Simplified Code for goblin script:
public class GoblinBehaviour : MonoBehaviour
{
Transform player;
public static bool isDead;
public static bool isPunching;
public static bool isHit;
public GameObject particles;
public Animator anim;
public void IncrementHits(int hitCount)
{
isHit = true;
hits += hitCount;
if (hits >= 2) isDead = true;
}
void Die()
{
Instantiate(particles, transform.position, transform.rotation);
Destroy(gameObject);
}
void updateAnim()
{
anim.SetBool("Punch_b", isPunching);
anim.SetBool("hit", isHit);
}
}
Things should animate and act separately, I'm not sure how to reference only the current instance of the script.
While your code is incomplete and the problem cannot be said for sure, it looks like you are using statics improperly.
Static properties are instance analagous. In other words, all of your goblin instances share any static properties (isPunching, isHit, isDead, etc.). Making these values static allows you to reference them directly without having to obtain the instance you're affecting, but results in you updating all goblins at once.
Your solution will involve removing the static modifier from your GoblinBehaviour properties unless the properties are meant to be shared across all instances.
Related
I am making Replay logic for my game, where when I click replay I got to the Main Page. The problem I am facing is that after clicking Play on the game after coming from Replay, the Zombie character in my game is not showing up. The game is running without the player. I am posting the script, check the Replay function which is attached to Replay button in the game.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Assertions;
public class GameManager : MonoBehaviour
{
public static GameManager instance = null;
private bool playerActive = false;
private bool gameOver = false;
private bool gameStarted = false;
private GameObject newZombie;
[SerializeField] private GameObject mainMenu; //contains main menu content
[SerializeField] private GameObject endGame; //contains game over content
[SerializeField] private GameObject zombie;
public bool PlayerActive{
get{
return playerActive;
}
}
public bool GameOver{
get{
return gameOver;
}
}
public bool GameStarted{
get{
return gameStarted;
}
}
void Awake()
{
if(instance == null){
instance = this;
}else if(instance != this){
Destroy(gameObject);
}
Assert.IsNotNull(mainMenu);
Assert.IsNotNull(endGame);
DontDestroyOnLoad(gameObject);
}
// Start is called before the first frame update
void Start()
{
endGame.SetActive(false);
mainMenu.SetActive(true);
}
// Update is called once per frame
void Update()
{
}
public void PlayerCollided()
{
gameOver = true;
endGame.SetActive(true);
mainMenu.SetActive(false);
DontDestroyOnLoad(gameObject);
}
public void PlayerStartedGame()
{
playerActive = true;
}
public void EnterGame()
{
endGame.SetActive(false);
mainMenu.SetActive(false);
gameStarted = true;
}
public void Replay()
{
endGame.SetActive(false);
mainMenu.SetActive(true);
gameOver = false;
newZombie = Instantiate(zombie) as GameObject;
}
There are a lot of assumptions we have to make based on the information you gave.
Try instantiating the zombie on a specific location. You're using Instantiate(gameObject), but there's a different variant to the Instantiate method which also takes a Vector3 position to spawn the object as a second argument.
If that doesn't work, please reply with answers to the following questions:
Does the zombie spawn at all (is it in the hierarchy)
Which methods exactly do the buttons invoke, for example:
[1]: https://i.stack.imgur.com/9xbgy.png
You're using a singleton pattern but you don't change scenes in this class. Are you changing scenes in any of your scripts? If yes, you will have to consider looking into them because every script that is persisting between scenes with the DontDestroyOnLoad() method could potentially interfere with your player.
Vlad
This question already has answers here:
What is a NullReferenceException, and how do I fix it?
(27 answers)
Closed 1 year ago.
For some reason when I'm in the normal view in-game I am able to link the scripts that I need to call in order to make an animation like so
however, whenever I start the game it automatically removes them from the slots, and it doesn't work
I am completely clueless as to why this may be happening and unity keep giving me errors that say that I'm not setting an instance to my script I really have no clue why this may be happening.
I have 3 scripts that I'm working with that is giving me the problem
one is the main script for the enemy vision (I am referencing the other scripts in this one)
the second is the enemy animation script which makes him go from idle to attack and the third is an animation for the gun model since I had to make it follow the hands of the enemy
scripts attached bellow
1st script(enemyAI):
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EnemyAi : MonoBehaviour
{
public bool detected;
GameObject target;
public Transform enemy;
public GameObject Bullet;
public Transform shootPoint;
public float shootSpeed = 10f;
public float timeToShoot = 1f;
public EnemyAni Animation;
public GunAni GunAnimation;
void Start()
{
Animation = GetComponent<EnemyAni>();
GunAnimation = GetComponent<GunAni>();
}
public void Update()
{
//makes the enemy rotate on one axis
Vector3 lookDir = target.transform.position - transform.position;
lookDir.y = 0f;
//makes enemy look at the players position
if (detected)
{
enemy.LookAt(target.transform.position, Vector3.up);
enemy.rotation = Quaternion.LookRotation(lookDir, Vector3.up);
}
if (detected == true)
{
Animation.LookPlayer = true;
GunAnimation.ShootPlayer = true;
}
if (detected == false)
{
Animation.LookPlayer = false;
GunAnimation.ShootPlayer = false;
}
}
//detects the player
void OnTriggerEnter(Collider other)
{
if (other.tag == "Player")
{
detected = true;
target = other.gameObject;
}
}
void OnTriggerExit(Collider other)
{
if (other.tag == "Player")
{
detected = false;
}
}
}
2nd Script (EnemyAnimation):
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EnemyAni : MonoBehaviour
{
public Animator animator;
public bool LookPlayer;
public void Start()
{
animator = GetComponent<Animator>();
}
public void Update()
{
if (LookPlayer == true)
{
animator.SetBool("IsShootingStill", true);
}
else
{
animator.SetBool("IsShootingStill", false);
}
}
}
3rd script (GunAnimation):
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GunAni : MonoBehaviour
{
public Animator animator;
public bool ShootPlayer;
public void Start()
{
animator = GetComponent<Animator>();
}
public void Update()
{
if (ShootPlayer == true)
{
animator.SetBool("IsShooting", true);
}
else
{
animator.SetBool("IsShooting", false);
}
}
}
Your code:
void Start()
{
Animation = GetComponent<EnemyAni>();
GunAnimation = GetComponent<GunAni>();
}
Searches the GameObject that holds the EnemyAI script for EnemyAni and GunAni. If the GameObject doesn't have those it will return null.
Sounds like you want to remove that code.
Note that if the scripts exist on a child of that GameObject you will need to use GetComponentInChildren
There are some things to review, good practices:
GameObject target is private for C#, but it is better to put private before.
variable names like Bullet Animation GunAnimation should always begin with lowercase, because they might be confused with a Class Name (search in internet for CamelCase and PascalCase).
Name should be clear. EnemyAni Animation is not clear enough, because Animation maybe any animation (player, enemy, cube, car, etc.).
It is better enemyAnimation to be clear, as you did with GunAnimation (only just with lower case at beginning)
As slaw said, the Animation must be in the GO attached.
about last item
Let's say you have an empty GO (GameObject) called GameObject1 and you attach EnemyAi script to it.
In Runtime (when game mode begins), it will try to find Animation = GetComponent<EnemyAni>();, but it won't find it
Why?
Because GetComponent<> searches for the Component(Class) that is in the <> (in this case EnemyAni) in its GO (in this case, GameObject1), but the unique script attached to GameObject1 is EnemyAI.
So, you have 3 options:
Attach EnemyAni to GameObject1
Create another GO (for example, GameObjectEnemyAni), attach the script EnemyAni and drag and drop GameObjectEnemyAni to GameObject1 and delete Animation = GetComponent<EnemyAni>(); in Start
Keep in mind: if you leave that line of code, instead of getting the script EnemyAni from GameObjectEnemyAni, it will run the code Animation = GetComponent<EnemyAni>(); in Start, and obviously, it won't find it
Create events. It's a really good practice for avoiding code tight coupling, but that is more advanced stuff.
This is a simple script that turns a saw blade in my game. The problem is there is approx 18 active blades on the scene, at a time. I am trying to eliminate any probability of lag. This made me wonder if using such a script in "Update", can cause lag?
public class SawBladesRotate : MonoBehaviour
{
public bool GameOver;
public GameObject Player;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
GameOver = Player.GetComponent<PlayerController>().GameOver;
if(GameOver == false)
{
transform.Rotate(new Vector3(0, 0, -45) * Time.deltaTime);
}
}
}
Put this on top of the Start method as class field
private PlayerController playerController;
and this into Start:
playeController = Player.GetComponent<PlayerController>()
Then re-use the reference:
private void Update()
{
if(playerController.GameOver) return;
//...
}
The rest is fine but ofcourse it always depends completely on your usecase.
Even more efficient it would be to directly reference the Component within unity:
[SerializeField] private PlayerController playerController;
Now you can simply drag&drop the Player GameObject into that field in the Inspector and can get rid of the GetComponent call.
I have a GameObject that I am spawning from a pool, that when I try to return it to the pool I get the error:
Setting the parent of a transform which resides in a prefab is
disabled to prevent data corruption.
I have used my ObjectPooler in many projects and have never once encountered this error.
I have created simple test GameObjects and it still throws this error, using the below code.
I have a Bomb abstract class. I have a PowerBomb class that inherits from Bomb. Bomb conforms to an interface IExplodable.
When my player spawns a bomb, I put it as the child of the player object so the player can move around with the bomb.
My ObjectPool has the correct bomb gameobject prefab ( a PowerBomb ). and my player has the same prefab as a reference to spawn. They player also holds a reference so it knows what it is holding.
I have spent the better part of 2 days trying to figure out why my PowerBomb will not return to the object pool.
Any help?
Bomb.cs
public abstract class Bomb : MonoBehaviour, IExplodable {
//Interface
public virtual void OnDetonate() {
Die();
}
public virtual void Die() {
Debug.Log("Bomb Die()");
ObjectPool.instance.PoolObject(gameObject);
}
}
PowerBomb.cs
public class PowerBomb : Bomb {
[SerializeField]
private AudioClip sfxSpawn;
}
BombManager.cs
public GameObject bomb;
public void SpawnBomb(){
GameObject obj = ObjectPool.instance.GetObject(bomb);
if (obj == null) return;
obj.SetActive(true);
PlayerInstance.HoldObject(obj);
}
public void DetonateBomb(){
bomb.GetComponent<PowerBomb>().OnDetonate();
}
Player.cs
[SerializeField]
private bool isHolding = false;
public bool IsHolding {
get { return isHolding; }
set { isHolding = value; }
}
private GameObject holdingObject;
public GameObject HoldingObject {
get { return holdingObject; }
set { holdingObject = value;}
}
public void HoldObject(GameObject o) {
HoldingObject = o;
HoldingObject.transform.SetParent(transform);
HoldingObject.transform.position = holdingArea.transform.position;
IsHolding = true;
}
ObjectPool.cs
private GameObject containerObject;
private List<GameObject>[] pooledObjects;
//Code removed for brevity. Getting from the pool is fine.
public void PoolObject(GameObject obj) {
for (int i = 0; i < objectsToPool.Count; i++) {
if (objectsToPool[i].objectToPool.name.Equals(obj.name)) {
obj.SetActive(false);
//THIS LINE IS THROWING THE ERROR
obj.transform.SetParent(containerObject.transform);
pooledObjects[i].Add(obj);
return;
}
}
}
You don't and can't modify a prefab. It might work in the Editor if you use some Editor API to modify it but it won't during run-time and it won't change the changes. You are getting this error because obj is a prefab which you are trying to modify at obj.transform.SetParent(containerObject.transform);.
You don't even use prefabs in a pool. That's not normal. What you do do is use the Instantiate function to instantiate the prefab then put the instantiated object in a pool.
GameObject obj = Instantiate(prefab, new Vector3(i * 2.0F, 0, 0), Quaternion.identity);
//Add instantiated Object to your pool List
pooledObjects[i].Add(obj);
I'm doing some practice with Unity. What I'm basically trying to do is by using the singleton design pattern from the gameManager script. I want to prompt the player to hit space, causing score to increase after each time, health to decrease after each time, and money increase once health reaches 0 and the cube is destroyed. I think I got some script assignments mixed up or I'm not sure how to reference the right game object. For whatever reason, hitting space won't increase score or decrease health.
I got 3 texts: score, health, and money, along with a cube in the middle of the scene, with the script, DestroyBySpace, assigned to it.
I have a gameManager empty object assigned to the gameManager script
Here's the script for Game Manager:
private int currentScore;
private int currentHealth;
private int currentMoney;
public Text scoreText;
public Text healthText;
public Text moneyText;
public static GameManager instance;
void Start()
{
currentScore = 0;
currentHealth = 20;
currentMoney = 0;
updateScore();
updateMoney();
updateHealth();
}
void Awake()
{
instance = this;
}
public void adjustScore(int newScore)
{
currentScore += newScore;
updateScore();
}
public void adjustHealthAndMoney(int newHealth, int newMoney)
{
currentHealth -= newHealth;
updateHealth();
if (currentHealth == 0)
{
currentMoney += newMoney;
updateMoney();
Destroy(gameObject);
}
}
void updateScore()
{
scoreText.text = "Score: " + currentScore;
}
void updateHealth()
{
healthText.text = "Health: " + currentHealth;
}
void updateMoney()
{
moneyText.text = "Money: " + currentMoney;
}
and here's my script for DestroyBySpace:
public int scoreValue;
public int healthValue;
public int moneyValue;
public GameObject target;
public GameManager gameManager;
void Start()
{
GameObject gameManagerObject = GameObject.FindWithTag("GameManager");
if (gameManagerObject != null)
{
gameManager = gameManagerObject.GetComponent<GameManager>();
}
if (gameManager == null)
{
Debug.Log("Cannot find Game Manager script");
}
}
void onTriggerEnter()
{
if (Input.GetKeyDown(KeyCode.Space))
{
GameManager.instance.adjustHealthAndMoney(healthValue,moneyValue);
GameManager.instance.adjustScore(scoreValue);
}
}
If you could help steer me in the right direction, I would appreciate it.
I think you've mixed up what the monobehavior onTriggerEnter() does.
onTriggerEnter() is called when another GameObject enters the collider of the GameObject the script is attached to. This is not the place to check for keyboard presses.
What you probably want to use is the Update() monobehavior. It runs every frame, so you are guaranteed that if the user presses a key the function will be able to see it.
Your code would look something like this:
void Update(){
if (Input.GetKeyDown(KeyCode.Space))
{
GameManager.instance.adjustHealthAndMoney(healthValue,moneyValue);
GameManager.instance.adjustScore(scoreValue);
}
}
Also, I think you misunderstood what gameObject refers to when you did Destroy(gameObject) in the gameManager script. gameObject refers to the gameObject the script it's mentioned in is attached to. This means when you do Destroy(gameObject) in the gameManager script you are destroying the object that the gameManager script it attached to.
To get rid of the GameObject called target you need to get access to it. You can do that in a couple ways:
1) Destroy(GameObject.Find("target"));
This directly searches for a GameObject called "target" and destroys it. The downside to this is that it requires the GameObject to be called exactly "target" and if there are multiple GameObjects called "target" it will only destroy the first one.
2) Create a public variable (like public GameObject target) and store the physical instance of the target in it through the inspector. Then when you want to destroy it, just do Destroy(target);
This is how I would most likely do it.