Unity3D - how to use arrays with custom inspector code? - arrays

I seem to be stuck in a catch 22 situation with the OnInspectorGUI method of Unity's UnityEditor class. I want to name array elements in the inspector for easy editing, currently I'm using, as per the documentation:
public override void OnInspectorGUI()
{
J_Character charScript = (J_Character)target;
charScript.aBaseStats[0] = EditorGUILayout.FloatField("Base Health", charScript.aBaseStats[0]);
}
In my J_Character script I initialise the aBaseStats array like so:
public float[] aBaseStats = new float[35];
The problem is that whenever I try to do anything in the editor (and thus OnInspectorGUI is called) I get an index out of range error pointing to the line
charScript.aBaseStats[0] = EditorGUILayout.FloatField("Base Health", charScript.aBaseStats[0]);
I'm guessing this is because my array is initialized on game start while the editor code is running all the time while developing.
How can I get round this situation?
Many thanks.

You have to initialize aBaseStats in an function that runs only once.
The code below is BAD:
public float[] aBaseStats = new float[35];
void Start(){
}
The code below is GOOD:
public float[] aBaseStats;
void Start(){
aBaseStats = new float[35];
}
Initialize it in an Editor callback function that runs once.
EDIT:
I don't know a Start callback function that will run before the OnInspectorGUI function(). The hack below should work.
public float[] aBaseStats;
bool initialized = false;
public override void OnInspectorGUI()
{
if (!initialized)
{
initialized = true;
aBaseStats = new float[35];
}
J_Character charScript = (J_Character)target;
charScript.aBaseStats[0] = EditorGUILayout.FloatField("Base Health",aBaseStats[0]);
}

As an addition to the answer by Programmer I would like to point you to the following:
http://docs.unity3d.com/ScriptReference/ExecuteInEditMode.html
This seems to be exactly what you are looking for in terms of functionality. (it runs the method even when playmode is not active)
using UnityEngine;
using System.Collections;
[ExecuteInEditMode]
public class ExampleClass : MonoBehaviour {
public Transform target;
void Update() {
if (target)
transform.LookAt(target);
}
}

Related

How do I use my own function in a timer class?

The code is taken from here:
https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.timer?view=windowsdesktop-6.0
private:
static System::Windows::Forms::Timer^ myTimer = gcnew System::Windows::Forms::Timer;
static int alarmCounter = 1;
static bool exitFlag = false;
// This is the method to run when the timer is raised.
static void TimerEventProcessor( Object^ /*myObject*/, EventArgs^ /*myEventArgs*/ )
{
myTimer->Stop();
// Displays a message box asking whether to continue running the timer.
if ( MessageBox::Show( "Continue running?", String::Format( "Count is: {0}", alarmCounter ), MessageBoxButtons::YesNo ) == DialogResult::Yes )
{
// Restarts the timer and increments the counter.
alarmCounter += 1;
myTimer->Enabled = true;
}
else
{
// Stops the timer.
exitFlag = true;
}
}
For example, after the line myTimer->Stop(); I want to use my own method. How do I identify it? E0020 ID "draw 1" is not defined.
System:: Void Practform::MyForm::draw1() {
. . .
}
Please tell me, because I'm a little stalled, since I've never worked with this.
I have a feeling that what you are bumping up against is attempting to invoke an instance method from a static method. To do so, you would need to have an instance of the class which has the method, e.g:
ref struct Foo {
void InstanceMethod() {}
static void StaticMethod() {
auto instance = gcnew Foo();
instance->InstanceMethod();
}
}
called like so:
Foo::StaticMethod();
However, taking the example code, it could be easier (and more appropriate) to change the static methods to instance methods, like so:
using namespace System;
using namespace System::Windows::Forms;
ref struct MyForm : Form {
Timer ^myTimer = gcnew Timer();
MyForm(void) {
myTimer->Tick += gcnew EventHandler(this, &MyForm::TimerEventProcessor);
myTimer->Interval = 5000;
myTimer->Start();
}
void TimerEventProcessor(Object ^, EventArgs ^) {
myTimer->Stop();
draw1();
}
void draw1() {
MessageBox::Show("Done", "Timer is done", MessageBoxButtons::OK);
}
};
called like so:
auto form = gcnew MyForm();
form->Show();
Notes:
I'm assuming that you've added the code from the example into your own class, called MyForm
I've used struct throughout instead of class to make everything public - you should use the appropriate access modifiers to your use case
The most notable change is the use of the EventHandler constructor which takes an instance of the handler as its first argument, and the method to execute as its second.
The advantages of using instance methods and properties are that:
you will have access to this in the draw1() method (given the name of the method, is likely to want to draw using the form instance), and
the Timer instance will be garbage collected as appropriate,

