im trying to hold a path to a game object (bat.tscn) in a script and use it to instanceate a object from a different script. Name of the script that holds the variable is called a.gd and the one that is responsible for instantiating is b.gd and the name of the variable is x. But for some reason every time i try to use the variable it gives me an error;
Invalid to get index 'x' (On Base: 'Node (a.gd)')
a.gd;
func _process(delta):
var SlotOne = $"res://Objects/Weapons/Bat.tscn
b.gd;
onready var InvManager = get_node("../a") #gets the script.
func _physics_process(delta):
changeWeapons()
func changeWeapons():
if Input.is_key_pressed(KEY_1):
load(InvManager.SlotOne)
elif Input.is_key_pressed(KEY_2):
print("2")
elif Input.is_key_pressed(KEY_3):
print("3")
elif Input.is_key_pressed(KEY_4):
print("4")
any ideas how can i fix this issue?
Im pretty new to the game engine so im kinda stuck here.
It appears you declared SlotOne inside of _process. That makes it a local variable. In other words, this variable will only be availabe inside that _process, and thus, you cannot reach it from another script.
Define the variables you want to reach from other scripts outside of any method (func) - the language guidlines encourage to place them near the start of the file, before any methods.
also a possible explanation is that instead of using or instancing the node you are trying to access it
so first try to add the node to the main scene like this
get_tree().current_scene().add_child("#the path to a.gd")
but anyways only the code can can tell us what is wrong
Well so it looks like you are just loading the scene instead of instancing it
this might help
first make sure that a.gd is a autoload script
then instead of writing the a.gd like this
func _process(delta):
var SlotOne = $"res://Objects/Weapons/Bat.tscn
write it like this
extends node
const SlotOne= preload("res://Objects/Weapons/Bat.tscn")
then write this on b.gd
func _physics_process(delta):
changeWeapons()
func changeWeapons():
if Input.is_key_pressed(KEY_1):
var Bat_instance = A.SlotOne.instance()
Bat_instance.position = Vector_2() #some position
self.add_child(Bat_instance)
elif Input.is_key_pressed(KEY_2):
print("2")
elif Input.is_key_pressed(KEY_3):
print("3")
elif Input.is_key_pressed(KEY_4):
print("4")
Related
here is my code in the spinner script:
extends KinematicBody
var glob = preload("res://Glabal.gd")
var rotation_speed = glob.rot_sped
func _physics_process(delta):
if rotation_speed == 300:
rotation_speed -= 1
rotation_speed += 0.5
rotate_y(deg2rad(rotation_speed * delta))
print(rotation_speed)
here is my code in the global script and yes it is supposed to be glabal.gd:
extends Node
var rot_sped = 300
and here is my code in the settings script:
extends "res://Glabal.gd"
var glob = preload("res://Glabal.gd")
func _on_Increase_button_pressed():
glob.rot_sped = glob.rot_sped + 1
func _on_Decrease_button_pressed():
glob.rot_sped = glob.rot_sped - 1
func _process(delta):
$"current speed/Label".text = "current rotation speed" + glob.rot_sped
The file "res://Glabal.gd" is a script, more precisely a GDScript file.
So when you load it, for example like this:
var glob = preload("res://Glabal.gd")
You get a GDScript object. In fact, you can type it:
var glob:GDScript = preload("res://Glabal.gd")
The GDScript effectively represents a GDScript class. You can, for example, create an instance of it using new:
var glob:GDScript = preload("res://Glabal.gd")
var instance = glob.new()
What will be the type of the instance? Well, that GDScript file is the class. And - as the source code in that GDScript file says - it extends Node:
var glob:GDScript = preload("res://Glabal.gd")
var instance:Node = glob.new()
To answer the question the title. You are getting the error because rot_speed is not a property of GDScript (I remind you that you get a GDScript object from preload("res://Glabal.gd")). And thus this does not work:
var glob = preload("res://Glabal.gd")
var rotation_speed = glob.rot_sped
It might be a property of the instances of that particular script… But you don't have an instance of that particular script (which, again, you can create with new).
Anyway, if you instance your script with new, you are getting a new instance. Yet, I believe you want to get the same instance everywhere. So using preload("res://Glabal.gd") is wrong, I'll get back to that.
I don't know how you use your settings script, but it makes little sense to do this:
extends "res://Glabal.gd"
var glob = preload("res://Glabal.gd")
You are saying that your script extends the class in the file "res://Glabal.gd", and it also has a reference to said class.
The settings script could access the properties on its parent class directly. However, we would be going down the wrong path.
Instead, write your settings script the way you will write anything else…
So, how? - You have a couple options:
Make "res://Glabal.gd" an autoload (sigleton), with a name, for example Global. You can do that in Project Settings, on the AutoLoad tab.
Then you can access it every where you need. For example:
var rotation_speed = Global.rot_sped
Be aware that it is copying the value of rot_sped into the variable rotation_speed, so changes in rot_sped won't be reflected in rotation_speed.
Use resource based communication. For this, you are going to change the base type from Node to Resource. I also recommend giving it a name to the class, like this:
class_name Global
extends Resource
var rot_sped = 300
That defines a new Resource type called Global. And we are going to create a resource of that type. To do so, use the context menu on the FileSystem and choose New Resource…. Godot will ask you what kind of resource, and you pick Global. And save it.
Then you will have a resource file. For example "res://Global.tres". That is what you preload:
var glob = preload("res://Glabal.tres")
And you can access it the way you intended:
var glob = preload("res://Glabal.tres")
var rotation_speed = glob.rot_sped
And yes, you are going to get the same instance everywhere you use preload("res://Glabal.tres"). That was also true for preload("res://Glabal.gd"), but that was an instance of GDScript, which is not what you want.
Here glob will be an instance of Global. In fact, you can type it:
var glob:Global = preload("res://Glabal.tres")
var rotation_speed = glob.rot_sped
Please notice that you need to configure autoloads in project settings, but not resources. Also notice that autoloads exist in the scene tree. Also they can be scenes, not just scripts. On the flip side, resources are not, and don't depend on, the scene tree. So there will be situations where one approach is preferible to the other. Although, for what you are doing so far, either way will work.
I'm trying to stop a running function from another function. I know you can use a global variable but from the reading I've done its bad practice to make variables global ("CONSTANTS" are ok). In any case is there a way to change the value of a variable in a running function?
Any help would greatly be appreciated.
from time import sleep
def spin(stop):
while not stop:
# do something
stop = control()
if stop:
break
def control():
start = spin(stop=False)
sleep(5)
start.stop = True # This is where I want to send a stop signal
control()
I am trying to modify the player's movement speed depending on if they are in a bush or not. This is the gist of what I am trying to accomplish:
const Grass = preload("res://World/Grass/Grass.tscn")
onready var grass = Grass.instance()
func move():
grass = Grass.instance()
if grass.player_in_grass():
velocity = move_and_slide(velocity / BUSH_FRICTION_MULTIPLIER)
else:
velocity = move_and_slide(velocity)
The issue that I am having is that I cannot figure out what the code to check should be. I have tried to create a player detection zone for the grass, switching its value when inside of it:
var player = null
func player_is_visible():
return player != null
func _on_PlayerDetectionZone_body_entered(body):
player = body
func _on_PlayerDetectionZone_body_exited(body):
player = null
And my Grass.gd looks like this:
onready var playerDetectionZone = $PlayerDetectionZone
func player_in_grass():
if playerDetectionZone.player != null:
return true
else:
return false
After all of this, I am hit with the error:
Invalid get index 'player' (on base: 'Nil').
The error forwards me to 'if playerDetectionZone.player != null:'.
What am I doing wrong? Should I be checking for this/doing this a different, easier way that you know of? All feedback appreciated. Thank you.
To sum it up:
The error is
Invalid get index 'player' (on base: 'Nil').
And the line of code where this error occurs is:
if playerDetectionZone.player != null:
(which is unfortunately not part of the posted code).
This error means that the variable playerDetectionZone has the value null. Assuming the rest of the code is as posted the problem should be located here:
onready var playerDetectionZone = $PlayerDetectionZone
Something is wrong with the node path ($PlayerDetectionZone). Spelling, capitalisation, maybe wrong position in the tree. Could be a number of things.
Edit: Based on your comment it is probably the wrong position in the tree. The line above only works if the PlayerDecetionZone node is a child of the grass node (the one with grass.gd attached).
I'm new to both Python and PyCharm, so please forgive ignorance.
I was trying to tech myself about the execution of functions when initialising classes - specifically, I want to re-use a database connection object if passed into a new instance, but create one if not. I have a function get_cnx() that creates a connection. I discovered that, whether using a default argument in the __init__ statement to call get_cnx():
def __init__(self, db_cnx=get_cnx())
...or whether using a keyword argument:
self.db_cnx = kwargs.get('db_cnx', get_cnx())
...the function is always executed regardless of the presence (or content) of the connection argument that's passed in. Defeats the object of re-using a connection, so I reverted to an if condition. I believe there's a way of doing this with a decorator, but that felt like gilding the Lilly.
Anyway, this is the context for my actual question: to help me work out what was going on I created this simple test, as a module called "classes.py":
greeting = 'Good Day'
def my_func():
global greeting
greeting = 'Changed'
return 'Hello'
class Animal:
def __init__(self, greet):
if not greet:
self.greet = my_func()
else:
self.greet = greet
if __name__ == '__main__':
cat = Animal(None)
If I run this module (with "Run with Python console" checked in the configuration), I see the global variable greeting shown in blue as 'Changed', which is what I'd expect.
If I change the last bit to this:
if __name__ == '__main__':
cat = Animal('Wotcha')
I see the global variable shown in blue as 'Good Day', which is also what I'd expect.
However, when I then type this into the console:
dog = Animal(None)
...the global variable name turns red but still shows 'Good Day'.
Similarly, using the PyCharm console does the same thing:
>>> print(greeting)
Good Day
>>> dog = Animal(None)
>>> print(greeting)
Good Day
Now, I loaded the module into IDLE and hit F5 (run module), and in the console, did this:
>>> greeting
'Good Day'
>>> dog = Animal(None)
>>> greeting
'Changed'
This is what I would have expected to see in the PyCharm console.
Can someone explain what's going on? Could it be a bug, or is it my lack of understanding of the way PyCharm deals with scope? Or my lack of broader understanding of execution scope?
Thanks!!
JetBrains have opened a bug report for me - confirmed the behaviour isn't as expected.
First of all I'm using python 3.3 & 3.2 on windows and Linux respectively.
I am starting to build an rpn calculator. It looks like cross-platform key listeners is a kind of holy grail for python. So far this seems to be doing the trick, but I've created other problems:
I cannot get away from the global variable for entries and using my
stack.
It looks like I have to build the program from inside
callback()
Here is a rough skeleton that shows my direction. Am I missing a way to pass information in and out of callback()
The goal was to build an RPN class before i found myself stuck inside callback().
import tkinter as tk
entry = ""
stack = list()
operators = {"+",
"-",
"*",
"/",
"^",
"sin",
"cos",
"tan"}
def operate(_op):
if _op == "+":
print("plus")
def callback(event):
global entry, stack
entry = entry + event.char
if event.keysym == 'Escape': # exit program
root.destroy()
elif event.keysym=='Return': # push string onto stack TODO
print(entry)
entry = ""
elif entry in operators:
operate(entry)
root = tk.Tk()
root.withdraw()
root.bind('<Key>', callback)
root.mainloop()
You have several options to do what you want to do.
1. Use a Class for your application
The canonical way of doing what you wish without resorting to a global variable is to place the application within a class, and pass a method as a callback (see print_contents) the following is straight from the docs:
class App(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
self.pack()
self.entrythingy = Entry()
self.entrythingy.pack()
# here is the application variable
self.contents = StringVar()
# set it to some value
self.contents.set("this is a variable")
# tell the entry widget to watch this variable
self.entrythingy["textvariable"] = self.contents
# and here we get a callback when the user hits return.
# we will have the program print out the value of the
# application variable when the user hits return
self.entrythingy.bind('<Key-Return>',
self.print_contents)
def print_contents(self, event):
print("hi. contents of entry is now ---->",
self.contents.get())
2. Curry the callback over your state
You can also use Python's functional programming constructs to curry a function over a global variable and then pass the curried function as a callback.
import functools
global_var = {}
def callback(var, event):
pass
#...
root.bind('<Key>', functools.partial(callback, global_var))
Although this probably isn't what you want.
3. Use a global variable
Sometimes, a global variable is ok.
4. Re-architect for cleanliness and readability
However, you most definitely do not have to build your program inside the callback.
In fact, I would recommend that you create a suite of test with various valid and invalid input, and make a Calculator class or function that takes a string input of RPN commands and returns a value. This is easy to test without a tkinter interface, and will be far more robust.
Then, use your callback to build a string which you pass to your Calculator.
If you want incremental calculation (ie, you're building a simulator), then simply make your Calculator accept single tokens rather than entire equations, but the design remains similar. All the state is then encapsulated inside the Calculator rather than globally.