Trouble implementing IComparable<T> in C# - c#

I'm trying to sort an array in Unity by name, using Array.Sort().
I've been reading as much as I can but still can't adapt it into my little project here. Here is what I have so far:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System;
public class UIController : MonoBehaviour, IComparable<Slot>
{
public static UIController instance;
public Text uiMessageBox;
public Slot[] slots;
private void Awake()
{
if (instance == null)
instance = this;
else
Destroy(this);
DontDestroyOnLoad(this);
slots = FindObjectsOfType<Slot>();
Array.Sort(slots, ); // HELP: NOT SURE WHAT TO PUT HERE
}
public int CompareTo(Slot other)
{
return this.name.CompareTo(other.name);
}
}
Note, I deleted the parts I think are irrelevant in this class (such as the code that displays a message string on screen etc).
ALSO NOTE: I implement here IComparable<Slot> but I also tried it with IComparable<UIController>. (like I say, I've seen lots of examples here and other websites, but cannot quite get it to work in my code.)

Why not use delegate form?
Array.Sort(slots, (slot1, slot2) => slot1.name.CompareTo(slot2.name));
If you still want to implement the IComparable interface, you must write it inside the Slot class.
And you can also implement IComparer interface in any class.
class AnyClass : IComparer<Slot>
{
public int Compare(Slot slot1, Slot slot2)
{
return slot1.name.CompareTo(slot2.name);
}
}

I was able to keep the code within my UIController class which is how I imagined it would be (since I built the array of slots there , it felt right for me to sort it there also.)
Here's how its done:
public class UIController : MonoBehaviour, IComparer<Slot>
{
public static UIController instance;
public Text uiMessageBox;
public Slot[] slots;
private void Awake()
{
slots = FindObjectsOfType<Slot>();
Array.Sort(slots, this); // i just passed 'this' as the IComparer parameter :)
}
public int Compare(Slot x, Slot y)
{
return x.name.CompareTo(y.name);
}
}

Related

Integer only increases for a split second when "++" operator is used

The integer gemsCollected does not increase its value when the NextLevel method is called.
The console shows that the value only becomes "1" for a split second. How do I make the script store the added value, instead of resetting it?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class gemTaken : MonoBehaviour
{
public GameObject gem1;
public Transform playerNearCheck01;
public float playerCheckRadius01;
public LayerMask playerLayer;
private bool takenByPlayer;
public int gemsCollected;
void Update()
{
Debug.Log(gemsCollected);
takenByPlayer = Physics2D.OverlapCircle(playerNearCheck01.position, playerCheckRadius01, playerLayer);
if (takenByPlayer)
{
NextLevel(); //calls method
gemsCollected ++;
}
}
void NextLevel() //method
{
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex +1 );
}
}
if you declare the gemsCollected variable as "static", it will maintain it's value throughout scenes.
Furthermore, to keep it public you can make a getter method like this:
public int GetGemsCollected() => gemsCollected;
which is the same as this, if you're not familiar with the syntax:
public int GetGemsCollected()
{
return gemsCollected;
}
Then anytime you need the gemsCollected variable just call the method.
Let me know if that works.
Vlad

Data safety in Scriptable Object based architecture

Immediately I would like to say that if a similar question has been asked before, please point me to it.
Let's say I have a Scriptable Object that I use to pass a player's health from one system to another.
public class HealthSO: ScriptableObject {
[ReadOnly]
public float health;
}
A class called PlayerHealth sets the value in the Scriptable Object so that other systems can use it. E.g: the player's health bar.
It's great because I can freely connect different systems without referencing them, but it is not without its problems, and there is one that concerns me the most.
How do I make sure that the only class that can change the health value in the Scriptable Object is PlayerHealth?
Or maybe it is something that I shouldn't worry about too much? Sure if it is only one person working on a project then there isn't too much to worry about. But what if this approach would be applied in a bigger project?
Thanks!
This might be a bit controversial, but so is using ScriptableObject for this in the first place ^^
Unfortunately Unity still doesn't really support serializing of interface type fields. But in this case there is only two different access levels - read and write.
So you could do something like
// Just going generic here as latest Unity versions finally support it
// and you have way less re-implementation of the same functionality
public abstract class ReadonlyValueSO<T> : ScriptableObject
{
[SerializeField]
[ReadOnly]
protected T _value;
public T Value
{
get => _value;
}
}
public abstract class WriteableValueSO<T> : ReadonlyValueSO<T>
{
public void Set(T value)
{
_value = value;
}
}
// Some constants could even be ReadonlyValueSO if you never want to write over them anyway
[CreateAssetMenu]
public class HealthSO : WriteableValueSO<float>
{
}
This way in your setter component you would use the writeable type and do e.g.
public class SomeSetter : MonoBehaviour
{
[SerializeField] WriteableValueSO<float> health;
private void Update()
{
health.Set(health.Value + .1f * Time.deltaTime);
}
}
while in the consumers you only give it the readable
public class Consumer : MonoBehaviour
{
[SerializeField] ReadonlyValueSO<float> health;
private void Update()
{
Debug.Log(health.Value);
}
}
This way you have full control over who can read and who can write.
Another huge advantage: This way you also don't have to poll check values how I did above. You can rather simply add an even to be invoked whenever the value is set:
public abstract class ReadonlyValueSO<T> : ScriptableObject
{
[SerializeField]
protected T _value;
public T Value
{
get => _value;
}
public abstract event Action<T> ValueChanged;
}
public abstract class WriteableValueSO<T> : ReadonlyValueSO<T>
{
public void Set(T value)
{
_value = value;
ValueChanged?.Invoke(_value);
}
public override event Action<T> ValueChanged;
}
now your consumer could rather look like e.g.
public class Consumer : MonoBehaviour
{
[SerializeField] ReadonlyValueSO<float> health;
private void Awake()
{
// subscribe to event
health.ValueChanged -= OnHealthChanged;
health.ValueChanged += OnHealthChanged;
// invoke now once with the current value
OnHealthChanged(health.Value);
}
private void OnDestroy()
{
// IMPORTANT: Unsubscribe!
health.ValueChanged -= OnHealthChanged;
}
// Always and only called whenever something sets the value
private void OnHealthChanged(float newHealth)
{
Debug.Log(newHealth);
}
}

