chore: init
This commit is contained in:
commit
822116d607
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
# godot
|
||||
.godot
|
21
LICENSE.txt
Normal file
21
LICENSE.txt
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 yusdacra, HackTrout
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
10
addons/dynamic_water_2d/dynamic_water_2d.gd
Normal file
10
addons/dynamic_water_2d/dynamic_water_2d.gd
Normal file
@ -0,0 +1,10 @@
|
||||
@tool
|
||||
extends EditorPlugin
|
||||
|
||||
|
||||
func _enter_tree() -> void:
|
||||
add_custom_type("DynamicWater2D", "Node2D", preload("water.gd"), preload("dynamic_water_2d.svg"))
|
||||
|
||||
|
||||
func _exit_tree() -> void:
|
||||
remove_custom_type("DynamicWater2D")
|
73
addons/dynamic_water_2d/dynamic_water_2d.svg
Normal file
73
addons/dynamic_water_2d/dynamic_water_2d.svg
Normal file
@ -0,0 +1,73 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="15.999992"
|
||||
height="15.99998"
|
||||
viewBox="0 0 15.999992 15.99998"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
|
||||
sodipodi:docname="dynamic_water_2d.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="px"
|
||||
showgrid="true"
|
||||
inkscape:zoom="32"
|
||||
inkscape:cx="8.140625"
|
||||
inkscape:cy="6.609375"
|
||||
inkscape:window-width="1858"
|
||||
inkscape:window-height="1057"
|
||||
inkscape:window-x="1912"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1">
|
||||
<inkscape:grid
|
||||
id="grid1"
|
||||
units="mm"
|
||||
originx="-60.000008"
|
||||
originy="-125"
|
||||
spacingx="0.99999998"
|
||||
spacingy="1"
|
||||
empcolor="#0099e5"
|
||||
empopacity="0.30196078"
|
||||
color="#0099e5"
|
||||
opacity="0.14901961"
|
||||
empspacing="5"
|
||||
dotted="false"
|
||||
gridanglex="30"
|
||||
gridanglez="30"
|
||||
visible="true" />
|
||||
</sodipodi:namedview>
|
||||
<defs
|
||||
id="defs1" />
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-60.000007,-125)">
|
||||
<path
|
||||
style="fill:#45ffa2;fill-opacity:1;stroke-width:0.98994;stroke-linejoin:bevel"
|
||||
d="m 61.000009,138.125 3.111109,-1.75 2.333332,1.75 3.111108,-1.75 2.333333,1.75 L 75,136.375 v 1.75 l -3.111109,1.75 -2.333333,-1.75 -3.111108,1.75 -2.333332,-1.75 -3.111109,1.75 v -1.75"
|
||||
id="path3" />
|
||||
<path
|
||||
style="fill:#45ffa2;fill-opacity:1;stroke-width:0.98994;stroke-linejoin:bevel"
|
||||
d="m 61.000009,132.96875 3.111109,-1.75 2.333332,1.75 3.111108,-1.75 2.333333,1.75 3.111109,-1.75 v 1.75 l -3.111109,1.75 -2.333333,-1.75 -3.111108,1.75 -2.333332,-1.75 -3.111109,1.75 v -1.75"
|
||||
id="path4" />
|
||||
<path
|
||||
style="fill:#45ffa2;fill-opacity:1;stroke-width:0.98994;stroke-linejoin:bevel"
|
||||
d="m 61.000009,128.125 3.111109,-1.75 2.333332,1.75 3.111108,-1.75 2.333333,1.75 L 75,126.375 v 1.75 l -3.111109,1.75 -2.333333,-1.75 -3.111108,1.75 -2.333332,-1.75 -3.111109,1.75 v -1.75"
|
||||
id="path5" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.6 KiB |
38
addons/dynamic_water_2d/dynamic_water_2d.svg.import
Normal file
38
addons/dynamic_water_2d/dynamic_water_2d.svg.import
Normal file
@ -0,0 +1,38 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dj0ovl2e65oy1"
|
||||
path="res://.godot/imported/dynamic_water_2d.svg-b9cd5300b6b73b0bd8d2e21de8ccddd1.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/dynamic_water_2d/dynamic_water_2d.svg"
|
||||
dest_files=["res://.godot/imported/dynamic_water_2d.svg-b9cd5300b6b73b0bd8d2e21de8ccddd1.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=true
|
||||
editor/convert_colors_with_editor_theme=true
|
7
addons/dynamic_water_2d/plugin.cfg
Normal file
7
addons/dynamic_water_2d/plugin.cfg
Normal file
@ -0,0 +1,7 @@
|
||||
[plugin]
|
||||
|
||||
name="DynamicWater2D"
|
||||
description="Simple sideview water that reacts to forces applied to it."
|
||||
author="yusdacra"
|
||||
version="0.1"
|
||||
script="dynamic_water_2d.gd"
|
217
addons/dynamic_water_2d/water.gd
Normal file
217
addons/dynamic_water_2d/water.gd
Normal file
@ -0,0 +1,217 @@
|
||||
@tool
|
||||
|
||||
extends Node2D
|
||||
class_name DynamicWater2D
|
||||
|
||||
## controls which node to use for pulling the top left corner of the water from.
|
||||
@export var top_left_marker: Node2D
|
||||
## controls which node to use for pulling the bottom right corner of the water from.
|
||||
@export var bottom_right_marker: Node2D
|
||||
|
||||
@export_group("visuals")
|
||||
## controls the thickness of the surface water.
|
||||
@export var surface_thickness: float = 6.0
|
||||
## controls the color of the water on the surface.
|
||||
@export var surface_color: Color = Color("2c998e66")
|
||||
## controls the color of the water below the surface.
|
||||
@export var water_color: Color = Color("87e0d733")
|
||||
|
||||
@export_group("waves")
|
||||
## enables or disables passive waves.
|
||||
@export var waves_enabled: bool = true
|
||||
## controls how high the passive waves are.
|
||||
@export var wave_height: float = 4.0
|
||||
## controls how quick the passive waves are.
|
||||
@export var wave_speed: float = 4.0
|
||||
## controls how wide the passive waves are.
|
||||
@export var wave_width: float = 16.0
|
||||
## controls how many times forces should be calculated between neighbouring points per frame.
|
||||
## higher values means waves travel faster.
|
||||
@export var wave_spread_amount: int = 4
|
||||
|
||||
@export_group("points")
|
||||
## controls how many surface points are created per nth unit of distance.
|
||||
## this is basically the "resolution" of the surface water.
|
||||
@export_range(2, 32, 2) var point_per_distance: int = 8
|
||||
## controls the damping that will be applied to all points each frame.
|
||||
## lower value will cause motion to die out quicker.
|
||||
@export_range(0.0, 1.0) var point_damping = 0.98
|
||||
## controls the stiffness between a point and it's resting y pos.
|
||||
@export var point_independent_stiffness: float = 1.0
|
||||
## controls the stiffness between neighbouring points.
|
||||
## higher values mean motion is transferred between points quicker.
|
||||
@export var point_neighbouring_stiffness: float = 2.0
|
||||
|
||||
var top_left_point: Vector2
|
||||
var top_right_point: Vector2
|
||||
var bottom_right_point: Vector2
|
||||
var bottom_left_point: Vector2
|
||||
var extents_valid: bool = false
|
||||
|
||||
var points_positions: PackedVector2Array = PackedVector2Array([])
|
||||
var points_motions: PackedVector2Array = PackedVector2Array([])
|
||||
|
||||
func point_add(pos: Vector2) -> void:
|
||||
points_positions.append(pos)
|
||||
points_motions.append(Vector2.ZERO)
|
||||
|
||||
func points_size() -> int:
|
||||
return points_positions.size()
|
||||
|
||||
func points_clear() -> void:
|
||||
points_positions.clear()
|
||||
points_motions.clear()
|
||||
|
||||
func point_global_pos(point_idx: int) -> Vector2:
|
||||
return position + points_positions[point_idx]
|
||||
|
||||
## add some motion (force) to a given point.
|
||||
func point_add_motion(point_idx: int, d: Vector2) -> Vector2:
|
||||
var motion := points_motions[point_idx]; motion += d
|
||||
points_motions[point_idx] = motion
|
||||
return d
|
||||
|
||||
func _point_calc_motion(point_idx: int, target_y: float, stiffness: float) -> void:
|
||||
var target_point := Vector2(point_global_pos(point_idx).x, target_y)
|
||||
var motion := (target_point - point_global_pos(point_idx)) * stiffness
|
||||
points_motions[point_idx] += motion
|
||||
|
||||
func _point_calc_physics(point_idx: int, delta: float) -> void:
|
||||
var motion := points_motions[point_idx]
|
||||
var pos := points_positions[point_idx]
|
||||
pos += motion * delta
|
||||
motion *= point_damping
|
||||
points_motions[point_idx] = motion
|
||||
points_positions[point_idx] = pos
|
||||
|
||||
func _points_get_circle(origin: Vector2, radius: float) -> Array[int]:
|
||||
var results: Array[int] = []
|
||||
# convert global coords to local coords
|
||||
var local_pos := to_local(origin)
|
||||
# find the furthest positions that could be affected to the left and right
|
||||
var left_most := local_pos.x - radius
|
||||
var right_most := local_pos.x + radius
|
||||
# convert those local positions to indices in the "points" array
|
||||
var left_most_index := _get_index_from_local_pos(left_most)
|
||||
var right_most_index := _get_index_from_local_pos(right_most)
|
||||
# test which points are in the circle provided
|
||||
for idx in range(left_most_index, right_most_index + 1):
|
||||
var point_pos := points_positions[idx]
|
||||
var dx := absf(point_pos.x - origin.x)
|
||||
var dy := absf(point_pos.y - origin.y)
|
||||
if dx + dy <= radius:
|
||||
results.append(idx); continue
|
||||
if dx ** 2 + dy ** 2 <= radius ** 2:
|
||||
results.append(idx); continue
|
||||
return results
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
calc_extents()
|
||||
calc_surface_points()
|
||||
|
||||
|
||||
## calculates the extents of the water.
|
||||
func calc_extents() -> void:
|
||||
top_left_point = top_left_marker.position
|
||||
bottom_right_point = bottom_right_marker.position
|
||||
extents_valid = _validate_extents()
|
||||
if not extents_valid:
|
||||
push_error("invalid extents: top left corner cannot be bigger or equal on the X or Y axis than the bottom right corner")
|
||||
return
|
||||
top_right_point = Vector2(bottom_right_point.x, top_left_point.y)
|
||||
bottom_left_point = Vector2(top_left_point.x, bottom_right_point.y)
|
||||
|
||||
|
||||
func _validate_extents() -> bool:
|
||||
var is_x_axis_valid := top_left_point.x < bottom_right_point.x
|
||||
var is_y_axis_valid := top_left_point.y < bottom_right_point.y
|
||||
return is_x_axis_valid and is_y_axis_valid
|
||||
|
||||
|
||||
## calculates surface points.
|
||||
func calc_surface_points() -> void:
|
||||
points_clear()
|
||||
if not extents_valid: return
|
||||
# populate the points arrays
|
||||
var point_amount := int(floor((top_right_point.x - top_left_point.x) / point_per_distance))
|
||||
for i in range(point_amount):
|
||||
var pos := Vector2(top_left_point.x + (point_per_distance * (i + 0.5)), top_left_point.y)
|
||||
point_add(pos)
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
# update extents and recalculate surface points if any of our size markers change position
|
||||
if (not top_left_point.is_equal_approx(top_left_marker.position)
|
||||
or not bottom_right_point.is_equal_approx(bottom_right_marker.position)):
|
||||
calc_extents()
|
||||
calc_surface_points()
|
||||
# only process if extents are valid
|
||||
if not extents_valid: return
|
||||
|
||||
var target_y := global_position.y + top_left_point.y
|
||||
var points_len := points_size()
|
||||
for idx in range(points_len):
|
||||
# calculate motion for point
|
||||
_point_calc_motion(idx, target_y, point_independent_stiffness)
|
||||
# add the passive wave if enabled
|
||||
if waves_enabled:
|
||||
var time := fmod(float(Time.get_ticks_msec()) / 1000.0, PI * 2.0)
|
||||
point_add_motion(idx, Vector2.UP * sin(((idx / float(points_len)) * wave_width) + (time * wave_speed)) * wave_height)
|
||||
# calculate and apply spring forces between neighbouring points
|
||||
for j in range(wave_spread_amount):
|
||||
var apply_nforce: Callable = func(nidx: int) -> void:
|
||||
_point_calc_motion(idx, point_global_pos(nidx).y, point_neighbouring_stiffness)
|
||||
# to the left
|
||||
if idx - 1 >= 0: apply_nforce.call(idx - 1)
|
||||
# to the right
|
||||
if idx + 1 < points_len: apply_nforce.call(idx + 1)
|
||||
|
||||
# run surface point physics
|
||||
for idx in range(points_len):
|
||||
_point_calc_physics(idx, delta)
|
||||
|
||||
queue_redraw()
|
||||
|
||||
|
||||
## apply some force to provided position.
|
||||
## will be applied as a circle, all points in the radius will be affected.
|
||||
func apply_force(pos: Vector2, force: Vector2, radius: float = 16.0) -> void:
|
||||
# ignore if position outside of area
|
||||
if (point_global_pos(0).x - radius * 2) > pos.x or (point_global_pos(points_size() - 1).x + radius * 2) < pos.x:
|
||||
return
|
||||
var local_pos := to_local(pos)
|
||||
# get points around the pos
|
||||
var idxs := _points_get_circle(pos, radius)
|
||||
for idx in idxs:
|
||||
# direct force to the point
|
||||
force *= local_pos.direction_to(points_positions[idx])
|
||||
point_add_motion(idx, force)
|
||||
|
||||
|
||||
func _get_index_from_local_pos(x: float) -> int:
|
||||
# returns an index of the "points" array on water's surface to the local pos
|
||||
var index = floor((abs(top_left_point.x - x) / (top_right_point.x - top_left_point.x)) * points_size())
|
||||
# ensure the index is a possible index of the array
|
||||
return int(clamp(index, 0, points_size() - 1))
|
||||
|
||||
|
||||
func _draw() -> void:
|
||||
if not extents_valid: return
|
||||
|
||||
var surface := PackedVector2Array([top_left_point])
|
||||
var polygon := PackedVector2Array([top_left_point])
|
||||
var colors := PackedColorArray([water_color])
|
||||
for idx in range(points_size()):
|
||||
surface.append(points_positions[idx])
|
||||
polygon.append(points_positions[idx])
|
||||
colors.append(water_color)
|
||||
|
||||
surface.append(top_right_point)
|
||||
|
||||
for p in [top_right_point, bottom_right_point, bottom_left_point]:
|
||||
polygon.append(p)
|
||||
colors.append(water_color)
|
||||
|
||||
draw_polygon(polygon, colors)
|
||||
draw_polyline(surface, surface_color, surface_thickness, true)
|
16
addons/dynamic_water_2d/water.tscn
Normal file
16
addons/dynamic_water_2d/water.tscn
Normal file
@ -0,0 +1,16 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://cor8igwxetpgn"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/dynamic_water_2d/water.gd" id="1"]
|
||||
|
||||
[node name="Water" type="Node2D" node_paths=PackedStringArray("top_left_marker", "bottom_right_marker")]
|
||||
script = ExtResource("1")
|
||||
top_left_marker = NodePath("TopLeft")
|
||||
bottom_right_marker = NodePath("BottomRight")
|
||||
|
||||
[node name="TopLeft" type="Marker2D" parent="."]
|
||||
position = Vector2(0, 64)
|
||||
gizmo_extents = 32.0
|
||||
|
||||
[node name="BottomRight" type="Marker2D" parent="."]
|
||||
position = Vector2(480, 272)
|
||||
gizmo_extents = 32.0
|
11
examples/dynamic_water/example.gd
Normal file
11
examples/dynamic_water/example.gd
Normal file
@ -0,0 +1,11 @@
|
||||
@tool
|
||||
extends Node2D
|
||||
|
||||
|
||||
@onready var water: DynamicWater2D = $Water
|
||||
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
var mouse_coords := water.get_global_mouse_position()
|
||||
# apply 128 units of downwards force on mouse position in a 48 unit radius
|
||||
water.apply_force(mouse_coords, 128.0 * Vector2.DOWN, 48.0)
|
16
examples/dynamic_water/example.tscn
Normal file
16
examples/dynamic_water/example.tscn
Normal file
@ -0,0 +1,16 @@
|
||||
[gd_scene load_steps=4 format=3 uid="uid://dua6al8j0x7wx"]
|
||||
|
||||
[ext_resource type="Script" path="res://examples/dynamic_water/example.gd" id="1_tfv2h"]
|
||||
[ext_resource type="Texture2D" uid="uid://c2vwnj65wuypc" path="res://examples/dynamic_water/icon.png" id="1_yawdq"]
|
||||
[ext_resource type="PackedScene" uid="uid://cor8igwxetpgn" path="res://addons/dynamic_water_2d/water.tscn" id="2_wkqr4"]
|
||||
|
||||
[node name="Example" type="Node2D"]
|
||||
script = ExtResource("1_tfv2h")
|
||||
|
||||
[node name="Icon" type="Sprite2D" parent="."]
|
||||
position = Vector2(241, 167)
|
||||
texture = ExtResource("1_yawdq")
|
||||
|
||||
[node name="Water" parent="." instance=ExtResource("2_wkqr4")]
|
||||
surface_thickness = 4.0
|
||||
waves_enabled = false
|
BIN
examples/dynamic_water/icon.png
Normal file
BIN
examples/dynamic_water/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.2 KiB |
34
examples/dynamic_water/icon.png.import
Normal file
34
examples/dynamic_water/icon.png.import
Normal file
@ -0,0 +1,34 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://c2vwnj65wuypc"
|
||||
path="res://.godot/imported/icon.png-7881f45d0d39850fc2fce1a9552a2e02.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://examples/dynamic_water/icon.png"
|
||||
dest_files=["res://.godot/imported/icon.png-7881f45d0d39850fc2fce1a9552a2e02.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
25
project.godot
Normal file
25
project.godot
Normal file
@ -0,0 +1,25 @@
|
||||
; Engine configuration file.
|
||||
; It's best edited using the editor UI and not directly,
|
||||
; since the parameters that go here are not all obvious.
|
||||
;
|
||||
; Format:
|
||||
; [section] ; section goes between []
|
||||
; param=value ; assign values to parameters
|
||||
|
||||
config_version=5
|
||||
|
||||
[application]
|
||||
|
||||
run/main_scene="res://examples/dynamic_water/example.tscn"
|
||||
config/features=PackedStringArray("4.3")
|
||||
|
||||
[display]
|
||||
|
||||
window/size/viewport_width=480
|
||||
window/size/viewport_height=270
|
||||
window/size/resizable=false
|
||||
window/stretch/mode="2d"
|
||||
|
||||
[editor_plugins]
|
||||
|
||||
enabled=PackedStringArray("res://addons/dynamic_water_2d/plugin.cfg")
|
Loading…
Reference in New Issue
Block a user