How do I pass a variable between scripts in Unity 2d C# - c#

For example, I have a variable "Wisps" that I want to change when the player picks up an object. But I don't know how to do it. I tried to add a WispDisplay object to call the classes, like in Java, but it doesn't seem to work.
public class WispCode : MonoBehaviour
{
WispDisplay wd = new WispDisplay();
private void OnTriggerEnter2D(Collider2D other)
{
if (other.tag == "Player")
{
wd.setWisp(wd.getWisp()+1);
Destroy(gameObject);
}
}
}
public class WispDisplay : MonoBehaviour
{
public int Wisp = 5;
public Text WispText;
void Start()
{
}
void Update()
{
WispText.text = "Wisp: " + Wisp.ToString();
}
public int getWisp()
{
return Wisp;
}
public void setWisp(int newWisp)
{
Wisp = newWisp;
}
}

Easiest (a tiny bit dirty) way is to use a static variable. Downside: you can only have exactly ONE.
Example:
public class MyClass: MonoBehaviour {
public static int wisps;
}
Then, in ANY class, just use this to access it:
MyClass.wisps = 1234;
The more elegant way, working with multiple class instances, is using references.
Example:
public class PlayerClass: MonoBehaviour {
public int wisps = 0;
}
public class MyClass: MonoBehaviour {
public PlayerClass player;
void Update(){
player.wisps += 1;
}
}
Then, you need to drag-drop (aka "assign") the "PlayerClass" Component (attached to the player) to the the Gameobject that should increase the Wisps count. You can duplicate these objects after assigning the reference.
Now, if you actually want to have some sort of collectible, I'd suggest this approach:
You Have a Player "PlayerClass" and some Objects that are collectible, which have Trigger Colliders.
The objects have this code:
public class Example : MonoBehaviour
{
private void OnTriggerEnter(Collider other)
{
// probably a good idea to check for player tag:
// other.compareTag("Player");
// but you need to create the "Player" Tag and assign it to Player Collider Object.
if(TryGetComponent(out PlayerClass player))
{
player.wisps += 1;
}
}
}

Related

Unity 2D: How to confirm if specific amount of coins had been collected before loading to next level?

