2D Pond System Help

Godot Version

4.3

Question


Hi everyone, I have a question regarding my pond. Essentially I want fishes (which will probably be a TextureRect I am not sure) to spawn randomly and swim around in the pool area selected. I want to make sure they don’t collide with the UI or player sprites either. To be honest, I have no idea how to implement this and am looking for some idea which I can start with.

The goal is the fishes will be picked randomly depending on the rarity (Common, Rare, Epic, Legendary) and fade into the pond. They will swim a bit until someone hooks them (ill implement that after) or after a certain while to prevent crowding. This continues on for the entire game. If anybody has an idea how to start this, thanks. I am new to Godot UI so I am lost

I don’t know how complex you want to get, but this could be a very interesting simulation.

To start with, you need a fish to spawn randomly in the pond, and move about avoiding the pond edges.

There are lots of ways to go about this. Start with creating a fish scene, using a png of your fish as the texture for a sprite2D. You can then use the process function to move the fish around.

I would start with a node2d as the root of my fish scene, with the sprite of the fish as a child. You can then set a random direction for the fish to move, and a speed for it to move at. Once your fish is moving you can look at resetting the direction when a boundary is encountered.

Again for setting boundaries for your pond, there are lots of ways to do this. You could hard code the boundaries, or set collision areas etc. When you encounter a boundary, you can set a new random direction for your fish and even a random speed (within limits of course).

Now you can add lots of copies of the fish scene and start looking at having the fish detect each other. At this point you may want to change to using a characterbody and use move and slide, or use raycast nodes so the fish look ahead of themselves. With ray casts you can then look at making the fish follow each other and show schooling behaviours. Or even use pathfinding for little fish to dart around the big fish! Lots of fun avenues to follow here.

But start simple. One fish that moves randomly when it encounters the edge of the pond. However try to build your fish in a way that will later let you save it as a separate scene, so that you can spawn multiple fish of the same scene.

Hope that helps in some way. Happy to help further if you need anything.

EDIT: Oh did you mean how to do a UI to pick the fish?

3 Likes

Can you explain the boundaries part more?


The white border is the area I want fishes to spawn in

Well in the picture below I started with an Area2D and added eight collision shape 2D nodes to create your pond boundary.

Now suppose each of these collision areas are added to a group called “pond_edges”. Your fish will have it’s own collision area, and will emit a signal when their collision area meets the edge. At that point you check if the met collision area is in the group ‘pond_edges’ and if so you can change the direction of the fish to move away.

You fish can then also, using the same signal, see if the met area is in the group “fishes”, and if so, react to meeting another fish.

You might want to do the collision detection with a raycast2D node sticking out the front of the fish too.

If you use CharacterBody2D you can use move and collide to move the fish and detect the collisions and their bounce directions too? (If you want the fish to bounce but you may want them to do something else).

Something like this (not tested but you get the idea):

# In your fish script
func _physics_process(delta):
	var collision = move_and_collide(velocity * delta)
	if collision:
		if collision.get_collider().is_in_group("pond_edge"):
			# deal with bouncing off the pond edge
			var pond_normal = collision.get_normal()
			velocity = velocity.bounce(normal) * 0.8
		elif collision.get_collider().is_in_group("fish"):
			# deal with interactions with other fish
		elif collision.get_collider().is_in_group("fishing_rod"):
			# deal with being hooked

Hope that helps.

PS As for spawning, start them in the center but invisible (set their modulate.a = 0) and fade in their opacity once they are moving (perhaps with a tween) in a random direction. That way your fish will not suddenly ‘appear’ out of nowhere.

3 Likes

how would you store the fishes until they are used

I would do each fish type as its own scene. Then when you need a new fish you would instantiate it into the pond scene.

var fish_scene: PackedScene = preload("res://fish.tscn")

# in your pond scene you would add fish as children
func spawn_fish():
    # Instantiate the fish scene
    var fish_instance = fish_scene.instantiate()
    add_child(fish_instance)

Then to add, say, five fish:

number_of_fish: int = 5

func add_fish(number_of_fish: int):
     for i in number_of_fish:
            spawn_fish()

To have different fish you could keep the scenes in a dictionary:

var fish_types: Dictionary = {
    "trout": preload("res://fish/trout.tscn"),
    "salmon": preload("res://fish/salmon.tscn"),
    "bass": preload("res://fish/bass.tscn"),
    "carp": preload("res://fish/carp.tscn"),
    "catfish": preload("res://fish/catfish.tscn")
}

Then call them like this:

func spawn_fish(fish_family: String):
    # Instantiate the fish scene
    var fish_instance = fish_scene.instantiate(fish_types[fish_family])
    add_child(fish_instance)
2 Likes

How would you make it so the fish automatically gets destroyed in 15 seconds if they still exist by then

In your fish scene give them a lifetime.

var fish_lifespan: int = 15
var fish_age: float = 0

Then in their process, keep track of the time like this:

func _process(delta):
      fish_age += delta
      if fish_age > fish_lifespan:
             queue_free()

However, I would fade the fish away rather than just making it dissappear, so instead of a queue_free you would call a function like do_fish_death() with tweens or whatever you decide to do in them.

3 Likes

@pauldrewett needs an award for his work in this topic

No human has devoted such time to a single topic in Help category! Didn’t know people can be so helpful!

2 Likes

