C# UNITY Instantiate Class (Create Object) without using new operator - c#

example I have class name "PlayerClass" and "StatClass"
PlayerClass
public float health = 100 ;
public float experience;
StatClass
PlayerClass playerclass = new PlayerClass()
public float health2;
public float experience2;
health2 = playerclass.health;
experience2 = playerclas.experience;
usually I have to create "PlayerClass playerclass = new PlayerClass()"
the result will be the same health2 and experience2;
but whenever i made a change on healt from player class with code of course, the result healt2 from StatClass will be the same it was 100
Sory for my bad english.
----------------- Edited -----
GameControl
public class GameControl : MonoBehaviour {
public static GameControl control;
public float health = 100;
public float experience = 1000;
// Use this for initialization
void Awake () {
/*
* Awake Start Before Start() Happen
*/
if (control == null) {
DontDestroyOnLoad (gameObject);
control = this;
} else if (control != this) {
Destroy (gameObject);
}
}
}
GetHealth
public class GetHealth : MonoBehaviour {
public static GetHealth getHealth;
GameControl gameControl = new GameControl();
//GameControl gameControl;
public static float health;
Text text;
void Awake () {
/*
* Awake Start Before Start() Happen
*/
if (getHealth == null) {
DontDestroyOnLoad (gameObject);
getHealth = this;
} else if (getHealth != this) {
Destroy (gameObject);
}
text = GetComponent<Text> ();
health = 0;
}
void Update(){
health = gameControl.health;
text.text = "Health: " + health;
}
}
I made Change on value variable Health in GameControl. but Get Health always access 100 the same as first time when variable GameControl haven't changed. I think it was maybe because we use "new" on GetHealth class.
GameControl gameControl = new GameControl();
Is there another way to solve this without "new".

If instantiation of player was in updateStat method and it is calling in void update() method, it cause to instantiate player object again and again. You can try this or instantiate playerclass2 in constructor. Also making health and experience static will help if only player object exist.
Class StatClass{
PlayerClass playerclass2;
public float health2;
public float experience2;
public void updateStat(){
if(playerclass2==null) playerclass2 = new PlayerClass();
health2 = playerclass2.health;
experience2 = playerclas2.experience;
}
}
EDIT
You can't use new keyword since it is derived from MonoBehavior. Have to use ClassName.method() format to call methods of derived classes from MonoBehavior

Thanks all, I have Found it.
It just have to
health = GameControl.control.health;
I don't have to instantiate it I just Call class and then initialize of class and then variable of class.

Related

How do I make my cloned game objects global, so I can use them outside of the function in Unity?

