I am doing a Unity project.
I have two scenes in my project.
In the first scene. I set three buttons texted "1 min", "2 mins" and "3 mins".
And I want to have the corresponding game duration in my second scene (game scene).
I have countdown function in my script.
now I am using singleton pattern following the steps of this tutorial. https://www.youtube.com/watch?v=CPKAgyp8cno
Problem is when I run the game, three texts of time start simultaneously.
And my buttons in Scene one could control load scenes but not choose time.
The following are the codes. In the PersistentManager Script which attached to the PersistentManager game object:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class PersistentManagerScript : MonoBehaviour
{public static PersistentManagerScript Instance { get; private set; }
public int Value;
// Start is called before the first frame update
private void Awake()
{
if (Instance == null)
{
Instance = this;
DontDestroyOnLoad(gameObject);
}
else
{
Destroy(gameObject);
}
}
}
In the SceneManager Script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class SceneManagerScript : MonoBehaviour
{
public GameObject CountDownIn1;
public GameObject CountDownIn2;
public GameObject CountDownIn3;
public Text ValueText;
public Text ValueText1;
public Text ValueText2;
private void Start()
{
switch (this.gameObject.name)
{
case "Button1min":
SetGameTimeEqualOne();
break;
case "Button2mins":
SetGameTimeEqualTwo();
break;
case "Button3mins":
SetGameTimeEqualThree();
break;
}
}
public void SetGameTimeEqualOne()
{
CountDownIn1.SetActive(true);
ValueText.text = PersistentManagerScript.Instance.Value.ToString();
Debug.Log("COUNTDOWN1 active");
//DontDestroyOnLoad(this.CountDownIn1);
}
public void SetGameTimeEqualTwo()
{
CountDownIn2.SetActive(true);
Debug.Log("COUNTDOWN2 active");
ValueText1.text = PersistentManagerScript.Instance.Value.ToString();
DontDestroyOnLoad(this.gameObject);
}
public void SetGameTimeEqualThree()
{
CountDownIn3.SetActive(true);
Debug.Log("COUNTDOWN3 active");
ValueText2.text = PersistentManagerScript.Instance.Value.ToString();
DontDestroyOnLoad(this.gameObject);
}
Any suggestions will be appreciated.
There are multiple ways to pass data between scenes. The most common recommendation is to use a Singleton pattern that is consumed by other scripts in each scene. However, this can be overly complicated.
Interestingly enough, Unity's newer ScriptableObjects can be used for a similar strategy. Since ScriptableObjects are persisted at a higher scope than scenes, you can use them to hold data between scenes without having to utilize statics and singletons. Unity even markets them as more than just data containers! I prefer to pass data between objects and scenes by creating a common scriptable object, and referencing that asset in each script that needs to share data (regardless of scene).
Here's an example using your requirement:
GameData.cs
[CreateAssetMenu(fileName = "GameData", menuName = "Game Data")]
public class GameData: ScriptableObject
{
float gameDuration; //This is where you will store and read your selected game duration
}
GameSelection.cs This script exists on Scene 1
public class GameSelection : Monobehaviour
{
public GameData gameData;
public void SetDuration(float time) //Call this method from each button with a different parameter
{
gameData.gameDuration = time;
}
}
Countdown.cs This script exists in Scene 2
public class Countdown: Monobehaviour
{
public GameData gameData;
float countdownTimer;
public void Start()
{
countdownTimer = gameData.gameDuration;
}
//... rest of your countdown script
}
Now you can create a GameData asset in your project folder and drag it into the inspector in Scene1, and the inspector in Scene2. If Scene1 changes the value of the timer, Scene2 will know about it. Additionally, you can set a default value for gameDuration on the GameData asset and run/test Scene2 without having to run Scene1 first!
Related
Hello i make my 2d game with unity and i feel confuse about oop design.
There is a 4 class in my game.
StageView : The view(scene) where the game logic run.
ObjectPool : The object pool that can manage the gameobjects, and it is the member field of
stage view. (It is not a singleton)
Projectile : The projectile class that can attack the monster.
Monster : The monster class that could be attacked by projectile.
public class Monster : MonoBehaviour
{
private int hp;
public delegate void OnMobHitHandler(Projectile projectile, Monster monster, long damage);
public delegate void OnMobDieHandler(Monster monster);
public OnMobHitHandler OnMobHit;
public OnMobDieHandler OnMobDie;
public void OnTriggerEnter2D(Collider2D collision)
{
Projectile projectile = collision.gameObject.GetComponent<Projectile>();
hp -= projectile.Power;
OnMobHit?.Invoke(projectile, this, projectile.Power);
if(hp <= 0)
OnMobDie?.Invoke(this);
}
}
If the monster got hit, then the fragment object will be created.
And if the monster die, then it must be released.
But the fragment objects are managed by ObjectPool which is in StageView.
(Also monster are managed by ObjectPool samely)
So there are 2 choices in my head.
Make Init(Objectpool objectpool) Method and pass the objectpool to monster as a parameter.
Then put "create fragment logic" and "Release(die) monster logic" in to the Monster class.
Expose the monster's callback event to the StageView like above example code.
Their 2 logics must be in StageView's OnMonsterHit, OnMonsterDie.
I choiced second, because i want to loosen the dependencies of Monster class.
(You know, if i don't loosen the dependencies then it's gonna be more complicate later)
But sometimes, I think that it may seem more appropriate for the object to handle it internally rather than entrusting it to the outside.
I'm very confused which one is more correct in OOP aspect.
I think it's a good idea to make one more layer between ObjectPool and Monster classes that will manage what you want. It will complete Single responsibility principle of SOLID.
So both classes ObjectPool and Monster will not depend on each other and every class will be making their jobs.
I done it next way (similar to your second example): Spawner components containing and sometimes sharing the same Pool ScriptableObject. When a Spawner spawns an object it passes OnDespawn handler to Despawner component on the pooled object. From there the spawned object is on its own.
In a sense Spawners act as a middle (sub-pool) layer that (together with other components) are responsible to setup the freshly pooled object.
Btw Spawner adds Despawner component on the pooled object if it is missing and Despawner have destroy fall-back if OnDespawn handler is not assigned. This ensures all prefabs can work both as pool's prototype and stand-alone instantiable.
The Spawner component goes on a spawner game object (for example tied to some trigger-collision region that calls one of the spawn public methods):
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
namespace SpawnSystem
{
public class Spawner : MonoBehaviour
{
[Header("Pooler")]
// in this case this is pool randomizer but it could be directly a ScriptableObject pool
[SerializeField] private PoolAlternator pooler;
[Header("Debug")]
// this is just to see in the inspector what is spawned for debugging, it can be skipped in a build
[SerializeField] [ReadOnly] private List<GameObject> spawned;
[Header("Events")]
[SerializeField] private UnityEvent<GameObject> onSpawn;
[SerializeField] private UnityEvent<GameObject> onDespawn;
#region Spawn
public GameObject GetSpawn()
{
GameObject spawnedObject = pooler.Spawn();
SpawnRoutine(spawnedObject);
return spawnedObject;
}
public void Spawn()
{
GameObject spawnedObject = pooler.Spawn();
SpawnRoutine(spawnedObject);
}
public void Spawn(Vector3 position)
{
GameObject spawnedObject = pooler.Spawn(position);
SpawnRoutine(spawnedObject);
}
public void Spawn(Vector3 position, Quaternion rotation)
{
GameObject spawnedObject = pooler.Spawn(position, rotation);
SpawnRoutine(spawnedObject);
}
public void Despawn(GameObject go)
{
if (go == null) { return; }
onDespawn?.Invoke(go);
spawned.Remove(go);
pooler.Despawn(go);
}
public void DespawnAll()
{
for (int i = spawned.Count - 1; i >= 0; i--)
{
Despawn(spawned[i]);
}
}
#endregion Spawn
private void SpawnRoutine(GameObject spawnedObject)
{
if (spawnedObject == null) { return; }
spawned.Add(spawnedObject);
SetupDespawner(spawnedObject);
onSpawn?.Invoke(spawnedObject);
}
private void SetupDespawner(GameObject spawnedObject)
{
Despawner despawner =
spawnedObject.GetComponentInChildren<Despawner>()
?? spawnedObject.AddComponent<Despawner>();
despawner.SetReturnToPool(Despawn);
}
}
}
The Despawner component lives at the spawned/pooled object. When the entity is dead (say health handling/changing component detects HP is <= 0) it calls Despawner.Despawn():
using UnityEngine;
using UnityEngine.Events;
namespace SpawnSystem
{
public class Despawner : MonoBehaviour
{
[Tooltip("Fall-back strategy when the object is not spawned from a pool.")]
[SerializeField] private FallbackStrat fallbackTo = FallbackStrat.Destroy;
[Header("Events")]
[SerializeField] UnityEvent<GameObject> onDespawn;
private enum FallbackStrat { Destroy = 0, Disable = 1 }
private UnityAction<GameObject> returnToPoolRoutine;
public void SetReturnToPool(UnityAction<GameObject> returnToPool)
{
this.returnToPoolRoutine = returnToPool;
}
public void Despawn()
{
onDespawn?.Invoke(this.gameObject);
if (returnToPoolRoutine != null)
{
returnToPoolRoutine.Invoke(this.gameObject);
returnToPoolRoutine = null;
}
else { Fallback(this.gameObject); }
}
private void Fallback(GameObject go)
{
switch (fallbackTo)
{
default:
case FallbackStrat.Destroy:
Destroy(go);
break;
case FallbackStrat.Disable:
go.SetActive(false);
break;
}
}
}
}
I'm trying to make a gun/weapon system that allows me to be more flexible by using one script that contains "the shooting mechanics of that specific weapon" per gun instead of one script for all gun (because the weapons I'm making has its own characteristics).
what I did was:
Basically I have 3 main scripts,
the script from the player that acts as the input (on the player's game object)
the script that holds the script for shooting mechanic for left and right mouse button (on the weapon game object)
the script that does the shooting mechanics (on the weapon)
I have a script for the player itself (for the weapon controls), so when the player click LMB it will get the current weapon that it's holding, then get the first weapon script (1st) that holds the other shooting mechanic scrips (2nd) and then run a function which is "LMB()", that runs a function inside the 3rd script which is to shoot (3rd).
1st script
public class PlayerShooting : MonoBehaviour
{
public GameObject[] WeaponList;
public int ListLength;
public int currentWeapon;
// Start is called before the first frame update
void Start()
{
ListLength = WeaponList.Length;
}
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.Mouse0))
{
// get the 2nd script for the current weapon and runs "LMB()"
}
}
}
2nd script
public class WeaponScript : MonoBehaviour
{
public GameObject selfRef;
public int WeaponId;
public void LMB()
{
//I don't understand this one
//var type = Type.GetType(WeaponId + "LMB");
//selfRef.GetComponent(type);
// or maybe?
//selfRef.GetComponent<WeaponId + "LMB">().shoot();
}
}
3rd script
public void shoot()
{
//the shooting happens
}
I'm sorry if it confuses you, feel free to ask questions.
How about having an intermediate class for shooting behaviors?
public abstract class WeaponScript : MonoBehaviour
{
public virtual void PrimaryFire(){}
public virtual void SecondaryFire(){}
}
Then derive e.g.
public class LaserScript: WeaponScript {
public override void PrimaryFire() { /* pew pew! */ }
}
and attach that to your laser gun.
You should then be able to
if (Input.GetKeyDown(KeyCode.Mouse0)) {
// Get the first weapon script attached to the weapon
var weaponScript = WeaponList[currentWeapon].GetComponent<WeaponScript>();
weaponScript.PrimaryFire();
}
I'm working on a 2D Pixel Platformer RPG, I need to develop a save and load mechanism in it. There are several scenes in the game (and will have many more), the question is, how do I save the scene number the player is currently in, so that when he quits and reloads the game, he's in the same scene. How can I implement it in C# Unity. (please be clear as I'm somewhat a beginner).
Ok, so there are a few things you need to do in order to achive this:
First, in the first scene in your build - create an Empty GameObject, name it "SceneManager".
Then, create a new tag "SceneManager" and add it to the "SceneManager" GameObject
Finally, add the "SceneManager" script to the "SceneManager" GameObject:
using System.Collections;
using UnityEngine;
using UnityEngine.SceneManagement;
public class SceneManager : MonoBehaviour
{
void Awake()
{
DontDestroyOnLoad(gameObject);
}
public void SaveScene()
{
int activeScene = SceneManager.GetActiveScene().buildIndex;
PlayerPrefs.SetInt("ActiveScene", activeScene);
}
public void LoadScene()
{
int activeScene = PlayerPrefs.GetInt("ActiveScene");
SceneManager.LoadScene(activeScene);
}
}
Then, you can load/save scenes by using this script:
using UnityEngine;
public class UsageScript: MonoBehaviour {
private SceneManager SceneManager;
void Awake ()
{
sceneManager = GameObject.FindGameObjectWithTag("SceneManager").GetComponent<SceneManager>();
}
void UsageManager()
{
sceneManager.SaveScene();
sceneManager.LoadScene();
}
}
I'm trying to create a shop where you can buy a different player sprite from in-game currency. (The shop is a separate scene from the level) I was told that using scriptableobject is the way to go, so I made the following:
[CreateAssetMenu(fileName = "New Sprite", menuName = "Player Sprites")]
public class PlayerSprites : ScriptableObject
{
public string spriteName;
public int cost;
public Sprite sprite;
}
And I just added to the player script
public SpriteRenderer spriteRenderer;
void Start()
{
spriteRenderer = GetComponent<SpriteRenderer>();
}
I'm not really sure where to go from here... how to render the sprite on to the player from a different scene when the sprite button is pressed... Any help is greatly appreciated thank you!
Though your question is quite hazy and I don't really see what you tried so far:
As you have it right now you will need one ScriptableObject for each Sprite item ... I don't think that's what you want. You should rather use one ScriptableObject storing the information of all Sprite items.
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(fileName = "Assets/New Store", menuName = "Sprites-Store")]
public class SpriteStoreContainer : ScriptableObject
{
public List<StoreSpriteItem> SpriteItems = new List<StoreSpriteItem>();
// you can/should also implement methods here as in any usual component!
}
Also make sure your fileName starts with Assets/
And a separate class for the Items which uses [System.Serializable] so you can display it in the Inspector.
using UnityEngine;
[System.Serializable]
public class StoreSpriteItem
{
public string spriteName;
public int cost;
public Sprite sprite;
public bool IsAvailable;
// also here you could/should implement some methods e.g.
public void BuyItem()
{
IsAvailable = true;
}
}
And back in Unity:
Now you first have to Instantiate the ScriptableObject asset:
Go to the Project View (Assets) -> right mouse click -> in the menu click on Create -> click on Sprites-Store
This should create a new asset called New Store(.asset) under Assets
Now in the Inspector of this created asset you fill in the information you need. You should see our List SpriteItems with Size = 0.
To create elements just increase the Size value and hit enter(Carefull: Unity doesn't ask if you change this value => take care you don't delete items by reducing this value accidently later)
Now you can adjust all information for those SpriteItems
Later wherever you need access to the information of this Asset you can just use the reference as any other Component => you can assign it e.g. via the Inspector using a public field e.g.
using UnityEngine;
public class ScriptThatUsesStore : MonoBehaviour
{
public SpriteStoreContainer Store;
public void DoSomthingWithStore()
{
// example: just get the first List element
Sprite someSprite = Store.SpriteItems[0].sprite;
}
}
and than access the data in it. Though I strongly recommend you rather implement some methods in the ScriptableObject like e.g. BuyItem, GetSprite, etc.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class SceneLoader : MonoBehaviour
{
Scene scene;
private void Awake()
{
scene = SceneManager.GetActiveScene();
}
public void LoadScene(string level)
{
if (level == "Game")
{
SceneManager.LoadScene("Game");
}
else
{
UnityEngine.SceneManagement.SceneManager.LoadScene(level);
}
}
}
When it reloads the game scene the objects are stationary like when they were when the scene finished.
What you do is supposed to work.
The only objects that are not reloaded are those that have a script containing DontDestroyOnLoad. You should check if this is the case for your objects
By the way, your if-else blocks are useless, they do the same thing as if you wrote
public void LoadScene(string level)
{
SceneManager.LoadScene(level);
}
If the objects are showing in the Scene view but not in the hierarchy, you should check if the scene is collapsed. If so, click the arrow to the left of the scene name.