Unity 5.3 onClick with parameters issue - unity3d

In my game I programmatically create a bunch of buttons for a level select screen, and my intention was to have their onClick fire a function with a parameter that corresponds to level number.
Pseudocode:
public class UIManager extends MonoBehaviour {
public var button : Transform;
public function Start() {
for(var i : int = 0; i < 10; i++) {
var level : int = i + 1;
var b : Transform = Instantiate(button);
b.GetComponent(UI.Button).onClick.AddListener(function(){
StartGame(level);
});
}
}
public function StartGame(level : int) {
Debug.Log(level);
}
}
However, in this situation, when any of these buttons is pressed, the console shows number 10.
How can I achieve what I'm aiming for?

You should have a look at how anonymous functions in JavaScript capture outer variables.
You are creating 10 anonymous functions in your for-loop. All these functions refer to the local variable level. However, they do not yet create their own scope for this variable until they are first called. This means, there is not 10 level locals, but only one until the first function is actually called. Only then will the current value of level be evaluated. And this will be the value it was assigned last: 10.
To get around this you'll have to force creation of a new scope during every iteration of the loop:
public class UIManager extends MonoBehaviour
{
public var button : Transform;
public function Start()
{
for(var i : int = 0; i < 10; i++)
{
var level : int = i + 1;
var b : Transform = Instantiate(button);
b.GetComponent(UI.Button).onClick.AddListener
(
(function(newLocalLevel)
{
return function()
{
StartGame(newLocalLevel);
};
}) (level)
);
}
}
public function StartGame(level : int)
{
Debug.Log(level);
}
}
I changed your direct assigned of an anonymous handler function to a factory function call, which takes the current (!) value of level as a parameter. As it is immediately executed, the factory function creates its own scope, with an own local called newLocalLevel that carries the correct value of level.
The factory function now creates and returns your anonymous handler function, which, even though it not yet creates its own scope, refers to the local newLocalLevel of the enclosing factory function, which has one seperate scope for every iteration of the loop.
See also http://www.mennovanslooten.nl/blog/post/62.

Related

How to pass widget and data from one file to another file in different class?

