A question about architecture

Godot Version

4.6.3

Question

I have a similar question but basically cause I am doing a gamejam, it shall give away what I am building

But the question is say you have a mystery box in Mario, u can

  1. Give a coin
  2. Give a mushroom
  3. Give a feather
  4. Give a star
  5. Give a fire flower

How would u design it?

Would u just duplicate the box like 5 times and just write custom code in each one?

What if you had say, 100 items u wanted to disburse?

Saying this cause I destroyed myself over thinking abit from something working although I kinda have a mess where I am doing abit of both now XD

In a meeting at the moment, but here’s what I just refactored for a game yesterday. I can answer questions in an hour or so.

image

@tool
class_name Pickup extends Area2D

@export var pickup_scene: PackedScene
@export var pickup_sound_stream: AudioStream
@export var pickup_sound_volume_db: float = 0.0
@export var display_size: Vector2 = Vector2(0.5, 0.5)

var pickup: Sprite2D
var is_collected: bool = false

@onready var collision_shape_2d: CollisionShape2D = $CollisionShape2D
@onready var audio_stream_player_2d: AudioStreamPlayer2D = $AudioStreamPlayer2D


func _ready() -> void:
	pickup = pickup_scene.instantiate()
	add_child(pickup)
	pickup.display(Vector2(0,0), display_size)
	body_entered.connect(_collect_item)
	if pickup_sound_stream:
		audio_stream_player_2d.stream = pickup_sound_stream
		audio_stream_player_2d.volume_db = pickup_sound_volume_db


# Save that the player has already picked this up.
func save_node() -> bool:
	return is_collected


# This pickup has already bee collected. Remove it.
func load_node(collected: bool) -> void:
	if collected:
		queue_free()


func _collect_item(body: Node2D) -> void:
	if audio_stream_player_2d.stream:
		audio_stream_player_2d.play()
	collision_shape_2d.set_deferred("disabled", true)
	body.add_item(pickup_scene)
	is_collected = true
	Disk.save_game()
	remove_child.call_deferred(pickup)
	if audio_stream_player_2d.playing:
		await audio_stream_player_2d.finished
	queue_free()

image

@tool
class_name HealingPotion extends Sprite2D

@export var healing_amount := 1.0
@export var ability_display_scale: Vector2 = Vector2(0.5, 0.5)
@export var sound_effect: AudioStream


# Use
func use(player: Player) -> void:
	Sound.play_sound_effect(sound_effect)
	player.heal(healing_amount)


## Setup this instance of the ThrownWeapon as a display item for the UI.
func display(bounds: Vector2, display_scale: Vector2 = ability_display_scale) -> void:
	position += bounds / 2
	scale = scale * display_scale

image

Thanks dragonforge.

In the end my solution was similar for some parts. I now know why people always extract the stats or such objects to the scene.

They wanna avoid scene inheritance and duplicating it XD

The more advanced question actually is as a practice also, how do u exactly imagine how something would look like for example, if the sprite path is loaded in the resource file for rotation of players.

I could imagine this being done for pots, but if it is done this way, the editor doesn’t refresh when u drag it in or show a blank square. Do u have any advice on that?

Yup. And if you look at my solution, you can avoid scene inheritance. I use it specifically so that I can do two things:

  1. Add the same thing over and over in a level without having to copy and paste the original. It’s IMO the scene version of avoiding magic strings or magic numbers. I hard code the item in an inherited scene so that I change the scene at the base scene level, and the specific defaults at the individual scene level. Any changes I make in the level are much easier to spot.
  2. I can drop any scene as loot on the screen. I used to have a separate drop.gd scene that was almost identical. (The result of a time limit in a game jam.) But I was able to refactor it, and then just attach a pickup scene to an enemy as loot. If I didn’t have a scene to load, I’d have to add a Resource to be able to edit those instances in the Inspector.

But that was an implementation choice that is divorced completely from the code.

I actually addressed that problem in my example above. In the original version, my editor looked like this:

You couldn’t see what each item was in the editor. You could only see it in the game. So I made it a @tool script so I could see it all the time.