I wish to know if there is room for changing a variable's accessibility during runtime in Unity C#? The reason I want this is to hide unnecessary public variables when my script uses another state. I tried some dynamic variables, but I get errors that it can't be found.
I have that springDistance but I want to use it only when the state of the trapType is springOut.
Some code:
public class SpringTrap : MonoBehaviour
{
private Transform objectToMove;
// Use this for initialization
public enum TypeOfTrap
{
springOut
}
[Tooltip("Set the type of trap here to use.")]
public TypeOfTrap trapType = TypeOfTrap.springOut;
public float springDistance;
void Start ()
{
objectToMove = transform.FindChild("ObjectsToAnimate");
}
void OnTriggerEnter2D(Collider2D other)
{
if (other.gameObject.CompareTag("Player"))
{
//if the player has entered the danger zone
switch (trapType)
{
case TypeOfTrap.springOut:
break;
}
}
}
IEnumerator SpringOut(float springDistance)
{
float deltaDist = 0.0f;
while(deltaDist < springDistance)
{
objectToMove.position +=
}
}
}
Manipulation of the inspector is done with custom editor scripts. There you can display things, e.g. based on conditions.
Here is an example that does a very similar thing to the requested (displaying different things based on an enum field):
http://answers.unity3d.com/questions/417837/change-inspector-variables-depending-on-enum.html
An easier way to do what you are trying to achieve is to create an abstract trap class and then subclass different types of traps.
The abstract trap class handles collisions and then calls the abstract method Trigger.
public abstract class Trap : MonoBehaviour {
void OnTriggerEnter2D(Collider2D other)
{
if (other.gameObject.CompareTag("Player"))
Trigger(other.transform);
}
protected abstract void Trigger (Transform victim);
}
A spring trap can then be easily created by extending Trap and overriding the Trigger method.
public class SpringTrap : Trap {
public float SpringDistance;
protected override void Trigger (Transform victim) {
victim.position += Vector3.up * SpringDistance;
}
}
Now you don't have to worry about hiding irrelevant properties based on trap type because the only properties that will appear in the inspector are those you add to that variation of trap.
Related
I have two classes: Menu_Buttons, in which there are definitions for methods executed on clicking different buttons in the menu, and PauseMenu, which defines what happens when the Menu key is pressed during the game.
Menu_Buttons:
public class Menu_Buttons : MonoBehaviour
{
public void Menu_NewGameClick()
{
SceneManager.LoadScene(1);
}
public void Menu_ContinueClick()
{
Debug.Log("This will continue the game from the last save");
}
public void Menu_LoadGameClick()
{
SceneManager.LoadScene(1);
Debug.Log("Another menu will show to choose which save to load");
}
public void Menu_SaveGameClick()
{
SaveItem();
Debug.Log("This will save the game");
}
public void Menu_OptionsClick()
{
Debug.Log("This will show the game options");
}
public void Menu_QuitClick()
{
Application.Quit();
Debug.Log("The Game should quit now");
}
}
PauseMenu:
public class PauseMenu : MonoBehaviour
{
//private bool isPauseMenuOpened = false;
public GameObject pauseMenu;
void Update()
{
if (Input.GetKeyDown(KeyCode.B))
{
if (pauseMenu.activeSelf) { ClosePauseMenu(); }
else { OpenPauseMenu(); }
}
}
public void OpenPauseMenu()
{
pauseMenu.SetActive(true);
Cursor.visible = true;
Cursor.lockState = CursorLockMode.Confined;
//isPauseMenuOpened = true;
Time.timeScale = 0f;
}
public void ClosePauseMenu()
{
pauseMenu.SetActive(false);
Cursor.visible = false;
Cursor.lockState = CursorLockMode.Locked;
//isPauseMenuOpened = false;
Time.timeScale = 1f;
}
}
I wanted to add another method called Menu_ResumeClick, which would resume the game from the Pause Menu. Of course, I could just create this method in the PauseMenu script and then everything is fine. It looks like this:
public void Menu_ResumeClick()
{
ClosePauseMenu();
}
But since I would like to keep things organised, I thought it would be better to put this method in the Menu_Buttons script along with all the other similar methods. So I tried this:
public void Menu_ResumeClick()
{
PauseMenu.ClosePauseMenu();
}
And then problems begin... I get an error: an object reference is required for the non-static field method or property. Then if I change any of those classes to static, I get errors saying: cannot declare instance members in a static class. Not to mention that static classes canot inherit from MonoBehaviour. Maybe I would be able to solve those problems somehow, but the thing is I don't want to change the whole code just because I would rather have a method in another class. It's just for keeping things organised, nothing more.
I have to admit that I'm a bit frustrated by how these things work. I can easily put the Menu_ResumeClick() method in the PauseMenu class, but in the future it may be difficult to keep track of things if I have various methods scattered around different scripts. The most reasonable solution is to put every menu button in the Menu_Buttons class and then access them from there, but it poses problems that I described. Actually this is not the first time when I'm having problems with accessing methods or variables from other classes - there always seem to be some difficulties. It seems the best way to write code would be to just have a single class for the whole game because then I would be able to access absolutely anything easily - but again the problem would be with keeping things organised.
So, the question is: can I easily use methods (or variables) from other classes, without changing the whole code for this purpose? In other words can I somehow just call a method from another class like this: className.MethodName(); or set a variable from another class like this: className.varName = 2; without making everything static, etc.?
And a bonus question: If it's not possible, then how should I structure my code? Should I try to squeeze as many things as possible into a single class to be able to access them easily, should I make classes static, whenever it's possible, etc.?
In PauseMenu, you can add a field for the Menu_Buttons, which Unity can serialize, so you can pull the GameObject (what contains the Menu_Buttons) in the inspector, and you can call its public methods (and access its public members) from PauseMenu.
public Menu_Buttons MenuButtons;
// or (I much more like this version, keeping things as encapsulated as possible)
[SerializeField] private Menu_Buttons _menuButtons;
private void Resume() => _menuButtons.Menu_ResumeClick();
Edit based on comments:
Both script can have references to each other. As both logically related, I wouldn't separate them, because with the references, we couple them anyway.
Example:
public class Menu_Buttons : MonoBehaviour
{
[SerializeField] private PauseMenu _pauseMenu;
public void Menu_ResumeClick() => _pauseMenu.ClosePauseMenu();
// ...
}
public class PauseMenu : MonoBehaviour
{
[SerializeField] private Menu_Buttons _menuButtons;
// ...
public void ClosePauseMenu()
{
// ...
}
}
I want a button to do multiple things but not at the same time.
For e.g. in the game, when the player comes near the door and press the button, i want it to open the door. And when player comes near a weapon and press the SAME button, i want the player to pick up the weapon.
P.S. I'm making a game for mobile.
You could for example use an enum and set a value/type on it.
You can set a value when you are near a door, for example, and overwrite it when you are near a weapon.
As soon as the button is pressed, you simply check the value of the enum and execute an action based on the value.
enum Actions
{
OpenDoor,
NearWeapon
}
You actually do not need different callbacks for this.
I would have one single callback react differently to whatever you are close to.
This is pretty much the same as here. Slightly different use case but the principle is similar.
There is basically two main option
Either the target objects implement the logic => use a common interface/base class
Your player implements the logic => use whatever to differ between the object types
Then your player could check what it "is close to" - I will just assume physics again but how exactly you check what you are "close to" is up to you - and interact with that target object.
Interface
You could simply have a shared interface like e.g.
public interface IInteractable
{
void Interact(/*pass in whatever arguments you need e.g.*/Player player);
}
and then have your classes implement it and the logic e.g.
public class Door : MonoBehaviour, IInteractable
{
public void Interact(Player player)
{
// whatever
}
}
and
public class Weapon : MonoBehaviour, IInteractable
{
public void Interact(Player player)
{
// whatever
}
}
and then assuming you use physics (triggers) you could do e.g.
public class Player : Monobehaviour
{
[SerialzieField] private Button button;
// we will store multiple close objects so we can smoothly transition between them in case
// we are close to multiple ones - up to you of course
private readonly List<IInteractable> currentActives = new();
private void Awake()
{
i(!button) button = GetComponent<Buton>();
// you attach a snigle callback
button.onClick.AddListener(Interact);
}
private void OnTriggerEner(Collider other)
{
// does this object have an IInteractable
var interactable = other.GetComponentInParent<IInteractable>();
if(interactable == null) return;
currentCloses.Add(interactable);
}
private void OnTriggerExit(Collider other)
{
// was the object an IInteractable?
var interactable = other.GetComponentInParent<IInteractable>(true);
if(interactable == null) return;
currentCloses.Remove(interactable);
}
private void Interact()
{
// some Linq magic to pick the closest item - again up to you
var currentClosest = currentCloses.Select(item => (MonoBehaviour)item).OrderBy(item => (item.transform.position, transform.position).sqrMagnitude).FirstOrDefault();
if(currentClosest == null) return;
// now let that object handle the interaction
// this class doesn't need to know what exactly this means at all
currentClosest.Interact(this);
}
}
This is of course a little bit inflexible and you always need to have a common shared interface and pass along all needed parameters. It can also get quite dirty if e.g. the weapon implements its own "pick up" - doesn't feel quite right.
Different Types
You could simply have different types (components) on your target objects, lets say e.g.
// have a common shared base class
public abstract class Interactable : MonoBeaviour
{
}
and then have derived types
public class Door : Interactable
{
// they can either implement their own logic
public void Open()
{
// whatever
}
}
and e.g.
public class Weapon : Interactable
{
// this one is purely used to differ between the type
// we rather expect whoever uses this will implement the logic instead
}
and then pretty similar as above
public class InteractionController : Monobehaviour
{
[SerialzieField] private Button button;
private readonly List<Interactable> currentActive = new();
private void Awake()
{
i(!button) button = GetComponent<Buton>();
button.onClick.AddListener(Interact);
}
private void OnTriggerEner(Collider other)
{
var interactable = other.GetComponentInParent<Interactable>();
if(!interactable) return;
currentCloses.Add(interactable);
}
private void OnTriggerExit(Collider other)
{
var interactable = other.GetComponentInParent<Interactable>();
if(!interactable) return;
currentCloses.Remove(interactable);
}
private void Interact()
{
var currentClosest = currentCloses.OrderBy(item => (item.transform.position, transform.position).sqrMagnitude).FirstOrDefault();
if(currentClosest == null) return;
// just that this one you check which actual type this component has
// and deal with it accordingly, completely customizable
switch(currentActive)
{
case Door door:
door.Open();
break;
case Weapon weapon:
PickupWeapon(weapon);
break;
default:
Debug.LogError($"Interaction with {currentActive.GetType().AssemblyQualifiedName} is not implemented!");
break;
}
}
// Just as example that the target doesn't even need to implement the method itself
private void PickupWeapon(Weapon weapon)
{
// whatever
}
}
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);
}
}
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.
So I have this code: Variables:
enum gameState
{
gameLoading,
mainMenu,
gameOptions,
levelSelect,
gamePlaying,
gameOver
}
In the Update() method:
if (CurrentGameState == gameState.gameLoading)
{
if (Keyboard.GetState().IsKeyDown(Keys.E))
{
graphics.ToggleFullScreen(); //?
}
graphics.ApplyChanges();
}
...
//gameState.gameLoading logic
if (Keyboard.GetState().IsKeyDown(Keys.Enter))
CurrentGameState = gameState.mainMenu;
So what I want is to have Enter pressed in gameState.gameLoading and both resolution is fullscreen and the gameState is equal to mainMenu. When in gameState.mainMenu the resolution can't be changed from fullscreen to windowed by pressing enter. How to achieve this? Maybe using list?
I think you should have different classes for any gameState if they need to have different behaviours. This will ensure you that each Update affect only its own gameState.
Since you've already decided what the states of your game are, why don't use the State Machine design pattern to control your input handling behaviour?
This pattern's agenda is to delegate the work from the actual object to its states. What you do is create a class with an Update() method for each state like pinckerman suggested, and enter all the input handling + state transition logic there. You can find an example here: http://sourcemaking.com/design_patterns/state.
When I use it, I detach the context from it's states and transitions completly by using an abstract state class and inherit from it. This makes it easier to change the state machine if needed.
Here is a quick example:
public class Context
{
private GameState state;
public void Update()
{
state.Update();
}
public void ChangeState(GameState nextState)
{
state = nextState;
}
}
public abstract class GameState
{
protected Context context;
public virtual void Update()
{
// some basic implementation if you want.
}
}
public class GameLoadingState : GameState
{
public override void Update()
{
// Handle key presses.
context.ChaneState(new MainMenuState(context));
}
}
public class MainMenuState : GameState
{
public override void Update()
{
// Handle key presses in some other way.
// Change state if needed.
}
}
Again, if you don't like the implementation of the passive context, you can change it to be more active.
Hope this helps!