How do I fix when it says value assigned to it is never read in C#?

Hi I'm new in programming in c#, I just want to know how to fix this issue because I can't increment the value of the points in the game that I am making...
using UnityEngine;
public class GameManager: MonoBehaviour
{
private int point;
public void IncreaseScore()
{
point++;
}
}
view image
It's just a hint, so it doesn't stop you from run this code. However to fix, as information suggests you should read point property by for example assigning it to some value:
int someValue = point;
If you want this field to be obtainable by other classes in code, you can add public method for it and adding it will resolve your issue too:
using UnityEngine;
public class GameManager: MonoBehaviour
{
private int point;
public int GetScore() => point;
public void IncreaseScore()
{
point++;
}
}

Trying to change BattleState globally

Hi I'm a completely new to coding and am trying to create a card game. I've watched some tutorials and tried to take things into my own hands but cant seem to figure out something. I currently have a BattleState set up;
public enum BattleState { START, PLAYERMAINPHASE, PLAYERBATTLEPHASE, PLAYERENCORESTEP, ENEMYTURN, WON, LOST }
and would like it so when i change the BattleState with a script, it changes it for every other script that references this BattleState. Sorry for the bad wording. Coding is rough :/
You can use interfaces, create an interface such as IBattleStateChanger and have a method on it
interface IBattleStateChanger{
void ChangeBattleState(YourClass.BattleState state);
}
Then on every script you want the value to change implement this interface as
ClassExample : IBattleStateChanger {}
This will then force you to create a method in the script to change the state
After that, whenever you want to change the value globally on the scripts where you implemented this interface, you can do a foreach loop finding each type of this interface such as
BattleState newState = BattleState.START;
foreach (var obj in FindObjectsOfType<IBattleStateChanger>){
obj.SetBattleState(newState);
}
You could use a static event and attach listeners/callbacks to it like e.g.
public enum BattleState
{
START, PLAYERMAINPHASE, PLAYERBATTLEPHASE, PLAYERENCORESTEP, ENEMYTURN, WON, LOST
}
public static class BattleStateMgr
{
private static BattleState _state;
public static BattleState State => _state;
public static event System.Action<BattleState> OnStateChange;
public static ChangeState(BattleState s)
{
_state = s;
OnStateChange?.Invoke(_state);
}
}
public class OtherScript : MonoBehaviour
{
private void Awake()
{
BattleStateMgr.OnStateChagne += OnBattleStateChange;
}
private void OnDestroy()
{
BattleStateMgr.OnStateChagne -= OnBattleStateChange;
}
private void OnBattleStateChange(BatlleState newState)
{
Debug.Log($"Changed Battle State to{newState}", this);
}
}
I believe you are confused about the scope of your variable. Each script you place an instance of the enum Battlestate, is a local version of that enum. If you want the reference to be global, you will need to have a central point where all scripts can grab this reference.
public class BattleManager : MonoBehaviour
{
private BattleState battleState;
// setter / getters
public BattleState GetBattleState(){return battleState; }
public void SetBattleState(BattleState state){ battleState = state; }
}
You are going to want to make a single script that holds the only reference to your enum Battlestate, then have your other scripts reference the variable.
public class OtherScript : MonoBehaviour
{
// assign this reference in the inspector
[SerializeField] private BattleManager bm = null;
private void YourFunction()
{
if(bm.GetBattleState() == BattleState.randomStateHere)
{
// run logic here
}
}
}
There are a number of ways to go about doing this, but the easiest would most likely be by declaring the variable static.
public class BattleManager : MonoBehaviour
{
private static BattleState battleState;
// setter / getters
public static BattleState GetBattleState(){return battleState; }
public static void SetBattleState(BattleState state){ battleState = state; }
}
public class OtherScript : MonoBehaviour
{
private void YourFunction()
{
if(BattleManager.GetBattleState() == BattleState.randomStateHere)
{
// run logic here
}
}
}
I do not know how many scripts you need to access this variable, but if it is only a handful, I would instead assign references to the script that holds the enum to each of the scripts that need it. I would avoid simply using static as it is the easy approach but creates what is called a code smell. The reason for this is OOP (object-oriented programming) by design should generally not have mutable global variables.
If you have a single instance of an object that manages all of your battle activity and a lot of scripts need to access it, you can look into the Singleton pattern. As you are new to programming, I would not implement this pattern until you understand the time and place to properly use it. You can also completely avoid using it by properly assigning the references you need in the inspector or by using a Object.FindObjectOfType in either Start or Awake.

