Looks cleaner now. Still 3 warnings.
-
W 0:00:01:336 GDScript::reload: The function “looking_at()” is a static function but was called from an instance. Instead, it should be directly called from the type: “Basis.looking_at()”.
STATIC_CALLED_ON_INSTANCE
delivery_agent.gd:46 @ GDScript::reload()
-
W 0:00:01:343 GDScript::reload: The function “looking_at()” is a static function but was called from an instance. Instead, it should be directly called from the type: “Basis.looking_at()”.
STATIC_CALLED_ON_INSTANCE
character_body_3d.gd:20 @ GDScript::reload()
-
W 0:00:01:343 GDScript::reload: The parameter “event” is never used in the function “_unhandled_input()”. If this is intended, prefix it with an underscore: “_event”.
UNUSED_PARAMETER
character_body_3d.gd:22 @ GDScript::reload()
In my professional experience, it is hard to test functionality when there’s that much information in the debugger. It also tends to be a waste of time, because things change or are resolved when the errors are resolved.
I get that you’re in a hurry, but keep in mind you’re in essence asking us to do free work for you. It’s polite to make that work as easy as possible for us.
Good question. Depends on what you’re doing next. Who needs to know it is there? If you are using an Area3D body_entered signal, then you have access to whatever object the Area3D is attached to, as well as the delivery agent through the body variable. If you need to pass something between them, this is the signal to use.
If you’re using the NavigationAgent3D target_reached signal, it just means the delivery agent is close to wherever you sent it. It make not visually be inside the Area3D yet, depending on what tolerances you set on the NavigationAgent3D. If this is going to cause a visual problem, it’s probably not a good choice. Also, depending on what you’re doing next, and whose driving that - you may have to have something listening to this signal externally - which is going to create more closely coupled code you may not want.
You should always avoid that Manager Anti-Pattern. It’s a good idea to step back and ask yourself, “What am I trying to accomplish here?” TBH, I have no idea what you’re trying to accomplish. But if each delivery agent can manage it’s own life, the manager pattern becomes unnecessary. It might make sense to have a Dispatcher to tell delivery agents what they’re picking up, delivering, and from/to where. But doesn’t micro-manage the entire process.
I think it’s important to ask yourself, “What am I trying to accomplish here?” I started understanding your code by cleaning it up, and by renaming variables so they told me what they do. For example the variable name rn indicates that something is random. I changed it to random_house so that throughout the code, I know than I’m looking at data from the random house we picked. I don’t have to refer to the variable declaration to understand what we are doing 5 lines below. We reduce the cognitive load of reading the code.
Well, let’s take a look at your DeliveryManager code. (I’ve cleaned it up a bit according to the GDScript Style Guide to make it easier to read.)
class_name DeliveryManager extends NavigationRegion3D
var houses_to_order: Array[Node3D] = []
var orders_address: Array[Node3D] = []
var order_count: int = 0
var orders_queue: Array[CustomerOrder]= []
var orders_in_progress: Array[CustomerOrder] = []
var orders_complete: Array[CustomerOrder] = []
var time_per_day: float = 60.0
var curr_time: float = 0.0
var first_time: bool = true
var order_timer: float = 0.0
@onready var restaurants_list: Node = $RestaurantsList
@onready var houses_list: Node = $HousesList
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
for house in houses_list.houses:
houses_to_order.append(house)
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta: float) -> void:
if first_time:
first_time = false
curr_time += delta
order_timer += delta
if curr_time > time_per_day:
clear_lists()
if order_timer > 0.5:
order_timer = 0.0
random_order()
func random_order():
var random_house = randi_range(0,houses_to_order.size()-1)
var new_order: CustomerOrder = CustomerOrder.new()
new_order.order_id = order_count
new_order.house_id = houses_to_order.get(random_house).id
new_order.delivery_address = houses_to_order.get(random_house).get_address()
new_order.rest_id = randi_range(0, restaurants_list.restaurants.size()-1)
new_order.collection_address = restaurants_list.get_restaurant(new_order.rest_id).get_address()
# add the order id to the restaurant
restaurants_list.get_restaurant(new_order.rest_id).orders.append(new_order.order_id)
orders_queue.append(new_order)
var obj = houses_to_order.get(random_house)
orders_address.append(obj)
houses_to_order.erase(obj)
order_count += 1
func complete_order(order_id:int):
var ocount = 0
var order_found = false
for e in orders_in_progress:
if e.order_id == order_id:
order_found = true
break
ocount += 1
if order_found:
var order = orders_in_progress.get(ocount)
orders_in_progress.erase(order)
orders_complete.append(order)
func get_order():
if orders_queue.size() > 0:
var order = orders_queue.get(0)
orders_queue.erase(order)
orders_in_progress.append(order)
return order
return null
func clear_lists():
for e in orders_address:
houses_to_order.append(e)
orders_address.clear()
random_order()
Let’s look at the first line at is was originally:
var rn = randi_range(0,houses_to_order.size()-1)
What are we trying to accomplish here? We are randomly picking an item from an Array using the length of the array minus one. We are assigning it to a variable that indicates out value is random.
Let’s look at the Array declaration:
var orders_address: Array[Node3D] = []
Then the assignment of that Array:
@onready var houses_list: Node = $HousesList
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
for house in houses_list.houses:
houses_to_order.append(house)
Now let’s look at the Scene Tree:
Then the House Scene:
And the house.gd code:
extends MeshInstance3D
var id
var order_id = -1
var delivery_manager : DeliveryManager
@onready var delivery_area: Area3D = $Address/Area3D
@onready var address: Marker3D = $Address
func get_address()->Vector3:
return address.global_position
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
delivery_area.connect("body_entered",on_delivery)
func on_delivery(body:Node3D):
if body is DeliveryAgent:
if (body as DeliveryAgent).has_order(order_id):
(body as DeliveryAgent).complete_order()
delivery_manager.complete_order(order_id)
order_id = -1
So we have an Array of Node3D objects that are in fact MeshInstance3D objects. However, from an OOP perspective, they are House objects. By calling them that, it not only makes our code cleaner and easier to read, it reduces the chance for bugs - and most importantly in this context - it helps us think about our architecture. Because now instead of being a Node3D/MeshInstance3D object that is managed, it’s a House.
So let’s fix the code to say that. We will start with our House object.
House
Before writing code, let’s talk about a house. When I order delivery of food these days, I use an app and they show up at my door. My responsibility is to say what I want, what my address is, and where I want it left (ring the doorbell, leave it on porch, etc.) I do not tell the app it’s delivered. I do not call and tell the pizza place I got my pizza. Once it’s delivered, I’m eating it and I don’t care is you know it’s delivered.
So for our code for House, the first thing we want to do is declare it as one.
class_name House extends MeshInstance3D
Next, let’s look at the variables we have declared:
var id: int
From a programming perspective, this is our address. We don’t even refer to it ourselves. If someone else is using it, they could potentially already get that information another way? Is this a useful piece of information to provide? Maybe, but architecturally, we are passing around a reference to this House object, which is a Reference, which is in fact a memory address pointing to us. Maybe we don’t need this.
var order_id: int = -1
Have you ever had someone ask you what your order id is, and your immediate response is “I don’t know!” This is not typically information that homeowners track. Later on we use it to tell the DeliveryManager we got our stuff. But let’s be honest, the DeliveryAgent is responsible for telling them that (and taking a verification picture). Probably don’t need this.
var delivery_manager: DeliveryManager
You probably see where I’m going with this. How often have you called up the pizza delivery guy’s boss to tell them that you got your pizza. Never. And now we get to the question, “Does passing the manager into the child objects really break the system?” And the answer is no, but it seems kinda silly when you look at it from a real world example.
Next up, we have our @onready variables.
@onready var delivery_area: Area3D = $Address/DeliveryArea
@onready var address: Marker3D = $Address
You may notice I tweaked the node path. Why?
Well now it’s a lot clearer where the DeliveryArea is without reading the variable declaration. I can see it.
These variables make sense. One is the “address” that the NavigationAgent3D is going to actually use to find us. The other is the Area3D telling us when the thing was delivered. Except - we don’t do anything with that information, except tell the DeliveryManager, which as we’ve determined, is now a ridiculous use case.
So now we simplify everything.