I just recently got into Unity 3D and currently working on one of my first own projects. For the game im making, I need a spawner function, that respawns a clone of the enemy as soon as the enemy falls off the platform. This is the code I have right now:
using UnityEngine;
public class spawner : MonoBehaviour
{
public GameObject enemyPrefab;
public float spawnHeight = 0.75f;
// Start is called before the first frame update
void Start()
{
spawnEnemy();
}
// Update is called once per frame
void Update()
{
if (enemyClone.transform.position.y < -10)
{
Destroy(enemyClone);
spawnEnemy();
}
}
public void spawnEnemy()
{
var enemyPosition = new Vector3(Random.Range(-5, 5), spawnHeight, Random.Range(-5, 5));
var enemyClone = Instantiate(enemyPrefab, enemyPosition, Quaternion.identity);
}
}
The function spawnEnemy itself works fine, since it creates an enemy on game start, tho further enemies aren't spawned. I get the message: "Assets\spawner.cs(21,21): error CS0103: The name 'enemyClone' does not exist in the current context".
I do see why I get the message, don't know how to make enemyClone globally available however.
Thanks to everybody in advance,
bezunyl
In the spawnEnemy() function, you say var enemyClone = Instantiate(...);. enemyClone is a local variable that can only be used within the spawnEnemy function, or at least that's how you've written it.
If you want to use the enemyClone outside of the spawnEnemy function, you need to declare the enemyClone variable outside of the function. (The example below will work if you DON'T want enemyClone to be accessible to other GameObjects)
using UnityEngine;
public class spawner : MonoBehaviour
{
public GameObject enemyPrefab;
public float spawnHeight = 0.75f;
private GameObject enemyClone //Added to allow enemyClone to be used anywhere in the class
// Start is called before the first frame update
void Start()
{
spawnEnemy();
}
// Update is called once per frame
void Update()
{
if (enemyClone.transform.position.y < -10)
{
Destroy(enemyClone);
spawnEnemy();
}
}
public void spawnEnemy()
{
var enemyPosition = new Vector3(Random.Range(-5, 5), spawnHeight, Random.Range(-5, 5));
enemyClone = Instantiate(enemyPrefab, enemyPosition, Quaternion.identity);
}
}
Now if you want enemyClone to be accessible by other GameObjects, then you'll want to make the enemyClone variable public instead of private. If you don't want it to show up in the inspector, add [HideInInspector] above the declaration of enemyClone, as shown below:
[HideInInspector]
public GameObject enemyClone;
Your issue is based on scope. You might want to research it, it's important to know.
The scope of a variable determines its visibility to the rest of a program.
http://www.blackwasp.co.uk/CSharpVariableScopes.aspx
http://www.informit.com/articles/article.aspx?p=1609145&seqNum=4
Spawning GameObject on demand is expensive. Instead of spawning it everytime, you should pool the GameObject.
public class Spawner : MonoBehaviour {
public Enemy enemyPrefab;
public List<Enemy> enemyPool;
public const SPAWN_HEIGHT = 0.75f;
// Start is called before the first frame update
void Start()
{
enemyPool = new List<Enemy>();
spawnEnemy();
}
// Update is called once per frame
public void Despawn(Enemy deadEnemy)
{
deadEnemy.gameObject.SetActive(false);
enemyPool.Add(deadEnemy);
}
public void spawnEnemy() {
Enemy newEnemy;
if (enemyPool.Count > 0) {
newEnemy = enemyPool[0];
enemyPool.Remove(0);
} else {
newEnemy = Instantiate(enemyPrefab);
}
newEnemy.Init(this);
newEnemy.position = new Vector3(Random.Range(-5, 5), SPAWN_HEIGHT, Random.Range(-5, 5));
newEnemy.gameObject.SetActive(true);
}
}
public class Enemy : MonoBehaviour {
private Spawner spawner;
private const float DEATH_POSITION_Y = -10;
public void Init(Spawner spawner) {
this.spawner = spawner;
}
void Update() {
if (transform.position.y < DEATH_POSITION_Y) {
spawner.Despawn(this);
}
}
}

How to achieve awareness of "kill" events in a scene

I have been doing a RPG game in Unity with C # and when doing a system of quests, specifically those of killing a certain number of enemies, I found the problem of having 3 enemies in the scene and being the target of the quest: Kill 3 enemies. If I kill them before activating the quest and later active the quest does not give me the reward (in this case experience). How can I tell the enemies and make that if the quest detects that I have already killed the necessary enemies to get the quest give me the reward equally?
Here the two needed scripts i think:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class QuestObject : MonoBehaviour {
public int questNumber;
public QuestManager qManager;
public string startText;
public string endText;
public bool isItemQuest;
public string targetItem;
public bool isEnemyQuest;
public string targetEnemy;
public int enemiesToKill;
private int enemyKillCount;
private PlayerStats playerStats;
public int EXPToGive;
void Start () {
playerStats = FindObjectOfType <PlayerStats> ();
}
void Update () {
if (isItemQuest) {
if (qManager.itemCollected == targetItem) {
qManager.itemCollected = null;
EndQuest ();
}
}
if (isEnemyQuest) {
if (qManager.enemyKilled == targetEnemy) {
qManager.enemyKilled = null;
enemyKillCount++;
}
if (enemyKillCount >= enemiesToKill) {
EndQuest ();
}
}
}
public void StartQuest (){
qManager.ShowQuestText (startText);
}
public void EndQuest (){
qManager.ShowQuestText (endText);
playerStats.AddEXP (EXPToGive);
qManager.questCompleted [questNumber] = true;
gameObject.SetActive (false);
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EnemyHealth : MonoBehaviour {
public int startingHealth;
public int currentHealth;
public GameObject damageBurst;
private PlayerStats playerStats;
public int EXPToGive;
public string enemyQuestName;
private QuestManager qManager;
void Start ()
{
// Setting up the references.
//anim = GetComponent <Animator> ();
//enemyAudio = GetComponent <AudioSource> ();
//enemyMovement = GetComponent <EnemyMovement> ();
//enemyAttacking = GetComponentInChildren <EnemyAttack> ();
// Set the initial health of the player.
currentHealth = startingHealth;
playerStats = FindObjectOfType <PlayerStats> ();
qManager = FindObjectOfType <QuestManager> ();
}
void Update ()
{
if (currentHealth <= 0) {
qManager.enemyKilled = enemyQuestName;
Destroy (gameObject);
playerStats.AddEXP (EXPToGive);
}
}
public void TakeDamage (int amountDamage)
{
// Reduce the current health by the damage amount.
currentHealth -= amountDamage;
Instantiate (damageBurst, transform.position, transform.rotation);
}
public void SetMaxHelth () {
currentHealth = startingHealth;
}
}
One Aproach would be to create some type of "WorldManager" which counts every Enemy which has been slain. And when Starting a quest this quest could check the WorldManagers kill count and add it to it's own count.
public void StartQuest (){
qManager.ShowQuestText (startText);
this.enemyKillCount += worldManager.GetKillCount();
}
In your enemy class you have to add a kill to your worldManager.
void Update ()
{
if (currentHealth <= 0) {
qManager.enemyKilled = enemyQuestName;
this.worldManager.AddKill(this)
Destroy (gameObject);
playerStats.AddEXP (EXPToGive);
}
}
Alternative:
Make your QManager be aware of every kill in a Scene.
You can achieve this through many ways.
One of them is passing your EnemyObject an reference of your Qmanager and do the same as with the "WorldManager" provided above, or you use Messaging and fire a Message targeting the QManager when an enemy is slain.
Alternative 2:
Throw an Event when an enemy has been slain and subscribe to it on your QManager/WorldManager. This way u can reuse your enemy class in every game. From my point of view static dependencies are evil, but there are many discussions and SO and everywhere on the internet about that.
You can several approach. The most straight-forward is to use static.
The purpose of static is for the variable/method to belong to the class instead of an instance of the class.
In your case, you want each enemy to have its own health, this cannot be static.
And you want to count how many instances there are in the scene from the class. So static is fine.
public class Enemy:MonoBehaviour
{
private static int enemyCount = 0;
public static int EnemyCount {get{ return enemyCount;} }
public event Action<int> RaiseEnemyDeath;
public static void ResetEnemyCount(){
enemyCount = 0;
}
private int health;
public void Damage(int damage)
{
CheckForDamage(); // here you check that damage is not neg or too big...
this.health -= damage;
if(this.health <= 0)
{
OnDeath();
}
}
void OnActivate()
{
enemyCount++;
this.health = 20;
}
void OnDeath()
{
enemyCount--;
RaiseEnemyDeath(enemyCount); // Should check for nullity...
}
}
This one is fairly simple. The first part is all static and is relevant to the class. The second part is relevant to the instance. If you use a pool of enemy and then reuse the same instance multiple times, the OnActivate method is called when you make the enemy alive in the scene (it may have been there for a while as inactive). Then when the health is down, kill the enemy (there are not all the required actions there...) and trigger the event.
Using the public static property, you can know what is the enemy count from a GameManager (Enemy should not affect the gameplay, only takes care of the enemy).
public class GameManager:MonoBehaviour
{
void Start()
{
Enemy.RaiseEnemyDeath += Enemy_RaiseEnemyDeath;
}
void Enemy_RaiseEnemyDeath(int count)
{
if(count < 0){ // End of level }
// You can also access enemyCount
int count = Enemy.EnemyCount;
}
}
The good point of using this principle is that Enemy has no clue about GameManager and can be reused in another game without any modification. The GameManager is a higher level entity and knows about it.

Unity tells me to reference through an instance of an object, Visual Studio tells me not to

so I'm creating a save and load script, here's it's content:
using UnityEngine;
using System.Collections;
public class SaveLoad : MonoBehaviour {
public Player player = GameObject.FindGameObjectWithTag("Player").GetComponent<Player> ();
public void Save() {
PlayerPrefs.SetFloat ("X", transform.position.x);
PlayerPrefs.SetFloat ("Y", transform.position.y);
PlayerPrefs.SetFloat ("Z", transform.position.z);
PlayerPrefs.SetFloat("Health", Player.Health.CurrentVal);
PlayerPrefs.SetFloat ("Exp", Player.Exp.CurrentVal);
PlayerPrefs.SetFloat("Oxygen", Player.Oxygen.CurrentVal);
PlayerPrefs.SetFloat("PlayerLevel", Player.PlayerLevel.CurrentVal);
Debug.Log ("Saved");
}
public void Load() {
float x = PlayerPrefs.GetFloat ("X");
float y = PlayerPrefs.GetFloat ("Y");
float z = PlayerPrefs.GetFloat ("Z");
Player.Health.CurrentVal = PlayerPrefs.GetFloat("Health");
Player.Exp.CurrentVal = PlayerPrefs.GetFloat("Exp");
Player.Oxygen.CurrentVal = PlayerPrefs.GetFloat("Oxygen");
Player.PlayerLevel.CurrentVal = PlayerPrefs.GetFloat("PlayerLevel");
transform.position = new Vector3 (x, y, z);
Debug.Log ("Loaded");
}
}
The first version, without the "public Player player" line, returned a NullReferenceException and said to reference through an instance of an object in Unity. Now when I try to do that(instead of ("Health", Player.Health.CurrentVal) i use("Health", player.Health.CurrentVal), without capital in "Player", refering to the instance I creaded in the same script), Visual tells me "Player.Health cannot be accessed with an instance of an object; qualify it with a type name instead". As you can see, I'm kinda stuck in a loop here. Did I mess up with trying to get player as an instance? It's also a little bit of a mess cause Player is both the gameobject's name and the script's.
Player script:
[SerializeField] public Stat health;
public static Stat Health { get; set; }
[SerializeField] private Stat exp;
public static Stat Exp { get; set; }
[SerializeField] private Stat oxygen;
public static Stat Oxygen { get; set; }
[SerializeField] private Stat playerLevel;
public static Stat PlayerLevel { get; set; }
[SerializeField] private Text playerLevelText;
And here's the Stat custom variable class:
[SerializeField] private BarPrototype bar;
[SerializeField] private float maxVal;
[SerializeField] private float currentVal;
public float CurrentVal
{
get
{
return currentVal;
}
set
{
this.currentVal = Mathf.Clamp (value, 0, MaxVal);
if( bar != null )
bar.Value = this.currentVal;
}
}
SerializeField is used just to access those values from inspector.
The problem is you can't initialize public members like this:
public Player player = GameObject.FindGameObjectWithTag("Player").GetComponent<Player> ();
They should be initialized in Start, Awake or OnEnable method. If you change it to below it should work
public Player player;
void Start(){
player = GameObject.FindGameObjectWithTag("Player").GetComponent<Player> ();
}
Another thing is why should you make it public then? If assigned in inspector then its assigned value would be ignored after Start(). I suggest you make it internal. or to be safe:
public Player player;
void Start(){
if(player == null)
player = GameObject.FindGameObjectWithTag("Player").GetComponent<Player> ();
}

Accessing boolean from a different class in C#

I have one collision script in which I set a boolean to true or false
using UnityEngine;
using System.Collections;
public class IsTriggerLockCamera : MonoBehaviour {
public bool CameraLock = false;
public void OnTriggerStay2D(Collider2D other) {
CameraLock = true;
Debug.Log ("Im inside");
}
public void OnTriggerExit2D(Collider2D other) {
CameraLock = false;
Debug.Log ("I exited");
}
}
I want to access this boolean from my camera script, I tried this
if (CameraLock == true) {
Debug.Log ("Im locked");
}
However, I get an error saying that CameraLock doesn't exist in the current context. The boolean is public so I'm very confused.
EDIT: I feel like I didn't give good enough info so I'll start by posting the whole camera script and then clarifying.
using System;
using UnityEngine;
public class CameraFollowLockY : MonoBehaviour
{
public Transform target;
public float damping = 1;
public float lookAheadFactor = 3;
public float lookAheadReturnSpeed = 0.5f;
public float lookAheadMoveThreshold = 0.1f;
private float m_OffsetZ;
private Vector3 m_LastTargetPosition;
private Vector3 m_CurrentVelocity;
private Vector3 m_LookAheadPos;
private void Start()
{
m_LastTargetPosition = target.position;
m_OffsetZ = (transform.position - target.position).z;
transform.parent = null;
}
private void Update()
{
float xMoveDelta = (target.position - m_LastTargetPosition).x;
bool updateLookAheadTarget = Mathf.Abs(xMoveDelta) > lookAheadMoveThreshold;
if (updateLookAheadTarget)
{
m_LookAheadPos = lookAheadFactor*Vector3.right*Mathf.Sign(xMoveDelta);
}
else
{
m_LookAheadPos = Vector3.MoveTowards(m_LookAheadPos, Vector3.zero, Time.deltaTime*lookAheadReturnSpeed);
}
Vector3 aheadTargetPos = target.position + m_LookAheadPos + Vector3.forward*m_OffsetZ;
Vector3 newPos = Vector3.SmoothDamp(transform.position, aheadTargetPos, ref m_CurrentVelocity, damping);
transform.position = newPos;
transform.position = new Vector3(transform.position.x, 0, transform.position.z);
m_LastTargetPosition = target.position;
if (CameraLock == true) {
Debug.Log ("Im locked");
}
}
The IsTriggerLockCamera is a script I used for an invisible collider in Unity. My camera is focused on the player at all times, but I want it to stop moving when the player is close to reaching the edge of the map, so he can notice that the map is ending. The original plan was, that the collider would send out information when player enters it and then instead of Debug.Log ("Im Locked"); would be some code that would lock the camera in place. I don't know if this solution is very elegant and I'd like to apologize for not clarifying everything properly beforehand, but I started coding probably 2 months ago (I did only Rails websites) and I got into C# game development about a week ago so I'm still missing the terminology required to properly describe the problems I encounter. So far, no suggestion has worked. The closest working suggestion was OnoSendai's suggestion, but apparently it's not allowed to create MonoBehaviour using "new".
Edit2: Making the boolean static didn't work at first, but then I realized that I had to make some changes in my camera script as well, so it works now, but Philip said that it's a bad advice - I personally have no idea why, I assume that it's something like using !important in CSS, you just use it as a last resort because it makes the code not that flexible - so I'm still open to ideas.
You may want to try a full reference to the CameraLock property based on the object instance - as in
var objRef = new IsTriggerLockCamera(); // Just an example of object reference -
// You may already have one
// on your code.
if (objRef.CameraLock) {
Debug.Log ("Im locked");
}
That happens because CameraLock is marked as public, but not as static - it only exists on a instantiated object.
If your camera needs access to the IsTriggerLockCamera then dependency injection is good way to make it clear :
class Camera{
private readonly IsTriggerLockCamera _locker;
public Camera(IsTriggerLockCamera locker){
if (locker== null)
{
throw new ArgumentNullException("locker");
}
_locker = locker;
}
public void whatevermethod(){
if (_locker.CameraLock){
...
}
}
}
You need to set the variable you want to access from other scripts, static as well as public.
using UnityEngine;
using System.Collections;
public class IsTriggerLockCamera : MonoBehaviour {
public static bool CameraLock = false;
public void OnTriggerStay2D(Collider2D other) {
CameraLock = true;
Debug.Log ("Im inside");
}
public void OnTriggerExit2D(Collider2D other) {
CameraLock = false;
Debug.Log ("I exited");
}
}
Then you can access CameraLock :
if (CameraLock == true) {
Debug.Log ("Im locked");
}
I would like to suggest some approach,
Make CameraLock variable as protected.
Create a new class and make IsTriggerLockCamera class as base class.
consume the CameraLock variable and work on it.
Thanks,
C# has a function called { get; set } that you can use with a variable
This program is an example
Static Type
public static class AYO
{
public static string Variable1 { get; set; }
}
public class B
{
public void LOL()
{
string foo;
AYO.Variable1 = "Variable is now set";
foo = AYO.Variable1;
Console.WriteLine(foo);
}
}
Non Static
public class AYO
{
public string Variable1 { get; set; }
}
public class B
{
AYO ayo = new AYO();
public void LOL()
{
string foo;
ayo.Variable1 = "Variable is now set";
foo = ayo.Variable1;
Console.WriteLine(foo);
}
}

Use of addComponent instead of new keyword in unity

I have an interface which all my monsters should implement.
namespace Assets.Scripts
{
interface IMonster
{
void setSpeed(float s);
float getSpeed();
void SetMonsterPosition(Vector2 pos);
Vector2 GetMonsterPosition();
void DestroyMonster();
void MoveMonster();
}
}
Then I have a concrete monster class (I will add more):
public class Monster2 : MonoBehaviour, IMonster
{
public Monster2()
{
speed = Random.Range(0.05f, 0.15f);
monster = (GameObject)Instantiate(Resources.Load("Monster2"));
float height = Random.Range(0, Screen.height);
Vector2 MonsterStartingPosition = new Vector2(Screen.width, height);
MonsterStartingPosition = Camera.main.ScreenToWorldPoint(MonsterStartingPosition);
monster.transform.position = MonsterStartingPosition;
}
void Start()
{
}
// Update is called once per frame
void Update()
{
}
}
And a factory class that will produce my monsters:
class MonsterFactory : MonoBehaviour
{
public static IMonster getMonster()
{
return new Monster2();
}
}
This works but I read that I should not use new and I should use AddComponent. So I tried something like:
class MonsterFactory : MonoBehaviour
{
public static GameObject mymonster; //#first
public static IMonster getMonster()
{
return mymonster.AddComponent<Monster2>(); //#second
}
}
The problem is that now when I am trying to run the game there is a an error NullReferenceException: Object reference not set to an instance of an object
IMonster monster = MonsterFactory.getMonster();
As David said mymonster needs to be initialised. But even then you will run into trouble, as there is one GameObject containing a bunch of Monster2 components.
So instead I suggest:
GameObject go = new GameObject (GameObjectName);
return go.AddComponent<Monster2>();
Now every new monster has its own GameObject and thus can move indepently.

Categories