How to initialize instance variables in Unity - c#

I need stats in a game that I'm making and I used code from Brackeys(https://www.youtube.com/watch?v=e8GmfoaOB4Y&t=136s), he can only set code in the inspector, but I want to make a database-like manager that assigns stats to every unit in the game in one single script.
public class Stat
{
[SerializeField]
//public float baseValue; // Starting value
public float baseValue;
}
public class CharacterStats : MonoBehaviour
{
public Stat armor;
void SetArmorToFive(){
armor = ???
}
}

What you probably want to do is keep a reference to every CharacterStats instance in the scene.
Than Create some kind of function that makes you able to set the variables of every CharacterStats instance via the Stat class.
example:
Global script that holds a reference to all the CharacterStats Instances
public class CharacterStatsHolder : MonoBehaviour
{
public CharacterStats[] characterStats;
private void Start()
{
characterStats = FindObjectsOfType<CharacterStats>();
foreach(CharacterStats stat in characterStats)
{
stat.SetArmorLevel(5);
}
}
}
The same script as in the question but with a parameter in the SetArmorLevel function
public class CharacterStats : MonoBehaviour
{
public Stat armor;
void SetArmorLevel(int level){
armor = level;
}
}
The same stat script
public class Stat
{
[SerializeField]
//public float baseValue; // Starting value
public float baseValue;
}
Here is a quick link if you're interested in learning how parameters work. https://www.youtube.com/watch?v=1MFiyP0RPW0

Related

Unity code cleaning: writing general shooting script implementing same methods from different bullet classes