Index was outside the bounds of the array. // Array with AudioSource

I'm learning to code along with unity for the past 1.5month and I cannot get this problem resolved. I've tried looking it up several times and cannot get the answer that I need. I know it probably be a simple fix for everyone but for me - its not... i'm not good with Arrays.
I'm trying to make a script that going to handle my sounds but I keep getting this error. Here's the script;
public AudioSource[] mySounds;
public AudioSource fire;
public AudioSource reload;
public AudioSource zombieDeath;
void Start()
{
mySounds = GetComponents<AudioSource>();
fire = mySounds[0];
reload = mySounds[1]; // <---- line 22.
zombieDeath = mySounds[2];
}
public void PlayFire()
{
fire.Play();
}
public void PlayReload()
{
reload.Play();
}
public void PlayZombieDeath()
{
zombieDeath.Play();
}
And I keep getting the error in unity on line 22. I could use some help..

Autofixture, expected behavior?

Having a test similar to this:
public class myClass
{
public int speed100index = 0;
private List<int> values = new List<int> { 200 };
public int Speed100
{
get
{
return values[speed100index];
}
}
}
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestMethod1()
{
var fixture = new Fixture();
var sut = fixture.Create<myClass>();
Assert.AreEqual(sut.Speed100, 200);
}
}
Would have expected this to work, but I can see why it's not. But how do I argue, that this is not a problem with AutoFixture, but a problem with the code?
AutoFixture is giving you feedback about the design of your class. The feedback is, you should follow a more object-oriented design for this class.
Protect your private state, to prevent your class from entering an inconsistent state.
You need to make the speed100index field, private, to ensure it remains consistent with the values List.
Here is what I see if I run debugger on your test:
Autofixture assigns a random number to speed100index field because it is public, and in your array there is nothing at point 53 (from my screenshot)
If you set speed100index to be private, Autofixture will not re-assign the number and your test will pass.

Actionscript 3 eventlisteners, hittestobject and arrays of custom objects

