Rework island water and shop character
|
|
@ -37,6 +37,8 @@ written with or without assistance.
|
||||||
- “Gentle Ocean Waves Loop” by kkenny101, used for the saltwater shoreline
|
- “Gentle Ocean Waves Loop” by kkenny101, used for the saltwater shoreline
|
||||||
ambience. Freesound sound 852826, Creative Commons Zero (CC0):
|
ambience. Freesound sound 852826, Creative Commons Zero (CC0):
|
||||||
https://freesound.org/s/852826/
|
https://freesound.org/s/852826/
|
||||||
|
- Rain Loop Ontario by Ayton -- https://freesound.org/s/212799/ -- License:
|
||||||
|
Attribution 3.0
|
||||||
- “Quick Water Droplet” by qubodup, used for the bobber water-impact sound.
|
- “Quick Water Droplet” by qubodup, used for the bobber water-impact sound.
|
||||||
Freesound sound 792931, Creative Commons Zero (CC0):
|
Freesound sound 792931, Creative Commons Zero (CC0):
|
||||||
https://freesound.org/s/792931/
|
https://freesound.org/s/792931/
|
||||||
|
|
|
||||||
26
README.md
|
|
@ -38,32 +38,6 @@ scripts/run_validations.sh quick
|
||||||
Additional suites and individual commands are documented in
|
Additional suites and individual commands are documented in
|
||||||
[`docs/TESTING.md`](docs/TESTING.md).
|
[`docs/TESTING.md`](docs/TESTING.md).
|
||||||
|
|
||||||
## Texture import normalization
|
|
||||||
|
|
||||||
Texture import settings are cached in `.import` sidecars. To keep new textures
|
|
||||||
consistent by default, normalize imported textures before committing:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
./scripts/normalize_texture_profiles.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
To normalize only a specific directory (for example environment textures):
|
|
||||||
|
|
||||||
```sh
|
|
||||||
./scripts/normalize_texture_profiles.sh res://art/exported/environment/textures
|
|
||||||
```
|
|
||||||
|
|
||||||
The script enforces a single baseline texture profile by default and sets
|
|
||||||
`import_profile = "managed_default"` in `[params]`. If any texture needs
|
|
||||||
custom settings, set:
|
|
||||||
|
|
||||||
```ini
|
|
||||||
[params]
|
|
||||||
import_profile="custom"
|
|
||||||
```
|
|
||||||
|
|
||||||
in its `.import` file to opt it out.
|
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
|
|
||||||
Export presets are maintained for Linux, Windows, and Android. Repository
|
Export presets are maintained for Linux, Windows, and Android. Repository
|
||||||
|
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
[remap]
|
|
||||||
|
|
||||||
importer="texture"
|
|
||||||
type="CompressedTexture2D"
|
|
||||||
uid="uid://cd7qwsbeg1jxx"
|
|
||||||
path="res://.godot/imported/cliff_wall.png-7ce756d49c24c7b8fdda307d68086d87.ctex"
|
|
||||||
metadata={
|
|
||||||
"vram_texture": false
|
|
||||||
}
|
|
||||||
|
|
||||||
[deps]
|
|
||||||
|
|
||||||
source_file="res://art/exported/environment/textures/cliff_wall.png"
|
|
||||||
dest_files=["res://.godot/imported/cliff_wall.png-7ce756d49c24c7b8fdda307d68086d87.ctex"]
|
|
||||||
|
|
||||||
[params]
|
|
||||||
|
|
||||||
compress/mode=2
|
|
||||||
compress/high_quality=false
|
|
||||||
compress/lossy_quality=0.7
|
|
||||||
compress/uastc_level=0
|
|
||||||
compress/rdo_quality_loss=0.0
|
|
||||||
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/channel_remap/red=0
|
|
||||||
process/channel_remap/green=1
|
|
||||||
process/channel_remap/blue=2
|
|
||||||
process/channel_remap/alpha=3
|
|
||||||
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
|
|
||||||
import_profile="managed_default"
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
[remap]
|
|
||||||
|
|
||||||
importer="texture"
|
|
||||||
type="CompressedTexture2D"
|
|
||||||
uid="uid://b2ce1ikmnbktx"
|
|
||||||
path="res://.godot/imported/dirt.png-ca5bf3f3fbf583edaa719ac8c686549d.ctex"
|
|
||||||
metadata={
|
|
||||||
"vram_texture": false
|
|
||||||
}
|
|
||||||
|
|
||||||
[deps]
|
|
||||||
|
|
||||||
source_file="res://art/exported/environment/textures/dirt.png"
|
|
||||||
dest_files=["res://.godot/imported/dirt.png-ca5bf3f3fbf583edaa719ac8c686549d.ctex"]
|
|
||||||
|
|
||||||
[params]
|
|
||||||
|
|
||||||
compress/mode=2
|
|
||||||
compress/high_quality=false
|
|
||||||
compress/lossy_quality=0.7
|
|
||||||
compress/uastc_level=0
|
|
||||||
compress/rdo_quality_loss=0.0
|
|
||||||
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/channel_remap/red=0
|
|
||||||
process/channel_remap/green=1
|
|
||||||
process/channel_remap/blue=2
|
|
||||||
process/channel_remap/alpha=3
|
|
||||||
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
|
|
||||||
import_profile="managed_default"
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
[remap]
|
|
||||||
|
|
||||||
importer="texture"
|
|
||||||
type="CompressedTexture2D"
|
|
||||||
uid="uid://dtv2k327higrb"
|
|
||||||
path="res://.godot/imported/dirt_light_wall.png-40336902aac8516f68e99f8e8988d86f.ctex"
|
|
||||||
metadata={
|
|
||||||
"vram_texture": false
|
|
||||||
}
|
|
||||||
|
|
||||||
[deps]
|
|
||||||
|
|
||||||
source_file="res://art/exported/environment/textures/dirt_light_wall.png"
|
|
||||||
dest_files=["res://.godot/imported/dirt_light_wall.png-40336902aac8516f68e99f8e8988d86f.ctex"]
|
|
||||||
|
|
||||||
[params]
|
|
||||||
|
|
||||||
compress/mode=2
|
|
||||||
compress/high_quality=false
|
|
||||||
compress/lossy_quality=0.7
|
|
||||||
compress/uastc_level=0
|
|
||||||
compress/rdo_quality_loss=0.0
|
|
||||||
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/channel_remap/red=0
|
|
||||||
process/channel_remap/green=1
|
|
||||||
process/channel_remap/blue=2
|
|
||||||
process/channel_remap/alpha=3
|
|
||||||
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
|
|
||||||
import_profile="managed_default"
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
[remap]
|
|
||||||
|
|
||||||
importer="texture"
|
|
||||||
type="CompressedTexture2D"
|
|
||||||
uid="uid://c3j0gailr6yfy"
|
|
||||||
path="res://.godot/imported/dirt_wall.png-8ba7bb24831d83f929bfa44b53af9288.ctex"
|
|
||||||
metadata={
|
|
||||||
"vram_texture": false
|
|
||||||
}
|
|
||||||
|
|
||||||
[deps]
|
|
||||||
|
|
||||||
source_file="res://art/exported/environment/textures/dirt_wall.png"
|
|
||||||
dest_files=["res://.godot/imported/dirt_wall.png-8ba7bb24831d83f929bfa44b53af9288.ctex"]
|
|
||||||
|
|
||||||
[params]
|
|
||||||
|
|
||||||
compress/mode=2
|
|
||||||
compress/high_quality=false
|
|
||||||
compress/lossy_quality=0.7
|
|
||||||
compress/uastc_level=0
|
|
||||||
compress/rdo_quality_loss=0.0
|
|
||||||
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/channel_remap/red=0
|
|
||||||
process/channel_remap/green=1
|
|
||||||
process/channel_remap/blue=2
|
|
||||||
process/channel_remap/alpha=3
|
|
||||||
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
|
|
||||||
import_profile="managed_default"
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
[remap]
|
|
||||||
|
|
||||||
importer="texture"
|
|
||||||
type="CompressedTexture2D"
|
|
||||||
uid="uid://b4n8bf4cgua4s"
|
|
||||||
path="res://.godot/imported/grass.png-4d1108b11a421d9c16bd72fb0bc993d3.ctex"
|
|
||||||
metadata={
|
|
||||||
"vram_texture": false
|
|
||||||
}
|
|
||||||
|
|
||||||
[deps]
|
|
||||||
|
|
||||||
source_file="res://art/exported/environment/textures/grass.png"
|
|
||||||
dest_files=["res://.godot/imported/grass.png-4d1108b11a421d9c16bd72fb0bc993d3.ctex"]
|
|
||||||
|
|
||||||
[params]
|
|
||||||
|
|
||||||
compress/mode=2
|
|
||||||
compress/high_quality=false
|
|
||||||
compress/lossy_quality=0.7
|
|
||||||
compress/uastc_level=0
|
|
||||||
compress/rdo_quality_loss=0.0
|
|
||||||
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/channel_remap/red=0
|
|
||||||
process/channel_remap/green=1
|
|
||||||
process/channel_remap/blue=2
|
|
||||||
process/channel_remap/alpha=3
|
|
||||||
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
|
|
||||||
import_profile="managed_default"
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
[remap]
|
|
||||||
|
|
||||||
importer="texture"
|
|
||||||
type="CompressedTexture2D"
|
|
||||||
uid="uid://dusa52a3eghr7"
|
|
||||||
path="res://.godot/imported/grass_heavy.png-ba7f679d1bc5402f8b0a3465ac6a44b8.ctex"
|
|
||||||
metadata={
|
|
||||||
"vram_texture": false
|
|
||||||
}
|
|
||||||
|
|
||||||
[deps]
|
|
||||||
|
|
||||||
source_file="res://art/exported/environment/textures/grass_heavy.png"
|
|
||||||
dest_files=["res://.godot/imported/grass_heavy.png-ba7f679d1bc5402f8b0a3465ac6a44b8.ctex"]
|
|
||||||
|
|
||||||
[params]
|
|
||||||
|
|
||||||
compress/mode=2
|
|
||||||
compress/high_quality=false
|
|
||||||
compress/lossy_quality=0.7
|
|
||||||
compress/uastc_level=0
|
|
||||||
compress/rdo_quality_loss=0.0
|
|
||||||
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/channel_remap/red=0
|
|
||||||
process/channel_remap/green=1
|
|
||||||
process/channel_remap/blue=2
|
|
||||||
process/channel_remap/alpha=3
|
|
||||||
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
|
|
||||||
import_profile="managed_default"
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
[remap]
|
|
||||||
|
|
||||||
importer="texture"
|
|
||||||
type="CompressedTexture2D"
|
|
||||||
uid="uid://crci2on30ctoc"
|
|
||||||
path.s3tc="res://.godot/imported/grass_lite.png-f89cf6413257cd31582bd4f9f875ae85.s3tc.ctex"
|
|
||||||
path.etc2="res://.godot/imported/grass_lite.png-f89cf6413257cd31582bd4f9f875ae85.etc2.ctex"
|
|
||||||
metadata={
|
|
||||||
"imported_formats": ["s3tc_bptc", "etc2_astc"],
|
|
||||||
"vram_texture": true
|
|
||||||
}
|
|
||||||
|
|
||||||
[deps]
|
|
||||||
|
|
||||||
source_file="res://art/exported/environment/textures/grass_lite.png"
|
|
||||||
dest_files=["res://.godot/imported/grass_lite.png-f89cf6413257cd31582bd4f9f875ae85.s3tc.ctex", "res://.godot/imported/grass_lite.png-f89cf6413257cd31582bd4f9f875ae85.etc2.ctex"]
|
|
||||||
|
|
||||||
[params]
|
|
||||||
|
|
||||||
compress/mode=2
|
|
||||||
compress/high_quality=false
|
|
||||||
compress/lossy_quality=0.7
|
|
||||||
compress/uastc_level=0
|
|
||||||
compress/rdo_quality_loss=0.0
|
|
||||||
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/channel_remap/red=0
|
|
||||||
process/channel_remap/green=1
|
|
||||||
process/channel_remap/blue=2
|
|
||||||
process/channel_remap/alpha=3
|
|
||||||
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
|
|
||||||
import_profile="managed_default"
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
[remap]
|
|
||||||
|
|
||||||
importer="texture"
|
|
||||||
type="CompressedTexture2D"
|
|
||||||
uid="uid://btxhq3rdlum2"
|
|
||||||
path.s3tc="res://.godot/imported/grass_olive_tile.png-72072a382d0948818df97773afa5493f.s3tc.ctex"
|
|
||||||
path.etc2="res://.godot/imported/grass_olive_tile.png-72072a382d0948818df97773afa5493f.etc2.ctex"
|
|
||||||
metadata={
|
|
||||||
"imported_formats": ["s3tc_bptc", "etc2_astc"],
|
|
||||||
"vram_texture": true
|
|
||||||
}
|
|
||||||
|
|
||||||
[deps]
|
|
||||||
|
|
||||||
source_file="res://art/exported/environment/textures/grass_olive_tile.png"
|
|
||||||
dest_files=["res://.godot/imported/grass_olive_tile.png-72072a382d0948818df97773afa5493f.s3tc.ctex", "res://.godot/imported/grass_olive_tile.png-72072a382d0948818df97773afa5493f.etc2.ctex"]
|
|
||||||
|
|
||||||
[params]
|
|
||||||
|
|
||||||
compress/mode=2
|
|
||||||
compress/high_quality=false
|
|
||||||
compress/lossy_quality=0.7
|
|
||||||
compress/uastc_level=0
|
|
||||||
compress/rdo_quality_loss=0.0
|
|
||||||
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/channel_remap/red=0
|
|
||||||
process/channel_remap/green=1
|
|
||||||
process/channel_remap/blue=2
|
|
||||||
process/channel_remap/alpha=3
|
|
||||||
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
|
|
||||||
import_profile="managed_default"
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
[remap]
|
|
||||||
|
|
||||||
importer="texture"
|
|
||||||
type="CompressedTexture2D"
|
|
||||||
uid="uid://bnje415gq85nn"
|
|
||||||
path.s3tc="res://.godot/imported/sand.png-41fc96c392119deeba43c2d4e49d88a5.s3tc.ctex"
|
|
||||||
path.etc2="res://.godot/imported/sand.png-41fc96c392119deeba43c2d4e49d88a5.etc2.ctex"
|
|
||||||
metadata={
|
|
||||||
"imported_formats": ["s3tc_bptc", "etc2_astc"],
|
|
||||||
"vram_texture": true
|
|
||||||
}
|
|
||||||
|
|
||||||
[deps]
|
|
||||||
|
|
||||||
source_file="res://art/exported/environment/textures/sand.png"
|
|
||||||
dest_files=["res://.godot/imported/sand.png-41fc96c392119deeba43c2d4e49d88a5.s3tc.ctex", "res://.godot/imported/sand.png-41fc96c392119deeba43c2d4e49d88a5.etc2.ctex"]
|
|
||||||
|
|
||||||
[params]
|
|
||||||
|
|
||||||
compress/mode=2
|
|
||||||
compress/high_quality=false
|
|
||||||
compress/lossy_quality=0.7
|
|
||||||
compress/uastc_level=0
|
|
||||||
compress/rdo_quality_loss=0.0
|
|
||||||
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/channel_remap/red=0
|
|
||||||
process/channel_remap/green=1
|
|
||||||
process/channel_remap/blue=2
|
|
||||||
process/channel_remap/alpha=3
|
|
||||||
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
|
|
||||||
import_profile="managed_default"
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
[remap]
|
|
||||||
|
|
||||||
importer="texture"
|
|
||||||
type="CompressedTexture2D"
|
|
||||||
uid="uid://cyukfgkghrigk"
|
|
||||||
path="res://.godot/imported/sand_dark.png-ab4ec28097312f566fe7b882978a5858.ctex"
|
|
||||||
metadata={
|
|
||||||
"vram_texture": false
|
|
||||||
}
|
|
||||||
|
|
||||||
[deps]
|
|
||||||
|
|
||||||
source_file="res://art/exported/environment/textures/sand_dark.png"
|
|
||||||
dest_files=["res://.godot/imported/sand_dark.png-ab4ec28097312f566fe7b882978a5858.ctex"]
|
|
||||||
|
|
||||||
[params]
|
|
||||||
|
|
||||||
compress/mode=2
|
|
||||||
compress/high_quality=false
|
|
||||||
compress/lossy_quality=0.7
|
|
||||||
compress/uastc_level=0
|
|
||||||
compress/rdo_quality_loss=0.0
|
|
||||||
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/channel_remap/red=0
|
|
||||||
process/channel_remap/green=1
|
|
||||||
process/channel_remap/blue=2
|
|
||||||
process/channel_remap/alpha=3
|
|
||||||
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
|
|
||||||
import_profile="managed_default"
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
[remap]
|
|
||||||
|
|
||||||
importer="texture"
|
|
||||||
type="CompressedTexture2D"
|
|
||||||
uid="uid://whknttjshvv1"
|
|
||||||
path="res://.godot/imported/sand_lite.png-6962f2117f97a265a7074c81ef2370fa.ctex"
|
|
||||||
metadata={
|
|
||||||
"vram_texture": false
|
|
||||||
}
|
|
||||||
|
|
||||||
[deps]
|
|
||||||
|
|
||||||
source_file="res://art/exported/environment/textures/sand_lite.png"
|
|
||||||
dest_files=["res://.godot/imported/sand_lite.png-6962f2117f97a265a7074c81ef2370fa.ctex"]
|
|
||||||
|
|
||||||
[params]
|
|
||||||
|
|
||||||
compress/mode=2
|
|
||||||
compress/high_quality=false
|
|
||||||
compress/lossy_quality=0.7
|
|
||||||
compress/uastc_level=0
|
|
||||||
compress/rdo_quality_loss=0.0
|
|
||||||
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/channel_remap/red=0
|
|
||||||
process/channel_remap/green=1
|
|
||||||
process/channel_remap/blue=2
|
|
||||||
process/channel_remap/alpha=3
|
|
||||||
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
|
|
||||||
import_profile="managed_default"
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
[remap]
|
|
||||||
|
|
||||||
importer="texture"
|
|
||||||
type="CompressedTexture2D"
|
|
||||||
uid="uid://ctll4fbskvat8"
|
|
||||||
path.s3tc="res://.godot/imported/sandstone_heavy.png-c324c68e8c28417ecd3506dbd5f24ef5.s3tc.ctex"
|
|
||||||
path.etc2="res://.godot/imported/sandstone_heavy.png-c324c68e8c28417ecd3506dbd5f24ef5.etc2.ctex"
|
|
||||||
metadata={
|
|
||||||
"imported_formats": ["s3tc_bptc", "etc2_astc"],
|
|
||||||
"vram_texture": true
|
|
||||||
}
|
|
||||||
|
|
||||||
[deps]
|
|
||||||
|
|
||||||
source_file="res://art/exported/environment/textures/sandstone_heavy.png"
|
|
||||||
dest_files=["res://.godot/imported/sandstone_heavy.png-c324c68e8c28417ecd3506dbd5f24ef5.s3tc.ctex", "res://.godot/imported/sandstone_heavy.png-c324c68e8c28417ecd3506dbd5f24ef5.etc2.ctex"]
|
|
||||||
|
|
||||||
[params]
|
|
||||||
|
|
||||||
compress/mode=2
|
|
||||||
compress/high_quality=false
|
|
||||||
compress/lossy_quality=0.7
|
|
||||||
compress/uastc_level=0
|
|
||||||
compress/rdo_quality_loss=0.0
|
|
||||||
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/channel_remap/red=0
|
|
||||||
process/channel_remap/green=1
|
|
||||||
process/channel_remap/blue=2
|
|
||||||
process/channel_remap/alpha=3
|
|
||||||
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
|
|
||||||
import_profile="managed_default"
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
[remap]
|
|
||||||
|
|
||||||
importer="texture"
|
|
||||||
type="CompressedTexture2D"
|
|
||||||
uid="uid://bgbaiuts1c4sb"
|
|
||||||
path="res://.godot/imported/sandstone_lite.png-8d5c0c5c4e3a227e0e487db42a418aba.ctex"
|
|
||||||
metadata={
|
|
||||||
"vram_texture": false
|
|
||||||
}
|
|
||||||
|
|
||||||
[deps]
|
|
||||||
|
|
||||||
source_file="res://art/exported/environment/textures/sandstone_lite.png"
|
|
||||||
dest_files=["res://.godot/imported/sandstone_lite.png-8d5c0c5c4e3a227e0e487db42a418aba.ctex"]
|
|
||||||
|
|
||||||
[params]
|
|
||||||
|
|
||||||
compress/mode=2
|
|
||||||
compress/high_quality=false
|
|
||||||
compress/lossy_quality=0.7
|
|
||||||
compress/uastc_level=0
|
|
||||||
compress/rdo_quality_loss=0.0
|
|
||||||
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/channel_remap/red=0
|
|
||||||
process/channel_remap/green=1
|
|
||||||
process/channel_remap/blue=2
|
|
||||||
process/channel_remap/alpha=3
|
|
||||||
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
|
|
||||||
import_profile="managed_default"
|
|
||||||
|
Before Width: | Height: | Size: 8.8 KiB After Width: | Height: | Size: 8.8 KiB |
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 6.9 KiB |
|
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 9.4 KiB |
|
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 6.7 KiB |
|
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 7.5 KiB |
BIN
audio/ambience/rain_loop_ontario.ogg
Normal file
19
audio/ambience/rain_loop_ontario.ogg.import
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="oggvorbisstr"
|
||||||
|
type="AudioStreamOggVorbis"
|
||||||
|
uid="uid://cfqskba5j2iig"
|
||||||
|
path="res://.godot/imported/rain_loop_ontario.ogg-c206e8a7ee5398c9fdc6d59462050964.oggvorbisstr"
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://audio/ambience/rain_loop_ontario.ogg"
|
||||||
|
dest_files=["res://.godot/imported/rain_loop_ontario.ogg-c206e8a7ee5398c9fdc6d59462050964.oggvorbisstr"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
loop=false
|
||||||
|
loop_offset=0
|
||||||
|
bpm=0
|
||||||
|
beat_count=0
|
||||||
|
bar_beats=4
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
class_name FishingPresentation
|
class_name FishingPresentation
|
||||||
extends Node3D
|
extends Node3D
|
||||||
|
|
||||||
|
const WaterSurfaceMotionType = preload(
|
||||||
|
"res://world/water_surface_motion.gd"
|
||||||
|
)
|
||||||
|
|
||||||
signal cast_completed
|
signal cast_completed
|
||||||
signal outcome_completed(outcome: StringName)
|
signal outcome_completed(outcome: StringName)
|
||||||
signal presentation_interrupted
|
signal presentation_interrupted
|
||||||
|
|
@ -52,6 +56,7 @@ var _rod: Node3D
|
||||||
var _rod_tip: Marker3D
|
var _rod_tip: Marker3D
|
||||||
var _rod_neutral_rotation: Vector3
|
var _rod_neutral_rotation: Vector3
|
||||||
var _cast_arrival_position: Vector3
|
var _cast_arrival_position: Vector3
|
||||||
|
var _cast_tracks_water_surface: bool = false
|
||||||
var _bobber_surface_position: Vector3
|
var _bobber_surface_position: Vector3
|
||||||
var _bobber_idle_elapsed: float = 0.0
|
var _bobber_idle_elapsed: float = 0.0
|
||||||
var _active_tween: Tween
|
var _active_tween: Tween
|
||||||
|
|
@ -68,6 +73,9 @@ func _process(delta: float) -> void:
|
||||||
if _mode == VisualMode.FISHING and _bobber.visible:
|
if _mode == VisualMode.FISHING and _bobber.visible:
|
||||||
_bobber_idle_elapsed += delta
|
_bobber_idle_elapsed += delta
|
||||||
_apply_bobber_idle_motion()
|
_apply_bobber_idle_motion()
|
||||||
|
var bobber_position := _bobber.global_position
|
||||||
|
bobber_position.y += WaterSurfaceMotionType.get_default_height_offset()
|
||||||
|
_bobber.global_position = bobber_position
|
||||||
if _line_mode != LineMode.HIDDEN:
|
if _line_mode != LineMode.HIDDEN:
|
||||||
_update_line(delta)
|
_update_line(delta)
|
||||||
|
|
||||||
|
|
@ -154,6 +162,7 @@ func begin_cast(
|
||||||
_cast_arrival_position = (
|
_cast_arrival_position = (
|
||||||
blocked_landing_position if cast_is_blocked else target
|
blocked_landing_position if cast_is_blocked else target
|
||||||
)
|
)
|
||||||
|
_cast_tracks_water_surface = not cast_is_blocked
|
||||||
|
|
||||||
var cast_start: Vector3 = _rod_tip.global_position
|
var cast_start: Vector3 = _rod_tip.global_position
|
||||||
_bobber.global_position = cast_start
|
_bobber.global_position = cast_start
|
||||||
|
|
@ -289,6 +298,7 @@ func cleanup() -> void:
|
||||||
_bobber.scale = Vector3.ONE
|
_bobber.scale = Vector3.ONE
|
||||||
_bobber.rotation = Vector3.ZERO
|
_bobber.rotation = Vector3.ZERO
|
||||||
_cast_arrival_position = Vector3.ZERO
|
_cast_arrival_position = Vector3.ZERO
|
||||||
|
_cast_tracks_water_surface = false
|
||||||
_bobber_surface_position = Vector3.ZERO
|
_bobber_surface_position = Vector3.ZERO
|
||||||
_bobber_idle_elapsed = 0.0
|
_bobber_idle_elapsed = 0.0
|
||||||
set_line_mode(LineMode.HIDDEN)
|
set_line_mode(LineMode.HIDDEN)
|
||||||
|
|
@ -306,6 +316,11 @@ func _set_cast_sample(
|
||||||
) -> void:
|
) -> void:
|
||||||
var sample: Vector3 = start.lerp(target, progress)
|
var sample: Vector3 = start.lerp(target, progress)
|
||||||
sample.y += sin(progress * PI) * cast_arc_height
|
sample.y += sin(progress * PI) * cast_arc_height
|
||||||
|
if _cast_tracks_water_surface:
|
||||||
|
sample.y += (
|
||||||
|
WaterSurfaceMotionType.get_default_height_offset()
|
||||||
|
* smoothstep(0.72, 1.0, progress)
|
||||||
|
)
|
||||||
_bobber.global_position = sample
|
_bobber.global_position = sample
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,9 @@ extends RefCounted
|
||||||
const FishableWaterRegionType = preload(
|
const FishableWaterRegionType = preload(
|
||||||
"res://world/fishable_water_region.gd"
|
"res://world/fishable_water_region.gd"
|
||||||
)
|
)
|
||||||
|
const WaterSurfaceMotionType = preload(
|
||||||
|
"res://world/water_surface_motion.gd"
|
||||||
|
)
|
||||||
|
|
||||||
var has_surface: bool = false
|
var has_surface: bool = false
|
||||||
var position: Vector3 = Vector3.ZERO
|
var position: Vector3 = Vector3.ZERO
|
||||||
|
|
@ -19,7 +22,12 @@ func is_fishable() -> bool:
|
||||||
func get_marker_position(vertical_offset: float) -> Vector3:
|
func get_marker_position(vertical_offset: float) -> Vector3:
|
||||||
if not has_surface:
|
if not has_surface:
|
||||||
return position
|
return position
|
||||||
return position + normal.normalized() * vertical_offset
|
var presentation_offset := vertical_offset
|
||||||
|
if is_water_surface:
|
||||||
|
presentation_offset += (
|
||||||
|
WaterSurfaceMotionType.get_default_height_offset()
|
||||||
|
)
|
||||||
|
return position + normal.normalized() * presentation_offset
|
||||||
|
|
||||||
|
|
||||||
func get_bobber_position(solid_clearance: float) -> Vector3:
|
func get_bobber_position(solid_clearance: float) -> Vector3:
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
class_name RemoteFishingPresentation
|
class_name RemoteFishingPresentation
|
||||||
extends Node3D
|
extends Node3D
|
||||||
|
|
||||||
|
const WaterSurfaceMotionType = preload(
|
||||||
|
"res://world/water_surface_motion.gd"
|
||||||
|
)
|
||||||
|
|
||||||
signal return_completed
|
signal return_completed
|
||||||
|
|
||||||
var _owner: Player
|
var _owner: Player
|
||||||
|
|
@ -140,6 +144,10 @@ func _set_cast_sample(
|
||||||
) -> void:
|
) -> void:
|
||||||
_target = start.lerp(target, progress)
|
_target = start.lerp(target, progress)
|
||||||
_target.y += sin(progress * PI) * 2.0
|
_target.y += sin(progress * PI) * 2.0
|
||||||
|
_target.y += (
|
||||||
|
WaterSurfaceMotionType.get_default_height_offset()
|
||||||
|
* smoothstep(0.72, 1.0, progress)
|
||||||
|
)
|
||||||
_bobber.global_position = _target
|
_bobber.global_position = _target
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -152,7 +160,10 @@ func _on_cast_finished() -> void:
|
||||||
|
|
||||||
func _apply_bobber_idle_motion() -> void:
|
func _apply_bobber_idle_motion() -> void:
|
||||||
var phase: float = _bobber_idle_elapsed * 0.7 * TAU
|
var phase: float = _bobber_idle_elapsed * 0.7 * TAU
|
||||||
_target = _pending_target + Vector3.UP * sin(phase) * 0.035
|
_target = _pending_target + Vector3.UP * (
|
||||||
|
WaterSurfaceMotionType.get_default_height_offset()
|
||||||
|
+ sin(phase) * 0.035
|
||||||
|
)
|
||||||
_bobber.global_position = _target
|
_bobber.global_position = _target
|
||||||
_bobber.rotation.z = deg_to_rad(4.0) * sin(phase * 0.73)
|
_bobber.rotation.z = deg_to_rad(4.0) * sin(phase * 0.73)
|
||||||
|
|
||||||
|
|
|
||||||
18
main/main.gd
|
|
@ -91,6 +91,7 @@ const WorldWeatherServiceType = preload("res://world/world_weather_service.gd")
|
||||||
const NetworkWorldWeatherServiceType = preload(
|
const NetworkWorldWeatherServiceType = preload(
|
||||||
"res://network/network_world_weather_service.gd"
|
"res://network/network_world_weather_service.gd"
|
||||||
)
|
)
|
||||||
|
const RainAmbienceType = preload("res://world/rain_ambience.gd")
|
||||||
const PlayerJobServiceType = preload("res://jobs/player_job_service.gd")
|
const PlayerJobServiceType = preload("res://jobs/player_job_service.gd")
|
||||||
const NetworkJobServiceType = preload("res://network/network_job_service.gd")
|
const NetworkJobServiceType = preload("res://network/network_job_service.gd")
|
||||||
|
|
||||||
|
|
@ -210,10 +211,15 @@ var _pending_existing_root_path := ""
|
||||||
var _local_recovery_attempt_id: String = ""
|
var _local_recovery_attempt_id: String = ""
|
||||||
|
|
||||||
@onready var _shoreline_ambience: ShorelineAmbience = %ShorelineAmbience
|
@onready var _shoreline_ambience: ShorelineAmbience = %ShorelineAmbience
|
||||||
|
var _rain_ambience: RainAmbienceType
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
DisplayServer.window_set_title("NETfishing")
|
DisplayServer.window_set_title("NETfishing")
|
||||||
|
_rain_ambience = RainAmbienceType.new()
|
||||||
|
_rain_ambience.name = "RainAmbience"
|
||||||
|
add_child(_rain_ambience)
|
||||||
|
_rain_ambience.configure(_world_weather)
|
||||||
_shoreline_ambience.configure(
|
_shoreline_ambience.configure(
|
||||||
_player,
|
_player,
|
||||||
_test_world.get_saltwater_shoreline_mesh(),
|
_test_world.get_saltwater_shoreline_mesh(),
|
||||||
|
|
@ -1096,7 +1102,16 @@ func _unhandled_input(event: InputEvent) -> void:
|
||||||
|
|
||||||
|
|
||||||
func _process(_delta: float) -> void:
|
func _process(_delta: float) -> void:
|
||||||
_game_ui.set_shop_prompt_visible(_can_show_shop_prompt())
|
var show_shop_prompt := _can_show_shop_prompt()
|
||||||
|
var shop_prompt_anchor := (
|
||||||
|
_shop_interaction.get_prompt_anchor_position()
|
||||||
|
if _shop_interaction != null
|
||||||
|
else Vector3.ZERO
|
||||||
|
)
|
||||||
|
_game_ui.set_shop_prompt_visible(
|
||||||
|
show_shop_prompt,
|
||||||
|
shop_prompt_anchor,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
func _apply_runtime_settings(settings: PlayerSettingsType) -> void:
|
func _apply_runtime_settings(settings: PlayerSettingsType) -> void:
|
||||||
|
|
@ -1151,6 +1166,7 @@ func _reset_pixelation() -> void:
|
||||||
func _set_gameplay_active(active: bool) -> void:
|
func _set_gameplay_active(active: bool) -> void:
|
||||||
_gameplay_started = active
|
_gameplay_started = active
|
||||||
_shoreline_ambience.set_active(active)
|
_shoreline_ambience.set_active(active)
|
||||||
|
_rain_ambience.set_active(active)
|
||||||
_title_background.visible = not active
|
_title_background.visible = not active
|
||||||
_world_pixelation.set_gameplay_active(active)
|
_world_pixelation.set_gameplay_active(active)
|
||||||
_ui_pixelation.set_gameplay_active(active)
|
_ui_pixelation.set_gameplay_active(active)
|
||||||
|
|
|
||||||
|
|
@ -25,30 +25,27 @@ const DEFAULT_PROFILE: Dictionary = {
|
||||||
"process/hdr_clamp_exposure": false,
|
"process/hdr_clamp_exposure": false,
|
||||||
"process/size_limit": 0,
|
"process/size_limit": 0,
|
||||||
"detect_3d/compress_to": 0,
|
"detect_3d/compress_to": 0,
|
||||||
"import_profile": "managed_default",
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const CUSTOM_PROFILE_KEY := "custom"
|
var apply_changes: bool = false
|
||||||
|
var verbose: bool = false
|
||||||
var apply_changes := false
|
|
||||||
var verbose := false
|
|
||||||
var roots: PackedStringArray = PackedStringArray()
|
var roots: PackedStringArray = PackedStringArray()
|
||||||
var user_root_overrides := false
|
var should_exit: bool = false
|
||||||
var should_exit := false
|
|
||||||
|
|
||||||
var skipped_custom := 0
|
var errors: int = 0
|
||||||
var skipped_non_texture := 0
|
var skipped_non_texture: int = 0
|
||||||
var skipped_unreadable := 0
|
var updated: int = 0
|
||||||
var updated := 0
|
var unchanged: int = 0
|
||||||
var unchanged := 0
|
|
||||||
|
|
||||||
func _initialize() -> void:
|
func _initialize() -> void:
|
||||||
_parse_args()
|
_parse_args()
|
||||||
if should_exit:
|
if should_exit:
|
||||||
return
|
return
|
||||||
if roots.is_empty() and not user_root_overrides:
|
if roots.is_empty():
|
||||||
roots.append("res://")
|
printerr("At least one --root path is required.")
|
||||||
|
quit(2)
|
||||||
|
return
|
||||||
|
|
||||||
for root: String in roots:
|
for root: String in roots:
|
||||||
var normalized_root := root
|
var normalized_root := root
|
||||||
|
|
@ -63,7 +60,7 @@ func _initialize() -> void:
|
||||||
_scan_directory(normalized_root)
|
_scan_directory(normalized_root)
|
||||||
|
|
||||||
print_summary()
|
print_summary()
|
||||||
quit(0)
|
quit(1 if errors > 0 else 0)
|
||||||
|
|
||||||
|
|
||||||
func _parse_args() -> void:
|
func _parse_args() -> void:
|
||||||
|
|
@ -91,23 +88,21 @@ func _parse_args() -> void:
|
||||||
i += 1
|
i += 1
|
||||||
roots.append(args[i])
|
roots.append(args[i])
|
||||||
i += 1
|
i += 1
|
||||||
user_root_overrides = true
|
|
||||||
elif arg.begins_with("--root="):
|
elif arg.begins_with("--root="):
|
||||||
roots.append(arg.trim_prefix("--root="))
|
roots.append(arg.trim_prefix("--root="))
|
||||||
i += 1
|
i += 1
|
||||||
user_root_overrides = true
|
|
||||||
else:
|
else:
|
||||||
if arg.begins_with("-"):
|
printerr("Unknown argument: ", arg)
|
||||||
_print("Ignoring unknown option: %s" % arg)
|
should_exit = true
|
||||||
i += 1
|
quit(2)
|
||||||
continue
|
return
|
||||||
|
|
||||||
|
|
||||||
func _scan_directory(path: String) -> void:
|
func _scan_directory(path: String) -> void:
|
||||||
var dir := DirAccess.open(path)
|
var dir := DirAccess.open(path)
|
||||||
if dir == null:
|
if dir == null:
|
||||||
printerr("Failed to open root: ", path)
|
printerr("Failed to open root: ", path)
|
||||||
skipped_non_texture += 1
|
errors += 1
|
||||||
return
|
return
|
||||||
|
|
||||||
dir.list_dir_begin()
|
dir.list_dir_begin()
|
||||||
|
|
@ -136,7 +131,7 @@ func _handle_import_file(import_path: String) -> void:
|
||||||
var err := cfg.load(import_path)
|
var err := cfg.load(import_path)
|
||||||
if err != OK:
|
if err != OK:
|
||||||
printerr("Could not load import settings: ", import_path, " (", err, ")")
|
printerr("Could not load import settings: ", import_path, " (", err, ")")
|
||||||
skipped_unreadable += 1
|
errors += 1
|
||||||
return
|
return
|
||||||
|
|
||||||
if not cfg.has_section("remap") or not cfg.has_section_key("remap", "importer"):
|
if not cfg.has_section("remap") or not cfg.has_section_key("remap", "importer"):
|
||||||
|
|
@ -147,15 +142,6 @@ func _handle_import_file(import_path: String) -> void:
|
||||||
skipped_non_texture += 1
|
skipped_non_texture += 1
|
||||||
return
|
return
|
||||||
|
|
||||||
var current_profile := ""
|
|
||||||
if cfg.has_section("params") and cfg.has_section_key("params", "import_profile"):
|
|
||||||
current_profile = str(cfg.get_value("params", "import_profile"))
|
|
||||||
if current_profile == CUSTOM_PROFILE_KEY:
|
|
||||||
skipped_custom += 1
|
|
||||||
if verbose:
|
|
||||||
_print("Skipping custom profile: %s" % import_path)
|
|
||||||
return
|
|
||||||
|
|
||||||
var changed := false
|
var changed := false
|
||||||
for section_name: String in DEFAULT_PROFILE.keys():
|
for section_name: String in DEFAULT_PROFILE.keys():
|
||||||
var section_data: Dictionary = DEFAULT_PROFILE[section_name]
|
var section_data: Dictionary = DEFAULT_PROFILE[section_name]
|
||||||
|
|
@ -178,7 +164,7 @@ func _handle_import_file(import_path: String) -> void:
|
||||||
err = cfg.save(import_path)
|
err = cfg.save(import_path)
|
||||||
if err != OK:
|
if err != OK:
|
||||||
printerr("Could not save import settings: ", import_path, " (", err, ")")
|
printerr("Could not save import settings: ", import_path, " (", err, ")")
|
||||||
skipped_unreadable += 1
|
errors += 1
|
||||||
return
|
return
|
||||||
if verbose:
|
if verbose:
|
||||||
_print("Updated: %s" % import_path)
|
_print("Updated: %s" % import_path)
|
||||||
|
|
@ -190,11 +176,18 @@ func _handle_import_file(import_path: String) -> void:
|
||||||
|
|
||||||
func print_summary() -> void:
|
func print_summary() -> void:
|
||||||
if apply_changes:
|
if apply_changes:
|
||||||
_print("Texture import normalization complete: updated=%d unchanged=%d skipped_custom=%d skipped_non_texture=%d skipped_unreadable=%d" % [updated, unchanged, skipped_custom, skipped_non_texture, skipped_unreadable])
|
_print(
|
||||||
|
"Texture import normalization complete: "
|
||||||
|
+ "updated=%d unchanged=%d skipped_non_texture=%d errors=%d"
|
||||||
|
% [updated, unchanged, skipped_non_texture, errors]
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
_print("Dry-run complete: would-update=%d unchanged=%d skipped_custom=%d skipped_non_texture=%d skipped_unreadable=%d" % [updated, unchanged, skipped_custom, skipped_non_texture, skipped_unreadable])
|
_print(
|
||||||
|
"Dry-run complete: "
|
||||||
|
+ "would-update=%d unchanged=%d skipped_non_texture=%d errors=%d"
|
||||||
|
% [updated, unchanged, skipped_non_texture, errors]
|
||||||
|
)
|
||||||
_print("Pass --apply to write normalized settings into .import files.")
|
_print("Pass --apply to write normalized settings into .import files.")
|
||||||
_print("Set [params] import_profile = \"custom\" to opt a texture out of default normalization.")
|
|
||||||
|
|
||||||
|
|
||||||
func _print(msg: String) -> void:
|
func _print(msg: String) -> void:
|
||||||
|
|
@ -207,6 +200,5 @@ func _print_usage() -> void:
|
||||||
print()
|
print()
|
||||||
print("Defaults:")
|
print("Defaults:")
|
||||||
print("- Dry-run only unless --apply is provided")
|
print("- Dry-run only unless --apply is provided")
|
||||||
print("- Scans all .import files under each root")
|
print("- Requires at least one explicit --root path")
|
||||||
print("- Skips .godot")
|
print("- Skips .godot")
|
||||||
print("- Skips textures marked with [params] import_profile = \"custom\"")
|
|
||||||
|
|
|
||||||
|
|
@ -5,15 +5,26 @@ set -euo pipefail
|
||||||
readonly SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
readonly SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
readonly PROJECT_ROOT="$(cd -- "${SCRIPT_DIR}/.." && pwd)"
|
readonly PROJECT_ROOT="$(cd -- "${SCRIPT_DIR}/.." && pwd)"
|
||||||
readonly GODOT_BIN="${GODOT_BIN:-godot}"
|
readonly GODOT_BIN="${GODOT_BIN:-godot}"
|
||||||
readonly ROOT_PATH="${1:-res://}"
|
|
||||||
|
if [[ $# -ne 1 ]]; then
|
||||||
|
echo "usage: scripts/normalize_texture_profiles.sh res://path/to/textures" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
readonly ROOT_PATH="$1"
|
||||||
|
|
||||||
readonly WORK_ROOT="$(mktemp -d -t netfishing-godot-data.XXXXXX)"
|
readonly WORK_ROOT="$(mktemp -d -t netfishing-godot-data.XXXXXX)"
|
||||||
trap 'rm -rf -- "${WORK_ROOT}"' EXIT
|
trap 'rm -rf -- "${WORK_ROOT}"' EXIT
|
||||||
|
|
||||||
cd "${PROJECT_ROOT}"
|
cd "${PROJECT_ROOT}"
|
||||||
|
|
||||||
XDG_DATA_HOME="${WORK_ROOT}/data" \
|
export XDG_DATA_HOME="${WORK_ROOT}/data"
|
||||||
XDG_CONFIG_HOME="${WORK_ROOT}/config" \
|
export XDG_CONFIG_HOME="${WORK_ROOT}/config"
|
||||||
XDG_CACHE_HOME="${WORK_ROOT}/cache" \
|
export XDG_CACHE_HOME="${WORK_ROOT}/cache"
|
||||||
${GODOT_BIN} --headless --path . \
|
|
||||||
|
"${GODOT_BIN}" --headless --path . \
|
||||||
--script scripts/normalize_texture_imports.gd -- --apply --root "${ROOT_PATH}"
|
--script scripts/normalize_texture_imports.gd -- --apply --root "${ROOT_PATH}"
|
||||||
|
|
||||||
|
# Synchronize Godot-generated remap and destination metadata with the updated
|
||||||
|
# import parameters before the caller reviews or commits the sidecars.
|
||||||
|
"${GODOT_BIN}" --headless --path . --import
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ readonly -a QUICK_TESTS=(
|
||||||
"tests/player_experience_validation.gd"
|
"tests/player_experience_validation.gd"
|
||||||
"tests/shoreline_ambience_validation.gd"
|
"tests/shoreline_ambience_validation.gd"
|
||||||
"tests/surface_drawing_validation.gd"
|
"tests/surface_drawing_validation.gd"
|
||||||
"tests/terrain_projection_validation.gd"
|
"tests/terrain_blender_material_validation.gd"
|
||||||
"tests/world_time_validation.gd"
|
"tests/world_time_validation.gd"
|
||||||
"tests/world_weather_validation.gd"
|
"tests/world_weather_validation.gd"
|
||||||
)
|
)
|
||||||
|
|
|
||||||
133
tests/terrain_blender_material_validation.gd
Normal file
|
|
@ -0,0 +1,133 @@
|
||||||
|
extends SceneTree
|
||||||
|
|
||||||
|
const REGION := preload("res://world/regions/starter_island_region.tscn")
|
||||||
|
const TERRAIN_MODEL_PATH: String = (
|
||||||
|
"res://art/exported/environment/terrain/starter_island.glb"
|
||||||
|
)
|
||||||
|
const EXPECTED_SURFACES: Array[String] = [
|
||||||
|
"grass_lite",
|
||||||
|
"dirt",
|
||||||
|
"cliff_wall",
|
||||||
|
"dirt_wall",
|
||||||
|
"sand",
|
||||||
|
"sandstone_heavy",
|
||||||
|
]
|
||||||
|
|
||||||
|
var failures: Array[String] = []
|
||||||
|
|
||||||
|
|
||||||
|
func _initialize() -> void:
|
||||||
|
call_deferred("_run")
|
||||||
|
|
||||||
|
|
||||||
|
func _run() -> void:
|
||||||
|
var model_scene := load(TERRAIN_MODEL_PATH) as PackedScene
|
||||||
|
_check(model_scene != null, "Starter-island GLB must load as a scene.")
|
||||||
|
if model_scene == null:
|
||||||
|
_finish()
|
||||||
|
return
|
||||||
|
|
||||||
|
var model_root := model_scene.instantiate()
|
||||||
|
var model_terrain := _find_mesh(model_root, "starter_island")
|
||||||
|
_check(model_terrain != null, "GLB must contain the starter_island mesh.")
|
||||||
|
_check(_find_mesh(model_root, "Cube") == null, "GLB must not contain Cube.")
|
||||||
|
if model_terrain != null:
|
||||||
|
_validate_terrain_materials(model_terrain, "GLB")
|
||||||
|
model_root.free()
|
||||||
|
|
||||||
|
var region := REGION.instantiate()
|
||||||
|
root.add_child(region)
|
||||||
|
await process_frame
|
||||||
|
var runtime_terrain := region.get_node_or_null(
|
||||||
|
"Terrain/Visual/starter_island"
|
||||||
|
) as MeshInstance3D
|
||||||
|
_check(runtime_terrain != null, "Region must contain the terrain mesh.")
|
||||||
|
if runtime_terrain != null:
|
||||||
|
_validate_terrain_materials(runtime_terrain, "Region")
|
||||||
|
for surface_index: int in runtime_terrain.mesh.get_surface_count():
|
||||||
|
_check(
|
||||||
|
runtime_terrain.get_surface_override_material(surface_index) == null,
|
||||||
|
"Region surface %d must use its Blender material." % surface_index,
|
||||||
|
)
|
||||||
|
region.free()
|
||||||
|
_finish()
|
||||||
|
|
||||||
|
|
||||||
|
func _find_mesh(node: Node, mesh_name: String) -> MeshInstance3D:
|
||||||
|
if node is MeshInstance3D and node.name == mesh_name:
|
||||||
|
return node as MeshInstance3D
|
||||||
|
for child: Node in node.get_children():
|
||||||
|
var match := _find_mesh(child, mesh_name)
|
||||||
|
if match != null:
|
||||||
|
return match
|
||||||
|
return null
|
||||||
|
|
||||||
|
|
||||||
|
func _validate_terrain_materials(
|
||||||
|
terrain: MeshInstance3D,
|
||||||
|
context: String,
|
||||||
|
) -> void:
|
||||||
|
_check(terrain.mesh != null, "%s terrain must have a mesh." % context)
|
||||||
|
if terrain.mesh == null:
|
||||||
|
return
|
||||||
|
_check(
|
||||||
|
terrain.mesh.get_surface_count() == EXPECTED_SURFACES.size(),
|
||||||
|
"%s terrain must have %d surfaces." % [
|
||||||
|
context,
|
||||||
|
EXPECTED_SURFACES.size(),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
var checked_count := mini(
|
||||||
|
terrain.mesh.get_surface_count(),
|
||||||
|
EXPECTED_SURFACES.size(),
|
||||||
|
)
|
||||||
|
for surface_index: int in checked_count:
|
||||||
|
var expected_name := EXPECTED_SURFACES[surface_index]
|
||||||
|
_check(
|
||||||
|
terrain.mesh.surface_get_name(surface_index) == expected_name,
|
||||||
|
"%s surface %d must be named %s." % [
|
||||||
|
context,
|
||||||
|
surface_index,
|
||||||
|
expected_name,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
var material := terrain.get_active_material(surface_index)
|
||||||
|
_check(
|
||||||
|
material is StandardMaterial3D,
|
||||||
|
"%s surface %s must use a Blender material." % [
|
||||||
|
context,
|
||||||
|
expected_name,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
if material is StandardMaterial3D:
|
||||||
|
var texture := (material as StandardMaterial3D).albedo_texture
|
||||||
|
_check(
|
||||||
|
texture != null,
|
||||||
|
"%s surface %s must have an embedded texture." % [
|
||||||
|
context,
|
||||||
|
expected_name,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
if texture != null:
|
||||||
|
_check(
|
||||||
|
texture.resource_path.begins_with(TERRAIN_MODEL_PATH + "::"),
|
||||||
|
"%s surface %s must source its texture from the GLB." % [
|
||||||
|
context,
|
||||||
|
expected_name,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _check(condition: bool, message: String) -> void:
|
||||||
|
if not condition:
|
||||||
|
failures.append(message)
|
||||||
|
|
||||||
|
|
||||||
|
func _finish() -> void:
|
||||||
|
if failures.is_empty():
|
||||||
|
print("Blender terrain material validation: PASS")
|
||||||
|
quit(0)
|
||||||
|
return
|
||||||
|
for failure: String in failures:
|
||||||
|
printerr("Blender terrain material validation: ", failure)
|
||||||
|
quit(1)
|
||||||
|
|
@ -1,78 +0,0 @@
|
||||||
extends SceneTree
|
|
||||||
|
|
||||||
const REGION := preload("res://world/regions/starter_island_region.tscn")
|
|
||||||
|
|
||||||
|
|
||||||
func _initialize() -> void:
|
|
||||||
call_deferred("_run")
|
|
||||||
|
|
||||||
|
|
||||||
func _run() -> void:
|
|
||||||
var region := REGION.instantiate()
|
|
||||||
root.add_child(region)
|
|
||||||
await process_frame
|
|
||||||
var terrain := region.get_node(
|
|
||||||
"Terrain/Visual/starter_island"
|
|
||||||
) as MeshInstance3D
|
|
||||||
assert(terrain != null)
|
|
||||||
assert(terrain.mesh.get_surface_count() == 5)
|
|
||||||
assert(terrain.mesh.surface_get_name(0) == "grass_lite")
|
|
||||||
assert(terrain.mesh.surface_get_name(1) == "dirt")
|
|
||||||
assert(terrain.mesh.surface_get_name(2) == "sand")
|
|
||||||
assert(terrain.mesh.surface_get_name(3) == "cliff_wall")
|
|
||||||
assert(terrain.mesh.surface_get_name(4) == "dirt_wall")
|
|
||||||
|
|
||||||
_validate_projected_material(
|
|
||||||
terrain.get_surface_override_material(0),
|
|
||||||
"res://art/exported/environment/textures/grass_lite.png"
|
|
||||||
)
|
|
||||||
_validate_projected_material(
|
|
||||||
terrain.get_surface_override_material(2),
|
|
||||||
"res://art/exported/environment/textures/sand.png"
|
|
||||||
)
|
|
||||||
for surface: int in [1, 3, 4]:
|
|
||||||
assert(terrain.get_surface_override_material(surface) == null)
|
|
||||||
assert(terrain.mesh.surface_get_material(surface) is StandardMaterial3D)
|
|
||||||
|
|
||||||
print("Terrain projection validation: PASS")
|
|
||||||
region.free()
|
|
||||||
quit()
|
|
||||||
|
|
||||||
|
|
||||||
func _validate_projected_material(
|
|
||||||
material: Material,
|
|
||||||
expected_texture_path: String,
|
|
||||||
) -> void:
|
|
||||||
assert(material is ShaderMaterial)
|
|
||||||
var shader_material := material as ShaderMaterial
|
|
||||||
assert(
|
|
||||||
shader_material.shader.resource_path
|
|
||||||
== "res://world/materials/terrain_surface_projection.gdshader"
|
|
||||||
)
|
|
||||||
assert(
|
|
||||||
is_equal_approx(
|
|
||||||
float(shader_material.get_shader_parameter("tile_world_size")),
|
|
||||||
2.5
|
|
||||||
)
|
|
||||||
)
|
|
||||||
assert(
|
|
||||||
is_equal_approx(
|
|
||||||
float(shader_material.get_shader_parameter("blend_sharpness")),
|
|
||||||
6.0
|
|
||||||
)
|
|
||||||
)
|
|
||||||
assert(
|
|
||||||
is_equal_approx(
|
|
||||||
float(shader_material.get_shader_parameter("top_projection_bias")),
|
|
||||||
2.0
|
|
||||||
)
|
|
||||||
)
|
|
||||||
var texture: Texture2D = shader_material.get_shader_parameter(
|
|
||||||
"albedo_texture"
|
|
||||||
) as Texture2D
|
|
||||||
assert(texture != null)
|
|
||||||
assert(texture.resource_path == expected_texture_path)
|
|
||||||
var imported_image: Image = texture.get_image()
|
|
||||||
assert(imported_image != null)
|
|
||||||
assert(not imported_image.has_mipmaps())
|
|
||||||
assert(imported_image.get_format() == Image.FORMAT_RGBA8)
|
|
||||||
|
|
@ -3,17 +3,25 @@ extends Area3D
|
||||||
|
|
||||||
signal local_player_range_changed(in_range: bool)
|
signal local_player_range_changed(in_range: bool)
|
||||||
|
|
||||||
|
@export_node_path("Node3D") var look_character_path: NodePath = ^"../Shopkeeper"
|
||||||
|
|
||||||
var _local_player: Player
|
var _local_player: Player
|
||||||
var _local_player_in_range: bool = false
|
var _local_player_in_range: bool = false
|
||||||
|
var _look_character: WorldCharacterDisplay
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
|
_look_character = get_node_or_null(
|
||||||
|
look_character_path
|
||||||
|
) as WorldCharacterDisplay
|
||||||
body_entered.connect(_on_body_entered)
|
body_entered.connect(_on_body_entered)
|
||||||
body_exited.connect(_on_body_exited)
|
body_exited.connect(_on_body_exited)
|
||||||
|
|
||||||
|
|
||||||
func setup_local_player(player: Player) -> void:
|
func setup_local_player(player: Player) -> void:
|
||||||
_local_player = player
|
_local_player = player
|
||||||
|
if _look_character != null:
|
||||||
|
_look_character.set_head_look_target(player)
|
||||||
_refresh_overlaps()
|
_refresh_overlaps()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -29,6 +37,12 @@ func is_avatar_in_range(avatar: Player) -> bool:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func get_prompt_anchor_position() -> Vector3:
|
||||||
|
if _look_character != null:
|
||||||
|
return _look_character.get_head_anchor_position()
|
||||||
|
return global_position + Vector3.UP * 1.25
|
||||||
|
|
||||||
|
|
||||||
func _refresh_overlaps() -> void:
|
func _refresh_overlaps() -> void:
|
||||||
var is_overlapping: bool = (
|
var is_overlapping: bool = (
|
||||||
_local_player != null
|
_local_player != null
|
||||||
|
|
@ -49,6 +63,8 @@ func _on_body_exited(body: Node3D) -> void:
|
||||||
|
|
||||||
|
|
||||||
func _set_local_player_in_range(in_range: bool) -> void:
|
func _set_local_player_in_range(in_range: bool) -> void:
|
||||||
|
if _look_character != null:
|
||||||
|
_look_character.set_head_look_active(in_range)
|
||||||
if _local_player_in_range == in_range:
|
if _local_player_in_range == in_range:
|
||||||
return
|
return
|
||||||
_local_player_in_range = in_range
|
_local_player_in_range = in_range
|
||||||
|
|
|
||||||
|
|
@ -1,90 +1,27 @@
|
||||||
[gd_scene load_steps=11 format=3]
|
[gd_scene load_steps=5 format=3]
|
||||||
|
|
||||||
[ext_resource type="Script" path="res://world/fishing_shop_interaction.gd" id="1_shop"]
|
[ext_resource type="Script" path="res://world/fishing_shop_interaction.gd" id="1_shop"]
|
||||||
[ext_resource type="Script" path="res://world/world_prop.gd" id="2_prop"]
|
[ext_resource type="PackedScene" path="res://art/exported/characters/base/netfishing_base_character.glb" id="2_character"]
|
||||||
|
[ext_resource type="Script" path="res://world/world_character_display.gd" id="3_display"]
|
||||||
[sub_resource type="BoxMesh" id="CounterMesh"]
|
|
||||||
resource_local_to_scene = true
|
|
||||||
size = Vector3(4.2, 1.25, 1.8)
|
|
||||||
|
|
||||||
[sub_resource type="BoxMesh" id="RoofMesh"]
|
|
||||||
resource_local_to_scene = true
|
|
||||||
size = Vector3(5.2, 0.35, 3.2)
|
|
||||||
|
|
||||||
[sub_resource type="BoxMesh" id="PostMesh"]
|
|
||||||
resource_local_to_scene = true
|
|
||||||
size = Vector3(0.28, 3.2, 0.28)
|
|
||||||
|
|
||||||
[sub_resource type="BoxMesh" id="BackMesh"]
|
|
||||||
resource_local_to_scene = true
|
|
||||||
size = Vector3(5, 2.8, 0.25)
|
|
||||||
|
|
||||||
[sub_resource type="StandardMaterial3D" id="PrimaryMaterial"]
|
|
||||||
resource_local_to_scene = true
|
|
||||||
albedo_color = Color(0.12, 0.68, 0.62, 1)
|
|
||||||
|
|
||||||
[sub_resource type="StandardMaterial3D" id="AccentMaterial"]
|
|
||||||
resource_local_to_scene = true
|
|
||||||
albedo_color = Color(1, 0.68, 0.18, 1)
|
|
||||||
|
|
||||||
[sub_resource type="StandardMaterial3D" id="BackMaterial"]
|
|
||||||
resource_local_to_scene = true
|
|
||||||
albedo_color = Color(0.13, 0.34, 0.38, 1)
|
|
||||||
|
|
||||||
[sub_resource type="SphereShape3D" id="InteractionShape"]
|
[sub_resource type="SphereShape3D" id="InteractionShape"]
|
||||||
resource_local_to_scene = true
|
resource_local_to_scene = true
|
||||||
radius = 3.0
|
radius = 3.0
|
||||||
|
|
||||||
[sub_resource type="BoxShape3D" id="CounterShape"]
|
|
||||||
resource_local_to_scene = true
|
|
||||||
size = Vector3(4.2, 1.25, 1.8)
|
|
||||||
|
|
||||||
[node name="FishingShopWorld" type="Node3D"]
|
[node name="FishingShopWorld" type="Node3D"]
|
||||||
script = ExtResource("2_prop")
|
|
||||||
collision_root_path = NodePath("Collision")
|
|
||||||
interaction_area_path = NodePath("InteractionArea")
|
|
||||||
entrance_marker_path = NodePath("EntranceMarker")
|
|
||||||
|
|
||||||
[node name="Visual" type="Node3D" parent="."]
|
[node name="Shopkeeper" parent="." instance=ExtResource("2_character")]
|
||||||
|
script = ExtResource("3_display")
|
||||||
|
species_id = "pointy"
|
||||||
|
fur_color_id = "white"
|
||||||
|
ears_id = "pointy_wide"
|
||||||
|
eyes_id = "punk_purple"
|
||||||
|
nose_id = "triangle_small"
|
||||||
|
mouth_id = "slanted"
|
||||||
|
head_look_enabled = true
|
||||||
|
|
||||||
[node name="Counter" type="MeshInstance3D" parent="Visual"]
|
[node name="AnimationPlayer" parent="Shopkeeper"]
|
||||||
position = Vector3(0, 0.625, 0)
|
autoplay = "idle"
|
||||||
mesh = SubResource("CounterMesh")
|
|
||||||
material_override = SubResource("PrimaryMaterial")
|
|
||||||
|
|
||||||
[node name="Back" type="MeshInstance3D" parent="Visual"]
|
|
||||||
position = Vector3(0, 1.4, 1.25)
|
|
||||||
mesh = SubResource("BackMesh")
|
|
||||||
material_override = SubResource("BackMaterial")
|
|
||||||
|
|
||||||
[node name="Roof" type="MeshInstance3D" parent="Visual"]
|
|
||||||
position = Vector3(0, 3.15, 0.35)
|
|
||||||
mesh = SubResource("RoofMesh")
|
|
||||||
material_override = SubResource("AccentMaterial")
|
|
||||||
|
|
||||||
[node name="WestPost" type="MeshInstance3D" parent="Visual"]
|
|
||||||
position = Vector3(-2.15, 1.6, -0.8)
|
|
||||||
mesh = SubResource("PostMesh")
|
|
||||||
material_override = SubResource("PrimaryMaterial")
|
|
||||||
|
|
||||||
[node name="EastPost" type="MeshInstance3D" parent="Visual"]
|
|
||||||
position = Vector3(2.15, 1.6, -0.8)
|
|
||||||
mesh = SubResource("PostMesh")
|
|
||||||
material_override = SubResource("PrimaryMaterial")
|
|
||||||
|
|
||||||
[node name="ShopName" type="Label3D" parent="Visual"]
|
|
||||||
position = Vector3(0, 3.7, 0)
|
|
||||||
text = "fishing shop"
|
|
||||||
font_size = 42
|
|
||||||
outline_size = 9
|
|
||||||
billboard = 1
|
|
||||||
no_depth_test = true
|
|
||||||
|
|
||||||
[node name="Collision" type="StaticBody3D" parent="."]
|
|
||||||
|
|
||||||
[node name="CounterShape" type="CollisionShape3D" parent="Collision"]
|
|
||||||
position = Vector3(0, 0.625, 0)
|
|
||||||
shape = SubResource("CounterShape")
|
|
||||||
|
|
||||||
[node name="InteractionArea" type="Area3D" parent="."]
|
[node name="InteractionArea" type="Area3D" parent="."]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
|
|
@ -95,6 +32,3 @@ script = ExtResource("1_shop")
|
||||||
|
|
||||||
[node name="InteractionShape" type="CollisionShape3D" parent="InteractionArea"]
|
[node name="InteractionShape" type="CollisionShape3D" parent="InteractionArea"]
|
||||||
shape = SubResource("InteractionShape")
|
shape = SubResource("InteractionShape")
|
||||||
|
|
||||||
[node name="EntranceMarker" type="Marker3D" parent="."]
|
|
||||||
position = Vector3(0, 0, -2)
|
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,16 @@
|
||||||
shader_type spatial;
|
shader_type spatial;
|
||||||
render_mode blend_mix, depth_draw_never, cull_disabled, unshaded;
|
render_mode blend_mix, depth_draw_never, cull_disabled, unshaded;
|
||||||
|
|
||||||
uniform vec4 lead_color : source_color = vec4(0.620, 0.875, 0.855, 1.0);
|
uniform vec4 lead_color : source_color = vec4(0.780, 0.960, 0.930, 1.0);
|
||||||
uniform vec4 trail_color : source_color = vec4(0.430, 0.780, 0.770, 1.0);
|
uniform vec4 trail_color : source_color = vec4(0.500, 0.840, 0.820, 1.0);
|
||||||
uniform float ribbon_width : hint_range(0.2, 2.0, 0.01) = 0.85;
|
uniform float ribbon_width : hint_range(0.2, 2.0, 0.01) = 1.35;
|
||||||
uniform float land_inset : hint_range(0.0, 0.4, 0.01) = 0.10;
|
uniform float land_inset : hint_range(0.0, 0.4, 0.01) = 0.12;
|
||||||
uniform float tide_speed : hint_range(0.1, 3.0, 0.01) = 1.42;
|
uniform float tide_speed : hint_range(0.1, 3.0, 0.01) = 1.42;
|
||||||
uniform float tide_distance : hint_range(0.0, 0.6, 0.01) = 0.30;
|
uniform float tide_distance : hint_range(0.0, 0.6, 0.01) = 0.42;
|
||||||
uniform float lead_width : hint_range(0.01, 0.2, 0.005) = 0.065;
|
uniform float lead_width : hint_range(0.01, 0.2, 0.005) = 0.14;
|
||||||
uniform float trail_width : hint_range(0.02, 0.4, 0.005) = 0.18;
|
uniform float trail_width : hint_range(0.02, 0.4, 0.005) = 0.32;
|
||||||
uniform float lead_strength : hint_range(0.0, 1.0, 0.01) = 0.48;
|
uniform float lead_strength : hint_range(0.0, 1.0, 0.01) = 0.88;
|
||||||
uniform float trail_strength : hint_range(0.0, 1.0, 0.01) = 0.25;
|
uniform float trail_strength : hint_range(0.0, 1.0, 0.01) = 0.52;
|
||||||
uniform float phase_scale : hint_range(0.001, 0.2, 0.001) = 0.035;
|
uniform float phase_scale : hint_range(0.001, 0.2, 0.001) = 0.035;
|
||||||
uniform float phase_strength : hint_range(0.0, 2.0, 0.01) = 0.55;
|
uniform float phase_strength : hint_range(0.0, 2.0, 0.01) = 0.55;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,15 +5,15 @@
|
||||||
[resource]
|
[resource]
|
||||||
render_priority = 1
|
render_priority = 1
|
||||||
shader = ExtResource("1_shader")
|
shader = ExtResource("1_shader")
|
||||||
shader_parameter/lead_color = Color(0.619608, 0.87451, 0.854902, 1)
|
shader_parameter/lead_color = Color(0.78, 0.96, 0.93, 1)
|
||||||
shader_parameter/trail_color = Color(0.431373, 0.780392, 0.768627, 1)
|
shader_parameter/trail_color = Color(0.5, 0.84, 0.82, 1)
|
||||||
shader_parameter/ribbon_width = 0.85
|
shader_parameter/ribbon_width = 1.35
|
||||||
shader_parameter/land_inset = 0.1
|
shader_parameter/land_inset = 0.12
|
||||||
shader_parameter/tide_speed = 1.42
|
shader_parameter/tide_speed = 1.42
|
||||||
shader_parameter/tide_distance = 0.3
|
shader_parameter/tide_distance = 0.42
|
||||||
shader_parameter/lead_width = 0.065
|
shader_parameter/lead_width = 0.14
|
||||||
shader_parameter/trail_width = 0.18
|
shader_parameter/trail_width = 0.32
|
||||||
shader_parameter/lead_strength = 0.48
|
shader_parameter/lead_strength = 0.88
|
||||||
shader_parameter/trail_strength = 0.25
|
shader_parameter/trail_strength = 0.52
|
||||||
shader_parameter/phase_scale = 0.035
|
shader_parameter/phase_scale = 0.035
|
||||||
shader_parameter/phase_strength = 0.55
|
shader_parameter/phase_strength = 0.55
|
||||||
|
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
[gd_resource type="ShaderMaterial" load_steps=3 format=3]
|
|
||||||
|
|
||||||
[ext_resource type="Texture2D" path="res://art/exported/environment/textures/grass_lite.png" id="1_texture"]
|
|
||||||
[ext_resource type="Shader" path="res://world/materials/terrain_surface_projection.gdshader" id="2_shader"]
|
|
||||||
|
|
||||||
[resource]
|
|
||||||
resource_name = "Starter Island Grass"
|
|
||||||
shader = ExtResource("2_shader")
|
|
||||||
shader_parameter/albedo_texture = ExtResource("1_texture")
|
|
||||||
shader_parameter/albedo_tint = Color(1, 1, 1, 1)
|
|
||||||
shader_parameter/tile_world_size = 2.5
|
|
||||||
shader_parameter/blend_sharpness = 6.0
|
|
||||||
shader_parameter/top_projection_bias = 2.0
|
|
||||||
shader_parameter/roughness = 1.0
|
|
||||||
shader_parameter/specular = 0.5
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
[gd_resource type="ShaderMaterial" load_steps=3 format=3]
|
|
||||||
|
|
||||||
[ext_resource type="Texture2D" path="res://art/exported/environment/textures/sand.png" id="1_texture"]
|
|
||||||
[ext_resource type="Shader" path="res://world/materials/terrain_surface_projection.gdshader" id="2_shader"]
|
|
||||||
|
|
||||||
[resource]
|
|
||||||
resource_name = "Starter Island Sand"
|
|
||||||
shader = ExtResource("2_shader")
|
|
||||||
shader_parameter/albedo_texture = ExtResource("1_texture")
|
|
||||||
shader_parameter/albedo_tint = Color(1, 1, 1, 1)
|
|
||||||
shader_parameter/tile_world_size = 2.5
|
|
||||||
shader_parameter/blend_sharpness = 6.0
|
|
||||||
shader_parameter/top_projection_bias = 2.0
|
|
||||||
shader_parameter/roughness = 1.0
|
|
||||||
shader_parameter/specular = 0.5
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
[gd_resource type="ShaderMaterial" load_steps=3 format=3]
|
|
||||||
|
|
||||||
[ext_resource type="Texture2D" path="res://art/exported/environment/textures/sandstone_heavy.png" id="1_texture"]
|
|
||||||
[ext_resource type="Shader" path="res://world/materials/terrain_surface_projection.gdshader" id="2_shader"]
|
|
||||||
|
|
||||||
[resource]
|
|
||||||
resource_name = "Starter Island Sandstone"
|
|
||||||
shader = ExtResource("2_shader")
|
|
||||||
shader_parameter/albedo_texture = ExtResource("1_texture")
|
|
||||||
shader_parameter/albedo_tint = Color(1, 1, 1, 1)
|
|
||||||
shader_parameter/tile_world_size = 2.5
|
|
||||||
shader_parameter/blend_sharpness = 6.0
|
|
||||||
shader_parameter/top_projection_bias = 2.0
|
|
||||||
shader_parameter/roughness = 1.0
|
|
||||||
shader_parameter/specular = 0.0
|
|
||||||
|
|
@ -29,9 +29,29 @@ uniform float contour_corner_variation : hint_range(0.0, 0.15, 0.005) = 0.045;
|
||||||
uniform float contour_smoothing_pixels : hint_range(0.0, 2.5, 0.05) = 1.0;
|
uniform float contour_smoothing_pixels : hint_range(0.0, 2.5, 0.05) = 1.0;
|
||||||
uniform float contour_smoothing_strength : hint_range(0.0, 1.0, 0.01) = 0.72;
|
uniform float contour_smoothing_strength : hint_range(0.0, 1.0, 0.01) = 0.72;
|
||||||
uniform float contour_depth_reject : hint_range(0.05, 2.0, 0.01) = 0.60;
|
uniform float contour_depth_reject : hint_range(0.05, 2.0, 0.01) = 0.60;
|
||||||
|
uniform float shoreline_horizontal_reach : hint_range(0.05, 1.5, 0.01) = 0.32;
|
||||||
|
uniform float contact_fade_depth : hint_range(0.0, 0.1, 0.001) = 0.018;
|
||||||
uniform float open_water_drift_scale : hint_range(0.001, 0.3, 0.001) = 0.018;
|
uniform float open_water_drift_scale : hint_range(0.001, 0.3, 0.001) = 0.018;
|
||||||
uniform vec2 open_water_drift_speed = vec2(0.010, -0.007);
|
uniform vec2 open_water_drift_speed = vec2(0.010, -0.007);
|
||||||
uniform float open_water_drift_strength : hint_range(0.0, 0.08, 0.001) = 0.045;
|
uniform float open_water_drift_strength : hint_range(0.0, 0.08, 0.001) = 0.045;
|
||||||
|
uniform vec4 surface_mark_color : source_color = vec4(0.580, 0.850, 0.860, 1.0);
|
||||||
|
uniform vec2 surface_mark_density = vec2(1.30, 0.90);
|
||||||
|
uniform vec2 surface_mark_drift = vec2(0.045, -0.032);
|
||||||
|
uniform float surface_mark_strength : hint_range(0.0, 0.3, 0.005) = 0.12;
|
||||||
|
uniform float surface_mottle_scale : hint_range(0.01, 1.0, 0.01) = 0.11;
|
||||||
|
uniform vec2 surface_mottle_speed = vec2(-0.018, 0.013);
|
||||||
|
uniform float surface_mottle_strength : hint_range(0.0, 0.2, 0.005) = 0.07;
|
||||||
|
uniform vec2 shoreline_mark_density = vec2(4.60, 3.20);
|
||||||
|
uniform vec2 shoreline_mark_drift = vec2(0.085, -0.060);
|
||||||
|
uniform float shoreline_mark_depth : hint_range(0.1, 2.0, 0.01) = 0.95;
|
||||||
|
uniform float shoreline_mark_strength : hint_range(0.0, 1.0, 0.01) = 0.82;
|
||||||
|
uniform float shoreline_base_fill : hint_range(0.0, 1.0, 0.01) = 0.32;
|
||||||
|
uniform float shoreline_opacity_boost : hint_range(0.0, 0.5, 0.01) = 0.26;
|
||||||
|
uniform float surface_highlight_night_floor : hint_range(0.0, 1.0, 0.01) = 0.72;
|
||||||
|
uniform float shoreline_minimum_reach : hint_range(0.1, 1.0, 0.01) = 0.48;
|
||||||
|
uniform float shoreline_crest_width : hint_range(0.02, 0.5, 0.01) = 0.13;
|
||||||
|
uniform float shoreline_trail_spacing : hint_range(0.05, 0.8, 0.01) = 0.28;
|
||||||
|
uniform float shoreline_trail_strength : hint_range(0.0, 1.0, 0.01) = 0.55;
|
||||||
|
|
||||||
uniform float distant_alpha_start : hint_range(10.0, 2000.0, 1.0) = 260.0;
|
uniform float distant_alpha_start : hint_range(10.0, 2000.0, 1.0) = 260.0;
|
||||||
uniform float distant_alpha_end : hint_range(20.0, 5000.0, 1.0) = 1400.0;
|
uniform float distant_alpha_end : hint_range(20.0, 5000.0, 1.0) = 1400.0;
|
||||||
|
|
@ -61,7 +81,63 @@ float value_noise(vec2 point) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
float reconstruct_view_depth(
|
float block_mark(vec2 point, vec2 half_size) {
|
||||||
|
return (
|
||||||
|
step(abs(point.x), half_size.x)
|
||||||
|
* step(abs(point.y), half_size.y)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
float blocky_mark_layer(
|
||||||
|
vec2 flowing_grid,
|
||||||
|
float seed,
|
||||||
|
float occupancy_threshold
|
||||||
|
) {
|
||||||
|
float row = floor(flowing_grid.y);
|
||||||
|
flowing_grid.x += mod(row, 2.0) * 0.43;
|
||||||
|
vec2 cell = floor(flowing_grid);
|
||||||
|
vec2 local = fract(flowing_grid) - vec2(0.5);
|
||||||
|
vec2 jitter = vec2(
|
||||||
|
hash_21(cell + vec2(3.7 + seed, 9.1)),
|
||||||
|
hash_21(cell + vec2(7.3, 2.9 + seed))
|
||||||
|
) - vec2(0.5);
|
||||||
|
local -= jitter * vec2(0.52, 0.46);
|
||||||
|
local = floor((local + vec2(0.5)) * 8.0) / 8.0 - vec2(0.5);
|
||||||
|
float shape_key = hash_21(cell + vec2(13.1 + seed, 5.7));
|
||||||
|
float size_key = hash_21(cell + vec2(1.9, 17.3 + seed));
|
||||||
|
vec2 half_size = mix(vec2(0.10, 0.07), vec2(0.18, 0.12), size_key);
|
||||||
|
float mark = block_mark(local, half_size);
|
||||||
|
if (shape_key < 0.34) {
|
||||||
|
mark = max(
|
||||||
|
mark,
|
||||||
|
block_mark(
|
||||||
|
local - vec2(half_size.x * 0.8, -0.11),
|
||||||
|
vec2(half_size.x * 0.52, 0.045)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else if (shape_key < 0.67) {
|
||||||
|
mark *= 1.0 - block_mark(
|
||||||
|
local - vec2(half_size.x * 0.7, 0.0),
|
||||||
|
vec2(0.045)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
mark = max(
|
||||||
|
mark,
|
||||||
|
block_mark(
|
||||||
|
local + vec2(half_size.x * 0.75, 0.10),
|
||||||
|
vec2(0.055, 0.04)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return mark * step(
|
||||||
|
occupancy_threshold,
|
||||||
|
hash_21(cell + vec2(19.7 + seed, 23.3))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
vec3 reconstruct_view_position(
|
||||||
vec2 screen_uv,
|
vec2 screen_uv,
|
||||||
float raw_depth,
|
float raw_depth,
|
||||||
mat4 inverse_projection
|
mat4 inverse_projection
|
||||||
|
|
@ -72,22 +148,39 @@ float reconstruct_view_depth(
|
||||||
vec3 ndc = vec3(screen_uv * 2.0 - 1.0, raw_depth);
|
vec3 ndc = vec3(screen_uv * 2.0 - 1.0, raw_depth);
|
||||||
#endif
|
#endif
|
||||||
vec4 view_position = inverse_projection * vec4(ndc, 1.0);
|
vec4 view_position = inverse_projection * vec4(ndc, 1.0);
|
||||||
return -view_position.z / max(abs(view_position.w), 0.00001);
|
return view_position.xyz / max(abs(view_position.w), 0.00001);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
vec3 reconstruct_world_position(
|
||||||
|
vec2 screen_uv,
|
||||||
|
float raw_depth,
|
||||||
|
mat4 inverse_projection,
|
||||||
|
mat4 inverse_view
|
||||||
|
) {
|
||||||
|
vec3 view_position = reconstruct_view_position(
|
||||||
|
screen_uv,
|
||||||
|
raw_depth,
|
||||||
|
inverse_projection
|
||||||
|
);
|
||||||
|
return (inverse_view * vec4(view_position, 1.0)).xyz;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
float sample_water_depth(
|
float sample_water_depth(
|
||||||
vec2 screen_uv,
|
vec2 screen_uv,
|
||||||
float water_surface_depth,
|
float water_surface_height,
|
||||||
mat4 inverse_projection
|
mat4 inverse_projection,
|
||||||
|
mat4 inverse_view
|
||||||
) {
|
) {
|
||||||
float raw_depth = texture(depth_texture, screen_uv).r;
|
float raw_depth = texture(depth_texture, screen_uv).r;
|
||||||
float scene_depth = reconstruct_view_depth(
|
vec3 scene_world_position = reconstruct_world_position(
|
||||||
screen_uv,
|
screen_uv,
|
||||||
raw_depth,
|
raw_depth,
|
||||||
inverse_projection
|
inverse_projection,
|
||||||
|
inverse_view
|
||||||
);
|
);
|
||||||
return scene_depth - water_surface_depth;
|
return water_surface_height - scene_world_position.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -118,12 +211,13 @@ void vertex() {
|
||||||
|
|
||||||
void fragment() {
|
void fragment() {
|
||||||
float raw_depth = texture(depth_texture, SCREEN_UV).r;
|
float raw_depth = texture(depth_texture, SCREEN_UV).r;
|
||||||
float scene_view_depth = reconstruct_view_depth(
|
vec3 scene_world_position = reconstruct_world_position(
|
||||||
SCREEN_UV,
|
SCREEN_UV,
|
||||||
raw_depth,
|
raw_depth,
|
||||||
INV_PROJECTION_MATRIX
|
INV_PROJECTION_MATRIX,
|
||||||
|
INV_VIEW_MATRIX
|
||||||
);
|
);
|
||||||
float water_depth = max(scene_view_depth - surface_view_depth, 0.0);
|
float water_depth = max(world_position.y - scene_world_position.y, 0.0);
|
||||||
water_depth = min(water_depth, deep_depth * 4.0);
|
water_depth = min(water_depth, deep_depth * 4.0);
|
||||||
|
|
||||||
float contour_depth = water_depth;
|
float contour_depth = water_depth;
|
||||||
|
|
@ -137,23 +231,51 @@ void fragment() {
|
||||||
float contour_weight_sum = 1.0;
|
float contour_weight_sum = 1.0;
|
||||||
float left_depth = sample_water_depth(
|
float left_depth = sample_water_depth(
|
||||||
SCREEN_UV - vec2(contour_texel.x, 0.0),
|
SCREEN_UV - vec2(contour_texel.x, 0.0),
|
||||||
surface_view_depth,
|
world_position.y,
|
||||||
INV_PROJECTION_MATRIX
|
INV_PROJECTION_MATRIX,
|
||||||
|
INV_VIEW_MATRIX
|
||||||
);
|
);
|
||||||
float right_depth = sample_water_depth(
|
float right_depth = sample_water_depth(
|
||||||
SCREEN_UV + vec2(contour_texel.x, 0.0),
|
SCREEN_UV + vec2(contour_texel.x, 0.0),
|
||||||
surface_view_depth,
|
world_position.y,
|
||||||
INV_PROJECTION_MATRIX
|
INV_PROJECTION_MATRIX,
|
||||||
|
INV_VIEW_MATRIX
|
||||||
);
|
);
|
||||||
float upper_depth = sample_water_depth(
|
float upper_depth = sample_water_depth(
|
||||||
SCREEN_UV - vec2(0.0, contour_texel.y),
|
SCREEN_UV - vec2(0.0, contour_texel.y),
|
||||||
surface_view_depth,
|
world_position.y,
|
||||||
INV_PROJECTION_MATRIX
|
INV_PROJECTION_MATRIX,
|
||||||
|
INV_VIEW_MATRIX
|
||||||
);
|
);
|
||||||
float lower_depth = sample_water_depth(
|
float lower_depth = sample_water_depth(
|
||||||
SCREEN_UV + vec2(0.0, contour_texel.y),
|
SCREEN_UV + vec2(0.0, contour_texel.y),
|
||||||
surface_view_depth,
|
world_position.y,
|
||||||
INV_PROJECTION_MATRIX
|
INV_PROJECTION_MATRIX,
|
||||||
|
INV_VIEW_MATRIX
|
||||||
|
);
|
||||||
|
float upper_left_depth = sample_water_depth(
|
||||||
|
SCREEN_UV - contour_texel,
|
||||||
|
world_position.y,
|
||||||
|
INV_PROJECTION_MATRIX,
|
||||||
|
INV_VIEW_MATRIX
|
||||||
|
);
|
||||||
|
float upper_right_depth = sample_water_depth(
|
||||||
|
SCREEN_UV + vec2(contour_texel.x, -contour_texel.y),
|
||||||
|
world_position.y,
|
||||||
|
INV_PROJECTION_MATRIX,
|
||||||
|
INV_VIEW_MATRIX
|
||||||
|
);
|
||||||
|
float lower_left_depth = sample_water_depth(
|
||||||
|
SCREEN_UV + vec2(-contour_texel.x, contour_texel.y),
|
||||||
|
world_position.y,
|
||||||
|
INV_PROJECTION_MATRIX,
|
||||||
|
INV_VIEW_MATRIX
|
||||||
|
);
|
||||||
|
float lower_right_depth = sample_water_depth(
|
||||||
|
SCREEN_UV + contour_texel,
|
||||||
|
world_position.y,
|
||||||
|
INV_PROJECTION_MATRIX,
|
||||||
|
INV_VIEW_MATRIX
|
||||||
);
|
);
|
||||||
float left_weight = contour_neighbor_weight(
|
float left_weight = contour_neighbor_weight(
|
||||||
water_depth, left_depth, contour_depth_reject
|
water_depth, left_depth, contour_depth_reject
|
||||||
|
|
@ -167,11 +289,37 @@ void fragment() {
|
||||||
float lower_weight = contour_neighbor_weight(
|
float lower_weight = contour_neighbor_weight(
|
||||||
water_depth, lower_depth, contour_depth_reject
|
water_depth, lower_depth, contour_depth_reject
|
||||||
);
|
);
|
||||||
|
float diagonal_scale = 0.70710678;
|
||||||
|
float upper_left_weight = contour_neighbor_weight(
|
||||||
|
water_depth, upper_left_depth, contour_depth_reject
|
||||||
|
) * diagonal_scale;
|
||||||
|
float upper_right_weight = contour_neighbor_weight(
|
||||||
|
water_depth, upper_right_depth, contour_depth_reject
|
||||||
|
) * diagonal_scale;
|
||||||
|
float lower_left_weight = contour_neighbor_weight(
|
||||||
|
water_depth, lower_left_depth, contour_depth_reject
|
||||||
|
) * diagonal_scale;
|
||||||
|
float lower_right_weight = contour_neighbor_weight(
|
||||||
|
water_depth, lower_right_depth, contour_depth_reject
|
||||||
|
) * diagonal_scale;
|
||||||
contour_depth_sum += left_depth * left_weight;
|
contour_depth_sum += left_depth * left_weight;
|
||||||
contour_depth_sum += right_depth * right_weight;
|
contour_depth_sum += right_depth * right_weight;
|
||||||
contour_depth_sum += upper_depth * upper_weight;
|
contour_depth_sum += upper_depth * upper_weight;
|
||||||
contour_depth_sum += lower_depth * lower_weight;
|
contour_depth_sum += lower_depth * lower_weight;
|
||||||
float neighbor_support = left_weight + right_weight + upper_weight + lower_weight;
|
contour_depth_sum += upper_left_depth * upper_left_weight;
|
||||||
|
contour_depth_sum += upper_right_depth * upper_right_weight;
|
||||||
|
contour_depth_sum += lower_left_depth * lower_left_weight;
|
||||||
|
contour_depth_sum += lower_right_depth * lower_right_weight;
|
||||||
|
float neighbor_support = (
|
||||||
|
left_weight
|
||||||
|
+ right_weight
|
||||||
|
+ upper_weight
|
||||||
|
+ lower_weight
|
||||||
|
+ upper_left_weight
|
||||||
|
+ upper_right_weight
|
||||||
|
+ lower_left_weight
|
||||||
|
+ lower_right_weight
|
||||||
|
);
|
||||||
contour_weight_sum += neighbor_support;
|
contour_weight_sum += neighbor_support;
|
||||||
float smoothed_contour_depth = contour_depth_sum / contour_weight_sum;
|
float smoothed_contour_depth = contour_depth_sum / contour_weight_sum;
|
||||||
contour_depth = mix(
|
contour_depth = mix(
|
||||||
|
|
@ -182,6 +330,35 @@ void fragment() {
|
||||||
contour_feature_support = smoothstep(0.75, 1.75, neighbor_support);
|
contour_feature_support = smoothstep(0.75, 1.75, neighbor_support);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vec2 scene_world_dx = dFdx(scene_world_position.xz);
|
||||||
|
vec2 scene_world_dy = dFdy(scene_world_position.xz);
|
||||||
|
float horizontal_dx = max(length(scene_world_dx), 0.0001);
|
||||||
|
float horizontal_dy = max(length(scene_world_dy), 0.0001);
|
||||||
|
float depth_gradient = length(vec2(
|
||||||
|
dFdx(water_depth) / horizontal_dx,
|
||||||
|
dFdy(water_depth) / horizontal_dy
|
||||||
|
));
|
||||||
|
float estimated_shore_distance = min(
|
||||||
|
water_depth / max(depth_gradient, 0.001),
|
||||||
|
shoreline_horizontal_reach * 4.0
|
||||||
|
);
|
||||||
|
float proximity_center = shoreline_horizontal_reach * 0.86;
|
||||||
|
float proximity_filter_width = max(
|
||||||
|
fwidth(estimated_shore_distance) * 1.5,
|
||||||
|
shoreline_horizontal_reach * 0.12
|
||||||
|
);
|
||||||
|
float shoreline_proximity = 1.0 - smoothstep(
|
||||||
|
max(proximity_center - proximity_filter_width, 0.0),
|
||||||
|
proximity_center + proximity_filter_width,
|
||||||
|
estimated_shore_distance
|
||||||
|
);
|
||||||
|
float contact_filter_width = max(fwidth(water_depth) * 1.5, 0.004);
|
||||||
|
float contact_stability = smoothstep(
|
||||||
|
max(contact_fade_depth - contact_filter_width, 0.0),
|
||||||
|
contact_fade_depth + contact_filter_width,
|
||||||
|
water_depth
|
||||||
|
);
|
||||||
|
|
||||||
float shallow_mix = smoothstep(0.0, shallow_depth, water_depth);
|
float shallow_mix = smoothstep(0.0, shallow_depth, water_depth);
|
||||||
float deep_mix = smoothstep(shallow_depth, deep_depth, water_depth);
|
float deep_mix = smoothstep(shallow_depth, deep_depth, water_depth);
|
||||||
vec3 water_color = mix(shore_color.rgb, shallow_color.rgb, shallow_mix);
|
vec3 water_color = mix(shore_color.rgb, shallow_color.rgb, shallow_mix);
|
||||||
|
|
@ -207,6 +384,7 @@ void fragment() {
|
||||||
contour_depth
|
contour_depth
|
||||||
);
|
);
|
||||||
tide_wash *= contour_feature_support;
|
tide_wash *= contour_feature_support;
|
||||||
|
tide_wash *= shoreline_proximity;
|
||||||
tide_wash *= 1.0 - smoothstep(shallow_depth, deep_depth, water_depth);
|
tide_wash *= 1.0 - smoothstep(shallow_depth, deep_depth, water_depth);
|
||||||
water_color = mix(
|
water_color = mix(
|
||||||
water_color,
|
water_color,
|
||||||
|
|
@ -220,6 +398,148 @@ void fragment() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vec2 flowing_grid = (
|
||||||
|
world_position.xz * surface_mark_density
|
||||||
|
+ TIME * surface_mark_drift
|
||||||
|
);
|
||||||
|
float surface_row = floor(flowing_grid.y);
|
||||||
|
flowing_grid.x += mod(surface_row, 2.0) * 0.43;
|
||||||
|
vec2 surface_cell = floor(flowing_grid);
|
||||||
|
vec2 surface_local = fract(flowing_grid) - vec2(0.5);
|
||||||
|
vec2 surface_jitter = vec2(
|
||||||
|
hash_21(surface_cell + vec2(3.7, 9.1)),
|
||||||
|
hash_21(surface_cell + vec2(7.3, 2.9))
|
||||||
|
) - vec2(0.5);
|
||||||
|
surface_local -= surface_jitter * vec2(0.52, 0.46);
|
||||||
|
surface_local = (
|
||||||
|
floor((surface_local + vec2(0.5)) * 8.0) / 8.0
|
||||||
|
- vec2(0.5)
|
||||||
|
);
|
||||||
|
float surface_shape = hash_21(surface_cell + vec2(13.1, 5.7));
|
||||||
|
float surface_size = hash_21(surface_cell + vec2(1.9, 17.3));
|
||||||
|
vec2 surface_half_size = mix(
|
||||||
|
vec2(0.10, 0.07),
|
||||||
|
vec2(0.18, 0.12),
|
||||||
|
surface_size
|
||||||
|
);
|
||||||
|
float surface_mark = block_mark(surface_local, surface_half_size);
|
||||||
|
if (surface_shape < 0.34) {
|
||||||
|
surface_mark = max(
|
||||||
|
surface_mark,
|
||||||
|
block_mark(
|
||||||
|
surface_local - vec2(surface_half_size.x * 0.8, -0.11),
|
||||||
|
vec2(surface_half_size.x * 0.52, 0.045)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else if (surface_shape < 0.67) {
|
||||||
|
surface_mark *= 1.0 - block_mark(
|
||||||
|
surface_local - vec2(surface_half_size.x * 0.7, 0.0),
|
||||||
|
vec2(0.045)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
surface_mark = max(
|
||||||
|
surface_mark,
|
||||||
|
block_mark(
|
||||||
|
surface_local + vec2(surface_half_size.x * 0.75, 0.10),
|
||||||
|
vec2(0.055, 0.04)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
surface_mark *= step(
|
||||||
|
0.22,
|
||||||
|
hash_21(surface_cell + vec2(19.7, 23.3))
|
||||||
|
);
|
||||||
|
float mark_visibility = mix(1.0, 0.68, deep_mix);
|
||||||
|
float surface_highlight = (
|
||||||
|
surface_mark * surface_mark_strength * mark_visibility
|
||||||
|
);
|
||||||
|
|
||||||
|
vec2 shoreline_phase_coordinates = world_position.xz * phase_noise_scale;
|
||||||
|
float shoreline_phase = (
|
||||||
|
value_noise(shoreline_phase_coordinates)
|
||||||
|
+ value_noise(
|
||||||
|
shoreline_phase_coordinates * 0.47 + vec2(9.7, 3.1)
|
||||||
|
) * 0.5
|
||||||
|
- 0.75
|
||||||
|
) * phase_noise_strength;
|
||||||
|
float shoreline_cycle = (
|
||||||
|
sin(TIME * tide_speed + shoreline_phase) * 0.5 + 0.5
|
||||||
|
);
|
||||||
|
float animated_shoreline_depth = shoreline_mark_depth * mix(
|
||||||
|
shoreline_minimum_reach,
|
||||||
|
1.0,
|
||||||
|
shoreline_cycle
|
||||||
|
);
|
||||||
|
float shoreline_mask = 1.0 - smoothstep(
|
||||||
|
max(animated_shoreline_depth * 0.08, 0.01),
|
||||||
|
max(animated_shoreline_depth, 0.02),
|
||||||
|
contour_depth
|
||||||
|
);
|
||||||
|
float shoreline_depth_visibility = 1.0 - smoothstep(
|
||||||
|
shallow_depth,
|
||||||
|
deep_depth,
|
||||||
|
water_depth
|
||||||
|
);
|
||||||
|
shoreline_mask *= shoreline_depth_visibility;
|
||||||
|
float shoreline_lead_depth = animated_shoreline_depth * 0.82;
|
||||||
|
float shoreline_lead_crest = 1.0 - smoothstep(
|
||||||
|
shoreline_crest_width * 0.25,
|
||||||
|
shoreline_crest_width,
|
||||||
|
abs(contour_depth - shoreline_lead_depth)
|
||||||
|
);
|
||||||
|
float shoreline_trail_depth = max(
|
||||||
|
shoreline_lead_depth - shoreline_trail_spacing,
|
||||||
|
shoreline_crest_width
|
||||||
|
);
|
||||||
|
float shoreline_trail_crest = 1.0 - smoothstep(
|
||||||
|
shoreline_crest_width * 0.40,
|
||||||
|
shoreline_crest_width * 1.45,
|
||||||
|
abs(contour_depth - shoreline_trail_depth)
|
||||||
|
);
|
||||||
|
shoreline_lead_crest *= shoreline_depth_visibility;
|
||||||
|
shoreline_trail_crest *= (
|
||||||
|
shoreline_depth_visibility * shoreline_trail_strength
|
||||||
|
);
|
||||||
|
float shoreline_marks = max(
|
||||||
|
blocky_mark_layer(
|
||||||
|
world_position.xz * shoreline_mark_density
|
||||||
|
+ TIME * shoreline_mark_drift,
|
||||||
|
17.0,
|
||||||
|
0.04
|
||||||
|
),
|
||||||
|
blocky_mark_layer(
|
||||||
|
world_position.xz * shoreline_mark_density * 1.37
|
||||||
|
- TIME * shoreline_mark_drift.yx * 0.73,
|
||||||
|
41.0,
|
||||||
|
0.08
|
||||||
|
)
|
||||||
|
);
|
||||||
|
float shoreline_foam = shoreline_mask * mix(
|
||||||
|
shoreline_base_fill,
|
||||||
|
1.0,
|
||||||
|
shoreline_marks
|
||||||
|
);
|
||||||
|
float moving_crests = max(
|
||||||
|
shoreline_lead_crest,
|
||||||
|
shoreline_trail_crest
|
||||||
|
) * mix(0.68, 1.0, shoreline_marks);
|
||||||
|
shoreline_foam = max(shoreline_foam, moving_crests);
|
||||||
|
shoreline_foam *= shoreline_proximity;
|
||||||
|
water_alpha = min(
|
||||||
|
water_alpha + shoreline_foam * shoreline_opacity_boost,
|
||||||
|
0.98
|
||||||
|
);
|
||||||
|
water_alpha *= contact_stability;
|
||||||
|
|
||||||
|
float surface_mottle = value_noise(
|
||||||
|
world_position.xz * surface_mottle_scale
|
||||||
|
+ TIME * surface_mottle_speed
|
||||||
|
);
|
||||||
|
surface_mottle = floor(surface_mottle * 6.0) / 5.0;
|
||||||
|
water_color *= 1.0 + (
|
||||||
|
surface_mottle * 2.0 - 1.0
|
||||||
|
) * surface_mottle_strength;
|
||||||
|
|
||||||
vec2 drift_coordinates = (
|
vec2 drift_coordinates = (
|
||||||
world_position.xz * open_water_drift_scale
|
world_position.xz * open_water_drift_scale
|
||||||
+ TIME * open_water_drift_speed
|
+ TIME * open_water_drift_speed
|
||||||
|
|
@ -236,6 +556,19 @@ void fragment() {
|
||||||
water_alpha = mix(water_alpha, 0.96, distant_mix);
|
water_alpha = mix(water_alpha, 0.96, distant_mix);
|
||||||
|
|
||||||
water_color *= environment_tint.rgb * environment_brightness;
|
water_color *= environment_tint.rgb * environment_brightness;
|
||||||
|
vec3 visible_highlight_color = surface_mark_color.rgb * max(
|
||||||
|
environment_tint.rgb * environment_brightness,
|
||||||
|
vec3(surface_highlight_night_floor)
|
||||||
|
);
|
||||||
|
float visible_highlight = max(
|
||||||
|
surface_highlight,
|
||||||
|
shoreline_foam * shoreline_mark_strength
|
||||||
|
);
|
||||||
|
water_color = mix(
|
||||||
|
water_color,
|
||||||
|
visible_highlight_color,
|
||||||
|
clamp(visible_highlight, 0.0, 1.0)
|
||||||
|
);
|
||||||
|
|
||||||
ALBEDO = water_color;
|
ALBEDO = water_color;
|
||||||
ALPHA = clamp(water_alpha, 0.1, 0.96);
|
ALPHA = clamp(water_alpha, 0.1, 0.96);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[gd_resource type="ShaderMaterial" load_steps=2 format=3]
|
[gd_resource type="ShaderMaterial" format=3 uid="uid://6lafg0ac2qul"]
|
||||||
|
|
||||||
[ext_resource type="Shader" path="res://world/materials/stylized_water.gdshader" id="1_water_shader"]
|
[ext_resource type="Shader" uid="uid://lvo7xyr3c1k3" path="res://world/materials/stylized_water.gdshader" id="1_water_shader"]
|
||||||
|
|
||||||
[resource]
|
[resource]
|
||||||
render_priority = 0
|
render_priority = 0
|
||||||
|
|
@ -11,25 +11,45 @@ shader_parameter/deep_color = Color(0.109804, 0.345098, 0.435294, 1)
|
||||||
shader_parameter/tide_wash_color = Color(0.360784, 0.721569, 0.733333, 1)
|
shader_parameter/tide_wash_color = Color(0.360784, 0.721569, 0.733333, 1)
|
||||||
shader_parameter/shallow_depth = 0.7
|
shader_parameter/shallow_depth = 0.7
|
||||||
shader_parameter/deep_depth = 3.6
|
shader_parameter/deep_depth = 3.6
|
||||||
shader_parameter/shore_depth_width = 0.16
|
shader_parameter/shore_depth_width = 0.1
|
||||||
shader_parameter/shore_alpha = 0.44
|
shader_parameter/shore_alpha = 0.68
|
||||||
shader_parameter/shallow_alpha = 0.6
|
shader_parameter/shallow_alpha = 0.78
|
||||||
shader_parameter/deep_alpha = 0.82
|
shader_parameter/deep_alpha = 0.9
|
||||||
shader_parameter/tide_effect_enabled = true
|
shader_parameter/tide_effect_enabled = true
|
||||||
shader_parameter/tide_speed = 1.42
|
shader_parameter/tide_speed = 1.42
|
||||||
shader_parameter/tide_distance = 0.3
|
shader_parameter/tide_distance = 0.11
|
||||||
shader_parameter/tide_wash_width = 0.38
|
shader_parameter/tide_wash_width = 0.16
|
||||||
shader_parameter/tide_wash_strength = 0.11
|
shader_parameter/tide_wash_strength = 0.11
|
||||||
shader_parameter/tide_wash_alpha_shift = 0.045
|
shader_parameter/tide_wash_alpha_shift = 0.045
|
||||||
shader_parameter/phase_noise_scale = 0.055
|
shader_parameter/phase_noise_scale = 0.055
|
||||||
shader_parameter/phase_noise_strength = 0.68
|
shader_parameter/phase_noise_strength = 0.68
|
||||||
shader_parameter/contour_corner_variation = 0.045
|
shader_parameter/contour_corner_variation = 0.025
|
||||||
shader_parameter/contour_smoothing_pixels = 1.0
|
shader_parameter/contour_smoothing_pixels = 1.35
|
||||||
shader_parameter/contour_smoothing_strength = 0.72
|
shader_parameter/contour_smoothing_strength = 0.88
|
||||||
shader_parameter/contour_depth_reject = 0.6
|
shader_parameter/contour_depth_reject = 0.6
|
||||||
|
shader_parameter/shoreline_horizontal_reach = 0.32
|
||||||
|
shader_parameter/contact_fade_depth = 0.018
|
||||||
shader_parameter/open_water_drift_scale = 0.018
|
shader_parameter/open_water_drift_scale = 0.018
|
||||||
shader_parameter/open_water_drift_speed = Vector2(0.01, -0.007)
|
shader_parameter/open_water_drift_speed = Vector2(0.01, -0.007)
|
||||||
shader_parameter/open_water_drift_strength = 0.045
|
shader_parameter/open_water_drift_strength = 0.045
|
||||||
|
shader_parameter/surface_mark_color = Color(0.58, 0.85, 0.86, 1)
|
||||||
|
shader_parameter/surface_mark_density = Vector2(1.5, 1.05)
|
||||||
|
shader_parameter/surface_mark_drift = Vector2(0.045, -0.032)
|
||||||
|
shader_parameter/surface_mark_strength = 0.12
|
||||||
|
shader_parameter/surface_mottle_scale = 0.11
|
||||||
|
shader_parameter/surface_mottle_speed = Vector2(-0.018, 0.013)
|
||||||
|
shader_parameter/surface_mottle_strength = 0.07
|
||||||
|
shader_parameter/shoreline_mark_density = Vector2(4.6, 3.2)
|
||||||
|
shader_parameter/shoreline_mark_drift = Vector2(0.085, -0.06)
|
||||||
|
shader_parameter/shoreline_mark_depth = 0.55
|
||||||
|
shader_parameter/shoreline_mark_strength = 0.95
|
||||||
|
shader_parameter/shoreline_base_fill = 0.58
|
||||||
|
shader_parameter/shoreline_opacity_boost = 0.38
|
||||||
|
shader_parameter/surface_highlight_night_floor = 0.72
|
||||||
|
shader_parameter/shoreline_minimum_reach = 0.22
|
||||||
|
shader_parameter/shoreline_crest_width = 0.08
|
||||||
|
shader_parameter/shoreline_trail_spacing = 0.13
|
||||||
|
shader_parameter/shoreline_trail_strength = 0.55
|
||||||
shader_parameter/distant_alpha_start = 260.0
|
shader_parameter/distant_alpha_start = 260.0
|
||||||
shader_parameter/distant_alpha_end = 1400.0
|
shader_parameter/distant_alpha_end = 1400.0
|
||||||
shader_parameter/environment_tint = Color(1, 1, 1, 1)
|
shader_parameter/environment_tint = Color(1, 1, 1, 1)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[gd_resource type="ShaderMaterial" load_steps=2 format=3]
|
[gd_resource type="ShaderMaterial" format=3 uid="uid://d27er7sx5bcqw"]
|
||||||
|
|
||||||
[ext_resource type="Shader" path="res://world/materials/stylized_water.gdshader" id="1_water_shader"]
|
[ext_resource type="Shader" uid="uid://lvo7xyr3c1k3" path="res://world/materials/stylized_water.gdshader" id="1_water_shader"]
|
||||||
|
|
||||||
[resource]
|
[resource]
|
||||||
render_priority = 0
|
render_priority = 0
|
||||||
|
|
@ -11,14 +11,45 @@ shader_parameter/deep_color = Color(0.109804, 0.345098, 0.435294, 1)
|
||||||
shader_parameter/tide_wash_color = Color(0.360784, 0.721569, 0.733333, 1)
|
shader_parameter/tide_wash_color = Color(0.360784, 0.721569, 0.733333, 1)
|
||||||
shader_parameter/shallow_depth = 0.7
|
shader_parameter/shallow_depth = 0.7
|
||||||
shader_parameter/deep_depth = 3.6
|
shader_parameter/deep_depth = 3.6
|
||||||
shader_parameter/shore_depth_width = 0.16
|
shader_parameter/shore_depth_width = 0.1
|
||||||
shader_parameter/shore_alpha = 0.44
|
shader_parameter/shore_alpha = 0.68
|
||||||
shader_parameter/shallow_alpha = 0.6
|
shader_parameter/shallow_alpha = 0.78
|
||||||
shader_parameter/deep_alpha = 0.82
|
shader_parameter/deep_alpha = 0.9
|
||||||
shader_parameter/tide_effect_enabled = false
|
shader_parameter/tide_effect_enabled = false
|
||||||
|
shader_parameter/tide_speed = 1.42
|
||||||
|
shader_parameter/tide_distance = 0.3
|
||||||
|
shader_parameter/tide_wash_width = 0.38
|
||||||
|
shader_parameter/tide_wash_strength = 0.11
|
||||||
|
shader_parameter/tide_wash_alpha_shift = 0.045
|
||||||
|
shader_parameter/phase_noise_scale = 0.055
|
||||||
|
shader_parameter/phase_noise_strength = 0.68
|
||||||
|
shader_parameter/contour_corner_variation = 0.045
|
||||||
|
shader_parameter/contour_smoothing_pixels = 1.35
|
||||||
|
shader_parameter/contour_smoothing_strength = 0.88
|
||||||
|
shader_parameter/contour_depth_reject = 0.6
|
||||||
|
shader_parameter/shoreline_horizontal_reach = 0.32
|
||||||
|
shader_parameter/contact_fade_depth = 0.018
|
||||||
shader_parameter/open_water_drift_scale = 0.018
|
shader_parameter/open_water_drift_scale = 0.018
|
||||||
shader_parameter/open_water_drift_speed = Vector2(0.01, -0.007)
|
shader_parameter/open_water_drift_speed = Vector2(0.01, -0.007)
|
||||||
shader_parameter/open_water_drift_strength = 0.045
|
shader_parameter/open_water_drift_strength = 0.045
|
||||||
|
shader_parameter/surface_mark_color = Color(0.58, 0.85, 0.86, 1)
|
||||||
|
shader_parameter/surface_mark_density = Vector2(1.5, 1.05)
|
||||||
|
shader_parameter/surface_mark_drift = Vector2(0.045, -0.032)
|
||||||
|
shader_parameter/surface_mark_strength = 0.12
|
||||||
|
shader_parameter/surface_mottle_scale = 0.11
|
||||||
|
shader_parameter/surface_mottle_speed = Vector2(-0.018, 0.013)
|
||||||
|
shader_parameter/surface_mottle_strength = 0.07
|
||||||
|
shader_parameter/shoreline_mark_density = Vector2(4.6, 3.2)
|
||||||
|
shader_parameter/shoreline_mark_drift = Vector2(0.085, -0.06)
|
||||||
|
shader_parameter/shoreline_mark_depth = 0.55
|
||||||
|
shader_parameter/shoreline_mark_strength = 0.95
|
||||||
|
shader_parameter/shoreline_base_fill = 0.58
|
||||||
|
shader_parameter/shoreline_opacity_boost = 0.38
|
||||||
|
shader_parameter/surface_highlight_night_floor = 0.72
|
||||||
|
shader_parameter/shoreline_minimum_reach = 0.22
|
||||||
|
shader_parameter/shoreline_crest_width = 0.08
|
||||||
|
shader_parameter/shoreline_trail_spacing = 0.13
|
||||||
|
shader_parameter/shoreline_trail_strength = 0.55
|
||||||
shader_parameter/distant_alpha_start = 260.0
|
shader_parameter/distant_alpha_start = 260.0
|
||||||
shader_parameter/distant_alpha_end = 1400.0
|
shader_parameter/distant_alpha_end = 1400.0
|
||||||
shader_parameter/environment_tint = Color(1, 1, 1, 1)
|
shader_parameter/environment_tint = Color(1, 1, 1, 1)
|
||||||
|
|
|
||||||
121
world/rain_ambience.gd
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
class_name RainAmbience
|
||||||
|
extends AudioStreamPlayer
|
||||||
|
|
||||||
|
const RAIN_STREAM_PATH: String = (
|
||||||
|
"res://audio/ambience/rain_loop_ontario.ogg"
|
||||||
|
)
|
||||||
|
const SILENT_VOLUME_DB: float = -60.0
|
||||||
|
|
||||||
|
@export_range(-40.0, 0.0, 0.5) var rain_volume_db: float = -8.0
|
||||||
|
@export_range(0.0, 10.0, 0.1) var fade_seconds: float = 2.5
|
||||||
|
|
||||||
|
var _weather_service: WorldWeatherService
|
||||||
|
var _fade_tween: Tween
|
||||||
|
var _is_active: bool = false
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
bus = &"SFX"
|
||||||
|
var rain_stream := load(RAIN_STREAM_PATH) as AudioStreamOggVorbis
|
||||||
|
var runtime_stream := (
|
||||||
|
rain_stream.duplicate() as AudioStreamOggVorbis
|
||||||
|
if rain_stream != null
|
||||||
|
else null
|
||||||
|
)
|
||||||
|
if runtime_stream != null:
|
||||||
|
runtime_stream.loop = true
|
||||||
|
stream = runtime_stream
|
||||||
|
volume_db = SILENT_VOLUME_DB
|
||||||
|
|
||||||
|
|
||||||
|
func configure(weather_service: WorldWeatherService) -> void:
|
||||||
|
if (
|
||||||
|
_weather_service != null
|
||||||
|
and _weather_service.weather_changed.is_connected(
|
||||||
|
_on_weather_changed
|
||||||
|
)
|
||||||
|
):
|
||||||
|
_weather_service.weather_changed.disconnect(_on_weather_changed)
|
||||||
|
_weather_service = weather_service
|
||||||
|
if (
|
||||||
|
_weather_service != null
|
||||||
|
and not _weather_service.weather_changed.is_connected(
|
||||||
|
_on_weather_changed
|
||||||
|
)
|
||||||
|
):
|
||||||
|
_weather_service.weather_changed.connect(_on_weather_changed)
|
||||||
|
|
||||||
|
|
||||||
|
func set_active(active: bool) -> void:
|
||||||
|
if _is_active == active:
|
||||||
|
return
|
||||||
|
_is_active = active
|
||||||
|
if not active:
|
||||||
|
_silence_immediately()
|
||||||
|
return
|
||||||
|
_transition_to_rain(
|
||||||
|
_weather_service != null and _weather_service.is_raining()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_weather_changed(
|
||||||
|
weather: WorldWeatherService.Weather,
|
||||||
|
_seconds_remaining: float,
|
||||||
|
) -> void:
|
||||||
|
_transition_to_rain(weather == WorldWeatherService.Weather.RAINY)
|
||||||
|
|
||||||
|
|
||||||
|
func _transition_to_rain(is_raining: bool) -> void:
|
||||||
|
if not _is_active:
|
||||||
|
return
|
||||||
|
_stop_fade()
|
||||||
|
if is_raining:
|
||||||
|
if stream == null:
|
||||||
|
return
|
||||||
|
if not playing:
|
||||||
|
volume_db = SILENT_VOLUME_DB
|
||||||
|
play()
|
||||||
|
_fade_volume_to(rain_volume_db, false)
|
||||||
|
return
|
||||||
|
if playing:
|
||||||
|
_fade_volume_to(SILENT_VOLUME_DB, true)
|
||||||
|
|
||||||
|
|
||||||
|
func _fade_volume_to(target_volume_db: float, stop_after_fade: bool) -> void:
|
||||||
|
if fade_seconds <= 0.0:
|
||||||
|
volume_db = target_volume_db
|
||||||
|
if stop_after_fade:
|
||||||
|
_finish_fade_out()
|
||||||
|
return
|
||||||
|
_fade_tween = create_tween()
|
||||||
|
_fade_tween.set_trans(Tween.TRANS_SINE)
|
||||||
|
_fade_tween.set_ease(Tween.EASE_IN_OUT)
|
||||||
|
_fade_tween.tween_property(
|
||||||
|
self,
|
||||||
|
"volume_db",
|
||||||
|
target_volume_db,
|
||||||
|
fade_seconds,
|
||||||
|
)
|
||||||
|
if stop_after_fade:
|
||||||
|
_fade_tween.tween_callback(_finish_fade_out)
|
||||||
|
|
||||||
|
|
||||||
|
func _finish_fade_out() -> void:
|
||||||
|
if _weather_service != null and _weather_service.is_raining():
|
||||||
|
return
|
||||||
|
stop()
|
||||||
|
volume_db = SILENT_VOLUME_DB
|
||||||
|
_fade_tween = null
|
||||||
|
|
||||||
|
|
||||||
|
func _silence_immediately() -> void:
|
||||||
|
_stop_fade()
|
||||||
|
stop()
|
||||||
|
volume_db = SILENT_VOLUME_DB
|
||||||
|
|
||||||
|
|
||||||
|
func _stop_fade() -> void:
|
||||||
|
if _fade_tween == null:
|
||||||
|
return
|
||||||
|
_fade_tween.kill()
|
||||||
|
_fade_tween = null
|
||||||
1
world/rain_ambience.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://dbpt2uqm82kmn
|
||||||
|
|
@ -5,9 +5,6 @@ extends WorldRegion
|
||||||
const FishingShopInteractionType = preload(
|
const FishingShopInteractionType = preload(
|
||||||
"res://world/fishing_shop_interaction.gd"
|
"res://world/fishing_shop_interaction.gd"
|
||||||
)
|
)
|
||||||
const GRASS_SURFACE_NAME: String = "grass_lite"
|
|
||||||
const SAND_SURFACE_NAME: String = "sand"
|
|
||||||
const SANDSTONE_SURFACE_NAME: String = "sandstone_heavy"
|
|
||||||
|
|
||||||
@export_group("Owned Nodes")
|
@export_group("Owned Nodes")
|
||||||
@export_node_path("MeshInstance3D")
|
@export_node_path("MeshInstance3D")
|
||||||
|
|
@ -26,30 +23,11 @@ var player_spawn_path: NodePath = ^"PlayerSpawn"
|
||||||
var fishing_shop_path: NodePath = (
|
var fishing_shop_path: NodePath = (
|
||||||
^"Interactables/FishingShopWorld/InteractionArea"
|
^"Interactables/FishingShopWorld/InteractionArea"
|
||||||
)
|
)
|
||||||
@export_node_path("Node3D")
|
@export_group("Terrain Collision")
|
||||||
var pelican_landmark_path: NodePath = (
|
@export var rebuild_terrain_collision_on_ready: bool = false
|
||||||
^"Interactables/PelicanCoolerPerch"
|
|
||||||
)
|
|
||||||
|
|
||||||
@export_group("Grass Surface")
|
|
||||||
@export var apply_surface_overrides: bool = true
|
|
||||||
@export var grass_material: Material
|
|
||||||
@export_range(0, 16, 1) var grass_surface_index: int = 0
|
|
||||||
|
|
||||||
@export_group("Sand Surface")
|
|
||||||
@export var sand_material: Material
|
|
||||||
@export_range(0, 16, 1) var sand_surface_index: int = 4
|
|
||||||
|
|
||||||
@export_group("Sandstone Surface")
|
|
||||||
@export var sandstone_material: Material
|
|
||||||
@export_range(0, 16, 1) var sandstone_surface_index: int = 5
|
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
if apply_surface_overrides:
|
if rebuild_terrain_collision_on_ready or not has_terrain_collision():
|
||||||
_apply_grass_material()
|
|
||||||
_apply_sand_material()
|
|
||||||
_apply_sandstone_material()
|
|
||||||
_build_terrain_collision()
|
_build_terrain_collision()
|
||||||
if Engine.is_editor_hint():
|
if Engine.is_editor_hint():
|
||||||
update_configuration_warnings()
|
update_configuration_warnings()
|
||||||
|
|
@ -64,10 +42,6 @@ func get_fishing_shop() -> FishingShopInteractionType:
|
||||||
return get_node_or_null(fishing_shop_path) as FishingShopInteractionType
|
return get_node_or_null(fishing_shop_path) as FishingShopInteractionType
|
||||||
|
|
||||||
|
|
||||||
func get_pelican_landmark() -> Node3D:
|
|
||||||
return get_node_or_null(pelican_landmark_path) as Node3D
|
|
||||||
|
|
||||||
|
|
||||||
func get_saltwater_shoreline_mesh() -> MeshInstance3D:
|
func get_saltwater_shoreline_mesh() -> MeshInstance3D:
|
||||||
return get_node_or_null(^"ShorelineRibbons/Ocean") as MeshInstance3D
|
return get_node_or_null(^"ShorelineRibbons/Ocean") as MeshInstance3D
|
||||||
|
|
||||||
|
|
@ -79,81 +53,6 @@ func has_terrain_collision() -> bool:
|
||||||
return collision_shape != null and collision_shape.shape != null
|
return collision_shape != null and collision_shape.shape != null
|
||||||
|
|
||||||
|
|
||||||
func _apply_grass_material() -> void:
|
|
||||||
var visual_mesh: MeshInstance3D = (
|
|
||||||
get_node_or_null(visual_mesh_path) as MeshInstance3D
|
|
||||||
)
|
|
||||||
if visual_mesh == null or visual_mesh.mesh == null:
|
|
||||||
push_error("Starter island visual mesh is unavailable.")
|
|
||||||
return
|
|
||||||
if grass_material == null:
|
|
||||||
push_error("Starter island grass material is unavailable.")
|
|
||||||
return
|
|
||||||
if grass_surface_index >= visual_mesh.mesh.get_surface_count():
|
|
||||||
push_error("Starter island grass surface index is invalid.")
|
|
||||||
return
|
|
||||||
if (
|
|
||||||
visual_mesh.mesh.surface_get_name(grass_surface_index)
|
|
||||||
!= GRASS_SURFACE_NAME
|
|
||||||
):
|
|
||||||
push_error("Starter island grass surface name does not match.")
|
|
||||||
return
|
|
||||||
visual_mesh.set_surface_override_material(
|
|
||||||
grass_surface_index,
|
|
||||||
grass_material
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
func _apply_sand_material() -> void:
|
|
||||||
var visual_mesh: MeshInstance3D = (
|
|
||||||
get_node_or_null(visual_mesh_path) as MeshInstance3D
|
|
||||||
)
|
|
||||||
if visual_mesh == null or visual_mesh.mesh == null:
|
|
||||||
push_error("Starter island visual mesh is unavailable.")
|
|
||||||
return
|
|
||||||
if sand_material == null:
|
|
||||||
push_error("Starter island sand material is unavailable.")
|
|
||||||
return
|
|
||||||
if sand_surface_index >= visual_mesh.mesh.get_surface_count():
|
|
||||||
push_error("Starter island sand surface index is invalid.")
|
|
||||||
return
|
|
||||||
if (
|
|
||||||
visual_mesh.mesh.surface_get_name(sand_surface_index)
|
|
||||||
!= SAND_SURFACE_NAME
|
|
||||||
):
|
|
||||||
push_error("Starter island sand surface name does not match.")
|
|
||||||
return
|
|
||||||
visual_mesh.set_surface_override_material(
|
|
||||||
sand_surface_index,
|
|
||||||
sand_material
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
func _apply_sandstone_material() -> void:
|
|
||||||
var visual_mesh: MeshInstance3D = (
|
|
||||||
get_node_or_null(visual_mesh_path) as MeshInstance3D
|
|
||||||
)
|
|
||||||
if visual_mesh == null or visual_mesh.mesh == null:
|
|
||||||
push_error("Starter island visual mesh is unavailable.")
|
|
||||||
return
|
|
||||||
if sandstone_material == null:
|
|
||||||
push_error("Starter island sandstone material is unavailable.")
|
|
||||||
return
|
|
||||||
if sandstone_surface_index >= visual_mesh.mesh.get_surface_count():
|
|
||||||
push_error("Starter island sandstone surface index is invalid.")
|
|
||||||
return
|
|
||||||
if (
|
|
||||||
visual_mesh.mesh.surface_get_name(sandstone_surface_index)
|
|
||||||
!= SANDSTONE_SURFACE_NAME
|
|
||||||
):
|
|
||||||
push_error("Starter island sandstone surface name does not match.")
|
|
||||||
return
|
|
||||||
visual_mesh.set_surface_override_material(
|
|
||||||
sandstone_surface_index,
|
|
||||||
sandstone_material
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
func _build_terrain_collision() -> void:
|
func _build_terrain_collision() -> void:
|
||||||
var terrain_root: Node = get_node_or_null(terrain_visual_root_path)
|
var terrain_root: Node = get_node_or_null(terrain_visual_root_path)
|
||||||
if terrain_root == null:
|
if terrain_root == null:
|
||||||
|
|
@ -218,39 +117,6 @@ func _get_configuration_warnings() -> PackedStringArray:
|
||||||
warnings.append("Terrain/Visual must provide the island mesh.")
|
warnings.append("Terrain/Visual must provide the island mesh.")
|
||||||
return warnings
|
return warnings
|
||||||
|
|
||||||
if apply_surface_overrides:
|
|
||||||
if grass_surface_index >= visual_mesh.mesh.get_surface_count():
|
|
||||||
warnings.append("Grass surface index is outside the terrain mesh.")
|
|
||||||
elif (
|
|
||||||
visual_mesh.mesh.surface_get_name(grass_surface_index)
|
|
||||||
!= GRASS_SURFACE_NAME
|
|
||||||
):
|
|
||||||
warnings.append("Grass surface index must identify the grass surface.")
|
|
||||||
if grass_material == null:
|
|
||||||
warnings.append("Assign the starter-island grass material.")
|
|
||||||
|
|
||||||
if sand_surface_index >= visual_mesh.mesh.get_surface_count():
|
|
||||||
warnings.append("Sand surface index is outside the terrain mesh.")
|
|
||||||
elif (
|
|
||||||
visual_mesh.mesh.surface_get_name(sand_surface_index)
|
|
||||||
!= SAND_SURFACE_NAME
|
|
||||||
):
|
|
||||||
warnings.append("Sand surface index must identify the sand surface.")
|
|
||||||
if sand_material == null:
|
|
||||||
warnings.append("Assign the starter-island sand material.")
|
|
||||||
if sandstone_surface_index >= visual_mesh.mesh.get_surface_count():
|
|
||||||
warnings.append(
|
|
||||||
"Sandstone surface index is outside the terrain mesh."
|
|
||||||
)
|
|
||||||
elif (
|
|
||||||
visual_mesh.mesh.surface_get_name(sandstone_surface_index)
|
|
||||||
!= SANDSTONE_SURFACE_NAME
|
|
||||||
):
|
|
||||||
warnings.append(
|
|
||||||
"Sandstone surface index must identify the sandstone surface."
|
|
||||||
)
|
|
||||||
if sandstone_material == null:
|
|
||||||
warnings.append("Assign the starter-island sandstone material.")
|
|
||||||
if get_node_or_null(terrain_collision_shape_path) == null:
|
if get_node_or_null(terrain_collision_shape_path) == null:
|
||||||
warnings.append("Terrain/Collision must provide a collision shape.")
|
warnings.append("Terrain/Collision must provide a collision shape.")
|
||||||
if get_node_or_null(terrain_visual_root_path) == null:
|
if get_node_or_null(terrain_visual_root_path) == null:
|
||||||
|
|
@ -259,6 +125,4 @@ func _get_configuration_warnings() -> PackedStringArray:
|
||||||
warnings.append("PlayerSpawn marker is missing.")
|
warnings.append("PlayerSpawn marker is missing.")
|
||||||
if get_node_or_null(fishing_shop_path) == null:
|
if get_node_or_null(fishing_shop_path) == null:
|
||||||
warnings.append("Fishing Shop placement is missing.")
|
warnings.append("Fishing Shop placement is missing.")
|
||||||
if get_node_or_null(pelican_landmark_path) == null:
|
|
||||||
warnings.append("Pelican placement is missing.")
|
|
||||||
return warnings
|
return warnings
|
||||||
|
|
|
||||||
|
|
@ -57,10 +57,6 @@ func get_fishable_water_regions() -> Array[FishableWaterRegion]:
|
||||||
return waters
|
return waters
|
||||||
|
|
||||||
|
|
||||||
func get_pelican_convenience_landmark() -> Node3D:
|
|
||||||
return _starter_island.get_pelican_landmark()
|
|
||||||
|
|
||||||
|
|
||||||
func get_saltwater_shoreline_mesh() -> MeshInstance3D:
|
func get_saltwater_shoreline_mesh() -> MeshInstance3D:
|
||||||
return _starter_island.get_saltwater_shoreline_mesh()
|
return _starter_island.get_saltwater_shoreline_mesh()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,11 @@ const WATER_PLANE_EPSILON := 0.001
|
||||||
const ENDPOINT_MERGE_TOLERANCE := 0.03
|
const ENDPOINT_MERGE_TOLERANCE := 0.03
|
||||||
const LOOP_CLOSURE_TOLERANCE := 0.06
|
const LOOP_CLOSURE_TOLERANCE := 0.06
|
||||||
const MINIMUM_FRAGMENT_LENGTH := 2.0
|
const MINIMUM_FRAGMENT_LENGTH := 2.0
|
||||||
const SIMPLIFICATION_TOLERANCE := 0.20
|
const SIMPLIFICATION_TOLERANCE := 0.025
|
||||||
const SMOOTHING_ITERATIONS := 2
|
const SMOOTHING_ITERATIONS := 0
|
||||||
const RESAMPLE_SPACING := 0.22
|
const RESAMPLE_SPACING := 0.10
|
||||||
const RIBBON_WIDTH := 0.85
|
const RIBBON_WIDTH := 1.35
|
||||||
const LAND_INSET := 0.10
|
const LAND_INSET := 0.12
|
||||||
const SURFACE_OFFSET := 0.018
|
const SURFACE_OFFSET := 0.018
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -342,6 +342,11 @@ static func _build_mesh(
|
||||||
for path_data: Dictionary in paths:
|
for path_data: Dictionary in paths:
|
||||||
var points: PackedVector2Array = path_data["points"]
|
var points: PackedVector2Array = path_data["points"]
|
||||||
var closed: bool = path_data["closed"]
|
var closed: bool = path_data["closed"]
|
||||||
|
var closed_water_side := (
|
||||||
|
_closed_path_water_side(points, water_is_inside)
|
||||||
|
if closed
|
||||||
|
else 0.0
|
||||||
|
)
|
||||||
var base_index := vertices.size()
|
var base_index := vertices.size()
|
||||||
var path_distance := 0.0
|
var path_distance := 0.0
|
||||||
for index: int in points.size():
|
for index: int in points.size():
|
||||||
|
|
@ -352,16 +357,39 @@ static func _build_mesh(
|
||||||
if not closed:
|
if not closed:
|
||||||
previous = points[maxi(index - 1, 0)]
|
previous = points[maxi(index - 1, 0)]
|
||||||
following = points[mini(index + 1, points.size() - 1)]
|
following = points[mini(index + 1, points.size() - 1)]
|
||||||
var tangent := previous.direction_to(following)
|
var point := points[index]
|
||||||
var water_normal := Vector2(-tangent.y, tangent.x)
|
var incoming := previous.direction_to(point)
|
||||||
var toward_reference := points[index].direction_to(water_reference)
|
var outgoing := point.direction_to(following)
|
||||||
|
if incoming.is_zero_approx():
|
||||||
|
incoming = outgoing
|
||||||
|
if outgoing.is_zero_approx():
|
||||||
|
outgoing = incoming
|
||||||
|
var incoming_normal := Vector2(-incoming.y, incoming.x)
|
||||||
|
var outgoing_normal := Vector2(-outgoing.y, outgoing.x)
|
||||||
|
var water_normal := incoming_normal + outgoing_normal
|
||||||
|
if water_normal.is_zero_approx():
|
||||||
|
water_normal = outgoing_normal
|
||||||
|
water_normal = water_normal.normalized()
|
||||||
|
if closed:
|
||||||
|
water_normal *= closed_water_side
|
||||||
|
else:
|
||||||
|
var toward_reference := point.direction_to(water_reference)
|
||||||
if (
|
if (
|
||||||
(water_is_inside and water_normal.dot(toward_reference) < 0.0)
|
(water_is_inside and water_normal.dot(toward_reference) < 0.0)
|
||||||
or (not water_is_inside and water_normal.dot(toward_reference) > 0.0)
|
or (not water_is_inside and water_normal.dot(toward_reference) > 0.0)
|
||||||
):
|
):
|
||||||
water_normal = -water_normal
|
water_normal = -water_normal
|
||||||
var land_point := points[index] - water_normal * LAND_INSET
|
var join_scale := minf(
|
||||||
var water_point := points[index] + water_normal * (RIBBON_WIDTH - LAND_INSET)
|
1.0 / maxf(absf(water_normal.dot(outgoing_normal)), 0.55),
|
||||||
|
1.65,
|
||||||
|
)
|
||||||
|
var land_point := (
|
||||||
|
point - water_normal * LAND_INSET * join_scale
|
||||||
|
)
|
||||||
|
var water_point := (
|
||||||
|
point
|
||||||
|
+ water_normal * (RIBBON_WIDTH - LAND_INSET) * join_scale
|
||||||
|
)
|
||||||
vertices.append(Vector3(land_point.x, water_height + SURFACE_OFFSET, land_point.y))
|
vertices.append(Vector3(land_point.x, water_height + SURFACE_OFFSET, land_point.y))
|
||||||
vertices.append(Vector3(water_point.x, water_height + SURFACE_OFFSET, water_point.y))
|
vertices.append(Vector3(water_point.x, water_height + SURFACE_OFFSET, water_point.y))
|
||||||
normals.append(Vector3.UP)
|
normals.append(Vector3.UP)
|
||||||
|
|
@ -388,6 +416,19 @@ static func _build_mesh(
|
||||||
return mesh
|
return mesh
|
||||||
|
|
||||||
|
|
||||||
|
static func _closed_path_water_side(
|
||||||
|
points: PackedVector2Array,
|
||||||
|
water_is_inside: bool,
|
||||||
|
) -> float:
|
||||||
|
var signed_area_twice := 0.0
|
||||||
|
for index: int in points.size():
|
||||||
|
signed_area_twice += points[index].cross(
|
||||||
|
points[(index + 1) % points.size()]
|
||||||
|
)
|
||||||
|
var interior_side := 1.0 if signed_area_twice >= 0.0 else -1.0
|
||||||
|
return interior_side if water_is_inside else -interior_side
|
||||||
|
|
||||||
|
|
||||||
static func _point_key(point: Vector2) -> Vector2i:
|
static func _point_key(point: Vector2) -> Vector2i:
|
||||||
return Vector2i(
|
return Vector2i(
|
||||||
roundi(point.x / ENDPOINT_MERGE_TOLERANCE),
|
roundi(point.x / ENDPOINT_MERGE_TOLERANCE),
|
||||||
|
|
|
||||||
68
world/water_surface_motion.gd
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
class_name WaterSurfaceMotion
|
||||||
|
extends MeshInstance3D
|
||||||
|
|
||||||
|
const DEFAULT_AMPLITUDE: float = 0.055
|
||||||
|
const DEFAULT_CYCLE_SECONDS: float = 7.0
|
||||||
|
const DEFAULT_SECONDARY_SWELL: float = 0.18
|
||||||
|
const DEFAULT_PHASE_OFFSET: float = 0.0
|
||||||
|
|
||||||
|
@export_range(0.0, 0.2, 0.005, "suffix:m") var amplitude: float = (
|
||||||
|
DEFAULT_AMPLITUDE
|
||||||
|
)
|
||||||
|
@export_range(1.0, 30.0, 0.1, "suffix:s") var cycle_seconds: float = (
|
||||||
|
DEFAULT_CYCLE_SECONDS
|
||||||
|
)
|
||||||
|
@export_range(0.0, 0.5, 0.01) var secondary_swell: float = (
|
||||||
|
DEFAULT_SECONDARY_SWELL
|
||||||
|
)
|
||||||
|
@export_range(0.0, TAU, 0.01, "radians") var phase_offset: float = 0.0
|
||||||
|
|
||||||
|
var _base_height: float
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
_base_height = position.y
|
||||||
|
set_process(amplitude > 0.0)
|
||||||
|
|
||||||
|
|
||||||
|
func _process(_delta: float) -> void:
|
||||||
|
position.y = _base_height + calculate_height_offset(
|
||||||
|
_current_time_seconds(),
|
||||||
|
amplitude,
|
||||||
|
cycle_seconds,
|
||||||
|
secondary_swell,
|
||||||
|
phase_offset,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
static func get_default_height_offset() -> float:
|
||||||
|
return calculate_height_offset(
|
||||||
|
_current_time_seconds(),
|
||||||
|
DEFAULT_AMPLITUDE,
|
||||||
|
DEFAULT_CYCLE_SECONDS,
|
||||||
|
DEFAULT_SECONDARY_SWELL,
|
||||||
|
DEFAULT_PHASE_OFFSET,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
static func calculate_height_offset(
|
||||||
|
time_seconds: float,
|
||||||
|
motion_amplitude: float,
|
||||||
|
motion_cycle_seconds: float,
|
||||||
|
motion_secondary_swell: float,
|
||||||
|
motion_phase_offset: float,
|
||||||
|
) -> float:
|
||||||
|
var safe_cycle := maxf(motion_cycle_seconds, 0.001)
|
||||||
|
var phase := (
|
||||||
|
fposmod(time_seconds, safe_cycle) / safe_cycle * TAU
|
||||||
|
+ motion_phase_offset
|
||||||
|
)
|
||||||
|
var swell := (
|
||||||
|
sin(phase)
|
||||||
|
+ sin(phase * 2.0 + 1.1) * motion_secondary_swell
|
||||||
|
) / (1.0 + motion_secondary_swell)
|
||||||
|
return swell * motion_amplitude
|
||||||
|
|
||||||
|
|
||||||
|
static func _current_time_seconds() -> float:
|
||||||
|
return float(Time.get_ticks_msec()) * 0.001
|
||||||
1
world/water_surface_motion.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://drdjm443o35tc
|
||||||
127
world/world_character_display.gd
Normal file
|
|
@ -0,0 +1,127 @@
|
||||||
|
class_name WorldCharacterDisplay
|
||||||
|
extends Node3D
|
||||||
|
|
||||||
|
const CharacterCustomizationCatalogType = preload(
|
||||||
|
"res://progression/character_customization_catalog.gd"
|
||||||
|
)
|
||||||
|
const PlayerVisualPresenterType = preload(
|
||||||
|
"res://player/player_visual_presenter.gd"
|
||||||
|
)
|
||||||
|
|
||||||
|
@export_enum("round", "pointy") var species_id: String = "round"
|
||||||
|
@export_enum(
|
||||||
|
"white",
|
||||||
|
"gray",
|
||||||
|
"charcoal",
|
||||||
|
"brown",
|
||||||
|
"orange",
|
||||||
|
"yellow",
|
||||||
|
"green",
|
||||||
|
"teal",
|
||||||
|
) var fur_color_id: String = "white"
|
||||||
|
@export_enum(
|
||||||
|
"none",
|
||||||
|
"pointy_long",
|
||||||
|
"pointy_short",
|
||||||
|
"pointy_wide",
|
||||||
|
) var ears_id: String = "none"
|
||||||
|
@export var eyes_id: String = "simple_shine"
|
||||||
|
@export var nose_id: String = "dog_round"
|
||||||
|
@export var mouth_id: String = "three"
|
||||||
|
@export_category("Head Look")
|
||||||
|
@export var head_look_enabled: bool = false
|
||||||
|
@export_range(0.5, 10.0, 0.1, "suffix:m") var head_look_distance: float = 4.0
|
||||||
|
@export_range(0.5, 3.0, 0.05, "suffix:m") var look_target_height: float = 1.25
|
||||||
|
@export_range(0.0, 80.0, 1.0, "degrees") var head_yaw_limit: float = 50.0
|
||||||
|
@export_range(0.0, 45.0, 1.0, "degrees") var head_pitch_limit: float = 22.0
|
||||||
|
@export_range(0.05, 2.0, 0.05, "suffix:s") var head_blend_seconds: float = 0.3
|
||||||
|
|
||||||
|
var _head_look: LookAtModifier3D
|
||||||
|
var _skeleton: Skeleton3D
|
||||||
|
var _look_target: Node3D
|
||||||
|
var _look_target_proxy: Node3D
|
||||||
|
var _head_look_requested: bool = false
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
var appearance := CharacterCustomizationCatalogType.default_snapshot()
|
||||||
|
appearance["species"] = species_id
|
||||||
|
appearance["fur_pattern"] = fur_color_id
|
||||||
|
appearance["ears"] = ears_id
|
||||||
|
appearance["eyes"] = eyes_id
|
||||||
|
appearance["nose"] = nose_id
|
||||||
|
appearance["mouth"] = mouth_id
|
||||||
|
PlayerVisualPresenterType.apply_appearance(self, appearance)
|
||||||
|
_prepare_head_look()
|
||||||
|
|
||||||
|
|
||||||
|
func _process(delta: float) -> void:
|
||||||
|
if _head_look == null or _look_target_proxy == null:
|
||||||
|
return
|
||||||
|
var target_is_valid := (
|
||||||
|
_look_target != null and is_instance_valid(_look_target)
|
||||||
|
)
|
||||||
|
if target_is_valid:
|
||||||
|
_look_target_proxy.global_position = (
|
||||||
|
_look_target.global_position + Vector3.UP * look_target_height
|
||||||
|
)
|
||||||
|
var target_influence := (
|
||||||
|
1.0 if _head_look_requested and target_is_valid else 0.0
|
||||||
|
)
|
||||||
|
_head_look.influence = move_toward(
|
||||||
|
_head_look.influence,
|
||||||
|
target_influence,
|
||||||
|
delta / maxf(head_blend_seconds, 0.001),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func set_head_look_target(target: Node3D) -> void:
|
||||||
|
_look_target = target
|
||||||
|
|
||||||
|
|
||||||
|
func set_head_look_active(active: bool) -> void:
|
||||||
|
_head_look_requested = active
|
||||||
|
|
||||||
|
|
||||||
|
func get_head_anchor_position() -> Vector3:
|
||||||
|
if _skeleton == null:
|
||||||
|
return global_position + Vector3.UP * 1.8
|
||||||
|
var head_index := _skeleton.find_bone("head")
|
||||||
|
if head_index < 0:
|
||||||
|
return global_position + Vector3.UP * 1.8
|
||||||
|
var head_pose := _skeleton.get_bone_global_pose(head_index)
|
||||||
|
return (
|
||||||
|
(_skeleton.global_transform * head_pose).origin
|
||||||
|
+ Vector3.UP * 0.85
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _prepare_head_look() -> void:
|
||||||
|
if not head_look_enabled:
|
||||||
|
set_process(false)
|
||||||
|
return
|
||||||
|
_skeleton = find_child("Skeleton3D", true, false) as Skeleton3D
|
||||||
|
if _skeleton == null or _skeleton.find_bone("head") < 0:
|
||||||
|
push_warning("World character head-look requires a head bone.")
|
||||||
|
set_process(false)
|
||||||
|
return
|
||||||
|
_look_target_proxy = Node3D.new()
|
||||||
|
_look_target_proxy.name = "HeadLookTarget"
|
||||||
|
add_child(_look_target_proxy)
|
||||||
|
_head_look = LookAtModifier3D.new()
|
||||||
|
_head_look.name = "HeadLookModifier"
|
||||||
|
_head_look.bone_name = "head"
|
||||||
|
_head_look.use_secondary_rotation = true
|
||||||
|
_head_look.use_angle_limitation = true
|
||||||
|
_head_look.symmetry_limitation = true
|
||||||
|
_head_look.primary_limit_angle = deg_to_rad(head_yaw_limit)
|
||||||
|
_head_look.secondary_limit_angle = deg_to_rad(head_pitch_limit)
|
||||||
|
_head_look.primary_damp_threshold = 0.65
|
||||||
|
_head_look.secondary_damp_threshold = 0.65
|
||||||
|
_head_look.duration = head_blend_seconds
|
||||||
|
_head_look.influence = 0.0
|
||||||
|
_head_look.set("origin_from", 1)
|
||||||
|
_head_look.set("origin_bone_name", "head")
|
||||||
|
_skeleton.add_child(_head_look)
|
||||||
|
_head_look.target_node = _head_look.get_path_to(_look_target_proxy)
|
||||||
|
set_process(true)
|
||||||
1
world/world_character_display.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://dqsehnkidygg7
|
||||||