I picked up unity and C# a month ago so I'm still a noobie.
So far I managed to build a simple space-based arcade shooter (i have my ship, i have a way to shoot bullets). What I'm trying to achieve is a way to keep the script that takes my keyboard input to shoot separate from the possible bullet types.
The way my bullet types are currently implemented is by having a gameobject for each with its own scripts for a) taking keyboard input and b) instancing a prefab with different properties to shoot. Currently i have 2 shooting modes, and a separate script lets me swap between them with the spacebar by enabling disabling the gameobjects. An example of the scripts I'm using for one bullet type:
Script for instantiating bullet. One method simply shoots every time a button is pressed, the other "charges" an array of bullets, accompanied in the second script by a "growing aura" signifing the power increase. These two methods have the same name across different bullet classes, but are implemented differently.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BulletA_Basic : MonoBehaviour
{
public GameObject bulletPrefab;
public GameObject aura;
public Transform firingPoint;
public Transform chargingPoint;
public float bulletForce = 20f;
public float altCooldown = 1f;
public float fireRate = 1f;
public float altFirePowerMultiplier = 1f;
private void Update()
{
}
public void Shoot()
{
GameObject bullet = Instantiate(bulletPrefab, firingPoint);
Rigidbody2D rb = bullet.GetComponent<Rigidbody2D>();
rb.AddForce(firingPoint.up * bulletForce, ForceMode2D.Impulse);
}
public void SpecialShoot(int n)
{
StartCoroutine(Special(n));
}
public IEnumerator Special(int n)
{
for (int i = 0; i < n; i++)
{
Shoot();
yield return new WaitForSeconds(0.1f);
}
}
}
Script for taking keyboard input
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Shooting_A : MonoBehaviour //This needs to be copied across all firing types
{
private BulletA_Basic bulletScript; //Change the class to make new projectile types with different firing modes
public Vector3 scaleChange;
private GameObject auraInstance;
private float timePassed = 0f;
private float timePassedMain = 0f;
public float timeToDetonation = 3f;
private void Start()
{
bulletScript = GetComponent<BulletA_Basic>();
}
// Update is called once per frame
void Update()
{
bool isFiring = Input.GetButtonDown("Main Cannon");
bool alternateFire = Input.GetButton("Main Cannon");
timePassedMain += Time.deltaTime;
if (isFiring && timePassedMain > bulletScript.fireRate)
{
bulletScript.Shoot();
timePassedMain = 0;
}
if (alternateFire)
{
timePassed += Time.deltaTime;
if (!auraInstance && timePassed >= bulletScript.altCooldown)
{
auraInstance = Instantiate(bulletScript.aura, bulletScript.chargingPoint);
}
if (alternateFire && auraInstance && timePassed < timeToDetonation)
{
Charge();
//Will need to add shader here
}
else if (timePassed >= timeToDetonation)
{
Destroy(auraInstance);
timePassed = 0;
}
}
else
{
if (auraInstance)
{
Destroy(auraInstance);
int powerAltFire = (int)(bulletScript.altFirePowerMultiplier * (Mathf.Pow(2 , timePassed))); //Equation returns a number of projectiles based on how long charge was held
bulletScript.SpecialShoot(powerAltFire);
}
timePassed = 0;
}
}
void Charge()
{
auraInstance.transform.localScale += scaleChange;
}
}
The key here is the bulletScript field.
Basically i'd like to make the second script general so that i don't have to implement it in a different way and copy-pasting it again and again for each type of bullet I'm going to make, and changing the bulletScript field type each time.
I tried doing it with interfaces but I'm not sure how to implement it in the general script since I need to access each field of the subclasses, which have each their own references (like bulletPrefab, or aura). In general i feel interfaces are not well integrated into unity but that might just be me.
I also tried with delegates, but i had similar problems. I simply changed the type of bulletScript to my delegate type (ShootingDelegate bulletScript), and wrote this:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public delegate void ShootDelegate();
public delegate void SpecialShootDelegate(int n);
public class ShootingDelegate : MonoBehaviour
{
public ShootDelegate delShoot;
public SpecialShootDelegate delSpecialShoot;
private int weaponIndex;
public GameObject bulletPrefab;
public GameObject aura;
public Transform firingPoint;
public Transform chargingPoint;
public float bulletForce;
public float altCooldown;
public float fireRate;
public float altFirePowerMultiplier;
// Start is called before the first frame update
void Start()
{
WeaponSwap weapon = GetComponent<WeaponSwap>();
weaponIndex = weapon.weaponIndex;
switch (weaponIndex)
{
case 1:
BulletB_Fan bulletB = GetComponent<BulletB_Fan>();
delShoot = bulletB.Shoot;
delSpecialShoot = bulletB.SpecialShoot;
bulletPrefab = bulletB.bulletPrefab;
aura = bulletB.aura;
firingPoint = bulletB.firingPoint;
chargingPoint = bulletB.chargingPoint;
bulletForce = bulletB.bulletForce;
altCooldown = bulletB.altCooldown;
fireRate = bulletB.fireRate;
altFirePowerMultiplier = bulletB.altFirePowerMultiplier;
break;
case 0:
BulletA_Basic bullet = GetComponent<BulletA_Basic>();
delShoot = bullet.Shoot;
delSpecialShoot = bullet.SpecialShoot;
bulletPrefab = bullet.bulletPrefab;
aura = bullet.aura;
firingPoint = bullet.firingPoint;
chargingPoint = bullet.chargingPoint;
bulletForce = bullet.bulletForce;
altCooldown = bullet.altCooldown;
fireRate = bullet.fireRate;
altFirePowerMultiplier = bullet.altFirePowerMultiplier;
break;
}
}
// Update is called once per frame
void Update()
{
}
}
This is the error it throws:
ArgumentException: Value does not fall within the expected range.
ShootingDelegate.Start () (at Assets/Scripts/ShootingDelegate.cs:54)
which corresponds to this line
delShoot = bullet.Shoot;
I don't really care if a solution employs either interfaces or delegates, those were just things I tried. Any thoughts?
I created a sample just for you.
Here I am creating BulletType parent class for all bullet types. And atting some variable and method for all bullets.
public class BulletType : MonoBehaviour
{
// Protected is less stric than private, more stric than public
// Protected variables only accesible to this class and its child.
protected string name;
protected int bulletDamage;
protected int bulletSpeed;
protected virtual void Start()
{
}
// Virtual means you can override this method in child classes
protected virtual void Damage() { }
public virtual void PlaySound() { }
protected virtual void ShowEffect() { }
}
Now I will add child class and inherit from parent BulletType class. Everything write in comment lines.
// This class inherits from BulletType class.
public class FireBullet : BulletType
{
// If you want to completely ignore parent class variable to by adding new keyword.
new int bulletSpeed = 3;
protected override void Start()
{
// when overriding a method automatically adds this method.
// base means parent class. with base you can access parent methods.
base.Start();
// If you remove base method, parent method won't be called in this class.
// Here I accessed parent class variable and set its value.
// This doesn't effect parent or other child classes. That's the beauty.
name = "Fire Bullet";
}
void StopSound()
{
}
protected override void PlaySound()
{
// Sound played from parent.
base.PlaySound();
// You can add your own variable and methods in parent method.
StopSound();
}
}
// FireBullet inherited from BulletType, and LavaBullet inherited from FireBullet.
// You can do this as much as you want.
public class LavaBullet : FireBullet
{
protected override void PlaySound()
{
// Here base will be FireBullet
base.PlaySound();
}
}
public class IceBullet : BulletType
{
// Add as much as you thing.
}
And for using BulletType script in your player just add this line
public class Player
{
public BulletType currentBulletType;
// You can get child from main class.
FireBullet fireBullet = currentBulletType.GetComponent<FireBullet>();
// Now you can access and use child class methods.
fireBullet.PlaySound();
}
and when you want to change bullet type, just assign new bullettype. Because all bullet type inherit from this class. You can assign every bullet type.
I don't know if it's the answer but,
delShoot = bulletB.Shoot;
delShoot is a reference to a script.
bulletB.Shoot is a function.
I don't think you can make one equal to the other.
If I understand your question, you want to make one script for two type of bullets shoot. You can create a scipts Shoot.cs who is instantiate with a value (1 for the default fire 2 for the second fire) and other scrits defaultFire.cs / secondFire.cs with they own properties.
Like this you'll juste instantiate once your Bullet like this :
public void Start(){
case 1 : Shoot shoot = new Shoot(1)
case 2 : Shoot shoot = new Shoot(2)
}
Hope this help a little..