Another desperately stuck first year student here. And I have to use FlashCS as my coding environment. And it sucks. So I'll try some well constructed and clear questions. There is:
public var object: symbol1;
public var objectarray: Array = new Array();
in my main. Then a function there that uses a timer and spawns a new object and pushes it onto the array:
object = new symbol1;
objectarray.push(object)
but then when I trace() the .length of the array it displays TWO numbers of the array length every timer period in the output. As in:
1 1 2 2 3 3
etc. This is my first mystery. Why two not one? because there is no way I'm calling the function that includes the trace () twice. Also I think I need to be able to remove my object from the objectarray when it goes off the stage, but the objectarray.pop() doesn't seem to work if I use it like so in a function:
if (object.y == stage.stageHeight)
objectarray.pop()
As in I try trace() the array.length before and after the .pop(), but it just keeps going up by one every timer period.
And the other, bigger issue is I want to know if you are allowed to put the .addEventListeners that you usually place right under the main function of any class into a statement loop. As in I've got
class extends Main {
class function() {
for (var i:Number = 0; i < objectarray.length; i++){
objectarray[i].addEventListener(Event.ENTER_FRAME, collision);}}
And then, if it is allowed, the program doesn't seem to enter the collision function of this same class anyway.
function collision (event:Event) : void{
if (this.hitTestObject(object)){
trace('hit');}}
so I searched and ended up adding a
var clip:MovieClip = MovieClip(e.target);
in the first line of the function, but then it didn't work and I realized I on't understand what's it meant to do, what's going on anymore and what is the syntax for this casting.
Thank you very much.
Edit/Update: adding more of my code eventhough I hate copypasting it like this. This is the symbol class that is going to change when an object of another class hits it
public class Head extends Main {
public function Head(){
this.stop();
for (var i:Number = 0; i < nicesnowflakearray.length; i++){
nicesnowflakearray[i].addEventListener(Event.ENTER_FRAME, snowhit);
}
}
public function snowhit(event:Event) : void {
if (this.hitTestObject(nicesnowflake)){
//I changed this line to (e.currentTarget.hitTestObject(nicesnowflake)) as Atriace suggested, but nothing changed, and I just don't understand why my version wouldn't work.
trace("hit");
}
}
And this is the class that spawns the objects that are supposed to hit the Head object:
public class Main extends MovieClip {
public var nicesnowflake: fallingsnow;
var nicesnowflakespawntimer: Timer = new Timer(1000);
public var nicesnowflakearray: Array = new Array();
public function Main() {
nicesnowflakespawntimer.addEventListener(TimerEvent.TIMER, nicesnowflakespawn);
nicesnowflakespawntimer.start();
}
public function nicesnowflakespawn(event:TimerEvent) : void {
nicesnowflake = new fallingsnow;
nicesnowflake.x = Math.random()* stage.stageWidth;
nicesnowflake.y = - stage.stageHeight + 100;
nicesnowflakearray.push(nicesnowflake);
stage.addChild(nicesnowflake);
trace(nicesnowflakearray.length);
}
Why two, not one?
Anytime you extend another class, it implicitly calls the parent class' constructor. We know this as super(), and can be quite handy. This is why you're getting doubles on your trace statement. Technically, you can choose not to call super().
.pop()
It should remove the last element from that array, however, I'm thinking that if an arbitrary object leaves the stage, you can't be gauranteed it'll be the last element. Ergo, you want splice()
if (object.y == stage.stageHeight) {
objectarray.splice(objectarray.indexOf(object), 1)
}
Event Listeners
I didn't follow your quandary, so I'll just try to rewrite what I think you were trying to do.
package {
import flash.display.MovieClip;
public class Main extends MovieClip {
private var objectarray:Array = []; // Note, I haven't populated it with anything, I'm assuming you have.
private var theWall:MovieClip = new MovieClip(); // I haven't added this to the stage, or given it shape. You need to for hitTestObject to work.
public function Main() {
// This is your constructor.
for (var i:Number = 0; i < objectarray.length; i++) {
objectarray[i].addEventListener(Event.ENTER_FRAME, collision);
}
}
private function collision(e:Event):void {
if (e.currentTarget.hitTestObject(theWall)) {
trace('hit');
}
}
}
}
Of note, you may want to look at a guide to hitTestObject() if it's giving you issues.

Accessing a variable in another scene

Is it possible to change a variable in another scene in unity. I have a script right now that has the user pick 5 heroes and those 5 heroes get saved to a array, but in order for the game to run how i want it, that array will be in another scene and I'm not sure how to go about saving the five heroes data to an array in another scene. I can do it all in one scene but 2 scenes would be more efficient. Here's my code:
using UnityEngine;
using System.Collections;
public class HeroChooser : MonoBehaviour {
public static GameObject Archer;
GameObject Berserker;
GameObject Rouge;
GameObject Warrior;
GameObject Mage;
GameObject MainCamera;
public int counter = 0;
public bool archerOn = false;
public bool berserkerOn = false;
public bool rougeOn = false;
public bool mageOn = false;
public bool warriorOn = false;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
}
void OnGUI(){
if(archerOn == false){
if (GUI.Button (new Rect(50,0,50,50), "Archer")){
Archer = GameObject.Find("Archer");
MainCamera = GameObject.Find("Main Camera");
HeroArraySaver heroArraySaver = MainCamera.GetComponent<HeroArraySaver>();
heroArraySaver.array[counter] = Archer;
archerOn = true;
counter++;
}
}
Its saying that: Static member HeroArraySaver.array cannot be accessed with an instance reference, qualify it with a type name instead im not sure how to go about fixing it.
A simple way would be to create an empty GameObject and attach a script/MonoBehaviour to that which holds your data. To make it persist you would have to call DontDestroyOnLoad() on that GameObject. This will ensure your GameObject will hang around when moving to a different scene.
So something like:
GameObject myPersistentDataObject = new GameObject("myPersistentDataObject");
MyDataClass data_class = myPersistentDataObject.AddComponent<MyDataClass>();
//set your data to whatever you need to maintain
And in your Awake of your MyDataClass you'd do something like
void Awake()
{
DontDestroyOnLoad(transform.gameObject);
}
Then in your other scene you can simply find your GameObject again and retrieve its data from the attached component.
Assuming you have integer IDs for the heroes, simply store them in a static variable:
public class GlobalData {
public static int[] heroIds;
}
Static variables can be accessed from any scene and will persist as long as your game runs. The same technique works for strings or enums.

Resources