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

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)