Unity5 Instantiate scriptable object

I have created a Scriptable Object with [CreateAssetMenu] option in the editor and it's called 'Assets/MyCard1.asset'. In there, I have specified some values like name, sprite, attack, health etc.
So my goal is to spawn a deck of 30 cards based on the same Prefab, but when I use 'Instantiate(gamobject)', it spawns a gameobject with default Prefab parameters. How do I assign 'Assets/MyCard[i].asset' data to EACH of newly spawned cards (with code)? I can do that with Inspector just fine by dragging Asset to the Prefab's script component.
[CreateAssetMenu(fileName = "New Card", menuName = "Card")]
public class CardScriptable : ScriptableObject
{
public new string name;
public string description;
public Sprite artwork;
public int manaCost;
public int attack;
public int health;
}
public class SpawnStuff : MonoBehaviour
{
public GameObject myPrefab;
GameObject[] tempKarta = new GameObject[30];
void Start()
{
for (int i = 0; i < 30; i++)
{
tempKarta[i] = Instantiate(myPrefab);
temp.Karta[i]. ?? DRAW_DATA_FROM "Assets/MyCard1.asset" (); // ??
}
}
}
Is my approach rational? If not, what is a better approach to this?
UPD:
Here's my presets, and prefab
also script that just displays stuff from CardScriptable
public class CardDisplay : MonoBehaviour
{
public CardScriptable card;
public Text nametext;
public Text descriptionText;
public Image artworkImage;
public Text manaText;
public Text attackText;
public Text healthText;
void Awake()
{
nametext.text = card.name;
descriptionText.text = card.description;
artworkImage.sprite = card.artwork;
}
}
You allready have your structure. All you would need is not do your stuff in Awake but rather have a method for it like
public class CardDisplay : MonoBehaviour
{
public Text nametext;
public Text descriptionText;
public Image artworkImage;
public Text manaText;
public Text attackText;
public Text healthText;
public void Initialize(CardScriptable card)
{
nametext.text = card.name;
descriptionText.text = card.description;
artworkImage.sprite = card.artwork;
}
}
Then have e.g. an array of CardScriptable[] in
public class SpawnStuff : MonoBehaviour
{
// Rather make this of the correct type!
public CardDisplay myPrefab;
private CardDisplay[] tempKarta = new CardDisplay[30];
// Adjust in Inspector
public CardScriptable[] availableCardConfigs;
void Start()
{
for (int i = 0; i < 30; i++)
{
var config = availableCardConfigs[Random.Range(0, availableCardConfigs.Length)];
// If you make the prefab of correct type this already returns a CardDisplay
tempKarta[i] = Instantiate(myPrefab);
tempKarta[i].Initialize(config);
}
}
}
After a few more details were added I think I have a handle on the issue.
You can create another ScriptableObject class which is used as a collection to avoid any external file issues which you may be having eg
Public class Deck : ScriptableObject {
Public CardScriptable[] cards;
}
ScriptableObjects can have methods of their own. The typical method I use in this situation is to make a method on your script able object to instantiate the cards and assign values there as opposed to instantiating the cards that way.
Edit: possible misinterpretation on my part, if the problem is you cannot easily change the text/sprites etc. I would make a handler class to put on your card prefab which holds references to where you need once instantiated.
That way you can use for eg GetComponent().sprite = newSprite;

Singleton – extra script not destroyed?

I want to have a singleton which will store, update and show player score through all levels (scenes), but something works wrong.
This my singletone script GameStatus.cs:
using UnityEngine;
using TMPro;
public class GameStatus : MonoBehaviour
{
public static GameStatus instance;
[SerializeField] int pointsPerBlock = 50;
[SerializeField] TextMeshProUGUI scoreText;
[SerializeField] public static int currentScore = 0;
private void Awake()
{
if (instance != null)
{
Destroy(gameObject);
}
else
{
instance = this;
DontDestroyOnLoad(gameObject);
}
}
public void AddToScore()
{
currentScore += pointsPerBlock;
scoreText.text = currentScore.ToString();
}
}
I also have another script for objects player need to destroy, called Block.cs. Here it is:
using UnityEngine;
public class Block : MonoBehaviour
{
Level level;
GameStatus gameStatus;
private void Start()
{
level = FindObjectOfType<Level>();
gameStatus = FindObjectOfType<GameStatus>();
level.CountBreakableBlocks();
}
private void OnCollisionEnter2D(Collision2D collision)
{
DestroyBlock();
}
private void DestroyBlock()
{
level.BlockDestroyed();
gameStatus.AddToScore();
Destroy(gameObject);
}
}
And on level 1 everything works fine, but when the game goes to the next level this happens:
Player score stops updating.
If I use Debug.Log(currentScore); in GameStatus.cs I can see that this variable doesn't change when player breaks blocks, but if use Debug.Log(gameStatus.currentScore); in Block.cs then I can see that this variable is getting updated.
Debug.Log(FindObjectsOfType().Length); shows that there is one GameStatus object in the first level and two objects in the next levels, although I can't see the second one GameStatus in hierarchy.
So my question is - what's wrong and how to fix it?
If you use singleton, there is no point to make
currentScore
static variable, just make it
public int currentScore;
Also in your block cs you can just call
GameStatus.instance.AddToScore();
No need to make reference at start

Why isn't my variable from another class method being registered?