A House object does not need a DeliveryArea, because our House and its imaginary occupants do not actually do anything with the delivered pizza/whatever.
Likewise, that simplifies our code immensely:
class_name House extends MeshInstance3D
@onready var address: Marker3D = $Address
func get_address()->Vector3:
return address.global_position
Let’s go back up to our DeliveryManager code and the random_order() function.
random_order() Redux
This function is to create a random order. But let’s take a look at our definition of a House from a practical point of view. A House contains people that place an order, and sits at an address where the order is delivered. A House doesn’t care if anyone knows the order is delivered once they get it. they’re done. But also… they’re the ones placing the order.
So if we have a Manager, it does make sense that they’re telling the houses what to order. But really, how is that realistic? If we break it down from an OOP perspective, let’s say we want different houses ordering different things? A Manager must not only know that all houses and restaurants exist, but every possible item that can be ordered, so that it can generate orders. If the houses handle ordering (and perhaps ultimately determining that they got what they ordered - which is different from the delivery arriving), the Manager never needs to know what was ordered. It only cares that its worker bees pick up and deliver orders. what’s inside them doesn’t matter to them. They in effect become more like a Dispatcher.
So let’s do a little refactoring.
class_name DeliveryManager extends NavigationRegion3D
var houses: Array[House]
I’ve renamed houses_to_order to houses. Why? Well, I like verbose, descriptive variable names, but there’s only one collection of houses and that’s ones ordering. also, now that we’ve typed our House object, we can make an Array of type House. In fact, if we just make those minor changes (without the deletions from before), the code continues to work as before.
So now every frame we are calling random_order(). For now, we can continue to do this here, but looking at our definition of a DeliveryManager, it might make sense for the Game itself to determine how often Houses get hungry and order.
With the name refactor, our code now looks like this:
func random_order():
var random_house = randi_range(0, houses.size()-1)
var new_order: CustomerOrder = CustomerOrder.new()
new_order.order_id = order_count
new_order.house_id = houses.get(random_house).id
new_order.delivery_address = houses.get(random_house).get_address()
new_order.rest_id = randi_range(0, restaurants_list.restaurants.size()-1)
new_order.collection_address = restaurants_list.get_restaurant(new_order.rest_id).get_address()
# add the order id to the restaurant
restaurants_list.get_restaurant(new_order.rest_id).orders.append(new_order.order_id)
orders_queue.append(new_order)
var obj = houses.get(random_house)
orders_address.append(obj)
houses.erase(obj)
order_count += 1
Let’s do a few optimizations and renaming of variables for clarity.
Our first line can be changed to something a lot clearer to read:
var random_house = houses.pick_random()
Now that I understand the code more now, random_house is just house, and calling it that will help us see what things the House should be doing. Something much harder to see when it was called rn.
var house = houses.pick_random()
That’s going to make some of our code fail, but it will also simplify it. Instead of using:
new_order.house_id = houses.get(house).id
We just do:
new_order.house_id = house.id
And so on, until we get to this line:
var obj = houses.get(house)
Now what is clear, is we are creating obj to hold a reference to house, but we already have that. This means the whole definition of obj can be deleted and house can be used instead. The next two lines become:
orders_address.append(house)
houses.erase(house)
And our code still runs, but instead of doing 4 Array lookups with an index, we just refer to the House object every time.
OrderTaker
Breaking it down a bit more, you can hopefully see that it makes sense to have an OrderTaker separate from the manager. They can prod the houses randomly to order to keep track of the number of orders, but each House when told it can order, can call up the OrderTaker with a signal.
class_name OrderTaker
signal place_order(house, restaurant_id)
var order_count: int = 0
var orders_queue: Array[CustomerOrder]
func _ready() -> void:
place_order.connect(_on_place_order)
func _on_place_order(house: House, restaurant_id: int) -> void:
var new_order: CustomerOrder = CustomerOrder.new()
new_order.order_id = order_count
new_order.house_id = house.id
new_order.delivery_address = house.get_address()
new_order.rest_id = restaurant_id
new_order.collection_address = restaurants_list.get_restaurant(restaurant_id).get_address()
restaurants_list.get_restaurant(restaurant_id).orders.append(new_order.order_id)
orders_queue.append(new_order)
orders_address.append(house)
houses.erase(house)
order_count += 1
Conclusion
As we continue on, and create objects to handle each thing, the need for a Manager should become less and less until their job is redundant, or their name changes to the job they have left.
Naming variables and classes is really important to that though. If nothing is named, then it’s all a part of the machine. As you think about intentional names for objects, it helps determine their responsibilities. It will allow you to abstract and encapsulate the objects.
Hope that helps.