So I began learning Swift, but I discovered that I wanted to use Unity3d because it looked interesting to learn, and for games it looked like the better option than Xcode. This meant that I had to learn a new language, however, so I started learning C#. In my project I have 2 classes.
My first Class is LivesDetract. This class is an edge collider. It catches the falling objects, and decreases the life amount:
public class LivesDetract : MonoBehavior {
public int currentLives; //set to 3 in Unity
private int livesDecrementAmnt = 1;
void OnTriggerEnter2D (Collider2D other) {
currentLives = currentLives - livesDecrementAmnt;
}
}
The second class is named GameController. GameController controls the flow of the game. I originally had LivesDetract as a part of GameController, but for organization purposes, I think it should be in its own class. The problem is that I get the following error when I try to inherit from the class LivesDetract:
Error:
"An object reference is required for the non-static field, method, or property 'LivesDetract.currentLives'"
public class GameController : MonoBehavior {
IEnumeratorSpawn () {
while(LivesDetract.currentLives > 0) { // This line is where the error occurs
//Game Actions Occur
}
}
}
I think I have provided enough information, but if more is needed let me know.
In Swift I was able to set the function as a variable:
var livesDetract = LivesDetract()
I could then use:
while livesDetract.currentLives > 0 {
}
That doesn't seem to work in C# though.
You are trying to access currentLives without instantiating the object. You need find the object in Unity before making use of it.
Related
If I want to make automated changes in editor, I generally use OnValidate. Over the years I've run into many issues with it so I'm looking for an alternative.
Example: There is a ladder object with a ladder script on it. When I change the Size variable on the ladder component, I want it to also change the size of the SpriteRenderer, which is set to Tiled mode. Unfortunately, OnValidate doesn't like you using SendMessage and you can get funky results.
I am NOT looking to solve this issue specifically. I already use an editor script to solve it. I am simply looking for a way to automate changes without using OnValidate so that I have more freedom.
I thought maybe an editor script could do this, but I'd really like to avoid having to do that for every single case individually. Maybe there is a way to do an editor script that could handle all scripts trying to do this? Maybe an editor script that works with interfaces?
EDIT: Changed the title and text of the post to be clearer.
I came up with a solution. I created a base class that all other classes in my game will inherit from. It looks like this:
public class BaseClass : MonoBehaviour {
[HideInInspector] public bool NeedsAutoUpdate;
protected void OnValidate() { NeedsAutoUpdate = !Application.isPlaying; }
public virtual void AutoUpdate() { NeedsAutoUpdate = false; }
}
So it's set up to track when OnValidate has happened only if it's not in play mode. If you want it to run in play mode too, just replace "!Application.isPlaying" with "true". Then there's a virtual AutoUpdate() which sets it back to false. Then I made an editor script.
[CustomEditor(typeof(BaseClass),true), CanEditMultipleObjects]
public class BaseClassEditor : Editor {
public override void OnInspectorGUI() {
base.OnInspectorGUI();
var Objs = serializedObject.targetObjects;
foreach (var Obj in Objs) {
var Base = Obj as BaseClass;
if (Base == null) continue;
EditorApplication.delayCall += () => {
if (Base.NeedsAutoUpdate) { Base.AutoUpdate(); }
};
}
}
}
This will check if anything that inherits from the base class needs an auto update, and then run it. Using delay call ensures inspectors are fully updated before it does this to avoid the issues that OnValidate has. Note that if you have a class inherit from BaseClass, but that class already has it's own CustomEditor then you'll need to have that custom editor inherit from BaseClassEditor instead of Editor to make it work.
Lastly, on any class I want to AutoUpdate, I make sure it is inheriting from BaseClass (at least somewhere down the line) and add an override of AutoUpdate.
public override void AutoUpdate() {
base.AutoUpdate();
// Make changes here
}
So basically I want to end with several ingame objects, each having few .cs scripts with monobehaviour. (different set of scripts on each object)
party of characters with their skills
Those scripts on each character can vary over time
characters learning new skills/abandoning old
So when the game starts, I want to attach the scripts to objects dynamically at runtime
based on player decision in skill tree
Is there any way how can I do this without using reflection?
EDIT: It seems I found solution how to make it work without reflection
public class TestSkill : MonoBehaviour {}
public class TestFireball : TestSkill {}
public class TestMeleeAttack : TestSkill {}
public class TestSkillBook : MonoBehaviour {
public MonoScript script;
void Start () {
System.Type t = script.GetClass();
TestSkill skill = gameObject.AddComponent(t) as TestSkill;
}
}
I want to attach the scripts to objects dynamically at run-time.
he AddComponent function is used to attach scripts to GameObjects.
Your Object
public GameObject yourgameObject;
Attach script to it:
yourgameObject.AddComponent<YourScript>();
EDIT:
The problem is, I dont know if it will be MyScript1 or MyScript2. I
dont want it to be hardcoded, but modifiable via editor/ingame UI.
I think that you are looking for AddComponent that can take string as param.
There used to be one like this:
public Component AddComponent(string className);
but it was deprecated years ago. I made a new one called AddComponentExt as extension method last year and you can get it here. It can be used like this:
yourgameObject.AddComponentExt<"YourScript">();
You can add script even if it doesn't exist yet. You will get run-time error instead of compile-time error in that case.
Is there any way how can I do this without using reflection?
No, you can't do this without reflection since it doesn't exist yet. That's what reflection is used for.
Since this doesn't fit into the comments of Programmers answer, some example and to clarify on what you can/must do:
// The component to add
public class B : MonoBehaviour
{
public void TestCall()
{
Debug.Log("Success");
}
}
public class A : MonoBehaviour
{
public string ToAddName = "B";
private void Start()
{
System.Type t = System.Type.GetType(ToAddName);
AddComponent(t); // This does successfully add the component (assuming it exists)
Debug.Log(t.GetType()); // This will give out "System.MonoType"
// This doesn't work since it is not known that this is actually "B", not "MonoScript"
GetComponent(t).TestCall();
// What is possible is this, requires hardcoding the method name though:
System.Reflection.MethodInfo mI = t.GetMethod("TestCall");
var c = GetComponent(t);
mI.Invoke(c, null); // null because "TestCall" doesn't take params
}
}
This is not meant to be an actual solution, I'd rather say that there is probably a (better) way to set up your whole construct so that you don't have this problem at all.
I am trying to access a function, with a bool parameter, from one script to another and just can't get it to work. I have been looking around to try to understand what i am doing wrong.
Here is the script i am calling:
public class MainScript : MonoBehaviour {
public void ManageBoxCollider2D (bool shouldColliderBeEnabled) {
print (">>>>>>>>>>>>>>>>ManageBoxCollider2D: " + shouldColliderBeEnabled);
}
}
I am trying to call it from this script:
public class Sidebar1_Script : MainScript {
public MainScript mainScript;
void Start () {
mainScript.ManageBoxCollider2D (true);
}
}
There is a lot of other stuff in the scripts as well but this is what matters for this question
In the "Sidebar1_Script" I am trying to access "ManageBoxCollider2D" in "MainScript" but it does not work.
I do get the following message:
Object reference not set to an instance of an object
...which i do understand but can't figure out what i am doing wrong.
I would appreciate some help how to do this.
Thanks
It's because unity doesn't know what script that is. If you see in the inspector you're gonna find that public variable MainScript is still empty, also you can't assign scripts in the inspector (I don't know why though, when you drag it there it won't fit in).
Instead change your Sidebar1_Script like this:
public GameObject obj;
void Start () {
MainScript main = obj.gameObject.GetComponent<MainScript>();
main.ManageBoxCollider2D(true);
}
And then assign the game object of obj in the inspector (drag the game object to the public variable).
I'm trying to add a Quest-object to a Person. It succeeds for one and gives a nullreferenceexception for the other, what am I doing wrong here?
P.S. The player and requestor are set in the Unity inspector.
public class GameCreator : MonoBehaviour {
private Quest quest;
public Player player;
public Requestor requestor;
void Start() {
quest = createQuest();
requestor.thisPerson.SetQuest(quest); //this is the problem
player.thisPerson.SetQuest(quest);
}
}
public class Player : MonoBehaviour {
public Person thisPerson;
void Start() {
thisPerson = new Person("Name");
}
}
public class Requestor: MonoBehaviour {
public Person thisPerson;
void Start() {
thisPerson = new Person("Name");
}
}
public class Person {
public Quest quest;
void SetQuest(Quest quest) {
this.quest = quest;
}
}
Any suggestions why this is going wrong?
Move your variable initialization in to Awake(), see the documentation for the following (paraphrased):
Awake is used to initialize any variables or game state before the
game starts.... and use Start to pass any information back and forth.
The way your GameCreator.Start() is written you are reliant on the arbitrary order in which Unity calls your scripts. GameCreator could be the first object called, in which case none of your other scripts have initialized their values.
Other possible errors:
You don't explicitly instantiate requestor, I'm going to assume this was done in Unity's Inspector.
You didn't include `createQuest()' which could be returning null.
As Jordak said, your Start methods can run in any possible order, so you can't rely on Start of some component in the other. You have several ways to address this issue:
You can move the basic initialization code to Awake(). However, this only allows you two levels of initialization, and can be insufficient in the future.
You can adjust script priority in the project settings. However, this is not really C# way, as this makes your code rely on logic that is not obvious from it.
Instead of initializing thisPerson field in the class initialization, create a public property to access it. (Public fields are bad practice in C# anyway). In this property, you can check if the field is null before returning it, and if it is, initialize it.
I am trying to get data from an int variable in Unity using C# code.
Below is the C# code I am using to get the int.
using UnityEngine;
using System.Collections;
public class endGameMessage : MonoBehaviour {
public static int score2;
void Start () {
GameObject thePlayer = GameObject.FindWithTag("Player");
gameScript game = thePlayer.GetComponent<gameScript>();
score2 = game.score;
}
// Update is called once per frame
void Update () {
Debug.Log (score2);
}
}
Below is the code from the other script I am trying to pull the data from.
using UnityEngine;
using System.Collections;
public class gameScript : MonoBehaviour {
//score
public int score = 0;
void OnTriggerEnter(Collider other) {
if(other.gameObject.tag =="hammer"){
GameObject.FindGameObjectWithTag("pickUpMessage").guiText.text = ("Picked Up A Hammer");
Destroy(other.gameObject);
Debug.Log("collision detected hammer");
audio.PlayOneShot(gotHit);
score = score+10;
}
}
}
I can get the the int value to come across to the other script but its always 0 even if the int was meant to be 10.
My question is how would i keep the value across the scripts? Any help is appreciated.
try this
public static int score2
{
get
{
return GameObject.FindWithTag("Player").GetComponent<gameScript>().score;
}
}
You have a lot of possibilities.
The first one is to set your Score as a static argument for you gameScript.
So you can access it anywhere just like that :
int myScore = gameScript.Score ;
And the declaration should be :
public static int score;
The second possibilities is far better if you want to save a lot of differents values from differents script.
In this case, you need to define a gameContext singleton.
If you don't know what is this, you should take a look at singleton in C# :
[https://msdn.microsoft.com/en-us/library/ff650316.aspx]
Singleton will allow you to have a single instance of your gameContext.
In your case, your singleton will have a Score attribute.
And you will be able to get the value from any scene and any scripts.
This is the best way so far.
score2 is read once at start and then never again. int is an integral type in C# and thus passed by value i.e. it receives a copy. There several ways to solve this problem.
The easiest solution is to access the gameScript.score directly - it provides read/write access to everyone anyway. To encapsulate it you may choose to define a property.
A better way could be to define a new class GameStatus which holds all relevant things. This can be implemented as singleton for example.