This commit is contained in:
Gerard Gascón 2025-04-24 17:23:34 +02:00
commit b99855351d
434 changed files with 50357 additions and 0 deletions

65
scripts/audio_manager.gd Normal file
View file

@ -0,0 +1,65 @@
extends Node
@export var audio_player: AudioStreamPlayer
@export var audio1: AudioStream
@export var audio2l: AudioStream
@export var audio3: AudioStream
@export var audio4l: AudioStream
@export var audio5: AudioStream
@export var audio6: AudioStream
@export var audio7l: AudioStream
@export var audio8: AudioStream
var characters_talked_to: int = 0
var ending: bool = false
# Called when the node enters the scene tree for the first time.
func _ready():
_play(audio1)
DialogueManager.dialogue_finished.connect(_quit_dialogue)
func _play(audio_source: AudioStream):
audio_player.stream = audio_source
audio_player.play()
func _on_audio_stream_player_finished():
match audio_player.stream:
audio1:
if characters_talked_to > 0:
_play(audio3)
else:
_play(audio2l)
audio2l:
if characters_talked_to > 0:
_play(audio3)
else:
_play(audio2l)
audio3:
if characters_talked_to > 1:
_play(audio3)
else:
_play(audio4l)
audio4l:
if characters_talked_to > 3:
_play(audio5)
else:
_play(audio4l)
audio5:
_play(audio6)
audio6:
_play(audio7l)
audio7l:
if ending:
_play(audio8)
else:
_play(audio7l)
func _quit_dialogue(_time_to_add: int):
characters_talked_to = (
int(DialogueManager.global_variables["Character1"]) +
int(DialogueManager.global_variables["Character2"]) +
int(DialogueManager.global_variables["Character3"]) +
int(DialogueManager.global_variables["Character4"])
)

20
scripts/counter.gd Normal file
View file

@ -0,0 +1,20 @@
extends Control
const MAX_TIME = 99
@export var start_time = 50
@export var decrease_speed = 0.5
# Called when the node enters the scene tree for the first time.
func _ready():
current_time = start_time;
$Label.text = str(int(current_time))
var current_time: float
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
current_time -= decrease_speed * delta
$Label.text = str(int(current_time))
func get_percentage() -> float:
return inverse_lerp(MAX_TIME, 0, int(current_time))

View file

@ -0,0 +1,133 @@
extends Control
var inkPlayer = load("res://addons/inkgd/ink_player.gd")
@onready var _ink_player = InkPlayer.new()
@onready var text_box = $ColorRect/Label
@export var buttons: Array[Button]
@export var type_sounds: Array[AudioStream]
var displaying_choices = false
var active = false
@onready var timer = $TextDisplayTimer
var text = ""
var character_index = 0
var character_time = 0.03
var finished_displaying = true
var variables: Dictionary
# Called when the node enters the scene tree for the first time.
func _ready():
_ink_player._add_runtime(get_tree().root)
_ink_player.loads_in_background = true
_ink_player.connect("loaded", _story_loaded)
for button in buttons:
button.hide()
func _setup_story(story: Resource):
_ink_player.ink_file = story
_ink_player.create_story()
active = true
func _process(delta):
if displaying_choices:
var i = 0
for button in buttons:
if button.button_pressed:
_select_choice(i)
i += 1
else:
if Input.is_action_just_pressed("advance_dialog"):
_continue_story()
func _story_loaded(successfully: bool):
if !successfully:
return
for key in variables:
var value = variables[key]
_ink_player.set_variable(key, value)
_continue_story()
func _continue_story():
if !finished_displaying:
return
if !_ink_player.can_continue and !_ink_player.has_choices:
text_box.text = ""
#TODO: Hardcodear las variables aquí
variables["time_to_add"] = _ink_player.get_variable("time_to_add")
variables["Foto1"] = _ink_player.get_variable("Foto1")
variables["Foto2"] = _ink_player.get_variable("Foto2")
variables["Foto3"] = _ink_player.get_variable("Foto3")
variables["Foto4"] = _ink_player.get_variable("Foto4")
variables["Foto5"] = _ink_player.get_variable("Foto5")
variables["Character1"] = _ink_player.get_variable("Character1")
variables["Character2"] = _ink_player.get_variable("Character2")
variables["Character3"] = _ink_player.get_variable("Character3")
variables["Character4"] = _ink_player.get_variable("Character4")
variables["Character5"] = _ink_player.get_variable("Character5")
#DIALOGUE END
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
active = false
return
if _ink_player.has_choices:
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
var i = 0
for choice in _ink_player.current_choices:
buttons[i].show()
buttons[i].text = choice.text
i += 1
for j in range(4 - i):
buttons[j + i].hide()
displaying_choices = true
return
text_box.text = ""
display_text(_ink_player.continue_story())
func _select_choice(index):
for button in buttons:
button.hide()
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
_ink_player.choose_choice_index(index)
_continue_story()
################## ANIMATIONS ###################
func display_text(text_to_display: String):
text = text_to_display
character_index = 0
finished_displaying = false
_display_letter()
func _display_letter():
if character_index >= text.length():
finished_displaying = true
return
text_box.text += text[character_index]
character_index += 1
$AudioStreamPlayer.stream = type_sounds.pick_random()
$AudioStreamPlayer.play()
if character_index >= text.length():
finished_displaying = true
return
timer.start(character_time)
func _on_text_display_timer_timeout():
_display_letter()

