Fundamental adjustments and fixes for vine spawning

This commit is contained in:
RealMelwei 2025-10-21 16:06:16 +02:00
parent 8786d30936
commit 49d48da550
14 changed files with 157 additions and 80 deletions

View file

@ -17,15 +17,14 @@ func _ready() -> void:
await get_tree().create_timer(0.2).timeout
if get_node_or_null("ObjectList") != null:
var objects_to_be_placed = $ObjectList.get_children()
$ObjectList.reparent(get_tree().get_root().get_node("main"), false)
var obj_list = $ObjectList
obj_list.reparent(get_tree().get_root().get_node("main"), false)
await get_tree().create_timer(0.2).timeout
var objects_to_be_placed = obj_list.get_children()
for object in objects_to_be_placed:
var offset = object.global_position;
object.global_position = Grid.get_world_position(location, offset)
# some objects, such as petals, require information about their position in the grid.
if "location" in object: object.location = Grid.get_location_from_world_pos(object.global_position)
if "offset" in object:
object.offset = Global.vec_mod(offset, 300)
# The building remembers these objects: If it is destroyed, so are they.
if object is Platform or object is Trap or object is Item:
objects.append(object)
@ -33,6 +32,14 @@ func _ready() -> void:
# This scales platforms hoizontally to make sure they still form a floor without gaps.
if(object.has_method("init_at_horizontal_distortion")):
object.init_at_horizontal_distortion(object.position.length() / Grid.ground_radius)
grid_entrance_callback(object)
func grid_entrance_callback(object : Node):
# Objects receive a callback when placed, starting from the deepest children
for child in object.get_children():
grid_entrance_callback(child)
if object.has_method("_enter_grid"):
object.call_deferred("_enter_grid")
func overlaps(other : Building):
# heights don't overlap

View file

