I have setup a simple shop where when the player clicks to 'BUY' a character, the item gets unlocked and stays saved as unlocked. This is in one scene (Shop scene).
I have a second scene where the character gets unlocked based on this purchase for the player to be able to select (character scene).
I am placing the scripts on empty gameObjects on each scene. It works fine when it is just buying one character. But how do I replicate this for multiple characters.
I could place the scripts on individual buttons and place corresponding gameobjects under the inspector but this would not be practical if I have like a 100 characters.
Please advice how I could make this work across multiple instances. Thought of tagging and that too doesn't seem feasible. I am open for suggestions if there is a better way of doing this. Thanks.
//Class controlling Shop Scene via an empty Gameobject
public class ShopManager : MonoBehaviour
{
private bool unlocked;
public GameObject greyImg;
void Start()
{
unlocked = PlayerPrefs.GetInt("unlocked") == 1;
greyImg.SetActive(unlocked);
}
public void Buy()
{
unlocked = true;
PlayerPrefs.SetInt("unlocked", 1);
PlayerPrefs.Save();
greyImg.SetActive(true);
}
}
This is how the unity setup looks for shop scene. If the item is already bought, the grey image is set to active thus not allowing the user to click the green buy button any more.
When character is unlocked/bought
//Class controlling Character select scene via an empty Gameobject
public class CharacterManager : MonoBehaviour
{
private bool unlocked;
public GameObject greySelect;
void Start()
{
unlocked = PlayerPrefs.GetInt("unlocked") == 1;
}
void Update()
{
if (unlocked)
{
greySelect.SetActive(false);
}
}
}
This is how Unity setup looks for Character select scene. If the character is already unlocked, the grey select image is set to inactive and the orange select button will be visible thus allowing the character to be selected.
when character unlocked
There are probably many ways in which to tackle this problem. Here's one;
You're going to need a separate player prefs entry for each character. So, you'll need a nice way to keep track of the characters you have and their unlock state. Instead of saving 'unlocked' in player prefs, why not create a class that contains unlocked information?
class UnlockedCharacters
{
bool characterAUnlocked = false;
bool characterBUnlocked = false;
bool characterCUnlocked = true;
}
You can then serialize this whole class and save the whole class inside player prefs. Then, when you load your game you can load this class from player prefs to populate your character information. This way, data is managed and is consistent across saved states.
You could go one step further and keep everything relating to your characters inside of a Dictionary whereby your int is an enum referring to a character and the bool is its unlock state. You can then save/load this dictionary again using player prefs.
Inside the class you could have helper methods with your generic gameobject scripts call to keep things nice and encapsulated.
This way, your individual GameObjects which handle specific characters can hold a reference to this enum and you can use your general script to set/modify your data contents based on your enum field that you can then set via your inspector, or in initialisation code for your object.
EDIT FOR COMMENT BELOW - AN EXAMPLE:
Your generic character controller would go on the individual objects, and by changing your CHAR_TYPE the same script will work to unlock multiple characters.
class CharacterManager
{
public enum CHAR_TYPE = { characterA, characterB, characterC }
private Dictionary<CHAR_TYPE, bool> characterUnlockState;
void Start()
{
// Seeding with some data for example purposes
characterUnlockState = new Dictionary<CHAR_TYPE, bool>();
characterUnlockState.Add(CHAR_TYPE.characterA, false);
characterUnlockState.Add(CHAR_TYPE.characterB, false);
characterUnlockState.Add(CHAR_TYPE.characterC, true);
}
public bool IsCharacterUnlocked(CHAR_TYPE character)
{
if (characterUnlockState.Contains(character)) return characterUnlockState[character];
return false;
}
public void UnlockCharacter(CHAR_TYPE character)
{
if (characterUnlockState.Contains(character)) characterUnlockState[character] = true;
}
}
class GenericCharacterController
{
public CHAR_TYPE character;
public CharacterManager manager;
public void UnlockButtonPressed()
{
manager.UnlockCharacter(character);
}
}
There are a number of ways to approach this. You are going to need an ID of some kind for each item in your store. So I would make a StoreItem script that you place on each thing for sale. The StoreItem script would need an ItemID property at the minimum. Then your StoreManager could check if it's unlocked like this:
PlayerPrefs.GetInt(selectedStoreItem.ItemID + "_unlocked");
Also, while this approach would work, it's not recommended. It's possible for playerprefs to be manipulated by the user outside of your game. So they could potentially give themselves free items. I don't know how important that is to you. The better approach would be store this info on a server somewhere and have the game sync up with that to determine what items the player owns.
Related
I am trying to get my 3-D Tic-Tac-Toe game project to work, I have game objects which are named cells that are instantitated I press OnMouseDown() click it makes a cell object spawn in its grid space. I don't want to use UI with the basic prefabs I created. Is there a way to get my game objects instantiated and once it reaches a certain number as a winning condition? I have considered using pathfinding but I am not certain if that would be the correct approach. I have looked every where to find a solution that is unique to my problem but could not find a solution. Perhaps, I am asking the wrong questions but I am desperate so that is why I came her to see if I could get input on how to approach this issue.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;
using UnityEngine.SceneManagement;
public class Cell : MonoBehaviour
{
public GameObject cell;
public GameObject negCell;
public GameObject alter;
public Transform transPos;
[SerializeField]
private bool isTapped = false;
private int counted;
public int gameObjectCount;
void Start()
{
gameObjectCount = GameObject.FindGameObjectsWithTag("Cell1").Length;
}
void Update()
{
}
public void OnMouseDown(int counted) //click and point to create and deestroy objects
{
counted = gameObjectCount;
isTapped = true;
transPos = alter.transform;
Instantiate(cell, transform.position, Quaternion.identity);
StartCoroutine(WaitForObject());
Debug.Log("Alter Destroyed!");
gameObjectCount++;
DestroyGameObject();
return;
}
IEnumerator WaitForObject()
{
if (isTapped == true)
{
Instantiate(negCell, -transform.position, Quaternion.identity);
isTapped = false;
}
yield return new WaitForSeconds(3f);
DestroyGameObject();
}
void DestroyGameObject()
{
if(gameObject == alter)
{
DestroyImmediate(alter, true);
}
else
{
DestroyImmediate(cell, true);
}
}
}
There are two easy ways to achieve this.
The first one would be to add a static member in your class, let's say :
private static int _instanceCounter = 0;
This will act as a class instances counter.
All you have to do is to increment this variable every time you instantiate a new game object.
Finally, base your win condition on the number of instances of the class you want.
You can also decrement this variable if for some reason at a moment you call the Destroy method on a specific game object.
The other way would be to use the FindObjectsOfType method from Unity which returns an array of all instances in your current scene.
By accessing the length of this array, you'll have the number of instances.
However, this only count for the current number of instances when this method is called. Note that you can also include the inactive game objects from the scene (those which are in grey within your hierarchy panel).
You now have two ways to do it, depending on how you want to achieve your win condition, i.e. the total of game objects instantiated OR a specific number of game objects at a given time.
In my game I have a big catalog of gear: Armors, weapons and shields. The combinations between these can be really immense.
Besides that, the player has the option of switching in-game to a different set of armor-weapon combination. In the end to solve this, I have used the following object structure.
Whenever I switch the weapons, I activate/deactivate the necessary GameObjects. The animations are set in this way:
Now, the problem is creating the animation. I first considered pre-rendering programatically all the combinations, but my catalog is so huge, that it would create 100s, if not 1000s of animations. So I opted for a different solution. Create in playtime the animation, once I knew what gear would the player select. For that, I created a script to take care of that. The problem is that I have been using APIs from UnityEditor, and now I have realized the build will not work. Specifically because of 2 different classes: EditorCurveBinding and ObjectReferenceKeyframe.
This is a couple snippets of how I was using this classes when creating the animations:
static EditorCurveBinding GetEditorCurveBinding(string path = "")
{
EditorCurveBinding spriteBinding = new EditorCurveBinding();
spriteBinding.type = typeof(SpriteRenderer);
spriteBinding.path = path;
spriteBinding.propertyName = "m_Sprite";
return spriteBinding;
}
static ObjectReferenceKeyframe GetKeyframe(float time, Sprite sprite)
{
ObjectReferenceKeyframe keyframe = new ObjectReferenceKeyframe();
keyframe.time = time / FRAMERATE;
keyframe.value = sprite;
return keyframe;
}
Now, the problem with the Curve, I think I managed to solve, replacing it with this code, replacing EditorCurveBinding with AnimationCurve:
AnimationClip clip = ...
AnimationCurve curve = new AnimationCurve();
clip.SetCurve(path, typeof(SpriteRenderer), "m_Sprite", curve);
But I have no idea how to set the sprites for each animation. I thought that using curve.AddKeycould be helpful, but I have seen no way to add a sprite there.
How could I rewrite that code to avoid using UnityEditor?
Full code
Personally, I would completely avoid built-in Animator and Animations, since this tool is for the very narrow purpose of animating a single object in a predefined way (eg: for a cutscene).
Offtopic - performance
Besides that, the player has the option of switching in-game to a different set of armor-weapon combination. In the end to solve this, I have used the following object structure.
As you probably know this is very inefficient memory-wise and will decrease performance (disabled objects still have marginal CPU overhead). And will incread load time and instantiation time of new objects.
Can I use Animator or Animation?
Because Animator has no API to access it's internals and animation type and overall you cannot do pretty mutch nothing with it except from telling it to Play or Stop.
Since I understand that your animation is "Sprite based" and not "Transform based" any sane idea is just inefficient!
Best solution that I vow against is as follows:
Create "trigger points" that you would "animate"
Based on trigger point - change sprite
public class AnimationController : MonoBehaviour
{
public int AnimationIndex;
public Sprite[] AnimationSprites;
public SpriteRenderer SpriteRenderer;
private void Update()
{
SpriteRenderer.sprite = AnimationSprites[AnimationIndex];
}
}
Better solution?
Since we already need to have custom structure that manages our sprites we might want to optimize the whole thing, since we are not using almost any of the features of Animator we could write custom controller that would replace Animator in this case. This should improve performance significantly since Animator is very heavy!
// MonoBehaviour is optional here
public class SpriteRendererAnimationHandler
{
// More fields that would control the animation timing
public Sprite[] AnimationSprites;
public SpriteRenderer SpriteRenderer;
public void OnAnimationUpdate(int index)
{
var resolvedIndex = ResolveIndex(index);
SpriteRenderer.sprite = AnimationSprites[resolvedIndex];
}
private int ResolveIndex(int index)
{
// Resolve animation index to sprite array index
}
}
// One controller per character - or global per game that synchronize all animations to locked FPS 12.
public class AnimationController : MonoBehaviour
{
private List<SpriteRendererAnimationHandler> Handlers = new List<SpriteRendererAnimationHandler>();
public void FixedUpdate()
{
foreach (var handler in Handlers)
{
// Calculate animation index
int calculatedAnimationIndex = ...;
handler.OnAnimationUpdate(calculatedAnimationIndex);
}
}
}
Add a public field named animation index to Tomasz last example, then create the animations in animator as youd normal do for animation pieces, then animate that animation index field. simple if(currentAnimationyion != lastAnimationIndex) to check if need to send to handlers and ya rocking
I am working on a AR project, where the virtual objects will be shown/hide in the scene based on information found in a text file. The text files will be updated from an external service. So I need to read the file on a frequent interval and update the scene. As a result I only have the Camera object and I am rendering the scene in OnPreCull() method.
The text files contain many objects but not all the objects are within the scene at any instance of time. I was looking for a way to render only those objects that are within the scene.
Will creating and placing the gameobjects in the OnPreCull() method crate any performance issue?
Will creating and placing the gameobjects in the OnPreCull() method crate any performance issue?
Yes absolutely ... so would it if you do it in Update or any other repeatedly called method.
Instead you should rather Instantiate objects in Awake and only activate or deactivate them.
Let's say you have 3 objects A, B and C than I would make a kind of controller class that looks like
public class ObjectsController : MonoBehaviour
{
// Define in which intervals the file should be read/ the scene should be updated
public float updateInterval;
// Prefabs or simply objects that are already in the Scene
public GameObject A;
public GameObject B;
public GameObject C;
/* Etc ... */
// Here you map the names from your textile to according object in the scene
private Dictionary<string, GameObject> gameObjects = new Dictionary<string, gameObjects>();
private void Awake ()
{
// if you use Prefabs than instantiate your objects here; otherwise you can skip this step
var a = Instantiate(A);
/* Etc... */
// Fill the dictionary
gameObjects.Add(nameOfAInFile, a);
// OR if you use already instantiated references instead
gameObjects.Add(nameOfAInFile, A);
}
}
private void Start()
{
// Start the file reader
StartCoroutine (ReadFileRepeatedly());
}
// Read file in intervals
private IEnumerator ReadFileRepeatedly ()
{
while(true)
{
//ToDo Here read the file
//Maybe even asynchronous?
// while(!xy.done) yield return null;
// Now it depends how your textile works but you can run through
// the dictionary and decide for each object if you want to show or hide it
foreach(var kvp in gameObjects)
{
bool active = someConditionDependingOnTheFile;
kvp.value.SetActive(active);
// And e.g. position it only if active
if (active)
{
kvp.value.transform.position = positionFromFile;
}
}
// Wait for updateInterval and repeat
yield return new WaitForSeconds (updateInterval);
}
}
If you have multiple instances of the same prefab you also should have a look at Object Pooling
I'd recommend adding each of the game objects to a registry and the switching them on or off (dis/enable SetActive) via the registry class's Update() cycle.
One Update() process to retrieve and handle the server file, another Update() process to dis/enable objects. Might sound oversimplified however it's the fastest way I think of getting the result.
Good Luck!
I have my own dialog system which is basically a gameobject with a collider. After triggering collider, Canvas with a Text component should show up. So far, this works. Now, I want to make it happen only once. Here how its work: I start level trigger showing dialog. Game is pretty hardcore so player probably will die. When player dies, I call Application.LoadLevel(Application.loadedLevel); (so basically I restart level)
If I use something like this in scrip with collider
private static bool firstTimeText = true;
void OnTriggerEnter2D(Collider2D coll)
{
if (coll.tag == "Player" && firstTimeText)
{
GetComponent<BoxCollider2D>().enabled = false;
firstTimeText = false;
}
}
everything works like a charm. EXCEPT if I make copy of this gameobject, static variable in script will "handle" that every instantion of this object will have firstTimeText on false after trigger dialog first time. So basically I can use it only once.
So is there any sollution for making trigger which run only once and not reset after Application.LoadLevel?
Also this doesn't work
void Awake()
{
DontDestroyOnLoad(transform.gameObject);
}
Static variables are globally identical - therefore if you need to have multiple gameobjects exhibiting this behavior, they'll need to hook into different static values in some way.
One way to do this would be to have a key, and then keeping a static list of all "keys" already displayed. My recommendation would be a HashSet - thus
private static HashSet<string> firstTimeSet = new HashSet<string>();
public string singleRunKey;
void OnTriggerEnter2D(Collider2D coll)
{
if (coll.tag == "Player"
&& firstTimeSet.Add(singleRunKey))
{ GetComponent<BoxCollider2D>().enabled = false; }
}
Note that .Add returns true if the item wasn't in the collection (and adds it), but false if it already was (and does not add it, because no duplicates). All you have to do now is assign singleRunKey a unique value per object via the Inspector, and each one will run exactly once
Consider just having a static class that will set a flag to indicate if the dialog should appear or not. Note that I am not inheriting from MonoBehaviour here.
public static class ApplicationData {
public static bool ShowDialog = true;
}
Usage:
if (ApplicationData.ShowDialog)
{
ShowDialog();
ApplicationData.ShowDialog = false;
}
The static class will retain its values during the lifetime of your application. So, it will retain the FALSE value even if you reload your scene.
Thanks for help in advance. Here is a short snippet of the code that I am having an issue with.
GameObject[] allMotor_array;
public List<GameObject> BrokenMotor_list = new List<GameObject>();
void Start()
{
allMotor_array = GameObject.FindGameObjectsWithTag ("Motors");
}
void Update()
{
foreach (GameObject motor in allMotor_array)
{
if(motor.GetComponent<Pump_event>().damaged)
{
BrokenMotor_list.Add(motor);
}
}
}
I have an array of Gameobjects that is created on Start, each of the gameobjects in the array have a script called Pump_event. What I want to do is add the gameobject with a true boolean (damaged) to the list so that I can create a GUI list of all the motors that are damaged (and then take further action on those motors).
With the current code it instantiates the array fine, but when One of the motors boolean changes to true the list tends to continuously add the motor gameobject to the list on each update cycle. So what I want is to figure out a way of adding the gameobject to the list ONCE.
Having it in the update() is probably not the best method but I really am stuck on how to approach this.
G
The Solution to my problem
Thanks for your answers, you all had well thought out responses. I appreciate it. I didn't go with 1 persons method but instead adapted logical approaches found here to work with my script/s.
Here is what I did.
In my pump_event script the events are sorted in a Case and switch as damage increased on the pump the event would escalate. So I added in a section to that script to include "reporting" the damage.
public class Pump_event : MonoBehaviour
//The damage has taken place and event_category=0\\
switch (event_category)
{
case 0:
Master_script.GetComponent<Control_room>().AddtoList (gameObject);
event_category = 1;
break;
I took advice not to insert these types of programing and placed it into its separate class which works out well.
public class Master_script: MonoBehaviour
public void AddtoList(GameObject motor_tobadded)
{
BrokenMotor_list.Add(motor_tobadded);
}
This also eliminated the need on having an array holding all of the pump event controllers as well.
Now the script all works fine. It may not be most efficient but it is doing its job.
Thank you again to all that helped.
In your Pump_event Script you can have a event Action which you register in this snippet and whenever damaged is set true you need to fire the event.
Example:
// in Pump_event Class
public static event Action<GameObject> OnDamagedValueChanged;
private bool _damaged;
public bool Damaged
{
get { return _damaged;}
set
{
_damaged = value;
if(_damaged)
{
if(OnDamagedValueChanged != null)
OnDamagedValueChanged(gameObject);
}
}
}
In your Current Class where you have array of GameObjects:
void OnEnable()
{
Pump_event.OnDamagedValueChanged += HandleOnDamagedValueChanged;
}
void OnDisable()
{
Pump_event.OnDamagedValueChanged -= HandleOnDamagedValueChanged;
}
void HandleOnDamagedValueChanged(GameObject obj)
{
if (!BrokenMotor_list.Contains (obj))
{
BrokenMotor_list.Add (obj);
}
}
Using Actions is a better approach than doing it in Update Method. It is not good for performance to keep checking for a bool in iteration in update method. and try to avoid GetComponent and Find/FindObjectWithTag Methods in Update. It is not good practice. I hope this is helpful.
According to the code you have posted, the problem lies within the fact that the damaged property is never reset. One solution would be to reset this property once you add it to the list, like so:
if(motor.GetComponent<Pump_event>().damaged)
{
motor.GetComponent<Pump_event>().damaged = false;
BrokenMotor_list.Add(motor);
}
However, multiple copies of the same object could still be added to your list if the motor is damaged again.
To go around this, you could use a HashSet. The hash set will allow only one copy of an object to exist within it, thus, if an object is already present is will not be added again.
The catch is that you will need to override the GetHashCode and Equals methods for your GameObject class since these will be used internally by the hash set to place items within itself and identify duplicates.
check if list already contains motor.
if(motor.GetComponent<Pump_event>().damaged)
{
if(BrokenMotor_list.Contains(motor))
{
BrokenMotor_list.Add(motor);
}
}
although on msdn describes how to implement IEquatable in case if you want compare different objects(with different references) https://msdn.microsoft.com/ru-ru/library/vstudio/bhkz42b3%28v=vs.100%29.aspx
if (!BrokenMotor_list.Contains (motor)) {
BrokenMotor_list.Add (motor);
}
You'd better do this after damage event occur by add a delegate.