9
scripts/main_menu.gd Normal file
View file

@ -0,0 +1,9 @@
extends Control
@export var player: CharacterBody3D
@export var eyes: Node2D
func _on_button_button_down():
player.enable()
eyes.enable()
queue_free()

23
scripts/npc/npc.gd Normal file
View file

@ -0,0 +1,23 @@
extends CharacterBody3D
@export var text: Resource
var moving: bool
func animate_move():
$Model/AnimationPlayer.play("walk")
func animate_idle():
$Model/AnimationPlayer.play("idle")
# Dummy function to avoid having to distinct npc from pickup
func pointing(point: bool): pass
func destroy(): pass
func get_text() -> Resource:
if moving:
return null
else:
return text
func is_photo() -> bool:
return false

View file

@ -0,0 +1,29 @@
extends Path3D
var can_move: bool
@export var maxSpeed = 0.01
var pos = 0.0;
@onready var character_body_3d = $PathFollow3D/CharacterBody3D
@export var character_id: String
func _ready():
DialogueManager.dialogue_finished.connect(_quit_dialogue)
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
var previous_pos = pos;
character_body_3d.moving = can_move
if can_move and pos < 1:
pos += maxSpeed * delta
if previous_pos != pos:
character_body_3d.animate_move()
else:
character_body_3d.animate_idle()
$PathFollow3D.progress_ratio = clamp(pos, 0, 1)
func _quit_dialogue(_time_to_add: int):
can_move = !character_id.is_empty() && DialogueManager.global_variables[character_id];

21
scripts/npc/photo.gd Normal file
View file

@ -0,0 +1,21 @@
extends Control
@export var photos: Array[Texture2D]
@export var photo_sound: Array[AudioStream]
@export var destroy_sound: AudioStream
var is_visible = false
func show_picture(index: int):
is_visible = true
$FotoTest.texture = photos[index]
$AnimationPlayer.play("appear")
$AudioStreamPlayer.stream = photo_sound.pick_random()
$AudioStreamPlayer.play()
func hide_picture():
if !is_visible: return
$AnimationPlayer.play("disappear")
is_visible = false
$AudioStreamPlayer.stream = destroy_sound
$AudioStreamPlayer.play()

27
scripts/npc/pickup.gd Normal file
View file

@ -0,0 +1,27 @@
extends CharacterBody3D
@export var text: Resource
@export var photo_index: int = 0
@export var animation_payer: AnimationPlayer
func _ready():
animation_payer.play("photo_hover")
func pointing(point: bool):
if point:
animation_payer.play("photo_point", 1)
else:
animation_payer.play("photo_hover", 1)
func get_text() -> Resource:
return text;
func destroy():
queue_free()
func is_photo():
return true
func get_photo_index():
return photo_index

93
scripts/player/eyes.gd Normal file
View file

@ -0,0 +1,93 @@
extends Node2D
const UP_MAX_POS = -200
const UP_MIN_POS = 100
const DOWN_MAX_POS = 900
const DOWN_MIN_POS = 600
const MAX_BLINK_VARIATION = 50
@export var eyeUp: Sprite2D
var eyeUpPos = UP_MAX_POS
@export var eyeDown: Sprite2D
var eyeDownPos = DOWN_MIN_POS
var progress: float
@export var counter_text: Label
const MAX_TIME = 99
@export var start_time = 50
@export var decrease_speed = 0.5
var current_time: float
var timer_going = true
var enabled = false
@onready var cutscene = load("res://scenes/ending.tscn")
func _get_percentage() -> float:
return inverse_lerp(MAX_TIME, 0, int(current_time))
func _quit_dialogue(time_to_add: int):
current_time += (time_to_add * 2)
current_time = clamp(current_time, 0, MAX_TIME)
# Called when the node enters the scene tree for the first time.
func _ready():
DialogueManager.dialogue_finished.connect(_quit_dialogue)
current_time = start_time;
counter_text.text = ""
progress = _get_percentage()
eyeUpPos = lerp(UP_MAX_POS, UP_MIN_POS, progress)
eyeDownPos = lerp(DOWN_MAX_POS, DOWN_MIN_POS, progress)
func enable():
counter_text.text = str(int(current_time))
enabled = true
var time = 0.0
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
time += delta
var pos_offset = _sine_operation(time)
_eye_pos_up(pos_offset)
_eye_pos_down(pos_offset)
if !enabled:
return
current_time -= decrease_speed * delta * int(timer_going)
counter_text.text = str(int(current_time))
progress = _get_percentage()
eyeUpPos = lerp(UP_MAX_POS, UP_MIN_POS, progress)
eyeDownPos = lerp(DOWN_MAX_POS, DOWN_MIN_POS, progress)
if current_time <= 0.5:
SceneTransition.change_scene(cutscene)
#t: time g: gap o: offset
func _sine_function(t: float, g: float, o: float) -> float:
return sin(t*g+o)
func _sine_operation(t: float) -> float:
return .25 * (
_sine_function(t, 1, 4) +
_sine_function(t, 2, 3) +
_sine_function(t, 3, 2) +
_sine_function(t, 4, 1)
)
func _eye_pos_up(t: float):
eyeUp.position.y = int(lerp(eyeUpPos - MAX_BLINK_VARIATION/2, eyeUpPos + MAX_BLINK_VARIATION/2, t) / 4.0) * 4
func _eye_pos_down(t: float):
eyeDown.position.y = int(lerp(eyeDownPos + MAX_BLINK_VARIATION/2, eyeDownPos - MAX_BLINK_VARIATION/2, t) / 4.0) * 4

