TypePHP 编译器
https://swoole.com/aot/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
869 lines
32 KiB
869 lines
32 KiB
extends Node3D
|
|
|
|
const OCEAN_SIZE := 960.0
|
|
const ISLAND_GRID := 44
|
|
const CLOUD_LAYER_COUNT := 3
|
|
const WEATHER_SUNNY := 0
|
|
const WEATHER_CLOUDY := 1
|
|
const WEATHER_RAIN := 2
|
|
const CAMERA_MIN_PITCH := deg_to_rad(-10.0)
|
|
const CAMERA_MAX_PITCH := deg_to_rad(28.0)
|
|
|
|
var boat_position := Vector3.ZERO
|
|
var boat_yaw := 0.0
|
|
var boat_velocity := Vector3.ZERO
|
|
var camera_yaw := 0.0
|
|
var camera_pitch := deg_to_rad(4.0)
|
|
var day_time := 0.27
|
|
var weather := WEATHER_SUNNY
|
|
var target_weather := WEATHER_CLOUDY
|
|
var weather_mix := 0.0
|
|
var next_weather_change := 14.0
|
|
var rain_amount := 0.0
|
|
var mouse_sensitivity := 0.0022
|
|
var bridge: Object
|
|
|
|
var ocean_material: ShaderMaterial
|
|
var island_material: ShaderMaterial
|
|
var sky_material: ShaderMaterial
|
|
var cloud_materials: Array[ShaderMaterial] = []
|
|
var island_root: Node3D
|
|
var marker_root: Node3D
|
|
var boat: Node3D
|
|
var sail_material: StandardMaterial3D
|
|
var ocean_tiles: Array[MeshInstance3D] = []
|
|
var sky_dome: MeshInstance3D
|
|
|
|
@onready var camera_rig: Node3D = $CameraRig
|
|
@onready var camera: Camera3D = $CameraRig/Camera3D
|
|
@onready var sun: DirectionalLight3D = $Sun
|
|
@onready var moon: DirectionalLight3D = $Moon
|
|
@onready var rain: GPUParticles3D = $Rain
|
|
@onready var world_environment: WorldEnvironment = $WorldEnvironment
|
|
|
|
|
|
func _ready() -> void:
|
|
_ensure_input_map()
|
|
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
|
|
bridge = _try_create_bridge()
|
|
_create_materials()
|
|
_create_sky()
|
|
_create_ocean()
|
|
_create_islands()
|
|
_create_markers()
|
|
_create_boat()
|
|
_update_environment(0.0)
|
|
|
|
|
|
func _unhandled_input(event: InputEvent) -> void:
|
|
if event.is_action_pressed("toggle_mouse"):
|
|
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE if Input.mouse_mode == Input.MOUSE_MODE_CAPTURED else Input.MOUSE_MODE_CAPTURED
|
|
get_viewport().set_input_as_handled()
|
|
elif event is InputEventMouseMotion and Input.mouse_mode == Input.MOUSE_MODE_CAPTURED:
|
|
camera_yaw -= event.relative.x * mouse_sensitivity
|
|
camera_pitch = clampf(camera_pitch - event.relative.y * mouse_sensitivity, CAMERA_MIN_PITCH, CAMERA_MAX_PITCH)
|
|
get_viewport().set_input_as_handled()
|
|
|
|
|
|
func _process(delta: float) -> void:
|
|
var dt := clampf(delta, 0.001, 0.05)
|
|
_update_boat(dt)
|
|
_update_weather(dt)
|
|
_update_environment(dt)
|
|
_update_camera(dt)
|
|
_update_ocean_tiles()
|
|
rain.position = boat_position + Vector3(0.0, 58.0, 0.0)
|
|
|
|
|
|
func _try_create_bridge() -> Object:
|
|
if not ClassDB.class_exists("TypePhpOceanBridge"):
|
|
return null
|
|
var obj: Object = ClassDB.instantiate("TypePhpOceanBridge")
|
|
if obj is Node:
|
|
add_child(obj)
|
|
return obj
|
|
|
|
|
|
func _create_materials() -> void:
|
|
ocean_material = _make_ocean_material()
|
|
island_material = _make_island_material()
|
|
sky_material = _make_sky_material()
|
|
sail_material = StandardMaterial3D.new()
|
|
sail_material.albedo_color = Color(0.94, 0.9, 0.78)
|
|
sail_material.roughness = 0.82
|
|
sail_material.metallic = 0.0
|
|
sail_material.cull_mode = BaseMaterial3D.CULL_DISABLED
|
|
|
|
|
|
func _make_ocean_material() -> ShaderMaterial:
|
|
var shader := Shader.new()
|
|
shader.code = """
|
|
shader_type spatial;
|
|
render_mode specular_schlick_ggx, cull_disabled;
|
|
|
|
uniform vec4 deep_color : source_color = vec4(0.002, 0.026, 0.105, 1.0);
|
|
uniform vec4 mid_color : source_color = vec4(0.006, 0.100, 0.290, 1.0);
|
|
uniform vec4 shallow_color : source_color = vec4(0.018, 0.260, 0.560, 1.0);
|
|
uniform vec4 storm_color : source_color = vec4(0.006, 0.026, 0.065, 1.0);
|
|
uniform float storm = 0.0;
|
|
uniform float rain = 0.0;
|
|
uniform float time_scale = 1.0;
|
|
uniform vec3 sun_dir = vec3(-0.45, 0.72, -0.54);
|
|
uniform vec3 boat_pos = vec3(0.0, 0.0, 0.0);
|
|
uniform vec2 boat_forward = vec2(0.0, -1.0);
|
|
uniform float boat_speed = 0.0;
|
|
|
|
float wave(vec2 p, vec2 d, float amp, float freq, float speed) {
|
|
float phase = dot(p, normalize(d)) * freq + TIME * speed * time_scale;
|
|
return sin(phase) * amp * (1.0 + storm * 0.55);
|
|
}
|
|
|
|
float hash(vec2 p) {
|
|
return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453);
|
|
}
|
|
|
|
float noise(vec2 p) {
|
|
vec2 i = floor(p);
|
|
vec2 f = fract(p);
|
|
f = f * f * (3.0 - 2.0 * f);
|
|
float a = hash(i);
|
|
float b = hash(i + vec2(1.0, 0.0));
|
|
float c = hash(i + vec2(0.0, 1.0));
|
|
float d = hash(i + vec2(1.0, 1.0));
|
|
return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
|
|
}
|
|
|
|
float height_at(vec2 p) {
|
|
float h = 0.0;
|
|
h += wave(p, vec2(0.92, 0.28), 0.72, 0.035, 0.54);
|
|
h += wave(p, vec2(0.38, 1.0), 0.38, 0.070, 0.86);
|
|
h += wave(p, vec2(-0.68, 0.55), 0.16, 0.155, 1.58);
|
|
h += wave(p, vec2(0.24, -0.92), 0.045, 0.410, 2.45);
|
|
return h;
|
|
}
|
|
|
|
void vertex() {
|
|
VERTEX.y += height_at(VERTEX.xz + NODE_POSITION_WORLD.xz) * 0.035;
|
|
}
|
|
|
|
void fragment() {
|
|
vec2 p = (VERTEX.xz + NODE_POSITION_WORLD.xz);
|
|
vec2 d0 = normalize(vec2(0.92, 0.28));
|
|
vec2 d1 = normalize(vec2(0.38, 1.0));
|
|
vec2 d2 = normalize(vec2(-0.68, 0.55));
|
|
vec2 d3 = normalize(vec2(0.24, -0.92));
|
|
float p0 = dot(p, d0) * 0.035 + TIME * 0.54;
|
|
float p1 = dot(p, d1) * 0.070 + TIME * 0.86;
|
|
float p2 = dot(p, d2) * 0.155 + TIME * 1.58;
|
|
float p3 = dot(p, d3) * 0.410 + TIME * 2.45;
|
|
float p4 = dot(p, normalize(vec2(0.78, -0.18))) * 0.82 + TIME * 3.35;
|
|
float p5 = dot(p, normalize(vec2(-0.22, 0.96))) * 1.37 + TIME * 4.25;
|
|
float h = sin(p0) * 0.72 + sin(p1) * 0.38 + sin(p2) * 0.16 + sin(p3) * 0.045 + sin(p4) * 0.018 + sin(p5) * 0.010;
|
|
vec2 micro = vec2(noise(p * 0.085 + vec2(TIME * 0.08, 3.7)), noise(p * 0.095 + vec2(11.4, TIME * 0.07))) - vec2(0.5);
|
|
vec2 slope = d0 * cos(p0) * 0.025 + d1 * cos(p1) * 0.027 + d2 * cos(p2) * 0.025 + d3 * cos(p3) * 0.018 + normalize(vec2(0.78, -0.18)) * cos(p4) * 0.015 + normalize(vec2(-0.22, 0.96)) * cos(p5) * 0.010 + micro * 0.018;
|
|
float view_distance = length(VERTEX.xz);
|
|
float near_detail = 1.0 - smoothstep(280.0, 820.0, view_distance);
|
|
NORMAL = normalize(vec3(-slope.x * mix(10.0, 19.0, near_detail), 1.0, -slope.y * mix(10.0, 19.0, near_detail)));
|
|
vec3 view_dir = normalize(VIEW);
|
|
vec3 light_dir = normalize(sun_dir);
|
|
vec3 half_dir = normalize(light_dir - view_dir);
|
|
float facing = pow(1.0 - clamp(dot(view_dir, NORMAL), 0.0, 1.0), 4.0);
|
|
float chop = clamp(length(slope) * 8.0 + abs(h) * 0.18 + storm * 0.36, 0.0, 1.0);
|
|
float sky_reflect = pow(facing, 0.85) * (0.10 - rain * 0.05);
|
|
float long_bands = sin(dot(p, vec2(0.010, 0.018)) + TIME * 0.16) * 0.5 + 0.5;
|
|
vec3 sea = mix(deep_color.rgb, mid_color.rgb, 0.30 + chop * 0.16 + long_bands * 0.08);
|
|
sea = mix(sea, shallow_color.rgb, smoothstep(0.80, 1.22, h) * 0.20 * near_detail);
|
|
sea = mix(sea, storm_color.rgb, storm * 0.72);
|
|
sea += vec3(0.025, 0.075, 0.14) * sky_reflect;
|
|
float foam_lines = smoothstep(0.84, 1.0, sin(dot(p, vec2(0.055, 0.072)) + TIME * 1.35) * 0.5 + 0.5);
|
|
float sun_glint = pow(max(dot(NORMAL, half_dir), 0.0), 110.0) * smoothstep(0.05, 0.65, light_dir.y) * (1.0 - rain * 0.75);
|
|
float sparkle_a = smoothstep(0.88, 1.0, sin(dot(p, vec2(0.72, -0.18)) + TIME * 3.4) * 0.5 + 0.5);
|
|
float sparkle_b = smoothstep(0.90, 1.0, sin(dot(p, vec2(-0.24, 1.18)) + TIME * 4.7) * 0.5 + 0.5);
|
|
float grid_breakup = sin(p.x * 0.173 + p.y * 0.097 + TIME * 0.41) * 0.5 + 0.5;
|
|
float glitter = (sparkle_a * 0.50 + sparkle_b * 0.34 + grid_breakup * 0.16) * smoothstep(0.38, 0.92, chop) * (1.0 - storm * 0.55) * near_detail;
|
|
float crest_foam = smoothstep(0.80, 1.0, chop) * foam_lines * (0.040 + storm * 0.22) * near_detail;
|
|
vec2 to_boat = p - boat_pos.xz;
|
|
vec2 forward = normalize(boat_forward);
|
|
vec2 right = vec2(forward.y, -forward.x);
|
|
float behind = smoothstep(3.0, -18.0, dot(to_boat, forward));
|
|
float side = abs(dot(normalize(to_boat + vec2(0.001)), right));
|
|
float wake_wing = smoothstep(0.72, 0.46, abs(side - 0.55));
|
|
float wake_fade = smoothstep(46.0, 3.0, length(to_boat));
|
|
float wake_core = smoothstep(0.18, 0.02, abs(dot(to_boat, right))) * smoothstep(22.0, 3.0, length(to_boat));
|
|
float wake = (wake_wing * 0.88 + wake_core * 0.32) * wake_fade * behind * clamp(boat_speed / 8.0, 0.0, 1.0);
|
|
float foam = clamp(crest_foam + wake * 0.72, 0.0, 1.0);
|
|
vec3 glint_color = vec3(0.80, 0.93, 1.0) * (sun_glint * 0.95 + glitter * 0.10);
|
|
float horizon_blend = smoothstep(520.0, 1180.0, view_distance);
|
|
vec3 horizon_water = vec3(0.050, 0.150, 0.310);
|
|
ALBEDO = mix(mix(sea, vec3(0.72, 0.88, 0.94), foam) + glint_color, horizon_water, horizon_blend * (0.22 + storm * 0.20));
|
|
ROUGHNESS = mix(0.070, 0.22, storm + rain * 0.45);
|
|
SPECULAR = mix(0.92, 0.45, rain);
|
|
RIM = facing * 0.28;
|
|
RIM_TINT = 0.28;
|
|
}
|
|
"""
|
|
var material := ShaderMaterial.new()
|
|
material.shader = shader
|
|
return material
|
|
|
|
|
|
func _make_island_material() -> ShaderMaterial:
|
|
var shader := Shader.new()
|
|
shader.code = """
|
|
shader_type spatial;
|
|
render_mode cull_back, diffuse_burley, specular_schlick_ggx;
|
|
|
|
uniform vec4 sand : source_color = vec4(0.76, 0.62, 0.36, 1.0);
|
|
uniform vec4 soil : source_color = vec4(0.62, 0.45, 0.22, 1.0);
|
|
uniform vec4 rock : source_color = vec4(0.46, 0.35, 0.21, 1.0);
|
|
uniform vec4 grass : source_color = vec4(0.12, 0.34, 0.12, 1.0);
|
|
|
|
float patch_noise(vec2 p) {
|
|
return fract(sin(dot(floor(p), vec2(127.1, 311.7))) * 43758.5453);
|
|
}
|
|
|
|
void fragment() {
|
|
float slope = 1.0 - clamp(NORMAL.y, 0.0, 1.0);
|
|
float height = VERTEX.y;
|
|
float beach = smoothstep(0.0, 1.8, height);
|
|
vec3 color = mix(sand.rgb, soil.rgb, beach);
|
|
color = mix(color, rock.rgb, smoothstep(0.38, 0.78, slope));
|
|
float vegetation_noise = patch_noise(VERTEX.xz * 0.11);
|
|
float vegetation = smoothstep(2.7, 7.0, height) * smoothstep(0.66, 0.18, slope) * smoothstep(0.36, 0.74, vegetation_noise);
|
|
color = mix(color, grass.rgb, vegetation * 0.78);
|
|
ALBEDO = color;
|
|
ROUGHNESS = 0.88;
|
|
SPECULAR = 0.25;
|
|
}
|
|
"""
|
|
var material := ShaderMaterial.new()
|
|
material.shader = shader
|
|
return material
|
|
|
|
|
|
func _make_sky_material() -> ShaderMaterial:
|
|
var shader := Shader.new()
|
|
shader.code = """
|
|
shader_type spatial;
|
|
render_mode unshaded, cull_front, depth_draw_never, fog_disabled;
|
|
|
|
uniform float day_time = 0.28;
|
|
uniform float storm = 0.0;
|
|
uniform vec3 sun_dir = vec3(-0.45, 0.72, -0.54);
|
|
|
|
float hash(vec2 p) {
|
|
return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453);
|
|
}
|
|
|
|
void fragment() {
|
|
vec3 v = normalize(VIEW);
|
|
float up = clamp(v.y * 0.5 + 0.5, 0.0, 1.0);
|
|
float daylight = smoothstep(-0.16, 0.18, sun_dir.y);
|
|
vec3 day_top = vec3(0.10, 0.46, 0.92);
|
|
vec3 day_horizon = vec3(0.70, 0.88, 1.0);
|
|
vec3 dusk_top = vec3(0.08, 0.12, 0.30);
|
|
vec3 dusk_horizon = vec3(1.0, 0.44, 0.22);
|
|
vec3 night_top = vec3(0.01, 0.018, 0.055);
|
|
vec3 night_horizon = vec3(0.05, 0.08, 0.16);
|
|
float dusk = 1.0 - smoothstep(0.08, 0.42, abs(sun_dir.y));
|
|
vec3 top = mix(night_top, day_top, daylight);
|
|
vec3 horizon = mix(night_horizon, day_horizon, daylight);
|
|
top = mix(top, dusk_top, dusk * 0.5);
|
|
horizon = mix(horizon, dusk_horizon, dusk);
|
|
vec3 sky = mix(horizon, top, smoothstep(0.02, 0.92, up));
|
|
sky += vec3(0.04, 0.10, 0.16) * smoothstep(0.36, 0.88, up) * daylight;
|
|
sky = mix(sky, vec3(0.09, 0.11, 0.13), storm * 0.78);
|
|
float sun = pow(max(dot(normalize(-v), normalize(sun_dir)), 0.0), 640.0) * daylight;
|
|
float glow = pow(max(dot(normalize(-v), normalize(sun_dir)), 0.0), 14.0) * daylight;
|
|
float stars = step(0.996, hash(floor(v.xz * 460.0))) * (1.0 - daylight) * smoothstep(0.18, 0.8, up);
|
|
ALBEDO = sky + vec3(1.0, 0.76, 0.35) * sun * 4.0 + vec3(1.0, 0.45, 0.20) * glow * dusk * 0.55 + vec3(stars);
|
|
}
|
|
"""
|
|
var material := ShaderMaterial.new()
|
|
material.shader = shader
|
|
return material
|
|
|
|
|
|
func _create_sky() -> void:
|
|
sky_dome = MeshInstance3D.new()
|
|
sky_dome.name = "SkyDome"
|
|
var sphere := SphereMesh.new()
|
|
sphere.radius = 900.0
|
|
sphere.height = 900.0
|
|
sphere.radial_segments = 64
|
|
sphere.rings = 32
|
|
sky_dome.mesh = sphere
|
|
sky_dome.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
|
|
sky_dome.material_override = sky_material
|
|
add_child(sky_dome)
|
|
|
|
for i in range(CLOUD_LAYER_COUNT):
|
|
var cloud := MeshInstance3D.new()
|
|
cloud.name = "CloudLayer%d" % i
|
|
var mesh := PlaneMesh.new()
|
|
mesh.size = Vector2(1100.0 + i * 120.0, 1100.0 + i * 120.0)
|
|
mesh.subdivide_width = 1
|
|
mesh.subdivide_depth = 1
|
|
cloud.mesh = mesh
|
|
cloud.position = Vector3((i - 2) * 80.0, 84.0 + i * 13.0, -70.0 + i * 45.0)
|
|
cloud.rotation.x = deg_to_rad(4.0 + i * 1.5)
|
|
var material := _make_cloud_material(i)
|
|
cloud_materials.append(material)
|
|
cloud.material_override = material
|
|
add_child(cloud)
|
|
_create_horizon_clouds()
|
|
_create_cirrus_clouds()
|
|
|
|
|
|
func _create_horizon_clouds() -> void:
|
|
var material := _make_horizon_cloud_material()
|
|
cloud_materials.append(material)
|
|
for i in range(8):
|
|
var cloud := MeshInstance3D.new()
|
|
cloud.name = "HorizonCloudBand%d" % i
|
|
var mesh := PlaneMesh.new()
|
|
mesh.size = Vector2(320.0, 76.0)
|
|
cloud.mesh = mesh
|
|
var angle := TAU * float(i) / 8.0
|
|
var pos := Vector3(sin(angle) * 620.0, 64.0 + sin(angle * 2.0) * 10.0, cos(angle) * 620.0)
|
|
cloud.position = pos
|
|
cloud.look_at_from_position(pos, Vector3(0.0, 58.0, 0.0), Vector3.UP)
|
|
cloud.rotation.x += deg_to_rad(-4.0)
|
|
cloud.material_override = material
|
|
add_child(cloud)
|
|
|
|
|
|
func _create_cirrus_clouds() -> void:
|
|
var material := _make_cirrus_cloud_material()
|
|
cloud_materials.append(material)
|
|
for i in range(3):
|
|
var cloud := MeshInstance3D.new()
|
|
cloud.name = "CirrusCloud%d" % i
|
|
var mesh := PlaneMesh.new()
|
|
mesh.size = Vector2(920.0, 210.0)
|
|
cloud.mesh = mesh
|
|
cloud.position = Vector3((i - 1) * 170.0, 210.0 + i * 16.0, -260.0 + i * 130.0)
|
|
cloud.rotation = Vector3(deg_to_rad(12.0), deg_to_rad(8.0 + i * 9.0), deg_to_rad(-4.0 + i * 3.0))
|
|
cloud.material_override = material
|
|
add_child(cloud)
|
|
|
|
|
|
func _make_cloud_material(layer: int) -> ShaderMaterial:
|
|
var shader := Shader.new()
|
|
shader.code = """
|
|
shader_type spatial;
|
|
render_mode blend_mix, depth_draw_never, cull_disabled, unshaded, fog_disabled;
|
|
|
|
uniform float coverage = 0.42;
|
|
uniform float density = 0.64;
|
|
uniform float storm = 0.0;
|
|
uniform float speed = 0.018;
|
|
uniform vec4 bright : source_color = vec4(1.0, 1.0, 0.96, 0.68);
|
|
uniform vec4 dark : source_color = vec4(0.33, 0.39, 0.46, 0.82);
|
|
|
|
float noise(vec2 p) {
|
|
vec2 i = floor(p);
|
|
vec2 f = fract(p);
|
|
f = f * f * (3.0 - 2.0 * f);
|
|
float a = fract(sin(dot(i, vec2(127.1, 311.7))) * 43758.5453);
|
|
float b = fract(sin(dot(i + vec2(1, 0), vec2(127.1, 311.7))) * 43758.5453);
|
|
float c = fract(sin(dot(i + vec2(0, 1), vec2(127.1, 311.7))) * 43758.5453);
|
|
float d = fract(sin(dot(i + vec2(1, 1), vec2(127.1, 311.7))) * 43758.5453);
|
|
return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
|
|
}
|
|
|
|
float fbm(vec2 p) {
|
|
float v = 0.0;
|
|
float a = 0.5;
|
|
for (int i = 0; i < 5; i++) {
|
|
v += noise(p) * a;
|
|
p *= 2.17;
|
|
a *= 0.52;
|
|
}
|
|
return v;
|
|
}
|
|
|
|
void fragment() {
|
|
vec2 uv = UV * 5.0 + vec2(TIME * speed, TIME * speed * 0.23);
|
|
float base = fbm(uv);
|
|
float detail = fbm(uv * 3.0 + vec2(17.0, 9.0));
|
|
float edge = smoothstep(coverage, 1.0, base * 0.82 + detail * 0.26);
|
|
float alpha = edge * density * (0.55 + storm * 0.42);
|
|
vec3 color = mix(bright.rgb, dark.rgb, storm * 0.72 + (1.0 - detail) * 0.18);
|
|
ALBEDO = color;
|
|
ALPHA = alpha;
|
|
}
|
|
"""
|
|
var material := ShaderMaterial.new()
|
|
material.shader = shader
|
|
material.set_shader_parameter("coverage", 0.45 + layer * 0.035)
|
|
material.set_shader_parameter("density", 0.36 + layer * 0.065)
|
|
material.set_shader_parameter("speed", 0.012 + layer * 0.003)
|
|
return material
|
|
|
|
|
|
func _make_horizon_cloud_material() -> ShaderMaterial:
|
|
var shader := Shader.new()
|
|
shader.code = """
|
|
shader_type spatial;
|
|
render_mode blend_mix, depth_draw_never, cull_disabled, unshaded, fog_disabled;
|
|
|
|
uniform float storm = 0.0;
|
|
uniform vec4 bright : source_color = vec4(1.0, 0.98, 0.91, 0.82);
|
|
uniform vec4 shade : source_color = vec4(0.52, 0.66, 0.78, 0.72);
|
|
|
|
float hash(vec2 p) {
|
|
return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453);
|
|
}
|
|
|
|
float noise(vec2 p) {
|
|
vec2 i = floor(p);
|
|
vec2 f = fract(p);
|
|
f = f * f * (3.0 - 2.0 * f);
|
|
float a = hash(i);
|
|
float b = hash(i + vec2(1.0, 0.0));
|
|
float c = hash(i + vec2(0.0, 1.0));
|
|
float d = hash(i + vec2(1.0, 1.0));
|
|
return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
|
|
}
|
|
|
|
float fbm(vec2 p) {
|
|
float v = 0.0;
|
|
float a = 0.55;
|
|
for (int i = 0; i < 4; i++) {
|
|
v += noise(p) * a;
|
|
p *= 2.1;
|
|
a *= 0.5;
|
|
}
|
|
return v;
|
|
}
|
|
|
|
void fragment() {
|
|
vec2 uv = UV;
|
|
float mound = smoothstep(0.12, 0.36, uv.y) * smoothstep(1.0, 0.44, uv.y);
|
|
float n = fbm(vec2(uv.x * 8.0 + TIME * 0.006, uv.y * 3.2));
|
|
float shape = smoothstep(0.34, 0.72, n + mound * 0.42);
|
|
float horizon_fade = smoothstep(0.02, 0.18, uv.y) * smoothstep(0.96, 0.48, uv.y);
|
|
vec3 color = mix(shade.rgb, bright.rgb, smoothstep(0.35, 0.86, uv.y + n * 0.22));
|
|
color = mix(color, vec3(0.42, 0.48, 0.54), storm * 0.72);
|
|
ALBEDO = color;
|
|
ALPHA = shape * horizon_fade * mix(0.70, 0.88, storm);
|
|
}
|
|
"""
|
|
var material := ShaderMaterial.new()
|
|
material.shader = shader
|
|
return material
|
|
|
|
|
|
func _make_cirrus_cloud_material() -> ShaderMaterial:
|
|
var shader := Shader.new()
|
|
shader.code = """
|
|
shader_type spatial;
|
|
render_mode blend_mix, depth_draw_never, cull_disabled, unshaded, fog_disabled;
|
|
|
|
uniform float storm = 0.0;
|
|
|
|
float hash(vec2 p) {
|
|
return fract(sin(dot(p, vec2(269.5, 183.3))) * 43758.5453);
|
|
}
|
|
|
|
float noise(vec2 p) {
|
|
vec2 i = floor(p);
|
|
vec2 f = fract(p);
|
|
f = f * f * (3.0 - 2.0 * f);
|
|
float a = hash(i);
|
|
float b = hash(i + vec2(1.0, 0.0));
|
|
float c = hash(i + vec2(0.0, 1.0));
|
|
float d = hash(i + vec2(1.0, 1.0));
|
|
return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
|
|
}
|
|
|
|
void fragment() {
|
|
vec2 uv = UV;
|
|
vec2 stretched = vec2(uv.x * 13.0 + uv.y * 4.5 + TIME * 0.01, uv.y * 2.3);
|
|
float fibers = noise(stretched) * 0.62 + noise(stretched * 2.4 + vec2(3.1, 7.4)) * 0.30;
|
|
float long_shape = smoothstep(0.06, 0.22, uv.y) * smoothstep(0.98, 0.34, uv.y);
|
|
float torn_edges = smoothstep(0.48, 0.78, fibers);
|
|
float alpha = torn_edges * long_shape * (1.0 - storm * 0.72) * 0.54;
|
|
ALBEDO = mix(vec3(0.82, 0.91, 1.0), vec3(1.0, 0.98, 0.92), smoothstep(0.36, 0.86, uv.y));
|
|
ALPHA = alpha;
|
|
}
|
|
"""
|
|
var material := ShaderMaterial.new()
|
|
material.shader = shader
|
|
return material
|
|
|
|
|
|
func _create_ocean() -> void:
|
|
var mesh := PlaneMesh.new()
|
|
mesh.size = Vector2(OCEAN_SIZE, OCEAN_SIZE)
|
|
mesh.subdivide_width = 16
|
|
mesh.subdivide_depth = 16
|
|
for x in range(-1, 2):
|
|
for z in range(-1, 2):
|
|
var tile := MeshInstance3D.new()
|
|
tile.name = "OceanTile_%d_%d" % [x, z]
|
|
tile.mesh = mesh
|
|
tile.material_override = ocean_material
|
|
tile.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
|
|
add_child(tile)
|
|
ocean_tiles.append(tile)
|
|
_update_ocean_tiles()
|
|
|
|
|
|
func _update_ocean_tiles() -> void:
|
|
var center_x: float = floor(boat_position.x / OCEAN_SIZE) * OCEAN_SIZE
|
|
var center_z: float = floor(boat_position.z / OCEAN_SIZE) * OCEAN_SIZE
|
|
var index := 0
|
|
for x in range(-1, 2):
|
|
for z in range(-1, 2):
|
|
var tile := ocean_tiles[index]
|
|
tile.position = Vector3(center_x + float(x) * OCEAN_SIZE, 0.0, center_z + float(z) * OCEAN_SIZE)
|
|
index += 1
|
|
|
|
|
|
func _create_islands() -> void:
|
|
island_root = Node3D.new()
|
|
island_root.name = "Islands"
|
|
add_child(island_root)
|
|
var islands: Array = _bridge_array("get_islands", [1180.0])
|
|
if islands.is_empty():
|
|
islands = _fallback_islands()
|
|
for island in islands:
|
|
if island is Dictionary:
|
|
_add_island(island)
|
|
|
|
|
|
func _fallback_islands() -> Array:
|
|
return [
|
|
{"x": 170.0, "z": -210.0, "radius": 58.0, "height": 20.0, "seed": 11.0},
|
|
{"x": -260.0, "z": 120.0, "radius": 42.0, "height": 14.0, "seed": 27.0},
|
|
{"x": 420.0, "z": 310.0, "radius": 78.0, "height": 28.0, "seed": 43.0},
|
|
{"x": -520.0, "z": -390.0, "radius": 64.0, "height": 22.0, "seed": 61.0},
|
|
{"x": 720.0, "z": -110.0, "radius": 52.0, "height": 18.0, "seed": 83.0},
|
|
{"x": -810.0, "z": 470.0, "radius": 70.0, "height": 31.0, "seed": 97.0},
|
|
{"x": 120.0, "z": 820.0, "radius": 46.0, "height": 16.0, "seed": 109.0},
|
|
{"x": -650.0, "z": -40.0, "radius": 88.0, "height": 34.0, "seed": 131.0},
|
|
{"x": 910.0, "z": 560.0, "radius": 61.0, "height": 24.0, "seed": 149.0},
|
|
]
|
|
|
|
|
|
func _add_island(info: Dictionary) -> void:
|
|
var center: Vector3 = Vector3(float(info.get("x", 0.0)), 0.0, float(info.get("z", 0.0)))
|
|
var radius: float = float(info.get("radius", 45.0))
|
|
var height: float = float(info.get("height", 16.0))
|
|
var seed: float = float(info.get("seed", 1.0))
|
|
var mesh := ArrayMesh.new()
|
|
var vertices := PackedVector3Array()
|
|
var normals := PackedVector3Array()
|
|
var uvs := PackedVector2Array()
|
|
var indices := PackedInt32Array()
|
|
for z in range(ISLAND_GRID + 1):
|
|
for x in range(ISLAND_GRID + 1):
|
|
var fx := (float(x) / ISLAND_GRID - 0.5) * radius * 2.35
|
|
var fz := (float(z) / ISLAND_GRID - 0.5) * radius * 2.35
|
|
var d := Vector2(fx, fz).length() / radius
|
|
var core := pow(clampf(1.0 - d, 0.0, 1.0), 2.0)
|
|
var beach_ring := smoothstep(1.04, 0.72, d)
|
|
var ridge_a := sin((fx + seed) * 0.055) * cos((fz - seed) * 0.047)
|
|
var ridge_b := sin((fx * 0.12 - fz * 0.09) + seed * 0.31)
|
|
var ridges := (ridge_a * 0.30 + ridge_b * 0.14) * core
|
|
var detail := sin((fx - fz) * 0.21 + seed) * sin((fx + fz) * 0.13) * 0.07 * core
|
|
var y := (core + ridges + detail) * height
|
|
y = y * beach_ring - smoothstep(0.76, 1.05, d) * 1.6
|
|
if d > 0.70:
|
|
y = minf(y, 1.3 + (1.0 - d) * 2.2)
|
|
vertices.append(center + Vector3(fx, y, fz))
|
|
normals.append(Vector3.UP)
|
|
uvs.append(Vector2(float(x) / ISLAND_GRID, float(z) / ISLAND_GRID))
|
|
for z in range(ISLAND_GRID):
|
|
for x in range(ISLAND_GRID):
|
|
var i := z * (ISLAND_GRID + 1) + x
|
|
indices.append_array([i, i + ISLAND_GRID + 1, i + 1, i + 1, i + ISLAND_GRID + 1, i + ISLAND_GRID + 2])
|
|
var arrays := []
|
|
arrays.resize(Mesh.ARRAY_MAX)
|
|
arrays[Mesh.ARRAY_VERTEX] = vertices
|
|
arrays[Mesh.ARRAY_NORMAL] = _derive_normals(vertices, indices)
|
|
arrays[Mesh.ARRAY_TEX_UV] = uvs
|
|
arrays[Mesh.ARRAY_INDEX] = indices
|
|
mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays)
|
|
var island := MeshInstance3D.new()
|
|
island.mesh = mesh
|
|
island.material_override = island_material
|
|
island_root.add_child(island)
|
|
_add_palms(center, radius, height, int(seed))
|
|
|
|
|
|
func _derive_normals(vertices: PackedVector3Array, indices: PackedInt32Array) -> PackedVector3Array:
|
|
var normals := PackedVector3Array()
|
|
normals.resize(vertices.size())
|
|
for i in range(0, indices.size(), 3):
|
|
var a := indices[i]
|
|
var b := indices[i + 1]
|
|
var c := indices[i + 2]
|
|
var n := (vertices[b] - vertices[a]).cross(vertices[c] - vertices[a]).normalized()
|
|
normals[a] += n
|
|
normals[b] += n
|
|
normals[c] += n
|
|
for i in range(normals.size()):
|
|
normals[i] = normals[i].normalized()
|
|
return normals
|
|
|
|
|
|
func _add_palms(center: Vector3, radius: float, height: float, seed: int) -> void:
|
|
var trunk_mat := StandardMaterial3D.new()
|
|
trunk_mat.albedo_color = Color(0.36, 0.22, 0.11)
|
|
trunk_mat.roughness = 0.9
|
|
var leaf_mat := StandardMaterial3D.new()
|
|
leaf_mat.albedo_color = Color(0.06, 0.38, 0.14)
|
|
leaf_mat.roughness = 0.8
|
|
for i in range(8):
|
|
var a := float(seed * 37 + i * 53)
|
|
var r := radius * (0.24 + absf(sin(a * 1.71)) * 0.42)
|
|
var angle := a * 0.37
|
|
var pos := center + Vector3(cos(angle) * r, height * 0.34, sin(angle) * r)
|
|
var trunk := MeshInstance3D.new()
|
|
trunk.mesh = CylinderMesh.new()
|
|
(trunk.mesh as CylinderMesh).top_radius = 0.22
|
|
(trunk.mesh as CylinderMesh).bottom_radius = 0.34
|
|
(trunk.mesh as CylinderMesh).height = 6.5
|
|
trunk.position = pos + Vector3(0, 3.0, 0)
|
|
trunk.rotation.z = sin(a) * 0.18
|
|
trunk.material_override = trunk_mat
|
|
island_root.add_child(trunk)
|
|
for k in range(7):
|
|
var leaf := MeshInstance3D.new()
|
|
var plane := PlaneMesh.new()
|
|
plane.size = Vector2(1.7, 6.4)
|
|
leaf.mesh = plane
|
|
leaf.position = pos + Vector3(0, 6.8, 0)
|
|
leaf.rotation = Vector3(deg_to_rad(68.0), angle + TAU * float(k) / 7.0, 0.0)
|
|
leaf.material_override = leaf_mat
|
|
island_root.add_child(leaf)
|
|
|
|
|
|
func _create_markers() -> void:
|
|
marker_root = Node3D.new()
|
|
marker_root.name = "Markers"
|
|
add_child(marker_root)
|
|
var markers: Array = _bridge_array("get_markers", [760.0])
|
|
if markers.is_empty():
|
|
markers = [
|
|
{"x": -90.0, "z": -155.0, "type": 1.0, "size": 2.6},
|
|
{"x": 285.0, "z": 70.0, "type": 2.0, "size": 4.5},
|
|
{"x": -390.0, "z": 280.0, "type": 3.0, "size": 7.0},
|
|
]
|
|
for marker in markers:
|
|
if marker is Dictionary:
|
|
_add_marker(marker)
|
|
|
|
|
|
func _add_marker(info: Dictionary) -> void:
|
|
var type: int = int(info.get("type", 0))
|
|
var size: float = float(info.get("size", 2.0))
|
|
var x: float = float(info.get("x", 0.0))
|
|
var z: float = float(info.get("z", 0.0))
|
|
var mat := StandardMaterial3D.new()
|
|
mat.albedo_color = Color(0.85, 0.12, 0.08) if type != 2 else Color(0.1, 0.25, 0.95)
|
|
mat.emission_enabled = type == 3
|
|
mat.emission = Color(1.0, 0.75, 0.32)
|
|
mat.emission_energy_multiplier = 1.4
|
|
var node := MeshInstance3D.new()
|
|
node.mesh = CylinderMesh.new()
|
|
(node.mesh as CylinderMesh).top_radius = size * 0.22
|
|
(node.mesh as CylinderMesh).bottom_radius = size * 0.35
|
|
(node.mesh as CylinderMesh).height = size * 2.4
|
|
node.position = Vector3(x, size * 1.1, z)
|
|
node.material_override = mat
|
|
marker_root.add_child(node)
|
|
if type == 3:
|
|
var light := OmniLight3D.new()
|
|
light.light_color = Color(1.0, 0.62, 0.26)
|
|
light.light_energy = 1.8
|
|
light.omni_range = 34.0
|
|
node.add_child(light)
|
|
|
|
|
|
func _create_boat() -> void:
|
|
boat = Node3D.new()
|
|
boat.name = "Sailboat"
|
|
add_child(boat)
|
|
var hull_mat := StandardMaterial3D.new()
|
|
hull_mat.albedo_color = Color(0.34, 0.14, 0.055)
|
|
hull_mat.roughness = 0.72
|
|
var trim_mat := StandardMaterial3D.new()
|
|
trim_mat.albedo_color = Color(0.72, 0.54, 0.30)
|
|
trim_mat.roughness = 0.62
|
|
var hull := MeshInstance3D.new()
|
|
var hull_mesh := BoxMesh.new()
|
|
hull_mesh.size = Vector3(3.2, 0.9, 8.0)
|
|
hull.mesh = hull_mesh
|
|
hull.position.y = 0.48
|
|
hull.scale = Vector3(1.0, 0.7, 1.0)
|
|
hull.material_override = hull_mat
|
|
boat.add_child(hull)
|
|
var mast := MeshInstance3D.new()
|
|
mast.mesh = CylinderMesh.new()
|
|
(mast.mesh as CylinderMesh).top_radius = 0.08
|
|
(mast.mesh as CylinderMesh).bottom_radius = 0.12
|
|
(mast.mesh as CylinderMesh).height = 8.0
|
|
mast.position = Vector3(0.0, 4.1, -0.7)
|
|
mast.material_override = trim_mat
|
|
boat.add_child(mast)
|
|
_add_sail(Vector3(0.08, 4.8, -0.8), 3.2, 6.0, false)
|
|
_add_sail(Vector3(-0.08, 3.5, -0.35), 2.2, 4.0, true)
|
|
var lamp := OmniLight3D.new()
|
|
lamp.name = "WarmLantern"
|
|
lamp.position = Vector3(0.0, 1.35, -3.4)
|
|
lamp.light_color = Color(1.0, 0.58, 0.24)
|
|
lamp.light_energy = 1.2
|
|
lamp.omni_range = 18.0
|
|
boat.add_child(lamp)
|
|
|
|
|
|
func _add_sail(offset: Vector3, width: float, height: float, flip: bool) -> void:
|
|
var mesh := ArrayMesh.new()
|
|
var vertices := PackedVector3Array([
|
|
Vector3(0, -height * 0.5, 0),
|
|
Vector3(width * ( -1.0 if flip else 1.0), -height * 0.35, 0),
|
|
Vector3(0, height * 0.5, 0),
|
|
])
|
|
var arrays := []
|
|
arrays.resize(Mesh.ARRAY_MAX)
|
|
arrays[Mesh.ARRAY_VERTEX] = vertices
|
|
arrays[Mesh.ARRAY_NORMAL] = PackedVector3Array([Vector3.BACK, Vector3.BACK, Vector3.BACK])
|
|
arrays[Mesh.ARRAY_INDEX] = PackedInt32Array([0, 1, 2])
|
|
mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays)
|
|
var sail := MeshInstance3D.new()
|
|
sail.mesh = mesh
|
|
sail.position = offset
|
|
sail.material_override = sail_material
|
|
boat.add_child(sail)
|
|
|
|
|
|
func _update_boat(dt: float) -> void:
|
|
var input: Vector2 = Input.get_vector("move_left", "move_right", "move_forward", "move_back")
|
|
var forward := Vector3(-sin(camera_yaw), 0.0, -cos(camera_yaw))
|
|
var right := Vector3(cos(camera_yaw), 0.0, -sin(camera_yaw))
|
|
var desired := (right * input.x + forward * -input.y)
|
|
if desired.length_squared() > 0.001:
|
|
desired = desired.normalized()
|
|
boat_yaw = lerp_angle(boat_yaw, atan2(desired.x, -desired.z), 1.0 - pow(0.05, dt))
|
|
var wind := Vector3(-0.35, 0.0, -0.94).normalized()
|
|
var sail_power := clampf(0.55 + max(0.0, desired.dot(wind)) * 0.85, 0.35, 1.6)
|
|
var boost: float = 1.55 if Input.is_action_pressed("boost") else 1.0
|
|
boat_velocity += desired * 13.0 * sail_power * boost * dt
|
|
boat_velocity *= pow(0.10, dt)
|
|
boat_position += boat_velocity * dt
|
|
var boat_forward := Vector2(-sin(boat_yaw), -cos(boat_yaw))
|
|
ocean_material.set_shader_parameter("boat_pos", boat_position)
|
|
ocean_material.set_shader_parameter("boat_forward", boat_forward)
|
|
ocean_material.set_shader_parameter("boat_speed", boat_velocity.length())
|
|
var bob := _water_height(boat_position.x, boat_position.z)
|
|
boat.position = Vector3(boat_position.x, bob + 0.52, boat_position.z)
|
|
boat.rotation = Vector3(sin(Time.get_ticks_msec() * 0.0017) * 0.035, boat_yaw, sin(Time.get_ticks_msec() * 0.0011) * 0.055)
|
|
|
|
|
|
func _water_height(x: float, z: float) -> float:
|
|
var t: float = Time.get_ticks_msec() * 0.001
|
|
var p := Vector2(x, z)
|
|
return sin(p.dot(Vector2(0.92, 0.28).normalized()) * 0.035 + t * 0.54) * 0.72 + sin(p.dot(Vector2(0.38, 1.0).normalized()) * 0.070 + t * 0.86) * 0.38 + sin(p.dot(Vector2(-0.68, 0.55).normalized()) * 0.155 + t * 1.58) * 0.16
|
|
|
|
|
|
func _update_weather(dt: float) -> void:
|
|
day_time = fmod(day_time + dt / 180.0, 1.0)
|
|
next_weather_change -= dt
|
|
if next_weather_change <= 0.0 and target_weather == weather:
|
|
target_weather = _choose_next_weather(weather)
|
|
weather_mix = 0.0
|
|
next_weather_change = 20.0 + randf() * 22.0
|
|
if target_weather != weather:
|
|
weather_mix = minf(1.0, weather_mix + dt / 9.0)
|
|
if weather_mix >= 1.0:
|
|
weather = target_weather
|
|
weather_mix = 0.0
|
|
var base_rain := 1.0 if weather == WEATHER_RAIN else 0.0
|
|
if target_weather == WEATHER_RAIN:
|
|
rain_amount = maxf(base_rain, weather_mix)
|
|
elif weather == WEATHER_RAIN:
|
|
rain_amount = 1.0 - weather_mix
|
|
else:
|
|
rain_amount = 0.0
|
|
|
|
|
|
func _choose_next_weather(current: int) -> int:
|
|
var roll := randf()
|
|
if bridge != null and bridge.has_method("choose_next_weather"):
|
|
return int(bridge.call("choose_next_weather", current, roll))
|
|
if current == WEATHER_SUNNY:
|
|
return WEATHER_CLOUDY if roll < 0.58 else WEATHER_RAIN
|
|
if current == WEATHER_CLOUDY:
|
|
return WEATHER_SUNNY if roll < 0.42 else WEATHER_RAIN
|
|
return WEATHER_CLOUDY if roll < 0.58 else WEATHER_SUNNY
|
|
|
|
|
|
func _update_environment(_dt: float) -> void:
|
|
var sun_angle := day_time * TAU - PI * 0.5
|
|
var sun_dir := Vector3(cos(sun_angle) * -0.52, sin(sun_angle), -0.62).normalized()
|
|
sun.rotation = Basis.looking_at(-sun_dir, Vector3.UP).get_euler()
|
|
var daylight := smoothstep(-0.12, 0.28, sun_dir.y)
|
|
var storm := clampf((0.55 if target_weather == WEATHER_CLOUDY else 0.0) + rain_amount, 0.0, 1.0)
|
|
sun.light_energy = lerpf(0.04, 3.4, daylight) * (1.0 - storm * 0.58)
|
|
moon.light_energy = (1.0 - daylight) * 0.48
|
|
ocean_material.set_shader_parameter("storm", storm)
|
|
ocean_material.set_shader_parameter("rain", rain_amount)
|
|
ocean_material.set_shader_parameter("sun_dir", sun_dir)
|
|
sky_material.set_shader_parameter("day_time", day_time)
|
|
sky_material.set_shader_parameter("storm", storm)
|
|
sky_material.set_shader_parameter("sun_dir", sun_dir)
|
|
for material in cloud_materials:
|
|
material.set_shader_parameter("storm", storm)
|
|
material.set_shader_parameter("coverage", lerpf(0.46, 0.28, rain_amount))
|
|
rain.emitting = rain_amount > 0.05
|
|
rain.amount_ratio = rain_amount
|
|
var env: Environment = world_environment.environment
|
|
env.fog_density = lerpf(0.004, 0.018, storm)
|
|
env.volumetric_fog_density = lerpf(0.012, 0.045, storm)
|
|
|
|
|
|
func _update_camera(dt: float) -> void:
|
|
var distance := lerpf(26.0, 34.0, clampf(boat_velocity.length() / 16.0, 0.0, 1.0))
|
|
var camera_offset := Basis(Vector3.UP, camera_yaw) * Vector3(0.0, 8.8, distance)
|
|
var target_height := maxf(2.2, 5.0 + sin(camera_pitch) * 10.0)
|
|
var target := boat_position + Vector3(0.0, target_height, 0.0)
|
|
camera_rig.position = camera_rig.position.lerp(target, 1.0 - pow(0.01, dt))
|
|
camera.position = camera.position.lerp(camera_offset, 1.0 - pow(0.02, dt))
|
|
camera.look_at(target, Vector3.UP)
|
|
|
|
|
|
func _bridge_array(method: StringName, args: Array) -> Array:
|
|
if bridge == null or not bridge.has_method(method):
|
|
return []
|
|
var value = bridge.callv(method, args)
|
|
return value if value is Array else []
|
|
|
|
|
|
func _ensure_input_map() -> void:
|
|
_add_key_action("move_forward", [KEY_W, KEY_UP])
|
|
_add_key_action("move_back", [KEY_S, KEY_DOWN])
|
|
_add_key_action("move_left", [KEY_A, KEY_LEFT])
|
|
_add_key_action("move_right", [KEY_D, KEY_RIGHT])
|
|
_add_key_action("toggle_mouse", [KEY_ESCAPE])
|
|
_add_key_action("boost", [KEY_SHIFT])
|
|
|
|
|
|
func _add_key_action(action: StringName, keys: Array) -> void:
|
|
if not InputMap.has_action(action):
|
|
InputMap.add_action(action)
|
|
for key in keys:
|
|
var exists := false
|
|
for event in InputMap.action_get_events(action):
|
|
if event is InputEventKey and event.keycode == key:
|
|
exists = true
|
|
break
|
|
if exists:
|
|
continue
|
|
var input := InputEventKey.new()
|
|
input.keycode = key
|
|
input.physical_keycode = key
|
|
InputMap.action_add_event(action, input)
|
|
|