grass cleanup, camera cleanup, animation test

This commit is contained in:
Daniel Kauss Serna 2026-02-08 14:03:18 +01:00
parent 561135b63a
commit b12c193636
13 changed files with 35136 additions and 44782 deletions

View file

@ -2,20 +2,31 @@
class_name GrassMultiMesh extends MultiMeshInstance2D
@export_group("Visuals")
@export var texture_sheet : Texture2D
@export var texture_sheet : Texture2D:
set(value):
texture_sheet = value
texture = value
@export var blade_size : Vector2 = Vector2(16, 16)
@export var texture_sheet_size : Vector2 = Vector2(64, 16)
@export var grass_tint : Color
@export var grass_tint : Color = Color.WHITE:
set(value):
grass_tint = value
material.set_shader_parameter("modulate", grass_tint)
@export_group("Editor")
@export var editing : bool = false
@export var editing : bool = false:
set(value):
editing = value
notify_property_list_changed()
@export var brush_radius : float = 50.0
@export var brush_density : int = 5
@export var grass_data : Array[Dictionary] = []
@export_storage var grass_data : Array[Dictionary] = []
enum GrassState {NORMAL = 0, CUT = 1}
var _blade_half_height: float
func _ready() -> void:
if not multimesh:
multimesh = MultiMesh.new()
@ -25,56 +36,63 @@ func _ready() -> void:
multimesh.mesh = _create_mesh_rect()
if not material:
material = ShaderMaterial.new()
if ResourceLoader.exists("res://shaders/grass_shader.gdshader"):
material.shader = load("res://shaders/grass_shader.gdshader")
if material is ShaderMaterial:
material.set_shader_parameter("frame_size", blade_size)
material.set_shader_parameter("sheet_size", texture_sheet_size)
material.set_shader_parameter("modulate", grass_tint)
if material.get_shader_parameter("noise_tex") == null:
var noise = FastNoiseLite.new()
noise.frequency = 0.005
var noise_tex = NoiseTexture2D.new()
noise_tex.noise = noise
noise_tex.seamless = true
material.set_shader_parameter("noise_tex", noise_tex)
if texture_sheet:
texture = texture_sheet
_blade_half_height = blade_size.y / 2.0
_rebuild_multimesh()
func _process(_delta):
# move_grass_away(%Player.global_position, 25)
if editing and Engine.is_editor_hint():
queue_redraw()
if Input.is_key_pressed(KEY_K):
_brush_paint()
if Input.is_key_pressed(KEY_E):
_brush_erase()
queue_redraw()
elif Input.is_key_pressed(KEY_E):
_brush_erase()
func cut_grass_at(pos: Vector2, radius: float):
_interact_with_grass(pos, radius, GrassState.CUT)
func move_grass_away(pos: Vector2, radius: float):
var half = radius / 2
var local_target = to_local(pos)
for i in range(multimesh.instance_count):
var t = multimesh.get_instance_transform_2d(i)
var dist = t.origin.distance_squared_to(local_target)
if dist < half * half:
var custom_data = multimesh.get_instance_custom_data(i)
custom_data.r = (t.origin.x - local_target.x) * 2
multimesh.set_instance_custom_data(i, custom_data)
elif dist < radius * radius:
var custom_data = multimesh.get_instance_custom_data(i)
custom_data.r = grass_data[i]["rot"]
multimesh.set_instance_custom_data(i, custom_data)
func interact_with_grass(global_target_pos: Vector2, rect_size: Vector2, new_state: int):
if global_position.distance_squared_to(global_target_pos) > 500 * 500:
return
func _interact_with_grass(global_target_pos: Vector2, radius: float, new_state: int):
var local_target = to_local(global_target_pos)
var half_w = rect_size.x * 0.5
var half_h = rect_size.y * 0.5
var left = local_target.x - half_w
var right = local_target.x + half_w
var top = local_target.y - half_h
var bottom = local_target.y + half_h
for i in range(multimesh.instance_count):
var t = multimesh.get_instance_transform_2d(i)
var t_origin = multimesh.get_instance_transform_2d(i).origin
if t.origin.distance_squared_to(local_target) < radius * radius:
var custom_data = multimesh.get_instance_custom_data(i)
if t_origin.x > left and t_origin.x < right and t_origin.y > top and t_origin.y < bottom:
var custom_data = multimesh.get_instance_custom_data(i)
if int(custom_data.b) == new_state:
continue
custom_data.b = float(new_state)
multimesh.set_instance_custom_data(i, custom_data)
grass_data[i]["state"] = new_state
@ -85,14 +103,13 @@ func _create_mesh_rect() -> QuadMesh:
func _rebuild_multimesh() -> void:
multimesh.instance_count = grass_data.size()
var pivot_offset = Vector2(0, -blade_size.y / 2.0)
var pivot_offset = Vector2(0, -_blade_half_height)
for i in range(grass_data.size()):
var d = grass_data[i]
var xform = Transform2D().translated(d.pos).rotated(d.get("rot", 0.0)).translated(pivot_offset)
multimesh.set_instance_transform_2d(i, xform)
multimesh.set_instance_color(i, grass_tint)
var custom = Color(
randf(),
@ -103,10 +120,11 @@ func _rebuild_multimesh() -> void:
multimesh.set_instance_custom_data(i, custom)
func _brush_paint() -> void:
var s = Time.get_ticks_msec()
print(s)
seed(s)
var mouse_pos = get_local_mouse_position()
var columns = int(texture_sheet_size.x / blade_size.x)
if columns == 0: columns = 1
for i in range(brush_density):
var angle = randf() * TAU
var rad = sqrt(randf()) * brush_radius
@ -114,9 +132,9 @@ func _brush_paint() -> void:
var new_blade = {
"pos": pos,
"tex_idx": int((randf() ** (1.0/3.0)) * 8),
"tex_idx": randi() % columns,
"state": GrassState.NORMAL,
"rot": randf_range(-0.1, 0.1)
"rot": 0# randf_range(-0.2, 0.2)
}
grass_data.append(new_blade)
@ -124,8 +142,10 @@ func _brush_paint() -> void:
func _brush_erase() -> void:
var mouse_pos = get_local_mouse_position()
var r_sq = brush_radius * brush_radius
for i in range(grass_data.size() - 1, -1, -1):
if grass_data[i].pos.distance_to(mouse_pos) < brush_radius:
if grass_data[i].pos.distance_squared_to(mouse_pos) < r_sq:
grass_data.remove_at(i)
_rebuild_multimesh()

View file

@ -7,47 +7,39 @@ func _ready():
rect.modulate.a = 0
log_display.text = ""
log_display.visible = false
# Ensure the rect doesn't block clicks while invisible
rect.mouse_filter = Control.MOUSE_FILTER_IGNORE
## Combined function to handle both success and error transitions
func change_scene(target_path: String, is_error: bool = false):
rect.mouse_filter = Control.MOUSE_FILTER_STOP
# Set color based on status
if is_error:
rect.color = Color.CRIMSON
else:
rect.color = Color.from_string("#364388", Color.BLUE)
# Fade In
var tween_in = create_tween()
await tween_in.tween_property(rect, "modulate:a", 1.0, 0.3).finished
# Display Log
log_display.visible = true
var log_file = "res://hex.txt" if is_error else "res://log.txt"
await display_log_file(log_file)
display_log_file(log_file)
await get_tree().create_timer(0.5).timeout
# Change Scene
var error = get_tree().change_scene_to_file(target_path)
if error != OK:
push_error("Failed to load scene: " + target_path)
# Cleanup and Fade Out
log_display.visible = false
log_display.text = ""
var tween_out = create_tween()
await tween_out.tween_property(rect, "modulate:a", 0.0, 0.5).finished
tween_out.tween_property(rect, "modulate:a", 0.0, 0.5)
rect.mouse_filter = Control.MOUSE_FILTER_IGNORE
func display_log_file(file_path: String):
if not FileAccess.file_exists(file_path):
log_display.add_text("ERROR: " + file_path + " not found in build.\n")
# Check Project -> Export -> Resources -> Filters to include non-resource files
return
var file = FileAccess.open(file_path, FileAccess.READ)
@ -55,13 +47,11 @@ func display_log_file(file_path: String):
log_display.add_text("ERROR: Could not open file.\n")
return
# Reading line by line for that "hacker" effect
while not file.eof_reached():
var line = file.get_line()
if line.strip_edges() == "": continue # Skip empty lines if desired
if line.strip_edges() == "": continue
log_display.add_text(line + "\n")
# Auto-scroll to bottom
log_display.scroll_to_line(log_display.get_line_count())
var delay = 0#randf_range(0.0001, 0.00015)