`Hello! This is about my first post for this forum, but anyway: my problem is with a mechanic that I am trying to develop for my game. I want to add a system wherein the player can shoot rivets that embed themselves into the ground. These rivets should be able to detect any source of electricity and become charged, allowing for a chain to be made with other rivets. This electric chain could activate nearby machines that need to be powered with electricity.
I am able to use Area3Ds to work as a system for detecting other electrified rivets, but it is only able to detect the ELECTRIFIED state using the ā_on_area_3d_entered()ā and ā_on_area_3d_exited()ā functions. It is NOT able to detect rivets that become electrified if it is already near them whilst they were un-electrified. This failure to detect updates with nearby rivets is my main issue with the system. I wanted to know if there is any examples, techniques, or simple tricks I could do to achieve a working chain system?
Cool idea. My first instinct would be to use a state machine of some sort. The rivet scene would still have an Area3D. Every time the state machine enters the ENABLED state, it could use the Area3D to obtain a list of overlapping Area3Ds with get_overlapping_areas, then iterate over the list and:
determine if the Area3D is another rivet.
if the Area3D is another rivet, set its state to ENABLED if it isnāt set to that value already.
The other rivet has the exact same state machine, so it will repeat the exact same trick until eventually there are no more rivets to electrify.
Disabling rivets is a bit trickier. If rivet #1 becomes DISABLED, it doesnāt automatically mean all other nearby rivets should become DISABLED as well. Example: If rivet #2 is in range of rivet #1 and rivet #3, and both #1 and #3 are electrified, and #1 suddenly becomes DISABLED, rivet #2 would still be electrified by #3. You would need to add some logic to the transition to DISABLED to see if a rivet should stay ENABLED or not.
there would need to be some logic attached to the rivet from which the electric source is coming. all other rivets that depend on this source would need to listen to it, disabling all of them if the state changes. how about using nesting for this ??
these ideas might be stupid I donāt know. I didnāt think too much about them.
Alternatively to what @TokyoFunkScene suggested, you could also implement custom signals for your rivets, a simple boolean variable to hold its current state, and a list of rivets and electric sources in the area. E.g.
class_name Rivet
extends Area3D
signal electrified_state_changed
var is_electrified: bool
var electric_sources: Array[ElectricSource]
var rivets: Array[Rivet]
func _on_area_3d_entered(area: Area3D) -> void:
if area is Rivet:
rivets.append(area)
area.electrified_state_changed.connect(check_state_change)
check_state_change()
elif area is ElectricSource:
electric_sources.append(area)
check_state_change()
func _on_area_3d_exited(area: Area3D) -> void:
if area is Rivet:
rivets.erase(area)
area.electrified_state_changed.disconnect(check_state_change)
check_state_change()
elif area is ElectricSource:
electric_sources.erase(area)
check_state_change()
func check_state_change() -> void:
if not electric_sources.is_empty():
change_state(true)
return
for rivet: Rivet in rivets:
if rivet.is_electrified:
change_state(true)
return
change_state(false)
func change_state(new_state: bool) -> void:
if is_electrified == new_state:
return
is_electrified = new_state
electrified_state_changed.emit()
I havenāt tested this code, I wrote it here raw, so treat it more as an idea for your solution rather than a copy-paste working solution.
Iām not sure if this aligns with your specific case, but Iāll drop it anyways, it might give some insights.
what if you nest all the rivets which meet the criteria of proximity required to be electrified to a source rivet X and then just loop through the tree of the children if the state of the source rivet changes ?
It is a fun challenge by the way. If I had more time Iād like to tackle this one.
A state machine sounds very interesting. Unfortunately I am very new to gdscript so I have no idea where to start with it. All the tutorials Iāve seen offer wildly different state machine implementations per video, do you have an idea of a very simple state machine for beginners and or a resource for such?
I will try this out! My other question would be how I would go about checking the state and or group of any overlapping areas, I donāt see any functions related to that.
The state machine in the example has a variable named current_state. When you make a state like this:
class_name State extends Node # Or some other sensible base class
You can extend from that base State class to create new states:
class_name EnergizedState extends State
func _on_enter()->void: ...
class_name UnenergizedState extends State
func _on_enter()->void: ...
If current_state is declared to be of type State, you can freely give it an object that extends State (i.e. the EnergizedState and UnenergizedState classes we just declared.) Checking which state is now active is simple; just check current_state with is:
# Some script in your `Rivet` scene.
@export var state_machine : StateMachine = null # Assign via inspector
func do_something_with_state()->void:
if state_machine.current_state is EnergizedState: ...
To assign groups, you can do that via the Node->Groups tab in the inspector window.
After thinking a bit I got a rough idea of how this could be achieved:
a single rivet scene (class) which has the following props:
is_energized (bool)
connected_to_source (bool)
Signals:
energy_state_changed
list_of_connected_rivets_changed
list of references:
a list with the references of rivets that are connected to self
Some logic:
a function that updates the list of references whenever necessary. (e.g. _on_area_3d_entered() signal )
check_energized() is invoked whenever the list of references changes
check_energized() loops through all the rivets in contact to check if at least one of them is_energized. This function should return an āeasyā TRUE in case self is connected_to_source.
I donāt know how you envision the connected_to_source should work. If that will change state or not. if not, then the default state could be false and just manipulate the specific instance to make it the source. E.g. some_random_rivet_instance.connected_to_source = true
If the connected_to_source state can change, then there would need to be a signal implemented or some logic within the _process() that would automatically switch the state.
This is a very rough sketch, read it with a few grains of salt.
This tutorial seems to be potentially outdated, I follow the steps that it gives me and it returns an error: āInvalid access to property or key āState2ā on a base object of type āDictionaryā.ā I might be doing something wrong.
EDIT: The problem lied in how the child nodes were named. Important to note that the exit(): and enter(): functions need to have the names of the nodes as strings within the parenthesis.
Do you mean the state machine and its states? Add a child node of type Node to your Rivet scene, then rename it to StateMachine. Append a script, then replace the contents of the script with:
@export var state_machine : StateMachine = null # Assign via inspector
func do_something_with_state()->void:
if state_machine.current_state is EnergizedState: ...
and the rest of the state machine code.
For the states, add a child node of type Node to the StateMachine node and give the child node a new name. (e.g. EnergizedState), then add the script. Note that you also have to define a loose script named state.gd or similar that contains the definition for the base class State. i.e. this code:
class_name State extends Node # Or some other sensible base class
so, IMO you need a way of making each rivet able to react to a bunch of different scenarios:
Letās call self our rivet protagonist.
a rivet in connection to self has been removed: you could figure out a way of sending this signal within your context to update the rivets connected to self. By updating I mean, removing the connection reference from the list and invoking a function that will check if energy is coming in or not - by looping through all the rivets to which this particular rivet is connected to (if self is not source)
a new nearby rivet has appeared making a new connection to self: it gets added to the ref list of self and then self should invoke a function in charge of checking the energy state by looping through the list and getting the energy state info of the other rivets. So that it can determine its state.
you also need a mechanism so that self can update its energized (bool) variable by keeping track of what is going on with the energize state of the other rivets connected to self. You can do this by connecting signals to each connected rivet and if any of these send a signal of energy changed that will invoke a function to determine if energy is coming in or not (I will repeat myself again: by looping through the connected rivets and getting their energized info)
If you are able to pull off a rivet scene that behaves in a self encapsulated way, then you can nest an instance of it within any projectile so that by composition you can build projectiles which have this behavior.
Shoot raycast to unelectrified rivet and conditioned it by range? Like if the rivet is within range and is_unelectrified then point the ray to it. Once the closet rivet is removed and next one is out of range then the raycast would automatically stop.
Unfortunately I was unable to think of anything that could do the job. I got very close, but nothing that works in all scenarios. If anyone wants to steal the idea go for it, I cannot sink anymore time into it. Thank you all for the help!