Beginner-level questions. I’m creating a counter application (first application from The 7 Tasks). I created this application in one file and it is working fine. Following is the code.
class Application : Gtk.Application {
public int val = 0;
public Application() {
Object(
application_id: "com.github.uname.counter",
flags: ApplicationFlags.FLAGS_NONE
);
}
protected override void activate() {
var window = new Gtk.ApplicationWindow(this);
window.default_height = 30;
window.default_width = 300;
window.title = "Counter";
var grid = new Gtk.Grid();
grid.column_homogeneous = true;
grid.row_homogeneous = true;
grid.row_spacing = 5;
grid.column_spacing = 5;
var entry = new Gtk.Entry();
entry.text = val.to_string();
entry.editable = false;
grid.attach(entry, 0, 0, 1, 1);
var button1 = new Gtk.Button.with_label("Counter");
grid.attach(button1, 1, 0, 1, 1);
button1.clicked.connect (() => {
this.val = this.val + 1;
entry.text = this.val.to_string();
});
window.add(grid);
window.show_all();
}
public static int main(string[] args) {
var application = new Application();
return application.run(args);
}
}
Now, I'm trying to divide the above code into separate files such as Application.vala, Entry.vala, and Button.vala. Here is the code for these files.
Code for Application.vala.
class Application : Gtk.Application {
public int val = 0;
public Application() {
Object(
application_id: "com.github.chauhankiran.counter",
flags: ApplicationFlags.FLAGS_NONE
);
}
protected override void activate() {
var window = new Gtk.ApplicationWindow(this);
window.default_height = 30;
window.default_width = 300;
window.title = "Counter";
var grid = new Gtk.Grid();
grid.column_homogeneous = true;
grid.row_homogeneous = true;
grid.row_spacing = 5;
grid.column_spacing = 5;
var entry = new Entry(val);
grid.attach(entry, 0, 0, 1, 1);
var button1 = new Button(val);
grid.attach(button1, 1, 0, 1, 1);
window.add(grid);
window.show_all();
}
public static int main(string[] args) {
var application = new Application();
return application.run(args);
}
}
Code for Entry.vala.
public class Entry : Gtk.Entry {
public Entry(int val) {
text = val.to_string();
}
construct {
editable = false;
}
}
Code for Button.vala.
public class Button : Gtk.Button {
// Is it correct?
public int val;
public Button(int val) {
this.val = val;
}
construct {
label = "Counter";
}
// How to write this within Button.vala from Application.vala?
// How to get entry widget in this class?
button1.clicked.connect (() => {
this.val = this.val + 1;
entry.text = this.val.to_string();
});
}
Now, I have the following questions.
Entry.vala accepts val as initial value. I don't know how to pass it in construct. So, I used public object method. Is it correct way?
In Button.vala I need val as well access to entry so that I can get access to entry in Button.vala? Or this is incorrect way to do the code? If that is that is the case, please suggest correct way. Currently separate files code throws error as I don’t know how to connect and pass the information correctly.
The 7 Tasks are a good exercise to learn, and you seem to be off to a great start!
Entry.vala accepts val as initial value. I don't know how to pass it in construct. So, I used public object method. Is it correct way?
The preferred way to handle construction in Vala is using GObject-style construction: https://wiki.gnome.org/Projects/Vala/Tutorial#GObject-Style_Construction
What you're doing would technically work, but using the GObject-style you'd end up with something like the following:
public class Entry : Gtk.Entry {
public Entry(int val) {
Object (
text: val.to_string(),
editable: false
);
}
}
The important things to note here are:
This only works for properties declared as construct or set
The syntax is slightly different than what you were doing (property: value vs. member = value)
And one other little optimization:
editable is also a property that can be set in the constructor, so no need for a construct block here!
Notice that you can make some similar changes to your Button class as well!
In Button.vala I need val as well access to entry so that I can get
access to entry in Button.vala? Or this is incorrect way to do the
code? If that is that is the case, please suggest correct way.
Currently separate files code throws error as I don’t know how to
connect and pass the information correctly.
Currently your Button code has references to the Entry in it. I would advise against this from an object-oriented programming (OOP) perspective (you may hear people toss around terms like Single-Responsibility or Separation of Concerns, etc.), but the gist is that the button should just focus on being what it is: a button. A button doesn't need to be aware of the presence of the entry, and the entry doesn't need to exist in order to have a button. Your logic of handling what happens between widgets when the button is clicked should happen at a level above. In this case, that would be in your Application class where you've created both of those widgets:
...
var entry = new Entry(val);
grid.attach(entry, 0, 0, 1, 1);
var button1 = new Button(val);
grid.attach(button1, 1, 0, 1, 1);
button1.clicked.connect (() => {
// Update the entry
});
...
Let's take a quick look at your button:
public class Button : Gtk.Button {
// Is it correct?
public int val;
...
It's not wrong, and there are many ways to do what you're doing. So let's roll with it as is.
At this point you've got your button which updates an internal int value every time it's clicked and you now need to update the entry to display the new value. Currently you have:
button1.clicked.connect (() => {
this.val = this.val + 1;
entry.text = this.val.to_string();
});
Since this is now being handled in the Application class, you'll need to change those references to this, since you want to reference and update val from the button, not the variable in Application that you're using for the initial value:
button1.clicked.connect (() => {
button1.val = button1.val + 1;
entry.text = button1.val.to_string();
});
Hopefully that helped a bit, you were 99% of the way there with splitting it into multiple classes! Keep it up, and good luck on the next tasks!

Flutter: How to Access Variable inside another Method?

I have these 2 methods in the same class, and I want to access a variable inside the second method. in C# we just set it to public variable.. but what about Dart and Flutter.. how to access this variable 'hours' in play method.
This is the way I tried but it tells me that it cannot recognize hours variable.
The problem is 'hours' variable is final and can not be declared at class level because it needs to be initialized and I want to initialize it only inside study method
class Student{
Future study(){
final hours = 5;
}
void play(){
int playhours = study().hours +2;
}
}
You cannot just make the variable global that you defined inside a function. What I normally do is that if I need to access a variable that will be set in another function of my class then I will define the variable outside the function and call it later when I need it. For your example I would do this:
class Student{
int hours;
Future study(){
hours = 5;
}
void play(){
//study(); You can call the function inside this one if you want
int playhours = hours + 2;
print(playhours.toString()); // Output: 7
}
}
Then when calling it:
void main() {
Student student = Student();
//student.study(); If you use it separately
student.play();
}
Another thing you could do is to return the value in your study() function!
Easy just do it like this:
class Student{
int hours;
Future study(){
hours = 5;
}
void play(){
//study(); You can call the function inside this one if you want
int playhours = hours + 2;
print(playhours.toString()); // Output: 7
}
}
then call it from the main function like this:
void main() {
Student().play();
}

Haxe: Function argument that matches an interface

I have a method (in Haxe) that needs to accept any object that has a 'width' and 'height' property. I'm assuming that I can do that somehow with an Interface, but for my purposes the object that is passed in does not need to implement the interface, it just needs to be any object with a width and height property.
This does not work, as the object you pass in needs to implement interface:
Interface IWidthAndHeight{
public var width : Float;
public var height : Float;
}
Class Main{
var o : IWidthAndHeight;
public function setObject( o : IWidthAndHeight ){
this.o = o;
}
}
Currently I am using Dynamic, and manually cheking that the properties exist, but is there a smarter way? Current method:
Class Main{
var o : Dynamic;
public function setObject( o : Dynamic ){
if (propertiesExist(o,['width','height'])){
this.o = o;
}
}
// Note: propertiesExist is my own method. Just assume it works :)
}
Any help appreciated. Thanks!
You can use anonymous structures here:
typedef WidthAndHeight = {
width:Float,
height:Float
}
class Main {
var o:WidthAndHeight;
public function setObject(o:WidthAndHeight) {
this.o = o;
}
}
This is just alternative to var o:{width:Float, height:Float}; with typedef.
Any class or structure in setObject argument will be checked for these fields in compile time, this is called structural subtyping.

Unity Static member `UpgradeManager.tickValue' cannot be accessed with an instance reference, qualify it with a type name instead

How can i keep the structure like this, if the tickValue is static?
public float GetMoneyPerSec()
{
float tick = 0;
foreach (UpgradeManager item in items)
{
tick += item.tickValue;
}
return tick;
}
This error means your UpgradeManager looks as follows
public class UpgradeManager
{
public static float tickValue;
}
remove the static keyword and it will work in the context you have in your question.
If you want to use it in a static context you need to access it as follows, but then you can not use it in an instanced object (new UpgradeManager() creates an instance)
UpgradeManager.tickValue
so using it in your example.
public float GetMoneyPerSec()
{
float tick = UpgradeManager.tickValue;
// it cannot be used in a for-loop with each instance referencing it, static is a global, single value.
return tick;
}
but what you may have wanted to do is this
public float GetMoneyPerSec()
{
float tick = UpgradeManager.tickValue / items.length;
// it cannot be used in a for-loop with each instance referencing it, static is a global, single value.
return tick;
}

Unity saying "the associated script is not valid" for the default scripts

I was working on a project and I got to the point when I had to write my first script. But when I wrote it, every other script in Unity stopped working.
It says
"the associated script cannot be loaded, please fix any compile errors and assign a valid script.".
I have NOT edited any of the other scripts, so they should work and when I import them in another project they do work. I can't really start over because I have put weeks worth of work into this and if I start over it won't guarantee that it won't happen again.
here is the script
#pragma strict
function Start () {
private var doorIsOpen:boolean=false;
private var doorTimer:float=0.0;
private var currentDoor:gameobject;
public var doorOpenTime:float=3.0;
public var doorOpenSound:AudioClip;
public var doorShutSound:AudioClip;
}
function Update () {
if(dooropen){
doortimer+=time.deltatime;
if(doortimer>dooropentime){
door(doorshutsound,false,"doorshut",currentdoor);
}
doortimer = 0.0;
}
}
}
function door(aClip : audioclip ,opencheck : boolean, animname : string, thisdoor : gameobject ){
audio.playoneshot(aclip);
doorisopen = opencheck;
thisdoor.transform.parent.animation.play(animname);
}
//function opendoor(){
//audio.playoneshot(dooropensound)
//var myoutpost:gameobject = gameobject.find("outpost");
//myoutpost.animation.play("dooropen");
}
function OnControllerColliderHit(hit:ControllerColliderHit){
if(hit.gameObject.tag == "outpostDoor" && doorisopen = false){
currentdoor = hit.gameobject;
door(dooropensound,true,"dooropen",currentdoor);
dooropen = true;
}
}
//function shutdoor(){
//audio.playoneshot(doorshutsound)
//doorisopen = false;
//var myoutpost:gameobject = gameobject.find("outpost");
//myoutpost.animation.play("doorshut");
}
#script requirecomponent(audiosource)
The reason you are seeing this is that the non-edited scripts cannot be compiled and loaded because of errors in your new script. You have to make your new script compiler-friendly.
What you have there will indeed not compile. And given that it will not compile, it will tell you to fix your errors first before you can continue.
So with that determined, let's have a look at your code. Given your function names, I'm assuming you want to extend a MonoBehaviour (that is, make a custom component to attach to a GameObject). In that case you want to write something like:
#pragma strict
class MyCustomComponent extends MonoBehaviour
{
function Start()
{
//Content here
}
function Update()
{
//Content here
}
//etc.
}
This will allow you at add a component named MyCustomComponent to a GameObject in your scene. Secondly, you do not declare your member variables in your Start() function. You do that outside of it. So
#pragma strict
class MyCustomComponent extends MonoBehaviour
{
private var doorIsOpen : boolean=false;
private var doorTimer : float=0.0;
private var currentDoor : GameObject; //Note the capitalization
public var doorOpenTime : float=3.0;
public var doorOpenSound : AudioClip;
public var doorShutSound : AudioClip;
function Start()
{
//Content here
}
function Update()
{
//Content here
}
//etc.
}
Those are the main causes of the errors you've listed. Making these corrections should get you on your way. And if anything, look at the many tutorials out there to get an idea about how UnityScript works. And of course, always look at the errors you get. They are usually spot on.