Creating An Electric Chain Between Projectiles

Godot Version

Godot 4.4

Question

`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?

Here is a diagram of what I want:

I appreciate any and all help! Thank you!`

1 Like

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.

3 Likes

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.

1 Like

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.

3 Likes

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.

4 edits, jesus I’m ashamed. :flushed_face:

3 Likes

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?

If you want a node based state machine this is a decent tutorial:

The simplest state machine is an enum:

enum States {IDLE, ATTACKING, HOLDING}

You set an initial state when the object is created, then define conditions in your code for when you switch to a different state.

You then use the state you are in to decide what needs to happen.

func _physics_process(_delta: float) -> void:
	set_state()
	choose_action()

Edit: Adding a 4th edit so @Gass feels less ashamed :wink:

4 Likes

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.

1 Like

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.

1 Like

Hey thanks for the support on my edit overflow shame.

By the way, thanks for the info about the Godot state machine, I’m not familiar with it. So I’ll take a glance.

@2steps2late

If you are okay with me trying to implement your idea I might give it a shot today at some point – maybe – :melting_face:

1 Like

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.

1 Like

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.

Go right ahead with implementation! I would love to see how people’s different interpretations would work

Okay! And how might I connect it to an Area3D or such?

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

why is that ?

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!