How do I get the number of coins collected in CoinPicker script and access it in LevelLoader script?
Here is my CoinPicker script:
public class CoinPicker : MonoBehaviour
{
public float coin = 0;
public TextMeshProUGUI textCoins;
private void OnTriggerEnter2D(Collider2D other)
{
if (other.transform.tag == "Coins")
{
coin++;
textCoins.text = coin.ToString();
Destroy(other.gameObject);
}
}
}
Here is my LevelLoader script:
public class LevelLoader : MonoBehaviour
{
public int iLevelToLoad;
public string sLevelToLoad;
public bool useIntegerToLoadLevel;
private void OnTriggerEnter2D(Collider2D collision)
{
GameObject collisionGameObject = collision.gameObject;
if(collisionGameObject.gameObject.tag == "Player" )
{
LoadScene();
}
}
void LoadScene()
{
if(useIntegerToLoadLevel)
{
SceneManager.LoadScene(iLevelToLoad);
}
else
{
SceneManager.LoadScene(sLevelToLoad);
}
}
}
Please help, I am weak at this :(
You can make a get direct from Coin picker and get from level loader access the coin from CoinPicker.
public class LevelLoader : MonoBehaviour
{
public CoinPicker CoinPicker;
public int iLevelToLoad;
public string sLevelToLoad;
public bool useIntegerToLoadLevel;
private void Start()
{
Debug.log(CoinPicker.coin); //you can get coin from here
}
private void OnTriggerEnter2D(Collider2D collision)
{
GameObject collisionGameObject = collision.gameObject;
if(collisionGameObject.gameObject.tag == "Player" )
{
LoadScene();
}
}
void LoadScene()
{
if(useIntegerToLoadLevel)
{
SceneManager.LoadScene(iLevelToLoad);
}
else
{
SceneManager.LoadScene(sLevelToLoad);
}
}
}
You could use the same TextMesh you used in the Collision script. You would just put
public TextMeshProUGUI textCoins;
in the field of the second one, so that textCoins.text is the number of coins you collected. Then drag the same TextMesh into the TextMesh box in the Unity editor.
I can't tell what you are using the coins for in the Level loader, but if you wanted to check the number of coins that you collected, you would just say
if(int.Parse(textCoins.text) == RequiredNumberOfCoins)
I hope this makes sense.

Item/Ability system using scriptableobjects in Unity

I'm new to Unity and I'm having some trouble creating scriptable objects for items to pick up and use in my project. I've created the initial set-up to make the items with variables that I can plug-and-play with.
What I'm trying to do is:
1 - plug in a monobehavior script for each new item's behavior when used.
2 - Allow the player to pick up the item on collision and use the item on key command. The player should only carry one item at a time.
Here is what I have so far.
the scriptable object (itemObject.cs)
[CreateAssetMenu(fileName = "New Item", menuName = "Item")]
public abstract class Item : ScriptableObject {
public new string name;
public string description;
public int damageAmount;
public int affectDelay;
public int affectDuration;
public int affectRange;
public float dropChance;
public float itemLife;
}
I have a script that will trigger if the player collides with the object (itemDisplay.cs)
Currently, this doesn't do anything but destroys the item.
public class itemDisplay : MonoBehaviour {
public Item item;
public static bool isActive = false;
void OnTriggerEnter(Collider other)
{
if (other.gameObject.tag == "Player")
{
var playerPickUp = other.gameObject;
var playerScript = playerPickUp.GetComponent<Player>();
var playerItem = playerScript.playerItem;
playerScript.pickUpItem();
bool isActive = true;
Object.Destroy(gameObject);
}
}
void Start () {
}
void Update(){
}
}
Currently, I have a pickup function and a use item function that's being handled by my player script(Player.cs). Currently, this just toggles a boolean saying that it did something when the item is collided with. My big question is how/where should I create and reference the item ability and how do I pass it to the player script here?
public void pickUpItem()
{
playerItem = true;
//itemInHand = itemInHand;
Debug.Log("You Picked Up An Item!");
// Debug.Log(playerItem);
if(playerItem == true){
Debug.Log("This is an item in your hands!");
}
}
public void useItem()
{
//use item and set
//playerItem to false
playerItem = false;
}
If your question is "how do I create an instance of an Item in my project", you do that by right clicking in your Project view, then selecting Create -> Item. Then drag the Item asset that you created from your Project view into the ItemDisplay scene object's item field in the Inspector.
Regarding how to pick up the item, you would pass the item to your Player script from your ItemDisplay script. You've got most of that already wired up.
public class ItemDisplay : MonoBehaviour
{
void OnTriggerEnter(Collider other)
{
if (other.gameObject.tag == "Player")
{
var playerPickUp = other.gameObject;
var playerScript = playerPickUp.GetComponent<Player>();
var playerItem = playerScript.playerItem;
// This is where you would pass the item to the Player script
playerScript.pickUpItem(this.item);
bool isActive = true;
Object.Destroy(gameObject);
}
}
}
Then in your Player script...
public class Player : MonoBehaviour
{
public void PickUpItem(Item item)
{
this.playerItem = true;
this.itemInHand = item;
Debug.Log(string.Format("You picked up a {0}.", item.name));
}
public void UseItem()
{
if (this.itemInHand != null)
{
Debug.Log(string.Format("You used a {0}.", this.itemInHand.name));
}
}
}
You should also make your Item class non-abstract, or leave it abstract and create the appropriate non-abstract derived classes (Weapon, Potion, etc.). I put an example of that below.
You can't attach a MonoBehaviour to a ScriptableObject if that's what you're trying to do. A ScriptableObject is basically just a data/method container that isn't tied to a scene. A MonoBehaviour has to live on an object within a scene. What you could do however, is add a "use" method to your Item class, and call that from a MonoBehaviour within the scene when the player uses an item.
public abstract class Item : ScriptableObject
{
// Properties that are common for all types of items
public int weight;
public Sprite sprite;
public virtual void UseItem(Player player)
{
throw new NotImplementedException();
}
}
[CreateAssetMenu(menuName = "Items/Create Potion")]
public class Potion : Item
{
public int healingPower;
public int manaPower;
public override void UseItem(Player player)
{
Debug.Log(string.Format("You drank a {0}.", this.name));
player.health += this.healingPower;
player.mana += this.manaPower;
}
}
[CreateAssetMenu(menuName = "Items/Create Summon Orb")]
public class SummonOrb : Item
{
public GameObject summonedCreaturePrefab;
public override void UseItem(Player player)
{
Debug.Log(string.Format("You summoned a {0}", this.summonedCreaturePrefab.name));
Instantiate(this.summonedCreaturePrefab);
}
}
Then change your UseItem method in Player:
public class Player : MonoBehaviour
{
public void UseItem()
{
if (this.itemInHand != null)
{
this.itemInHand.UseItem(this);
}
}
}

Unity 3D Attaching Score Display Script to prefab

I was following Unity 3d tutorial on the Learn Unity website, but here is the thing I wanted to do things a bit differently. It worked out well at start but in the end this turned out to be a bad decision and now I manually need to attach the script to every pickable object.
Here is my code:
Note: What it does is rotate the Pickups and display the score when the pickups collide with player ball.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class PickUps : MonoBehaviour {
public Vector3 Rotate;
private int Score;
public Text ScoreGUI;
private void Start()
{
Rotate = new Vector3(0, 25, 0);
Score = 0;
DisplayScore();
}
void Update () {
transform.Rotate(Rotate*Time.deltaTime);
}
private void OnTriggerEnter(Collider other)
{
if (other.gameObject.CompareTag("Ball"))
{
Destroy(this.gameObject);
Score = Score + 1;
DisplayScore();
}
}
void DisplayScore()
{
ScoreGUI.text = "SCORE " + Score.ToString();
}
}
Problem:
It works yes but I need to manually attach the text (under canvas) to every pickup object which is exhausting and not a good thing to do.
What I want To achieve:
Like in the tutorials mostly they use prefabs in this kind of work (I think), problem is I can attach the text to the pickups (objects/biscuits) in the current scene but I cannot drag and attach the text To the prefab of biscuits I made the text just wont attach in its blank for "Text".
You shouldn't change the score Text directly. Use a Controller to make the bridge instead. I would do something like this:
Put this script somewhere in your scene:
public class ScoreManager : Singleton<ScoreManager>
{
private int score = 0;
// Event that will be called everytime the score's changed
public static Action<int> OnScoreChanged;
public void SetScore(int score)
{
this.score = score;
InvokeOnScoreChanged();
}
public void AddScore(int score)
{
this.score += score;
InvokeOnScoreChanged();
}
// Tells to the listeners that the score's changed
private void InvokeOnScoreChanged()
{
if(OnScoreChanged != null)
{
OnScoreChanged(score);
}
}
}
This script attached in the Text game object:
[RequireComponent(typeof(Text))]
public class ScoreText : MonoBehaviour
{
private Text scoreText;
private void Awake()
{
scoreText = GetComponent<Text>();
RegisterEvents();
}
private void OnDestroy()
{
UnregisterEvents();
}
private void RegisterEvents()
{
// Register the listener to the manager's event
ScoreManager.OnScoreChanged += HandleOnScoreChanged;
}
private void UnregisterEvents()
{
// Unregister the listener
ScoreManager.OnScoreChanged -= HandleOnScoreChanged;
}
private void HandleOnScoreChanged(int newScore)
{
scoreText.text = newScore.ToString();
}
}
And in your PickUps class:
void DisplayScore()
{
ScoreManager.Instance.SetScore(Score); // Maybe what you need is AddScore to not
// reset the value everytime
}
A simple singleton you can use (you can find more complete ones on the internet):
public class Singleton<T> : MonoBehaviour where T : MonoBehaviour
{
static T instance;
public static T Instance
{
get
{
if (instance == null)
{
instance = (T)FindObjectOfType(typeof(T));
if (instance == null) Debug.LogError("Singleton of type " + typeof(T).ToString() + " not found in the scene.");
}
return instance;
}
}
}
But be careful, the singleton pattern can be a shot in the foot if not used correctly. You should only it them moderately for managers.

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.

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