This is what I currently have:

Fish Scene: (I’ll change the placeholders later)

This is the code for the pond script (controls spawning fish):

extends Node2D

var data = LibraryData.new();
var fish_amount = 0;
var fish = preload("res://Fish.tscn")
var current_pond = {
	"Common" = [],
	"Rare" = [],
	"Epic" = [],
	"Legendary" = [],
}

@onready var water = self.get_node("Water");

func _ready() -> void:
	water.child_entered_tree.connect(child_entered);
	water.child_exiting_tree.connect(child_exited);

	for n in 4:
		add_fish();
		await get_tree().create_timer(2).timeout;

var t = 0.0
func _process(delta: float) -> void:
	pass;
	t += delta;

	if fish_amount <= 10 and t >= 5.0:
		add_fish();
		t = 0.0;

func add_fish():
	var new_fish = fish.instantiate();
	water.add_child(new_fish);

	var chances = [];
	for key in data.fish_odds:
		for n in data.fish_odds[key]:
			chances.insert(0, key);

	randomize();
	var rarity = chances.pick_random();
# rarity is just a string either "Common" "Rare" "Epic" or "Legendary"
	new_fish.setup(rarity, data.fish_speed[rarity], data.fish_color[rarity]);

func child_entered():
	fish_amount += 1;

func child_exited():
	fish_amount -= 1;

And this is the code for the fish itself:

extends Node2D
var data = LibraryData.new();

var rarity;
var speed;
var color;

var is_fishable = true;
var fish_lifespan = 10;
var fish_age = 0;

@onready var body = self.get_node("CharacterBody2D");
@onready var sprite = body.get_node("FishSprite");

func _ready() -> void:
	pass

func _process(delta: float) -> void:
	fish_age += delta;

	if fish_age > fish_lifespan:
		is_fishable = false;
		var tween = get_tree().create_tween();
		tween.tween_property(self.get_node("FishSprite"), "modulate:a", 0, 2.5);
		await tween.finished;
		queue_free();

func setup(rarity, speed, color):
	self.rarity = rarity;
	self.color = color;
	self.modulate = color;
	body.velocity = speed;

func _physics_process(delta: float) -> void:
	var collision = body.move_and_collide(body.velocity, delta);
	if collision:
		if collision.get_collider().is_in_group("pond_edge"):
			var normal = collision.get_normal()
			body.velocity = body.velocity.bounce(normal) * 0.8;

func get_fishable() -> bool:
	return is_fishable;

Spawning, setting rarity, speed, etc all work but the fish simply do not move. What could be the issue? Sorry I’m still confused on the collisions

It is getting hard to see as your codebase grows, but what is speed set to?

Have you tried adding a print statement and checking it is a Vector2.

func _physics_process(delta: float) -> void:
	print(body.velocity)
	var collision = body.move_and_collide(body.velocity, delta);

Or here:

func setup(rarity, speed, color):
	print(speed)

Speed is normally a scalar quantity, not a Vector2, and velocity requires a Vector2 for move_and_collide.

As I cannot see your LibraryData I don’t know what you have set it to. And because you are not static typing I again cannot be sure it is a Vector2.

var speed: Vector2
var color: Color
var is_fishable: bool = true
etc

Also, why are you adding in semi-colons? Is this actually a C# project?

Anyway, my best guess is that your speed is not right. Although I am surprised you didn’t get an error when you assigned it here:

	body.velocity = speed;

PS I did suggest you first get the fish spawning, then get the fish moving, then get the fish avoiding the edges, then get them reacting to other fish, then add in lifetimes etc. It is often easier to get one thing done and working at a time, but you are certainly making great progress here, your code is looking pretty good for a first pass and you have not shied away from the complexities.

PS I don’t think your fish scene needs to be a node2D does it? You may have your reasons but why not make the characterbody2d the root of the fish scene?

1 Like

This is the fish speed in LibraryData

var fish_speed = {
	"Common" = Vector2(5,5),
	"Rare" = Vector2(10,10),
	"Epic" = Vector2(20,20),
	"Legendary" = Vector2(35,35),
};

honestly I just use semicolons because of just using java I guess

1 Like

I would suggest starting a new question about your moving fish and getting someone else to take a look. I don’t use move_and_collide at all so am only familiar with it in passing. Sorry.

For clarity you should call that dictionary initial_fish_velocities or something like that anyway and your speed variable should be called initial_velocity as it will be changing as the fish move around.

1 Like


Just changed it

Alright thanks for the help

Oh I think I saw the issue.

Try:

body.move_and_collide(body.velocity * delta);

PS even I am putting in the ; now lol.

1 Like

New issue:


For some reason after removing the Node2D self is broken

You do not need the self. Just delete it.

Self is only needed in custom classes if there is ambiguity about a reference.

Ok thanks, I changed extend Node2D to extend KinematicCollision2D and a bunch of methods which relied on it don’t work

Any way to double extend like

extend Node2D extend KinematicCollision2D

Edit: NVM just made it a Node2D again

You cannot double extend. Oh you just replied. NVM.

Just as a closing note, you should have a play with RayCast2D nodes. I use four of them on my enemy ships so they can detect obstacles, allies, foes etc. They are incredibly easy to use and work astonishingly well.

Especially good for schooling and swarming behaviour.

Anyway, good luck with your fishing game.