Crowd System:

First Design:

The hordes system went through many different iterations.

The first concept was inspired by Dream Luigi of Mario & Luigi: Dream Team Bros. and by a Clank minigame of the Ratchet and Clank saga.

It was fairly simple, for each 10 new members a new form would be unlocked, as depicted in this diagram:

Maybe with more development time I would have made the Giant Chicken Form the way I wanted but in the end I'm happy with what I've achieved.

Final Design:

The final version of the Controller is based solely on the Leader-Follower Mode. It works through a Singleton, the HiveMind that is being controlled by the HiveController. The HiveMind then relays all its inputs to the available leaders and they do the same with their followers.

A HiveMember can change Behaviour at any time with any other Behaviour using common methods.

1class_name HiveBehaviour
2extends Node2D
3
4var is_active := false
5var member : HiveMember
6
7func start(_info := {}): pass
8
9func update_orders(_direction : int, _wants_jump : bool, _wants_tower : bool) -> void: pass
10
11func end(): queue_free()
12
13
14func become_follower(_info := {}): pass
15
16func become_leader(_info := {}): pass
17
18func become_inactive(_info := {}): pass
19
20
21func free_member(): pass

Probably this isn't the most effective way to do this but it works and solves many problems I had with other iterations of such system.

To describe the modes in which a Member can change Behaviour, I synthesized all possible cases in 4 events described by this diagram. This helped me tackle the code and implementation of such system.

Case Study: LeaderHiveBehaviour

Very long code block!

The following section is a very long chunk of code, if you couldn't handle the first one don't go forwards. If you are into this, go for it, it's far from perfect but it may be interesting to look at idunno.

1class_name LeaderHiveBehaviour
2extends HiveBehaviour
3
4@export var leader_boost_factor := 1.0
5
6var followers : Array[HiveMember] = []
7
8func start(info := {}):
9 # Reset member's movement variables
10
11 member.leader_boost = leader_boost_factor
12 if info.has("followers"):
13 followers = info["followers"]
14 for follower in followers:
15 follower.behaviour.become_follower({"leader_member": member})
16
17func end():
18 member.leader_boost = 1
19 member.z_index -= 1
20 queue_free()
21
22func update_orders(new_direction : int, new_wants_jump : bool, new_wants_tower : bool) -> void:
23 if not is_active: return
24
25 # If hijacked order stop movement to followers
26
27 # If is able to tower and has requested:
28 # Try to build tower if not building one
29 # Stop building tower if already building
30
31 member.wants_jump = new_wants_jump and can_jump
32 member.direction = new_direction * (0.5 if building_tower else 1.0)
33
34 for follower in followers:
35 follower.behaviour.update_orders(new_direction, member.wants_jump, building_tower)
36
37func _build_tower_started():
38 # Calling each follower by index to maintain order.
39
40 # This makes the followers all line up and start to do little jumps to get
41 # on top of each other.
42 for i in range(len(followers)):
43 if followers[i].behaviour is FollowerHiveBehaviour:
44 followers[i].behaviour._tower_jump()
45 await followers[i].behaviour.finished_jumping
46 if not building_tower: return
47
48func become_follower(info := {}):
49 if info.has("leader_member"):
50 _release_followers(info["leader_member"])
51 member.change_behaviour(HiveMind.available_hive_behaviours[HiveMind.HiveBehaviours.FOLLOWER], {"leader_member": info["leader_member"]})
52 info["leader_member"].behaviour.followers.append(member)
53
54func become_leader(_info := {}):
55 pass
56
57func become_inactive(_info := {}):
58 for follower : HiveMember in followers:
59 follower.change_behaviour(HiveMind.available_hive_behaviours[HiveMind.HiveBehaviours.INACTIVE])
60 HiveMind.members_count -= 1
61 member.change_behaviour(HiveMind.available_hive_behaviours[HiveMind.HiveBehaviours.INACTIVE])
62
63func free_member():
64 _release_followers()
65 HiveMind.members_count -= 1
66 is_active = false
67 member.queue_free()
68
69
70func _release_followers(to_member : HiveMember = null):
71 HiveMind.leaders.erase(member)
72
73 # If to_member != null assign the followers to him
74 # otherwise pick the last follower and promote him to leader and assign the other followers to him
75
76## Activated when another Leader or an Inactive member gets near
77func _on_control_box_area_entered(area : Area2D) -> void:
78 if is_active and not is_hijacked:
79 var hive_behaviour : HiveBehaviour = area.get_parent()
80
81 if hive_behaviour is HiveBehaviour:
82 hive_behaviour.become_follower({"leader_member": member})
83 _handle_new_follower()
84
85## Activated when the player gets too far
86func _on_lost_check_box_body_exited(body) -> void:
87 if is_active and body is Player:
88 become_inactive()
89
90func _handle_new_follower() -> void:
91 %CanFollowersJumpTimer.start()
92 can_jump = false
93 if len(followers) >= 10:
94 can_tower = true
95 else:
96 can_tower = false
97 building_tower = false