My variable in my parameter method is not being called in another class.
I tried telling it that we are getting the PlayerdmgAmount from the playerlivesdisplayed class. I get an error that says that PlayerLivesDisplay can not be converted to an int.
So I comment that out and wrote in the int value again. The code runs, but it is not doing what I want it to do.
public class PlayerLivesDisplay : MonoBehaviour
{
public void takeLives(int PlayerdmgAmount)
{
playerLives -= PlayerdmgAmount;
displayUpdate();
if (playerLives <= 0)
{
//TODO load mainMenu
}
}
}//playerLives
public class DamgePlayer : MonoBehaviour
{
private void OnTriggerEnter2D(Collider2D othercollision)
{
//PlayerLivesDisplay PlayerdmgAmount = GetComponent<PlayerLivesDisplay>()
int PlayerdmgAmount = 1;
FindObjectOfType<PlayerLivesDisplay>().takeLives(PlayerdmgAmount);
}
}
public class Attacker : MonoBehaviour
{
[Range(0f, 10f)] [SerializeField] float walkSpeed = 1f;
[SerializeField] int PlayerdmgAmount = 1;
GameObject currentTarget;
public void hurtplayer(int PlayerdmgAmount)
{
FindObjectOfType<PlayerLivesDisplay>().takeLives(PlayerdmgAmount);
}
}
What I am trying to achieve:
Have the attacker script have Player dmg amount on them.
Golem1 = take 5 lives away
Fox: takes 2 lives away
Pass these variables (when collided) to the players health damage (DamagePlayer script)
Then go to the player lives display class takeLives method and input the damage variables into the parameters that was initiated from the attackers script.
If your takeLives method takes an int variable as argument, you cannot pass your PlayerLivesDisplay object, you need to pass an int instead (that's what the error is about). PlayerLivesDisplay may contain the PlayerdmgAmount (so the int) but is not in itself the PlayerdmgAmount.
Depending on what you are trying to accomplish, you could do something like:
In your PlayerLivesDisplay add a property and use it to store the value that you need to get later on:
public class PlayerLivesDisplay : MonoBehaviour
{
...
public int PlayerdmgAmount { get; set; }
...
public void takeLives(int playerdmgAmount)
{
...
this.PlayerdmgAmount = playerdmgAmount;
}
}
Now you can access the value in other classes:
public class DamgePlayer : MonoBehaviour
{
private void OnTriggerEnter2D(Collider2D othercollision)
{
int playerdmgAmount = GetComponent<PlayerLivesDisplay>().PlayerdmgAmount;
...
}
}

How to create correct script which instantiating objects using factory-pattern

I'm creating game. There are coins which player should collect. Now I'm trying to type factory-pattern and I have 2 problems.
In the factory I need to Instantiate prefab of coin and at the same time the factory must be available from other classes. It's unlikely to get factory by its instance, so it must be static. But in this case factory can't hold link to prefab. I thought about implementing singleton already, but in this way I should hold this script on the scene and it's look like some kind of "dirty way".
Coin.cs
public class Coin : MonoBehaviour
{
CellCoordinates coords;
public CellCoordinates Coords
{
get { return coords; }
set
{
coords = value;
transform.localPosition = coords.ToWorld();
}
}
static public event System.Action OnCollect;
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.tag == "Player")
{
Destroy(gameObject, 0.2f);
OnCollect?.Invoke();
}
}
}
CoinFactory.cs
public class CoinFactory : MonoBehaviour
{
[SerializeField]Coin coinPrefab;
public Coin Create(Maze.CellCoordinates coords)
{
Coin coin = Instantiate(coinPrefab) as Coin;
coin.Coords = coords;
return coin;
}
}
What I would do:
public class CoinFactory : MonoBehaviour
{
private static CoinFactory instance;
[SerializeField] private Coin coinPrefab;
private void Awake()
{
instance = this;
}
public static Coin Create(Maze.CellCoordinates coords)
{
var coin = Instantiate(instance.coinPrefab);
coin.Coords = coords;
return coin;
}
}

Categories

Resources