84
scripts/player/player.gd Normal file
View file

@ -0,0 +1,84 @@
extends CharacterBody3D
@export var SPEED = 5.0
const MOUSE_SENSITIVITY_X = 0.3
const MOUSE_SENSITIVITY_Y = 0.15
const MAX_ROTATION = 85.0
var rotation_x = 0.0
var in_dialogue = false
@export var raycast: RayCast3D
@export var eyes: Node2D
@export var photo_preview: Control
# Get the gravity from the project settings to be synced with RigidBody nodes.
var gravity = ProjectSettings.get_setting("physics/3d/default_gravity")
var enabled = false
func enable():
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
enabled = true
func _input(event):
if !enabled || DialogueManager.is_dialog_active:
return
if in_dialogue:
photo_preview.hide_picture()
eyes.timer_going = true
in_dialogue = false
if event is InputEventMouseMotion:
rotation_degrees.y -= MOUSE_SENSITIVITY_X * event.relative.x
rotation_x -= MOUSE_SENSITIVITY_Y * event.relative.y
rotation_x = clamp(rotation_x, -MAX_ROTATION, MAX_ROTATION)
$Camera3D.rotation_degrees.x = rotation_x
var npc: CharacterBody3D
func _process(delta):
if !enabled:
return
if raycast.is_colliding():
npc = raycast.get_collider()
npc.pointing(true)
if Input.is_action_just_pressed("interact"):
DialogueManager.start_dialog(raycast.get_collider().get_text())
if raycast.get_collider().is_photo():
photo_preview.show_picture(raycast.get_collider().get_photo_index())
raycast.get_collider().destroy()
in_dialogue = true
eyes.timer_going = false
elif npc != null:
npc.pointing(false)
npc = null
func _physics_process(delta):
if !enabled:
return
# Add the gravity.
if not is_on_floor():
velocity.y -= gravity * delta
# Get the input direction and handle the movement/deceleration.
# As good practice, you should replace UI actions with custom gameplay actions.
var input_dir = Input.get_vector("move_left", "move_right", "move_forwards", "move_backwards")
var direction = (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
if direction:
velocity.x = direction.x * SPEED
velocity.z = direction.z * SPEED
else:
velocity.x = move_toward(velocity.x, 0, SPEED)
velocity.z = move_toward(velocity.z, 0, SPEED)
if DialogueManager.is_dialog_active:
velocity = Vector3(0, velocity.y, 0)
move_and_slide()

BIN
scripts/shaders/Perlin_noise_example.png (Stored with Git LFS) Normal file

Binary file not shown.

View file

@ -0,0 +1,35 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cfux36pq3rp1p"
path.s3tc="res://.godot/imported/Perlin_noise_example.png-1d87d1cb590d7ea007c419d3f3ff388b.s3tc.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}
[deps]
source_file="res://scripts/shaders/Perlin_noise_example.png"
dest_files=["res://.godot/imported/Perlin_noise_example.png-1d87d1cb590d7ea007c419d3f3ff388b.s3tc.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

View file

@ -0,0 +1,46 @@
shader_type spatial;
render_mode unshaded;
uniform sampler2D dissolve_texture : hint_default_transparent;
uniform float threshold : hint_range(0,1);
uniform vec3 color : source_color;
uniform float alpha_add = 1;
uniform float speed : hint_range(0, 0.01) = 0.005;
float fresnel(vec3 Normal, vec3 ViewDir, float Power){
return pow((1.0 - clamp(dot(normalize(Normal), normalize(ViewDir)), 0, 1)), Power);
}
float _fresnel(vec2 uv, float Power){
return pow((clamp(sin(PI * uv.x) * sin(PI * uv.y), 0, 1)), Power);
}
vec2 loop(vec2 vec){
vec2 res = vec;
vec2 floors = floor(res);
res.x -= floor(res.x);
res.y -= floor(res.y);
if(int(floors.x) % 2 == 0){
res.x = 1.0 - res.x;
}
if(int(floors.y) % 2 == 0){
res.y = 1.0 - res.y;
}
return res;
}
void fragment(){
vec4 noise_texture = texture(dissolve_texture, loop(UV + vec2(TIME, TIME) * speed));
noise_texture *= texture(dissolve_texture, loop(UV + vec2(-TIME, -TIME) * speed)) / 0.5;
noise_texture *= texture(dissolve_texture, loop(UV + vec2(TIME, -TIME) * speed)) / 0.5;
noise_texture *= texture(dissolve_texture, loop(UV + vec2(-TIME, TIME) * speed)) / 0.5;
noise_texture *= texture(dissolve_texture, loop(UV * 2.0 + vec2(-TIME, TIME) * speed * 0.3)) / 0.5;
noise_texture *= texture(dissolve_texture, loop(UV * 2.0 + vec2(TIME, -TIME) * speed * 0.5)) / 0.5;
noise_texture *= texture(dissolve_texture, loop(UV * 2.0 + vec2(TIME, TIME) * speed * 0.8)) / 0.5;
noise_texture *= texture(dissolve_texture, loop(UV * 2.0 + vec2(-TIME, -TIME) * speed)) / 0.5;
ALPHA = clamp(noise_texture.x - threshold, 0, 1);
ALPHA *= _fresnel(UV, 3) * alpha_add;
ALPHA = clamp(ALPHA, 0, 1);
ALBEDO = color;
}

View file

@ -0,0 +1,231 @@
/*
Shader from Godot Shaders - the free shader library.
godotshaders.com/shader/VHS-and-CRT-monitor-effect
This shader is under CC0 license. Feel free to use, improve and
change this shader according to your needs and consider sharing
the modified result to godotshaders.com.
*/
shader_type canvas_item;
//*** IMPORTANT! ***/
// - If you are using this shader to affect the node it is applied to set 'overlay' to false (unchecked in the instepctor).
// - If you are using this shader as an overlay, and want the shader to affect the nodes below in the Scene hierarchy,
// set 'overlay' to true (checked in the inspector).
// On Mac there is potentially a bug causing this to not work properly. If that is the case and you want to use the shader as an overlay
// change all "overlay ? SCREEN_TEXTURE : TEXTURE" to only "SCREEN_TEXTURE" on lines 129-140, and "vec2 uv = overlay ? warp(SCREEN_UV) : warp(UV);"
// to "vec2 uv = warp(SCREEN_UV);" on line 98.
uniform bool overlay = false;
uniform float scanlines_opacity : hint_range(0.0, 1.0) = 0.4;
uniform float scanlines_width : hint_range(0.0, 0.5) = 0.25;
uniform float grille_opacity : hint_range(0.0, 1.0) = 0.3;
uniform vec2 resolution = vec2(640.0, 480.0); // Set the number of rows and columns the texture will be divided in. Scanlines and grille will make a square based on these values
uniform bool pixelate = true; // Fill each square ("pixel") with a sampled color, creating a pixel look and a more accurate representation of how a CRT monitor would work.
uniform bool roll = true;
uniform float roll_speed = 8.0; // Positive values are down, negative are up
uniform float roll_size : hint_range(0.0, 100.0) = 15.0;
uniform float roll_variation : hint_range(0.1, 5.0) = 1.8; // This valie is not an exact science. You have to play around with the value to find a look you like. How this works is explained in the code below.
uniform float distort_intensity : hint_range(0.0, 0.2) = 0.05; // The distortion created by the rolling effect.
uniform float noise_opacity : hint_range(0.0, 1.0) = 0.4;
uniform float noise_speed = 5.0; // There is a movement in the noise pattern that can be hard to see first. This sets the speed of that movement.
uniform float static_noise_intensity : hint_range(0.0, 1.0) = 0.06;
uniform float aberration : hint_range(-1.0, 1.0) = 0.03; // Chromatic aberration, a distortion on each color channel.
uniform float brightness = 1.4; // When adding scanline gaps and grille the image can get very dark. Brightness tries to compensate for that.
uniform bool discolor = true; // Add a discolor effect simulating a VHS
uniform float warp_amount :hint_range(0.0, 5.0) = 1.0; // Warp the texture edges simulating the curved glass of a CRT monitor or old TV.
uniform bool clip_warp = false;
uniform float vignette_intensity = 0.4; // Size of the vignette, how far towards the middle it should go.
uniform float vignette_opacity : hint_range(0.0, 1.0) = 0.5;
uniform sampler2D SCREEN_TEXTURE : hint_screen_texture, filter_linear_mipmap;
// Used by the noise functin to generate a pseudo random value between 0.0 and 1.0
vec2 random(vec2 uv){
uv = vec2( dot(uv, vec2(127.1,311.7) ),
dot(uv, vec2(269.5,183.3) ) );
return -1.0 + 2.0 * fract(sin(uv) * 43758.5453123);
}
// Generate a Perlin noise used by the distortion effects
float noise(vec2 uv) {
vec2 uv_index = floor(uv);
vec2 uv_fract = fract(uv);
vec2 blur = smoothstep(0.0, 1.0, uv_fract);
return mix( mix( dot( random(uv_index + vec2(0.0,0.0) ), uv_fract - vec2(0.0,0.0) ),
dot( random(uv_index + vec2(1.0,0.0) ), uv_fract - vec2(1.0,0.0) ), blur.x),
mix( dot( random(uv_index + vec2(0.0,1.0) ), uv_fract - vec2(0.0,1.0) ),
dot( random(uv_index + vec2(1.0,1.0) ), uv_fract - vec2(1.0,1.0) ), blur.x), blur.y) * 0.5 + 0.5;
}
// Takes in the UV and warps the edges, creating the spherized effect
vec2 warp(vec2 uv){
vec2 delta = uv - 0.5;
float delta2 = dot(delta.xy, delta.xy);
float delta4 = delta2 * delta2;
float delta_offset = delta4 * warp_amount;
return uv + delta * delta_offset;
}
// Adds a black border to hide stretched pixel created by the warp effect
float border (vec2 uv){
float radius = min(warp_amount, 0.08);
radius = max(min(min(abs(radius * 2.0), abs(1.0)), abs(1.0)), 1e-5);
vec2 abs_uv = abs(uv * 2.0 - 1.0) - vec2(1.0, 1.0) + radius;
float dist = length(max(vec2(0.0), abs_uv)) / radius;
float square = smoothstep(0.96, 1.0, dist);
return clamp(1.0 - square, 0.0, 1.0);
}
// Adds a vignette shadow to the edges of the image
float vignette(vec2 uv){
uv *= 1.0 - uv.xy;
float vignette = uv.x * uv.y * 15.0;
return pow(vignette, vignette_intensity * vignette_opacity);
}
void fragment()
{
vec2 uv = overlay ? warp(SCREEN_UV) : warp(UV); // Warp the uv. uv will be used in most cases instead of UV to keep the warping
vec2 text_uv = uv;
vec2 roll_uv = vec2(0.0);
float time = roll ? TIME : 0.0;
// Pixelate the texture based on the given resolution.
if (pixelate)
{
text_uv = ceil(uv * resolution) / resolution;
}
// Create the rolling effect. We need roll_line a bit later to make the noise effect.
// That is why this runs if roll is true OR noise_opacity is over 0.
float roll_line = 0.0;
if (roll || noise_opacity > 0.0)
{
// Create the areas/lines where the texture will be distorted.
roll_line = smoothstep(0.3, 0.9, sin(uv.y * roll_size - (time * roll_speed) ) );
// Create more lines of a different size and apply to the first set of lines. This creates a bit of variation.
roll_line *= roll_line * smoothstep(0.3, 0.9, sin(uv.y * roll_size * roll_variation - (time * roll_speed * roll_variation) ) );
// Distort the UV where where the lines are
roll_uv = vec2(( roll_line * distort_intensity * (1.-UV.x)), 0.0);
}
vec4 text;
if (roll)
{
// If roll is true distort the texture with roll_uv. The texture is split up into RGB to
// make some chromatic aberration. We apply the aberration to the red and green channels accorging to the aberration parameter
// and intensify it a bit in the roll distortion.
text.r = texture(SCREEN_TEXTURE, text_uv + roll_uv * 0.8 + vec2(aberration, 0.0) * .1).r;
text.g = texture(SCREEN_TEXTURE, text_uv + roll_uv * 1.2 - vec2(aberration, 0.0) * .1 ).g;
text.b = texture(SCREEN_TEXTURE, text_uv + roll_uv).b;
text.a = 1.0;
}
else
{
// If roll is false only apply the aberration without any distorion. The aberration values are very small so the .1 is only
// to make the slider in the Inspector less sensitive.
text.r = texture(SCREEN_TEXTURE, text_uv + vec2(aberration, 0.0) * .1).r;
text.g = texture(SCREEN_TEXTURE, text_uv - vec2(aberration, 0.0) * .1).g;
text.b = texture(SCREEN_TEXTURE, text_uv).b;
text.a = 1.0;
}
float r = text.r;
float g = text.g;
float b = text.b;
uv = warp(UV);
// CRT monitors don't have pixels but groups of red, green and blue dots or lines, called grille. We isolate the texture's color channels
// and divide it up in 3 offsetted lines to show the red, green and blue colors next to each other, with a small black gap between.
if (grille_opacity > 0.0){
float g_r = smoothstep(0.85, 0.95, abs(sin(uv.x * (resolution.x * 3.14159265))));
r = mix(r, r * g_r, grille_opacity);
float g_g = smoothstep(0.85, 0.95, abs(sin(1.05 + uv.x * (resolution.x * 3.14159265))));
g = mix(g, g * g_g, grille_opacity);
float b_b = smoothstep(0.85, 0.95, abs(sin(2.1 + uv.x * (resolution.x * 3.14159265))));
b = mix(b, b * b_b, grille_opacity);
}
// Apply the grille to the texture's color channels and apply Brightness. Since the grille and the scanlines (below) make the image very dark you
// can compensate by increasing the brightness.
text.r = clamp(r * brightness, 0.0, 1.0);
text.g = clamp(g * brightness, 0.0, 1.0);
text.b = clamp(b * brightness, 0.0, 1.0);
// Scanlines are the horizontal lines that make up the image on a CRT monitor.
// Here we are actual setting the black gap between each line, which I guess is not the right definition of the word, but you get the idea
float scanlines = 0.5;
if (scanlines_opacity > 0.0)
{
// Same technique as above, create lines with sine and applying it to the texture. Smoothstep to allow setting the line size.
scanlines = smoothstep(scanlines_width, scanlines_width + 0.5, abs(sin(uv.y * (resolution.y * 3.14159265))));
text.rgb = mix(text.rgb, text.rgb * vec3(scanlines), scanlines_opacity);
}
// Apply the banded noise.
if (noise_opacity > 0.0)
{
// Generate a noise pattern that is very stretched horizontally, and animate it with noise_speed
float noise = smoothstep(0.4, 0.5, noise(uv * vec2(2.0, 200.0) + vec2(10.0, (TIME * (noise_speed))) ) );
// We use roll_line (set above) to define how big the noise should be vertically (multiplying cuts off all black parts).
// We also add in some basic noise with random() to break up the noise pattern above. The noise is sized according to
// the resolution value set in the inspector. If you don't like this look you can
// change "ceil(uv * resolution) / resolution" to only "uv" to make it less pixelated. Or multiply resolution with som value
// greater than 1.0 to make them smaller.
roll_line *= noise * scanlines * clamp(random((ceil(uv * resolution) / resolution) + vec2(TIME * 0.8, 0.0)).x + 0.8, 0.0, 1.0);
// Add it to the texture based on noise_opacity
text.rgb = clamp(mix(text.rgb, text.rgb + roll_line, noise_opacity), vec3(0.0), vec3(1.0));
}
// Apply static noise by generating it over the whole screen in the same way as above
if (static_noise_intensity > 0.0)
{
text.rgb += clamp(random((ceil(uv * resolution) / resolution) + fract(TIME)).x, 0.0, 1.0) * static_noise_intensity;
}
// Apply a black border to hide imperfections caused by the warping.
// Also apply the vignette
text.rgb *= border(uv);
text.rgb *= vignette(uv);
// Hides the black border and make that area transparent. Good if you want to add the the texture on top an image of a TV or monitor.
if (clip_warp)
{
text.a = border(uv);
}
// Apply discoloration to get a VHS look (lower saturation and higher contrast)
// You can play with the values below or expose them in the Inspector.
float saturation = 0.5;
float contrast = 1.2;
if (discolor)
{
// Saturation
vec3 greyscale = vec3(text.r + text.g + text.b) / 3.;
text.rgb = mix(text.rgb, greyscale, saturation);
// Contrast
float midpoint = pow(0.5, 2.2);
text.rgb = (text.rgb - vec3(midpoint)) * contrast + midpoint;
}
COLOR = text;
}

View file

@ -0,0 +1,11 @@
shader_type canvas_item;
uniform sampler2D dissolve_texture : hint_default_transparent;
uniform float dissolve_value : hint_range(0,1);
void fragment(){
vec4 main_texture = texture(TEXTURE, UV);
vec4 noise_texture = texture(dissolve_texture, UV);
main_texture.a *= floor(dissolve_value + min(1, noise_texture.x));
COLOR = main_texture;
}

View file

@ -0,0 +1,45 @@
shader_type canvas_item;
uniform int num_colors: hint_range(2,32) = 8;
uniform sampler2D PALETTE_TEXTURE: hint_default_black;
uniform sampler2D PALETTE_TEXTURE_LOW: hint_default_black;
uniform sampler2D SCREEN_TEXTURE : hint_screen_texture, filter_linear_mipmap;
uniform int u_dither_size = 4;
vec3 palette_limiter (in vec3 albedo, in vec2 uv){
float estimation_cutoff = 0.001;
vec3 closest_color;
float min_dist = 2.0;
float n = float(num_colors);
bool palette_low = (int(uv.y)) % 2 == 0;
for (int i=0; i<num_colors; i++ ){
float index = 1.000/(2.000*n)+float(i)/n;
vec3 index_color = texture(PALETTE_TEXTURE, vec2(index,0.5)).rgb;
float dist = length(index_color - albedo);
if (dist < min_dist) {
min_dist = dist;
if(palette_low){
closest_color = texture(PALETTE_TEXTURE_LOW, vec2(index,0.5)).rgb;
}else{
closest_color = index_color;
}
if (min_dist < estimation_cutoff){
return closest_color;
}
}
}
return closest_color;
}
void fragment() {
vec2 screen_size = vec2(textureSize(SCREEN_TEXTURE, 0)) / float(u_dither_size);
vec2 uv_cord = floor(UV * screen_size);
vec2 screen_sample_uv = uv_cord / screen_size;
vec3 current_color = texture(SCREEN_TEXTURE, screen_sample_uv).rgb;
COLOR.rgb = palette_limiter(current_color, uv_cord);
}

View file

@ -0,0 +1,199 @@
// Wind Waker style water - NekotoArts
// Adapted from https://www.shadertoy.com/view/3tKBDz
// After which I added in some fractal Brownian motion
// as well as vertex displacement
shader_type spatial;
render_mode unshaded;
uniform vec4 WATER_COL : source_color = vec4(0.04, 0.38, 0.88, 1.0);
uniform vec4 WATER2_COL : source_color = vec4(0.04, 0.35, 0.78, 1.0);
uniform vec4 FOAM_COL : source_color = vec4(0.8125, 0.9609, 0.9648, 1.0);
uniform float distortion_speed = 2.0;
uniform vec2 tile = vec2(5.0, 5.0);
uniform float height = 2.0;
uniform vec2 wave_size = vec2(2.0, 2.0);
uniform float wave_speed = 1.5;
const float M_2PI = 6.283185307;
const float M_6PI = 18.84955592;
float random(vec2 uv) {
return fract(sin(dot(uv.xy,
vec2(12.9898,78.233))) *
43758.5453123);
}
float noise(vec2 uv) {
vec2 uv_index = floor(uv);
vec2 uv_fract = fract(uv);
// Four corners in 2D of a tile
float a = random(uv_index);
float b = random(uv_index + vec2(1.0, 0.0));
float c = random(uv_index + vec2(0.0, 1.0));
float d = random(uv_index + vec2(1.0, 1.0));
vec2 blur = smoothstep(0.0, 1.0, uv_fract);
return mix(a, b, blur.x) +
(c - a) * blur.y * (1.0 - blur.x) +
(d - b) * blur.x * blur.y;
}
float fbm(vec2 uv) {
int octaves = 6;
float amplitude = 0.5;
float frequency = 3.0;
float value = 0.0;
for(int i = 0; i < octaves; i++) {
value += amplitude * noise(frequency * uv);
amplitude *= 0.5;
frequency *= 2.0;
}
return value;
}
float circ(vec2 pos, vec2 c, float s)
{
c = abs(pos - c);
c = min(c, 1.0 - c);
return smoothstep(0.0, 0.002, sqrt(s) - sqrt(dot(c, c))) * -1.0;
}
// Foam pattern for the water constructed out of a series of circles
float waterlayer(vec2 uv)
{
uv = mod(uv, 1.0); // Clamp to [0..1]
float ret = 1.0;
ret += circ(uv, vec2(0.37378, 0.277169), 0.0268181);
ret += circ(uv, vec2(0.0317477, 0.540372), 0.0193742);
ret += circ(uv, vec2(0.430044, 0.882218), 0.0232337);
ret += circ(uv, vec2(0.641033, 0.695106), 0.0117864);
ret += circ(uv, vec2(0.0146398, 0.0791346), 0.0299458);
ret += circ(uv, vec2(0.43871, 0.394445), 0.0289087);
ret += circ(uv, vec2(0.909446, 0.878141), 0.028466);
ret += circ(uv, vec2(0.310149, 0.686637), 0.0128496);
ret += circ(uv, vec2(0.928617, 0.195986), 0.0152041);
ret += circ(uv, vec2(0.0438506, 0.868153), 0.0268601);
ret += circ(uv, vec2(0.308619, 0.194937), 0.00806102);
ret += circ(uv, vec2(0.349922, 0.449714), 0.00928667);
ret += circ(uv, vec2(0.0449556, 0.953415), 0.023126);
ret += circ(uv, vec2(0.117761, 0.503309), 0.0151272);
ret += circ(uv, vec2(0.563517, 0.244991), 0.0292322);
ret += circ(uv, vec2(0.566936, 0.954457), 0.00981141);
ret += circ(uv, vec2(0.0489944, 0.200931), 0.0178746);
ret += circ(uv, vec2(0.569297, 0.624893), 0.0132408);
ret += circ(uv, vec2(0.298347, 0.710972), 0.0114426);
ret += circ(uv, vec2(0.878141, 0.771279), 0.00322719);
ret += circ(uv, vec2(0.150995, 0.376221), 0.00216157);
ret += circ(uv, vec2(0.119673, 0.541984), 0.0124621);
ret += circ(uv, vec2(0.629598, 0.295629), 0.0198736);
ret += circ(uv, vec2(0.334357, 0.266278), 0.0187145);
ret += circ(uv, vec2(0.918044, 0.968163), 0.0182928);
ret += circ(uv, vec2(0.965445, 0.505026), 0.006348);
ret += circ(uv, vec2(0.514847, 0.865444), 0.00623523);
ret += circ(uv, vec2(0.710575, 0.0415131), 0.00322689);
ret += circ(uv, vec2(0.71403, 0.576945), 0.0215641);
ret += circ(uv, vec2(0.748873, 0.413325), 0.0110795);
ret += circ(uv, vec2(0.0623365, 0.896713), 0.0236203);
ret += circ(uv, vec2(0.980482, 0.473849), 0.00573439);
ret += circ(uv, vec2(0.647463, 0.654349), 0.0188713);
ret += circ(uv, vec2(0.651406, 0.981297), 0.00710875);
ret += circ(uv, vec2(0.428928, 0.382426), 0.0298806);
ret += circ(uv, vec2(0.811545, 0.62568), 0.00265539);
ret += circ(uv, vec2(0.400787, 0.74162), 0.00486609);
ret += circ(uv, vec2(0.331283, 0.418536), 0.00598028);
ret += circ(uv, vec2(0.894762, 0.0657997), 0.00760375);
ret += circ(uv, vec2(0.525104, 0.572233), 0.0141796);
ret += circ(uv, vec2(0.431526, 0.911372), 0.0213234);
ret += circ(uv, vec2(0.658212, 0.910553), 0.000741023);
ret += circ(uv, vec2(0.514523, 0.243263), 0.0270685);
ret += circ(uv, vec2(0.0249494, 0.252872), 0.00876653);
ret += circ(uv, vec2(0.502214, 0.47269), 0.0234534);
ret += circ(uv, vec2(0.693271, 0.431469), 0.0246533);
ret += circ(uv, vec2(0.415, 0.884418), 0.0271696);
ret += circ(uv, vec2(0.149073, 0.41204), 0.00497198);
ret += circ(uv, vec2(0.533816, 0.897634), 0.00650833);
ret += circ(uv, vec2(0.0409132, 0.83406), 0.0191398);
ret += circ(uv, vec2(0.638585, 0.646019), 0.0206129);
ret += circ(uv, vec2(0.660342, 0.966541), 0.0053511);
ret += circ(uv, vec2(0.513783, 0.142233), 0.00471653);
ret += circ(uv, vec2(0.124305, 0.644263), 0.00116724);
ret += circ(uv, vec2(0.99871, 0.583864), 0.0107329);
ret += circ(uv, vec2(0.894879, 0.233289), 0.00667092);
ret += circ(uv, vec2(0.246286, 0.682766), 0.00411623);
ret += circ(uv, vec2(0.0761895, 0.16327), 0.0145935);
ret += circ(uv, vec2(0.949386, 0.802936), 0.0100873);
ret += circ(uv, vec2(0.480122, 0.196554), 0.0110185);
ret += circ(uv, vec2(0.896854, 0.803707), 0.013969);
ret += circ(uv, vec2(0.292865, 0.762973), 0.00566413);
ret += circ(uv, vec2(0.0995585, 0.117457), 0.00869407);
ret += circ(uv, vec2(0.377713, 0.00335442), 0.0063147);
ret += circ(uv, vec2(0.506365, 0.531118), 0.0144016);
ret += circ(uv, vec2(0.408806, 0.894771), 0.0243923);
ret += circ(uv, vec2(0.143579, 0.85138), 0.00418529);
ret += circ(uv, vec2(0.0902811, 0.181775), 0.0108896);
ret += circ(uv, vec2(0.780695, 0.394644), 0.00475475);
ret += circ(uv, vec2(0.298036, 0.625531), 0.00325285);
ret += circ(uv, vec2(0.218423, 0.714537), 0.00157212);
ret += circ(uv, vec2(0.658836, 0.159556), 0.00225897);
ret += circ(uv, vec2(0.987324, 0.146545), 0.0288391);
ret += circ(uv, vec2(0.222646, 0.251694), 0.00092276);
ret += circ(uv, vec2(0.159826, 0.528063), 0.00605293);
return max(ret, 0.0);
}
// Procedural texture generation for the water
vec3 water(vec2 uv, vec3 cdir, float iTime)
{
uv *= vec2(0.25);
uv += fbm(uv) * 0.2;
// Parallax height distortion with two directional waves at
// slightly different angles.
vec2 a = 0.025 * cdir.xz / cdir.y; // Parallax offset
float h = sin(uv.x + iTime); // Height at UV
uv += a * h;
h = sin(0.841471 * uv.x - 0.540302 * uv.y + iTime);
uv += a * h;
// Texture distortion
float d1 = mod(uv.x + uv.y, M_2PI);
float d2 = mod((uv.x + uv.y + 0.25) * 1.3, M_6PI);
d1 = iTime * 0.07 + d1;
d2 = iTime * 0.5 + d2;
vec2 dist = vec2(
sin(d1) * 0.15 + sin(d2) * 0.05,
cos(d1) * 0.15 + cos(d2) * 0.05
);
vec3 ret = mix(WATER_COL.rgb, WATER2_COL.rgb, waterlayer(uv + dist.xy));
ret = mix(ret, FOAM_COL.rgb, waterlayer(vec2(1.0) - uv - dist.yx));
return ret;
}
void vertex(){
float time = TIME * wave_speed;
vec2 uv = UV * wave_size;
float d1 = mod(uv.x + uv.y, M_2PI);
float d2 = mod((uv.x + uv.y + 0.25) * 1.3, M_6PI);
d1 = time * 0.07 + d1;
d2 = time * 0.5 + d2;
vec2 dist = vec2(
sin(d1) * 0.15 + sin(d2) * 0.05,
cos(d1) * 0.15 + cos(d2) * 0.05
);
VERTEX.y += dist.y * height;
}
void fragment()
{
vec2 uv = UV;
ALBEDO = vec3(water(uv * tile, vec3(0,1,0), TIME * distortion_speed));
}

View file

@ -0,0 +1,56 @@
extends Node
var global_variables = {}
@onready var text_box_scene = preload("res://prefabs/ink_template.tscn")
var text_box: Control
var is_dialog_active = false
var can_enter_dialogue = true
@export var start_time = 50
@export var decrease_speed = 0.5
signal dialogue_finished(time_to_add: int)
func start_dialog(lines: Resource):
if lines == null:
return
if is_dialog_active or !can_enter_dialogue:
return
_show_text_box()
text_box.variables = global_variables
text_box._setup_story(lines)
is_dialog_active = true
can_enter_dialogue = false
func _show_text_box():
text_box = text_box_scene.instantiate()
get_tree().root.add_child(text_box)
const DIALOGUE_COOLDOWN = .5
var dialogue_cooldown = 0
func _process(delta):
if (
Input.is_action_just_pressed("advance_dialog") &&
is_dialog_active
):
text_box._continue_story()
if !text_box.active:
_quit_dialogue()
dialogue_cooldown -= delta
if !is_dialog_active and dialogue_cooldown < 0:
can_enter_dialogue = true
func _quit_dialogue():
is_dialog_active = false
global_variables = text_box.variables
dialogue_finished.emit(global_variables["time_to_add"])
global_variables["time_to_add"] = 0
dialogue_cooldown = DIALOGUE_COOLDOWN
text_box.queue_free()