@ -10,8 +10,10 @@ static var item_pool = ResourceLoader.load("res://items/generic/item_pool.tres",
@export var unique_bonus_multiplier = .05
@export var rare_bonus_multiplier = 0
@export var spawn_petal = false
@export var petal_scene : PackedScene = ResourceLoader.load("res://petal.tscn")
@export var spawn_petal = true
@export var petal_scene : PackedScene = ResourceLoader.load("res://vines_petals/petal.tscn")
var packed_item_scene : PackedScene
var remove_after_spawn = false
@ -42,12 +44,13 @@ func _ready():
# Pick a random pool and a random item from it, then remove it if unique.
var pool = choose_pool()
var index = randi_range(0, pool.size() - 1)
var packed_scene : PackedScene = pool[index]
packed_item_scene = pool[index]
if remove_after_spawn:
item_pool.unique.remove_at(index)
# Place the item, possibly inside a petal
var object = packed_scene.instantiate()
if packed_item_scene == null: return
var object = packed_item_scene.instantiate()
add_child.call_deferred(object)
object.reparent.call_deferred(get_parent())
if spawn_petal: instantiate_petal(object)

View file

@ -1,7 +1,7 @@
[gd_scene load_steps=4 format=3 uid="uid://xj0of571aur1"]
[ext_resource type="Script" uid="uid://b8em61mqgdi58" path="res://items/generic/item_spawn.gd" id="1_ms6tn"]
[ext_resource type="PackedScene" uid="uid://bhhhvaqhm3ctc" path="res://petal.tscn" id="2_5to52"]
[ext_resource type="PackedScene" uid="uid://bhhhvaqhm3ctc" path="res://vines_petals/petal.tscn" id="2_5to52"]
[ext_resource type="PackedScene" uid="uid://chs0u61f45nau" path="res://utils/earth_aligner.tscn" id="3_5pwuf"]
[node name="ItemSpawn" type="Node2D"]

View file

@ -236,6 +236,6 @@ func play_double_jump_animation() -> void:
# If there is any vine nearby, gain the corresponding statusses.
func update_vine_statuses():
var location = Grid.get_location_from_world_pos(global_position)
for vine : Vine in Grid.vines_per_node[location.x][location.y]:
for vine : Vine in Grid.get_vines_at(location):
if vine.active_depth > 0: #TODO: Properly manage procedural activation
Status.apply(vine.status_name, self, 0.1, vine.status_params)

View file

@ -6,7 +6,7 @@ var enemy_damage = 10
@onready var dmg_id = Global.next_dmg_id
var id_refreshing = false
func _ready() -> void:
func _enter_grid() -> void:
# Randomize starting position and rotation direction
rotate(randf() * TAU)
angular_speed = angular_speed * (2 * randi_range(0,1) - 1)

View file

@ -17,7 +17,7 @@ var left : Vector2 :
get():
return global_from_local(Vector2.LEFT)
func _ready() -> void:
func _enter_grid() -> void:
align()
func _process(_delta: float) -> void:

View file

@ -7,5 +7,7 @@ static var next_dmg_id : int = 0 :
return next_dmg_id
# Entrywise modulo
static func vec_mod(vec : Vector2, modulus : float):
return Vector2(fposmod(vec.x, modulus), fposmod(vec.y, modulus))
static func vec_mod(vec : Vector2, modulus : float, only_x = false, only_y = false):
return Vector2(
fposmod(vec.x, modulus) if not only_y else vec.x,
fposmod(vec.y, modulus) if not only_x else vec.y)

View file

@ -1,7 +1,6 @@
class_name Bud extends VineNode
signal opened
var img_path
@export var depth : int
# Triggers when a bud is hit. Spreads the vine, then removes the bud
func _on_opened():
@ -14,14 +13,11 @@ func _on_opened():
# Spread in all directions where the given vine is not yet present
func spread():
for dir in [Vector2.UP, Vector2.DOWN, Vector2.RIGHT, Vector2.LEFT]:
if not vine.vine_locations.has(Global.vec_mod(location + dir, Grid.num_collumns)):
if not vine.vine_locations.has(Global.vec_mod(location + dir, Grid.num_collumns, true)):
grow_to_next_bud(dir)
# Grow a vine
func grow_to_next_bud(dir):
var target_offset = vine.random_offset()
var target = Global.vec_mod(location + dir, Grid.num_collumns)
var pos1 = Grid.get_world_position(location, offset)
var pos2 = Grid.get_world_position(target, target_offset)
var num_seg = floor((pos1-pos2).length() / 96)
await vine.grow_vine_sequence(location, offset, target, target_offset, num_seg, depth, true, false)
var target_location = Global.vec_mod(location + dir, Grid.num_collumns, true)
var target = vine.random_vine_node_at(target_location)
await vine.grow_vine_sequence(self, target, true, false)

View file

@ -1,9 +1,8 @@
class_name Petal extends Node2D
@onready var location : Vector2 = Grid.get_location_from_world_pos(global_position)
@onready var offset : Vector2 = Grid.get_offset_from_world_pos(global_position)
class_name Petal extends VineNode
@export var vine_resource : PackedScene
var vine : Vine
var activated = false
# Adding an item here automatically locks it inside the petal
var item : Item:
set(item_in):
item = item_in
@ -12,15 +11,14 @@ var item : Item:
item.monitorable = false
item.modulate = Color(1,1,1,0.8)
func _ready() -> void:
await get_tree().create_timer(1).timeout
# Upon being placed inside a building, create a vine
func _enter_grid() -> void:
depth = 0
vine = vine_resource.instantiate()
vine.petal_location = Global.vec_mod(location, Grid.num_collumns)
vine.petal_offset = offset
vine.petal = self
get_parent().call_deferred("add_child",vine)
await get_tree().create_timer(1).timeout
vine.init_random()
# Upon interaction, release the item and activate the vine
func _on_interaction() -> void:
activated = true
vine.activate()

View file

@ -1,7 +1,7 @@
[gd_scene load_steps=7 format=3 uid="uid://bhhhvaqhm3ctc"]
[ext_resource type="Texture2D" uid="uid://d15c1exwtp7sc" path="res://vines_petals/bud_open.png" id="1_05pvv"]
[ext_resource type="Script" uid="uid://coi26l4ndnaw0" path="res://petal.gd" id="1_ybtdj"]
[ext_resource type="Script" uid="uid://coi26l4ndnaw0" path="res://vines_petals/petal.gd" id="1_ybtdj"]
[ext_resource type="PackedScene" uid="uid://b7o82cdfwuqd1" path="res://vines_petals/vine.tscn" id="2_4btcp"]
[ext_resource type="PackedScene" uid="uid://ds21d77ruuk4y" path="res://utils/interactable_area.tscn" id="2_ybtdj"]
[ext_resource type="PackedScene" uid="uid://chs0u61f45nau" path="res://utils/earth_aligner.tscn" id="5_uxqeq"]

View file

@ -1,11 +1,16 @@
class_name Vine extends Node2D
@export var petal_location : Vector2
@export var petal_offset : Vector2
# Grid position data of the petal
@export var petal : Petal
# Locations and offsets of all vine segments
@export var vine_locations : Array[Vector2]
@export var bud_resource : PackedScene
# Paths for vine segment sprites while active/inactive
var img_path_inactive = "res://vines_petals/vine_inactive.png"
@export var img_path_active : String
# The table from which to choose a status effect at random, including the corresponding vine color
const status_data = [
{
"name": "Slow",
@ -17,20 +22,25 @@ const status_data = [
"params" : {},
"img_path": "res://vines_petals/vine_active_purple.png"
},]
# The chosen status and its data
var status_name : String
var status_params : Dictionary = {}
# To which depths vine segments and nodes are active.
# When max depth is reached, everything is active from then on.
var active_depth = -1
var fully_active = false
var max_depth = 50
# Array containings lists of sprites, using their depths as index
var vine_data = []
var max_depth = 150
# List of "leaf locations" of the vine tree
var vine_end_data = []
func _ready() -> void:
var data : Dictionary = status_data.pick_random()
status_name = data.name
status_params = data.params if data.has("params") else {}
img_path_active = data.img_path
# Places a sprite for a vine segment from pos1 to pos2 at a given depth,
# which might be activated already, depending on the active depth.
func draw_vine(pos1 : Vector2, pos2 : Vector2, depth : int):
var sprite = Sprite2D.new()
get_tree().get_root().get_node("main").add_child(sprite)
@ -38,6 +48,7 @@ func draw_vine(pos1 : Vector2, pos2 : Vector2, depth : int):
sprite.texture = ResourceLoader.load(img_path_active)
else:
sprite.texture = ResourceLoader.load(img_path_inactive)
# If neccessary, extend vine_data to the current depth
while depth + 1 > vine_data.size():
vine_data.append([])
vine_data[depth].append(sprite)
@ -47,37 +58,47 @@ func draw_vine(pos1 : Vector2, pos2 : Vector2, depth : int):
sprite.scale.x = 3
sprite.z_index = -1
func grow_vine_sequence(location1 : Vector2, offset1 : Vector2, location2: Vector2, offset2 : Vector2, num_segments: int, depth: int, grow_bud = false, quick_spawn = false):
depth = min(depth, max_depth)
var pos1 = Grid.get_world_position(location1, offset1)
var pos2 = Grid.get_world_position(location2, offset2)
var positions = []
positions.append(pos1)
# Grows a sequence of vine segments from grid position 1 to grid position 2, starting at given depth.
func grow_vine_sequence(start : VineNode, target: VineNode, grow_bud = false, quick_spawn = false):
var depth = min(start.depth, max_depth)
var segment_length = (pos1 - pos2).length() / num_segments
# Calculate the number and length of segments from the distance of source and target
var dist = (start.position - target.position).length()
var num_segments = floor(dist / 96)
var segment_length = dist / num_segments
# Build a list of vine segment positions placed horizontally equidistant along the line from the source
# to the target, with vertical offset having controlled 2nd derivative to prevent sharp edges.
var positions = []
positions.append(start.position)
var offsets = generate_random_offsets(num_segments, 0.6 * segment_length)
for i in range(num_segments - 1):
var t = (i + 1) * 1.0/num_segments
var center = t * pos2 + (1 - t) * pos1
var offset = offsets[i + 1] * (pos2 - pos1).normalized().rotated(PI/2)
var center = t * target.position + (1 - t) * start.position
var offset = offsets[i + 1] * (target.position - start.position).normalized().rotated(PI/2)
positions.append(center + offset)
positions.append(pos2)
positions.append(target.position)
# Draw vine segments along the positions determined previously.
# Do so slowly unless quick_spawn is specified.
for i in range(num_segments):
draw_vine(positions[i], positions[i+1], depth + i + 1)
if not quick_spawn:
await get_tree().create_timer(0.2).timeout
if active_depth >= depth + num_segments:
if grow_bud and location2.y > 0 and location2.y <= Grid.max_bud_height:
spawn_bud(location2, offset2, depth + num_segments)
if grow_bud and target.location.y > 0 and target.location.y <= Grid.max_bud_height:
spawn_bud(target.location, target.offset, depth + num_segments)
else:
if location2.y > 0 and location2.y <= Grid.max_bud_height:
if target.location.y > 0 and target.location.y <= Grid.max_bud_height:
for i in range(vine_end_data.size()):
if vine_end_data[i].location == location1:
if vine_end_data[i].location == start.location:
vine_end_data.remove_at(i)
break
vine_end_data.append({"location": location2, "offset": offset2, "depth": depth+num_segments})
Grid.vines_per_node[location2.x][location2.y].append(self)
vine_locations.append(Global.vec_mod(location2, Grid.num_collumns))
target.depth = depth + num_segments
vine_end_data.append(target)
Grid.add_vine_to(self, target.location)
vine_locations.append(Global.vec_mod(target.location, Grid.num_collumns, true))
func generate_random_offsets(segment_count, max_second_derivative):
var differences = []
@ -122,26 +143,34 @@ func update_active_depth():
spawn_bud(data.location, data.offset, data.depth)
update_active_depth()
func _enter_tree() -> void:
var data : Dictionary = status_data.pick_random()
status_name = data.name
status_params = data.params if data.has("params") else {}
img_path_active = data.img_path
init_random()
func random_vine_node_at(location):
var offset = random_offset()
return VineNode.new(self, location, offset)
func init_random():
Grid.vines_per_node[petal_location.x][petal_location.y].append(self)
vine_locations.append(petal_location)
vine_end_data.append({"location": petal_location, "offset": petal_offset, "depth": 0})
Grid.add_vine_to(self, petal.location)
vine_locations.append(petal.location)
vine_end_data.append(petal)
for i in range(randi_range(2,2)):
var end = vine_end_data.pick_random()
var branches_count = 0
for branch in range(ceil(randf() * 4)):
var dir = [Vector2.UP, Vector2.DOWN, Vector2.RIGHT, Vector2.LEFT].pick_random()
var target = Global.vec_mod(end.location + dir, Grid.num_collumns)
if target.y <= Grid.max_bud_height + 1 and Grid.vines_per_node[target.x][target.y].is_empty():
if not (target.y <= 0 or target.y > Grid.max_bud_height):
var target_location = Global.vec_mod(end.location + dir, Grid.num_collumns, true)
if target_location.y <= Grid.max_bud_height + 1 and Grid.get_vines_at(target_location).is_empty():
if not (target_location.y <= 0 or target_location.y > Grid.max_bud_height):
branches_count += 1
var target_offset = random_offset()
var pos1 = Grid.get_world_position(end.location, end.offset)
var pos2 = Grid.get_world_position(target, target_offset)
var num_seg = floor((pos1-pos2).length() / 96)
grow_vine_sequence(end.location, end.offset, target, target_offset, num_seg, end.depth, true, true)
var target = random_vine_node_at(target_location)
grow_vine_sequence(end, target, true, true)
if i==0 and branches_count == 1:
vine_end_data.append({"location": petal_location, "offset": petal_offset, "depth": 0})
vine_end_data.append(petal)
func random_offset():
return Vector2(randf_range(60, 240), randf_range(90, 270))

View file

@ -1,8 +1,40 @@
class_name VineNode extends Node2D
@export var vine : Vine
@export var location : Vector2
@export var location : Vector2 :
set(new_loc):
location = Global.vec_mod(new_loc, Grid.num_collumns, true)
@export var offset : Vector2
@export var depth : int
func _ready() -> void:
position = Grid.get_world_position(location, offset)
func _get(property: StringName) -> Variant:
if property == "position":
update_position()
return position
if property == "global_position":
update_position()
return global_position
return null
func _set(property: StringName, value: Variant) -> bool:
if property == "global_position":
location = Grid.get_location_from_world_pos(value)
offset = Grid.get_offset_from_world_pos(value)
update_position()
return true
if property == "position":
update_position()
return false
return false
func update_position():
global_position = Grid.get_world_position(location, offset)
func _enter_grid() -> void:
update_position()
func _init(_vine = null, _location = Vector2.ZERO, _offset = Vector2.ZERO, _depth = 0):
vine = _vine
location = _location
offset = _offset
depth = _depth

View file

@ -11,7 +11,7 @@ extends Node2D
var buildings : Array[Building] = []
var max_bud_height = 8
var vines_per_node : Array = []
var vines_per_node : Array
func _ready() -> void:
reset()
@ -75,7 +75,17 @@ func reset():
for j in range(max_bud_height + 2):
arr.append([])
vines_per_node.append(arr)
func get_vines_at(location) -> Array:
location = Global.vec_mod(location, num_collumns, true)
return vines_per_node[location.x][location.y]
func add_vine_to(vine, location) -> void:
if location.y > max_bud_height + 1 or location.y < 0:
return
location = Global.vec_mod(location, num_collumns, true)
vines_per_node[location.x][location.y].append(vine)
# for testing
#func _ready() -> void:
#