How can I return a ReadOnly object class with mutable properties while allowing write access

I've got quite a number of classes, which have got the standard set and get methods. My problem is that many of these set methods should not be callable from outside the class which holds the objects. I'm not quite sure if there are any patterns or C# for lack of a better word - operations that would make this easier.
In reference to the code below, there are a number of classes similar to SecureSite, which the controller should be able to call functions or access variables to modify the SecureSite (and the other similar classes). However when the user asks to see SecureSite etc. they shouldn't be able to change this.
From my limited knowledge and the answers I've seen to similar questions on this site, the main issue appears to be that the Write_SecureSite can't be made fully immutable due to the List<String> AccessHistory variable. So, what I've come up with looks as ugly as a bulldogs backside and is just as messy. Essentially there is a Write version of the SecureSite class which contains a class within it, which returns a readonly version of the SecureSite class.
So, am I missing something magic in C# that would make this all so much easier?
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ReadOnlyExample {
public class Write_SecureSite {
private List<String> mAccessHistory;
public List<String> AccessHistory {
get {
return mAccessHistory;
}
}
public SecureSite ReadOnly {
get {
return new SecureSite(this);
}
}
public class SecureSite {
public SecureSite(Write_SecureSite aParent) {
AccessHistory=aParent.AccessHistory;
}
public IEnumerable<String> AccessHistory;
}
}
public static class Controller {
private static Write_SecureSite SimpleSecureSite=new Write_SecureSite();
public static Write_SecureSite.SecureSite Login(String MyLogin) {
SimpleSecureSite.AccessHistory.Add(MyLogin);
return SimpleSecureSite.ReadOnly;
}
public static Write_SecureSite.SecureSite Details() {
return SimpleSecureSite.ReadOnly;
}
}
public static class User {
public static void Miscellaneous() {
Controller.Login("Me");
Write_SecureSite.SecureSite SecureSite=Controller.Details();
//Not going to happen.
SecureSite.AccessHistory.Add("Me2");
//No problem.
foreach(String AccessedBy in SecureSite.AccessHistory) {
Console.Out.WriteLine("Accessed By: "+AccessedBy);
}
}
}
}
I suggest to use interfaces:
public interface IReadSecureSite
{
IEnumerable<String> AccessHistory { get; }
}
class Write_SecureSite : IReadSecureSite
{
public IList<String> AccessHistoryList { get; private set; }
public Write_SecureSite()
{
AccessHistoryList = new List<string>();
}
public IEnumerable<String> AccessHistory {
get {
return AccessHistoryList;
}
}
}
public class Controller
{
private Write_SecureSite sec= new Write_SecureSite();
public IReadSecureSite Login(string user)
{
return sec;
}
}
...
Controller ctrl = new Controller();
IReadSecureSite read = ctrl.Login("me");
foreach(string user in read.AccessHistory)
{
}
This is not so much an answer as a direction to look into. I am also struggling with the Immutable class
So far I am using my constructors to set my read-only private vars
I am using methods to update my lists internally instead of exposing them as public properties: ie. use public Void Add(string itemToAdd)
I am reading a book by Petricek and Skeet called "Real World Functional Programming" and it is helping me move in the direction you are discussing
Here is a small tutorial from the same author's that introduces some basic concepts: http://msdn.microsoft.com/en-us/library/hh297108.aspx
Hope this helps a bit
Update: I probably should have been clearer: I was looking to point you in the direction of a more functional view as opposed to rewriting the class you had listed in your question - my apologies (removed sample)

Categories