Closed Jetpackjules11 closed 1 year ago
How did you add the addon? Did you copy addon/godot_rl_agents to your project directory?
yep. I can see the sync node available to add and in the screenshot there is also RayCastSensor3D, which also comes with the plugin so it's def. working
Hmm this is strange, because Godot does not seem to find ISensor2D. Can you open that file inside the editor and see if there are any errors?
It aactually shows up when I search for "sens"...
Hmm this is strange, because Godot does not seem to find ISensor2D. Can you open that file inside the editor and see if there are any errors?
The ISensor2D.gd file?
Yes
Also this for the RaycastSensor2D.gd
Ah this was a bug that appeared when I moved to Godot 4. I thought I fixed it, somewhere _obs is declared to be null. Did you copy the addon from another example or from the plugin folder?
plugin folder
Could you try copying it from the BallChase example and see if you have the same error?
Still this:
and this:
Ok as a workaround you can probably remove the Array type annotation on that function.
Change line 70 to func get_observation():
I will give that a shot. I need to go now, though. Will have more time later today
Get Outlook for iOShttps://aka.ms/o0ukef
From: Edward Beeching @.> Sent: Saturday, January 7, 2023 12:59:36 PM To: edbeeching/godot_rl_agents @.> Cc: Jetpackjules11 @.>; Author @.> Subject: Re: [edbeeching/godot_rl_agents] Unable to add RayCastSemsor2D to node. (Issue #40)
Ok as a workaround you can probably remove the Array type annotation on that function. Change line 70 to func get_observation():
— Reply to this email directly, view it on GitHubhttps://github.com/edbeeching/godot_rl_agents/issues/40#issuecomment-1374610583, or unsubscribehttps://github.com/notifications/unsubscribe-auth/A27YGMBSDJGVSJYMML34B3TWRHKLRANCNFSM6AAAAAATUDWHAQ. You are receiving this because you authored the thread.Message ID: @.***>
Thanks for the help, that seemed to work!
I wm now encountering a new error... when I try to run gdrl on my custom environment, the raycast script returns this error:
Could this be due to the change from array? Is it not picking up on a _obs variable somewhere else?
Which version of the Godot 4 beta are you using?
I got the beta10 win64.exe version.
from this website: https://downloads.tuxfamily.org/godotengine/4.0/
(Also I am same person, just had forgotten main accnt (this one's) password...
could you paste the entire contents of ISensor2D.gd and RaycastSensor2D.gd here, somewhere _obs is set to be null. In a previous version this was the case and I removed the null initialization. I have a feeling it creeped back in somewhere.
I don't have access to the computer I am using for my project right now but can back to you tonight.
Here is the I sensor 2d Code:
extends Node2D
class_name ISensor2D
var _obs : Array
var _active := false
func get_observation():
pass
func activate():
_active = true
func deactivate():
_active = false
func _update_observation():
pass
func reset():
pass
And here is the RaycastSensor2D code:
extends ISensor2D
class_name RaycastSensor2D
@tool
@export var n_rays := 16.0:
get: return n_rays
set(value):
n_rays = value
_update()
@export var ray_length := 200:# (float,5,200,5.0)
get: return ray_length
set(value):
ray_length = value
_update()
@export var cone_width := 360.0:# (float,5,360,5.0)
get: return cone_width
set(value):
cone_width = value
_update()
@export var debug_draw := false :
get: return debug_draw
set(value):
debug_draw = value
_update()
var _angles = []
var rays := []
func _update():
if Engine.is_editor_hint():
_spawn_nodes()
func _ready() -> void:
_spawn_nodes()
func _spawn_nodes():
for ray in rays:
ray.queue_free()
rays = []
_angles = []
var step = cone_width / (n_rays)
var start = step/2 - cone_width/2
for i in n_rays:
var angle = start + i * step
var ray = RayCast2D.new()
ray.set_target_position(Vector2(
ray_length*cos(deg_to_rad(angle)),
ray_length*sin(deg_to_rad(angle))
))
ray.set_name("node_"+str(i))
ray.enabled = true
ray.collide_with_areas = true
add_child(ray)
rays.append(ray)
_angles.append(start + i * step)
func _physics_process(delta: float) -> void:
if self._active:
self._obs = calculate_raycasts()
func get_observation():
if len(self._obs) == 0:
print("obs was null, forcing raycast update")
return self.calculate_raycasts()
return self._obs
func calculate_raycasts() -> Array:
var result = []
for ray in rays:
ray.force_raycast_update()
var distance = _get_raycast_distance(ray)
result.append(distance)
return result
func _get_raycast_distance(ray : RayCast2D) -> float :
if !ray.is_colliding():
return 0.0
var distance = (global_position - ray.get_collision_point()).length()
distance = clamp(distance, 0.0, ray_length)
return (ray_length - distance) / ray_length
Ok, I am a bit stumped by this one. Last try, could you paste the content of sync.gd ?
Sure thing:
extends Node
# --fixed-fps 2000 --disable-render-loop
@export var action_repeat := 8
@export var speed_up = 1
var n_action_steps = 0
const MAJOR_VERSION := "0"
const MINOR_VERSION := "3"
const DEFAULT_PORT := "11008"
const DEFAULT_SEED := "1"
const DEFAULT_ACTION_REPEAT := "8"
var stream : StreamPeerTCP = null
var connected = false
var message_center
var should_connect = true
var agents
var need_to_send_obs = false
var args = null
@onready var start_time = Time.get_ticks_msec()
var initialized = false
var just_reset = false
# Called when the node enters the scene tree for the first time.
func _ready():
await get_tree().root.ready
get_tree().set_pause(true)
_initialize()
await get_tree().create_timer(1.0).timeout
get_tree().set_pause(false)
func _get_agents():
agents = get_tree().get_nodes_in_group("AGENT")
func _set_heuristic(heuristic):
for agent in agents:
agent.set_heuristic(heuristic)
func _handshake():
print("performing handshake")
var json_dict = _get_dict_json_message()
assert(json_dict["type"] == "handshake")
var major_version = json_dict["major_version"]
var minor_version = json_dict["minor_version"]
if major_version != MAJOR_VERSION:
print("WARNING: major verison mismatch ", major_version, " ", MAJOR_VERSION)
if minor_version != MINOR_VERSION:
print("WARNING: minor verison mismatch ", minor_version, " ", MINOR_VERSION)
print("handshake complete")
func _get_dict_json_message():
# returns a dictionary from of the most recent message
# this is not waiting
while stream.get_available_bytes() == 0:
stream.poll()
if stream.get_status() != 2:
print("server disconnected status, closing")
get_tree().quit()
return null
OS.delay_usec(10)
var message = stream.get_string()
var json_data = JSON.parse_string(message)
return json_data
func _send_dict_as_json_message(dict):
stream.put_string(JSON.stringify(dict))
func _send_env_info():
var json_dict = _get_dict_json_message()
assert(json_dict["type"] == "env_info")
var message = {
"type" : "env_info",
#"obs_size": agents[0].get_obs_size(),
"observation_space": agents[0].get_obs_space(),
"action_space":agents[0].get_action_space(),
"n_agents": len(agents)
}
_send_dict_as_json_message(message)
func connect_to_server():
print("Waiting for one second to allow server to start")
OS.delay_msec(1000)
print("trying to connect to server")
stream = StreamPeerTCP.new()
# "localhost" was not working on windows VM, had to use the IP
var ip = "127.0.0.1"
var port = _get_port()
var connect = stream.connect_to_host(ip, port)
stream.set_no_delay(true) # TODO check if this improves performance or not
stream.poll()
return stream.get_status() == 2
func _get_args():
print("getting command line arguments")
# var arguments = {}
# for argument in OS.get_cmdline_args():
# # Parse valid command-line arguments into a dictionary
# if argument.find("=") > -1:
# var key_value = argument.split("=")
# arguments[key_value[0].lstrip("--")] = key_value[1]
var arguments = {}
for argument in OS.get_cmdline_args():
print(argument)
if argument.find("=") > -1:
var key_value = argument.split("=")
arguments[key_value[0].lstrip("--")] = key_value[1]
else:
# Options without an argument will be present in the dictionary,
# with the value set to an empty string.
arguments[argument.lstrip("--")] = ""
return arguments
func _get_speedup():
print(args)
return args.get("speedup", str(speed_up)).to_int()
func _get_port():
return args.get("port", DEFAULT_PORT).to_int()
func _set_seed():
var _seed = args.get("env_seed", DEFAULT_SEED).to_int()
seed(_seed)
func _set_action_repeat():
action_repeat = args.get("action_repeat", DEFAULT_ACTION_REPEAT).to_int()
func disconnect_from_server():
stream.disconnect_from_host()
func _initialize():
_get_agents()
args = _get_args()
Engine.physics_ticks_per_second = _get_speedup() * 60 # Replace with function body.
Engine.time_scale = _get_speedup() * 1.0
prints("physics ticks", Engine.physics_ticks_per_second, Engine.time_scale, _get_speedup(), speed_up)
connected = connect_to_server()
if connected:
_set_heuristic("model")
_handshake()
_send_env_info()
else:
_set_heuristic("human")
_set_seed()
_set_action_repeat()
initialized = true
func _physics_process(delta):
# two modes, human control, agent control
# pause tree, send obs, get actions, set actions, unpause tree
if n_action_steps % action_repeat != 0:
n_action_steps += 1
return
n_action_steps += 1
if connected:
get_tree().set_pause(true)
if just_reset:
just_reset = false
var obs = _get_obs_from_agents()
var reply = {
"type": "reset",
"obs": obs
}
_send_dict_as_json_message(reply)
# this should go straight to getting the action and setting it checked the agent, no need to perform one phyics tick
get_tree().set_pause(false)
return
if need_to_send_obs:
need_to_send_obs = false
var reward = _get_reward_from_agents()
var done = _get_done_from_agents()
#_reset_agents_if_done() # this ensures the new observation is from the next env instance : NEEDS REFACTOR
var obs = _get_obs_from_agents()
var reply = {
"type": "step",
"obs": obs,
"reward": reward,
"done": done
}
_send_dict_as_json_message(reply)
var handled = handle_message()
else:
_reset_agents_if_done()
func handle_message() -> bool:
# get json message: reset, step, close
var message = _get_dict_json_message()
if message["type"] == "close":
print("received close message, closing game")
get_tree().quit()
get_tree().set_pause(false)
return true
if message["type"] == "reset":
print("resetting all agents")
_reset_all_agents()
just_reset = true
get_tree().set_pause(false)
#print("resetting forcing draw")
# RenderingServer.force_draw()
# var obs = _get_obs_from_agents()
# print("obs ", obs)
# var reply = {
# "type": "reset",
# "obs": obs
# }
# _send_dict_as_json_message(reply)
return true
if message["type"] == "call":
var method = message["method"]
var returns = _call_method_on_agents(method)
var reply = {
"type": "call",
"returns": returns
}
print("calling method from Python")
_send_dict_as_json_message(reply)
return handle_message()
if message["type"] == "action":
var action = message["action"]
_set_agent_actions(action)
need_to_send_obs = true
get_tree().set_pause(false)
return true
print("message was not handled")
return false
func _call_method_on_agents(method):
var returns = []
for agent in agents:
returns.append(agent.call(method))
return returns
func _reset_agents_if_done():
for agent in agents:
if agent.get_done():
agent.set_done_false()
func _reset_all_agents():
for agent in agents:
agent.needs_reset = true
#agent.reset()
func _get_obs_from_agents():
var obs = []
for agent in agents:
obs.append(agent.get_obs())
return obs
func _get_reward_from_agents():
var rewards = []
for agent in agents:
rewards.append(agent.get_reward())
agent.zero_reward()
return rewards
func _get_done_from_agents():
var dones = []
for agent in agents:
var done = agent.get_done()
if done: agent.set_done_false()
dones.append(done)
return dones
func _set_agent_actions(actions):
for i in range(len(actions)):
agents[i].set_action(actions[i])
Could this be due to _physics_process() not being called before the self._obs call? (In raycast sensor 2d)
func _physics_process(delta: float) -> void:
if self._active:
self._obs = calculate_raycasts()
I figured it out!
The issue was that in the RayCastSensor2D script, the get_observation() function checked if the self._obs length was 0. It should have been checking if the actual variable was null.
So from this:
func get_observation():
if len(self._obs) == 0:
print("obs was null, forcing raycast update")
return self.calculate_raycasts()
return self._obs
to this:
func get_observation():
if (self._obs == null):
print("obs was null, forcing raycast update")
return self.calculate_raycasts()
return self._obs
Ah nice one! It is strange that we have not seen this bug in the other examples. The problem could also be to do with where you put your sync node in the scene tree. I think it should be the first child of the root. Either way, I am glad you have solved it. Let me know if there are other issues!
I got the plugin working, but whenever I try to add RaycastSensor2D to my player node, I get this error:
Cannot get class 'ISensor2D'. editor/create_dialog.cpp:261 - Condition "inherits.is_empty()" is true. Cannot get class ''. editor/editor_data.cpp:551 - Parameter "p_object" is null. editor/scene_tree_dock.cpp:2184 - Condition "!child" is true.
It also looks a bit weird in the files system: