c# Unity Sharing Script with different instances - c#

The problem is I have a class for a potato, and when i use it via inventory, it should take a use off the potato, and only that potato. Instead, its taking it off every potato in the game, is there a way to make it where all the potatos have their own instance of the code?
public class Food : Item {
public float healthHealedOnUse;
public int uses;
}//name ect is in the Item base class
Code I am using for Inventory:
public void UseItem(){
if (item != null) {
if (item is Food) {
Debug.Log ("using "+item.name);
PHH.Heal (((Food)item).healthHealedOnUse);
((Food)item).uses--;
if(((Food)item).uses < 1){
ClearSlot();
}
} else {
item.Use ();
}
It is effecting all potatos, not just the one I click on.
Adding to Inventory
public List<Item> items = new List<Item>();
public bool add(Item item){
if (items.Count >= space) {
Debug.Log ("Inventory Full");
return false;
} else {
items.Add (item);
onItemChangedCallback.Invoke ();
return true;
}
}
Player picking up item script
void PickUp(){
Debug.Log ("Picking up " + item.name+"!");
bool wasPickedUp = Inventory.instance.add (item);
//Debug.Log(wasPickedUp);
if (wasPickedUp == true) {
Destroy (gameObject);
}
Code adding item into a inventory slot
public void AddItem(Item newItem){
item = newItem;
icon.sprite = item.icon;
icon.enabled = true;
removeButton.interactable = true;
}
How i am displaying items in inventory slots
Inventory inventory;
public GameObject Inventoryui;
public Transform itemsParent;
InventorySlot[] slots;
void Start () {
Inventoryui.SetActive (false);
inventory = Inventory.instance;
inventory.onItemChangedCallback += UpdateUI;
slots = itemsParent.GetComponentsInChildren<InventorySlot> ();
}
public void ToggleInventory(){
Inventoryui.SetActive (!Inventoryui.activeSelf);
}
void UpdateUI(){
Debug.Log ("Updating UI");
for (int i = 0; i < slots.Length; i++) {
if (i < inventory.items.Count) {
slots [i].AddItem (inventory.items [i]);
} else {
slots [i].ClearSlot ();
}
}
}

...which takes it a scriptible item
There's your "every reference is the same" problem.
You need to either:
clone the scriptable item when it's picked up and add the clone to the player's inventory
treat the scriptable item as a singleton that describes what an item is and create an "ItemStack" class the way Minecraft does (there is only one Potato object, but all stacks of potatoes are an instance of ItemStack which contains a reference to the idealized potato instance).

Related

Start, Pause, Resume and Stop Unity Coroutines?

I have a class Program which is a list of Nodes that contain a method IEnumerator Invoke() and the Program class iterates through each Node invoking it. I want to be able to provide methods to Start, Pause, Resume, and Stop execution. Starting would cause the invocation to start at the top of the list, Pausing would effectively 'Stop' the execution and allow Resume to be able to pick up wherever execution was when Pause was called, and Stop would cease all function and would require Start to be called to begin again. With Unity's built-in Coroutines is this even possible, and if it is how do I Pause/Resume a coroutine?
EDIT
what I'm looking for is how to essentially pause an instance of Program and be able to resume it at the same step.
If I understand one of the comments correctly the suggestion it makes would be something similar to this?
public abstract class Node {
public abstract IEnumerator Invoke(ProgramCaller caller);
}
public class Program : Node {
private List<Node> nodes;
public override IEnumerator Invoke(ProgramCaller caller) {
int index = 0;
while(index < nodes.Count) {
if(caller.Paused) {
yield return null;
}
else {
yield return nodes[index].Invoke(caller);
index++;
}
}
}
}
So from what I read is you have e.g.
public class Node
{
public IEnumerator Invoke()
{
yield return null;
}
}
Then a Unity Coroutine is basically using the IEnumerator and invoking MoveNext on certain intervals (Update by default except using the special ones like e.g. WaitForFixedUpdate etc).
So you could simply make Program implement that like e.g.
public class Program : IEnumerator
{
public Node[] nodes;
private int index = -1;
private IEnumerator currentNode;
public bool MoveNext()
{
if (nodes == null || nodes.Length == 0)
{
return false;
}
while (currentNode == null)
{
index++;
if (index >= nodes.Length)
{
return false;
}
currentNode = nodes[index]?.Invoke();
}
if (currentNode.MoveNext())
{
return true;
}
currentNode = null;
return true;
}
public void Reset()
{
index = -1;
currentNode = null;
}
public object Current => null;
}
and then you can link this up to a Coroutine from a MonoBehaviour like e.g.
public class Example : MonoBehaviour
{
public Program program;
private Coroutine currentRoutine;
// just a name alias
public void StartProgram() => RestartProgram();
public void RestartProgram()
{
StopProgram();
ResumeProgram();
}
public void ResumeProgram()
{
currentRoutine = StartCoroutine(program);
}
public void PauseProgram()
{
if (currentRoutine != null)
{
StopCoroutine(currentRoutine);
}
}
public void StopProgram()
{
PauseProgram();
program.Reset();
}
}
as you see the only difference between Start/Stop and Pause/Resume is resetting or not resetting the Program.
Alternatively and maybe even more simple: A Coroutine is paused automatically when disabling according MonoBehaviour and resumed when enabling it again.
=> If it is an option for you to have a dedicated runner component for each program then all you need really is the resetting part and you could simply do
public class Program
{
public Node[] nodes;
public IEnumerator Run()
{
foreach (var node in nodes)
{
yield return node.Invoke();
}
}
}
This way you can run them all as a single IEnumerator and then
public class Example : MonoBehaviour
{
public Program program;
private Coroutine currentRoutine;
// just a name alias
public void StartProgram() => RestartProgram();
public void RestartProgram()
{
StopProgram();
currentRoutine = StartCoroutine(program.Run());
}
public void ResumeProgram()
{
enabled = true;
}
public void PauseProgram()
{
enabled = false;
}
public void StopProgram()
{
if (currentRoutine != null)
{
StopCoroutine(currentRoutine);
}
}
}

How to get public int 'currentSlot' from grandchildren

im beginner and still learning please don't hate.
So my script is located in Canvas, (Canvas -> inv -> invslot) but I need to get currentSlot int from a script in invslot gameobj., how would I do that the right way? I've tried this so far but doesn't seem to work at all.
void UpdateUI () {
for (int i = 0; i < slots.Length; i++)
{
if (i < inventory.items.Count) {
currentSlot = GetComponentsInChildren<InventoryScroll>();
slots[i] = currentSlot;
currentSlot.AddItem(inventory.items[i]);
Debug.Log ("Updating UI");
} else
{
slots[i].ClearSlot();
}
}
}
EDIT!
Here is the top of my InventoryUI, where I want to bring the int currentSlot
public Transform itemsParent;
Inventory inventory;
InventorySlot[] slots;
// Start is called before the first frame update
void Start()
{
inventory = Inventory.instance;
inventory.onItemChangedCallback += UpdateUI;
slots = itemsParent.GetComponentsInChildren<InventorySlot>();
}
But the Inventoryscroll goes this way
List<GameObject> slots = new List<GameObject>();
public int currentSlot=0;
int slotsToScroll=3;
void Start() {
foreach(Transform child in this.transform) {
slots.Add(child.gameObject);
}
}
void Update () {
if (Input.GetKeyDown(KeyCode.Alpha1)) {
currentSlot=0;
UpdateDisplay();
}
if (Input.GetAxis("Mouse ScrollWheel") >0){
if (currentSlot<slotsToScroll) {
currentSlot++;
} else {
currentSlot=0;
}
UpdateDisplay();
}
}
void UpdateDisplay() {
for (int i = 0; i < slots.Count; i++)
{
if (i==currentSlot) {
slots[i].transform.GetChild(0).gameObject.SetActive(true);
} else {
slots[i].transform.GetChild(0).gameObject.SetActive(false);
}
}
}
Inventory script
#region Singleton
public static Inventory instance;
void Awake () {
if (instance != null) {
Debug.LogWarning("More than one instance of inventory found!");
return;
}
instance = this;
}
#endregion
public delegate void OnItemChanged();
public OnItemChanged onItemChangedCallback;
public int space = 6;
public List items = new List();
public bool Add (Item item) {
if (!item.isDefaultItem) {
if(items.Count >= space) {
Debug.Log("Not enough inventory space.");
return false;
}
items.Add(item);
if (onItemChangedCallback != null)
onItemChangedCallback.Invoke();
}
return true;
public void Remove (Item item) {
items.Remove(item);
if (onItemChangedCallback != null)
onItemChangedCallback.Invoke();
Careful, Unity has two methods:
GetComponentsInChildren
Returns all components of Type type in the GameObject or any of its children.
this returns a InventoryScroll []!
and GetComponentInChildren
Returns the component of Type type in the GameObject or any of its children using depth first search.
This returns one single InventoryScroll reference, the first one found!
Note the s!
From your description and how you use it in your code it seems like you wanted to use the latter but have an s too much.
The second mistake: From the variable name it sounds like you rather wanted to get an InventorySlot not an InventoryScroll!
So you should use
currentSlot = GetComponentInChildren<InventorySlot>();
Though it is hard to tell what your actual goal is with this code without seeing the types of slots and currentSlot.
It also appears really strange to me that you already get all slots in Start but here you overwrite
slots[i] = currentSlot;
for each i that is i < inventory.items.Coun. Also you iterate through slots.Length but you pass in inventory.izems[i] .. I don't understand what is supposed to happen here exactly.

Unity - Leaderboard data is delayed when showing up

I'm a beginner to coding. A friend of mine used to help me but now he's busy so I have to do things alone. The game has a function that remember the name the player input before playing the game as well as the score of the player. Right now, my problem is that when I play a game, it doesn't show up in my leaderboard scene. And then when I play another round with a different name, I can see the previous score but not my current one. It's like the data is delayed to appear.
void ReplaceRank(string player, int currentScore) {
string oldName;
int oldScoreNumber;
for (int i = 0; i < 10; i++) {
if (currentScore > scores[i]) {
highscoreShow.enabled = true;
oldScoreNumber = scores[i];
oldName = PlayerPrefs.GetString(oldPlayerName[i]);
PlayerPrefs.SetInt(oldScore[i], currentScore);
PlayerPrefs.SetString(oldPlayerName[i], player);
Debug.Log(currentScore);
if (i <= 9) {
ReplaceRank(oldName, oldScoreNumber);
}
break;
}
}
}
private void GetAllKeys() {
oldScore = new List<string>();
oldPlayerName = new List<string>();
scores = new List<int>();
for (int i = 11; i < 21; i++) {
if (PlayerPrefs.GetString("score" + i + "Name", "") == "") {
PlayerPrefs.SetString("score" + i + "Name", "");
}
oldPlayerName.Add("score" + i + "Name");
oldScore.Add("score" + i);
scores.Add(PlayerPrefs.GetInt("score" + i, 0));
}
}
Even though this seems trivial its not , saving is kinda tricky, they are a few things you can do to make it easier e.g you should save the scores as a list
Saving
add scores to list
convert list of scores to Json
save json string in player prefs
Loading
get json string from player prefs
convert Json to list
updated list in script
wrote this class quickly.. its tested and works but you might have to modify it to suit your needs and improve it
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace ScoreBoard
{
[Serializable]
public class PlayerScore
{
public string Name;
public int Score;
public PlayerScore(string name, int score)
{
Name = name;
Score = score;
}
}
[Serializable]
// we need to store list of scores in a container class so we can change it to json
public class PlayerScoresRankedListContainer
{
public List<PlayerScore> PlayerScoresRanked = new List<PlayerScore>();
}
[Serializable]
public class ScoresRanked : MonoBehaviour
{
public static PlayerScoresRankedListContainer PlayerScoresListContainer =new PlayerScoresRankedListContainer();
public void Awake()
{
//example of usage
//get saved items
if (PlayerPrefs.GetString("PlayerScoresRanked").Length > 0)
{
PlayerScoresListContainer.PlayerScoresRanked = GetSortedListFromPlayerPrefs();
DebugShowScores();
}
else
{
//test the class asving items
AddScoreToRankedList("player1", 1000);
AddScoreToRankedList("player2", 20);
AddScoreToRankedList("player3", 100);
SaveListAsJSONInPlayerPrefs();
}
}
private void AddScoreToRankedList(string player, int currentScore)
{
var score = new PlayerScore(player, currentScore);
if (DoesScoreAlreadyExist(score))
{
//we remove a score if it already exists so we can updated it
//you might not need this maybe you just want to keep adding scores
PlayerScoresListContainer.PlayerScoresRanked.RemoveAll(x => x.Name == score.Name);
PlayerScoresListContainer.PlayerScoresRanked.Add(score);
}
else
{
PlayerScoresListContainer.PlayerScoresRanked.Add(score);
}
}
public void SaveListAsJSONInPlayerPrefs()
{
var jsonlist = JsonUtility.ToJson(PlayerScoresListContainer);
Debug.Log("LOG ITEMS BEING SAVED: "+jsonlist);
PlayerPrefs.SetString("PlayerScoresRanked", jsonlist);
}
public List<PlayerScore> GetSortedListFromPlayerPrefs()
{
var jsonlist = PlayerPrefs.GetString("PlayerScoresRanked");
var ListContainer = JsonUtility.FromJson<PlayerScoresRankedListContainer>(jsonlist);
var listsorted = ListContainer.PlayerScoresRanked.OrderByDescending(x => x.Score).ToList();
return listsorted;
}
public bool DoesScoreAlreadyExist(PlayerScore scoreToChcek)
{
if (PlayerScoresListContainer.PlayerScoresRanked.Exists(x => x.Name == scoreToChcek.Name))
{
return true;
}
else
{
return false;
}
}
public void DebugShowScores()
{
foreach (var playerScore in PlayerScoresListContainer.PlayerScoresRanked)
{
Debug.Log(playerScore.Name + " " + playerScore.Score);
}
}
}
}

Unity3D Coroutine Error

I'm stuck at Unity scripting. I got 3 files : Scene.cs , Player.cs and NetworkUtil.cs . I can't compile my code, as I don't know how to pass Coroutine response back to Scene.
In Scene.cs ( MonoBehavior class ) :
void OnGUI() {
if(GUI.Button(new Rect(0, 0, 100, 100), "Register")) {
StartCoroutine(registerPlayer());
}
}
IEnumerator registerPlayer() {
return Player.NewPlayer("Raptor"); // since Player is not IEnumerator, this line causes error.
}
In Player.cs ( Object class ) :
public static Player NewPlayer(string name) {
Player p = new Player();
result = p.tryRegister();
if(result) {
return p;
} else {
return null;
}
}
private bool tryRegister() {
Dictionary<string, string> data = new Dictionary<string, string>();
data.Add("some_key", "some_value");
NetworkUtil.ExecuteAPI(data);
}
In NetworkUtil.cs ( Object class ) :
public static IEnumerator ExecuteAPI(Dictionary<string, string> data) {
WWWForm form = new WWWForm();
form.AddField("mode", request);
foreach(KeyValuePair<string, string> entry in data) {
form.AddField(entry.Key, entry.Value);
}
WWW handler = new WWW(API_URL, form);
yield return handler;
if(handler.error != null) {
Debug.Log("Error occurred: " + handler.error); // Server Error
} else {
Debug.Log("Response: " + handler.text);
}
}
How can I change the codes to make the flow complete?
Note: In Object class, StartCoroutine() does not exist.
EDIT
I have changed my codes to use Delegates & Events:
// DelegatesAndEvents.cs
public class DelegatesAndEvents : MonoBehaviour {
public delegate void RegisterEventHandler(Player player);
public static event RegisterEventHandler onPlayerRegister;
public static void NewPlayerRegistered(Player player) {
if(onPlayerRegister != null) {
onPlayerRegister(player);
}
}
}
Then in Scene.cs:
void Start() {
DelegatesAndEvents.onPlayerRegister += this.userRegistered;
}
public void userRegistered(Player player) {
Debug.Log("User registered.");
}
How should I put the trigger in NetworkUtil.cs & Player.cs ?
There are several ways of doing that, because you not only need to return an IEnumerator, but it's Unity itself that will iterate on it, so you don't have the possibility to catch the return value.
Technically is possible to do the following:
IEnumerator registerPlayer() {
yield return Player.NewPlayer("Raptor"); // this is legal, player will be the Current value of the IEnumerator
}
If you want to do do something like the code above, you need to wrap the coroutine inside another, and iterate on it by yourself (I did something similar implementing behavior trees):
IEnumerator Wrap(IEnumerator playerRegister)
{
while(playerRegister.MoveNext())
{
Player p = playerRegister.Current as Player; //this can be done if you know what you are doing
yield return null;
}
}
Another way is not return anything and pass a delegate to the coroutine, that will be called passing back to the caller the required parameter.
Something like:
IEnumerator DoSomething(Action<Response> whenDone)
{
while (doingSomething)
yield return null;
whenDone(response);
}
EDIT
The other problem with your code is that you are calling another coroutine (ExecuteAPI)from inside Player's constructor.
So, making registerPlayer a coroutine is pointless since you are not yielding anything.
In your case the simplest way to go is to pass a delegate to ExecuteAPI that will be called when you receive the response from the server.

C# Stack not updating

For some reason the code that I modified doesn't seem to be functioning correctly. There were no exception error when debugging however it does not function the same as in the original code (list) - that is it doesn't seem to update. I've targeted it down to an area of code where I believe is causing the problem:
My original code (using List) that works:
private List<Gem> gems = new List<Gem>();
private List<Enemy> enemies = new List<Enemy>();
private void UpdateGems(GameTime gameTime)
{
for (int i = 0; i < gems.Count; ++i)
{
Gem gem = gems[i];
gem.Update(gameTime);
if (gem.BoundingCircle.Intersects(Player.BoundingRectangle))
{
gems.RemoveAt(i--);
OnGemCollected(gem, Player);
}
}
}
Here's my modified code replacing List to Stack which doesn't work correctly:
private Stack<Gem> gems = new Stack<Gem>();
private Stack<Enemy> enemies = new Stack<Enemy>();
private void UpdateGems(GameTime gameTime)
{
for (int i = 0; i < gems.Count; ++i)
{
Gem gem = gems.Peek();
gem.Update(gameTime);
if (gem.BoundingCircle.Equals(Player.BoundingRectangle))
{
gems.Pop();
OnGemCollected(gem, Player);
}
}
}
Any ideas?
I don't know exactly what you need, but the loop looks a little strange: if you don't pop() anything from the stack at the first turn, peek() will return the same element always.
Stack data structure is not indexed, it allow just to pop/peek the last pushed element. The code with the list behave of course different since you are actually checking all the elements in the list.
By definition, when you pop something off a stack you remove the last item added. Because of this you have reversed the order in which you are checking and removing Gem objects from the collection - when you do the Peek and Pop and BoundingCircle.Equals() check in the Stack<Gem> version you are not checking the first item in the collection like you are in the List<Gem> version.
A List can be iterated in either direction, just adjust your indexer in the appropriate way. A Stack is LIFO (Last In, First Out), so you can only access the last item added.
I think when called to peak method, it maybe keep a references to that item on the top of stack collection. So when you called to pop method. It gonna working not correctly.
I have copy your example and put to a console application as below
class Program
{
private static Stack<Gem> gems = new Stack<Gem>();
private Stack<Enemy> enemies = new Stack<Enemy>();
static void Main(string[] args)
{
gems.Push(new Gem
{
BoundingCircle = new BoundingCircle
{
Name = "abc"
}
});
gems.Push(new Gem
{
BoundingCircle = new BoundingCircle
{
Name = "def"
}
});
UpdateGems(new GameTime());
}
private static void UpdateGems(GameTime gameTime)
{
for (int i = 0; i < gems.Count; ++i)
{
Gem gem = gems.Peek();
gem.Update(gameTime);
if (gem.BoundingCircle.Equals(Player.BoundingRectangle))
{
gems.Pop();
OnGemCollected(gem, null);
}
}
}
private static void OnGemCollected(Gem gem, Player player)
{
}
}
public class Gem
{
public void Update(GameTime gameTime)
{
}
public BoundingCircle BoundingCircle { get; set; }
}
public class Enemy
{
}
public class GameTime
{
}
public class BoundingCircle : Bounding
{
public override bool Equals(object obj)
{
var temp = (Bounding) obj;
return Name.Equals(temp.Name, StringComparison.InvariantCulture);
}
}
public class Player
{
static Player()
{
BoundingRectangle = new BoundingRectangle
{
Name = "def"
};
}
public static BoundingRectangle BoundingRectangle { get; set; }
}
public class BoundingRectangle : Bounding
{
}
public abstract class Bounding
{
public string Name { get; set; }
}
It worked fine. Please notice that your Equals function have to be overridden by the BoundingCircle class.

Categories

Resources