[Godot] 2D Splash Dynamic Wave 자연스러운 물결 파동 3. 응용
Godot 2024. 2. 11. 11:48 |
2023.10.18 - [Godot] - [Godot] 2D Splash Dynamic Wave 자연스러운 물결 파동 2. 구현
위 링크를 참고해 만들어진 물에 돌을 떨어뜨려 보자.
Stone(RigidBody2D) - Script(stone.cs)를 추가한다.
Sprite2D - Texture를 추가한다.
CollisionShape2D - Shape을 추가한다.
1
2
3
4
5
6
7
8
9
10
|
using Godot;
public partial class stone : RigidBody2D
{
public override async void _Process(double delta)
{
await ToSignal(GetTree().CreateTimer(2.0), SceneTreeTimer.SignalName.Timeout);
QueueFree();
}
}
|
생성되고 2초 후 삭제되는 스크립트(stone.cs)를 작성한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
using Godot;
public partial class Main : Node2D
{
public PackedScene Scene;
public override void _Ready()
{
Scene = ResourceLoader.Load<PackedScene>("res://stone.tscn");
}
public override void _Input(InputEvent @event)
{
if (@event is InputEventMouseButton button)
if (button.Pressed && button.ButtonIndex == MouseButton.Left)
{
RigidBody2D stone = Scene.Instantiate<RigidBody2D>();
stone.Position = GetViewport().GetMousePosition();
AddChild(stone);
}
}
}
|
왼쪽 마우스 버튼을 클릭하면 돌을 생성하는 스크립트(Main.cs)를 작성한다.
CollisionShape2D - RectangleShape2D를 추가한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
using Godot;
public partial class spring : Node2D
{
public float velocity;
public float force;
public float height;
public float target_height;
public CollisionShape2D collision;
public void Initialize(int x_position)
{
Position = new Vector2(x_position, 100);
height = Position.Y;
target_height = Position.Y;
velocity = 0;
collision = GetNode<CollisionShape2D>("Area2D/CollisionShape2D");
}
public void water_update(float spring_constant, float dampening)
{
height = Position.Y;
float x = height - target_height;
float resist = -dampening * velocity;
force = -spring_constant * x + resist;
velocity += force;
Position += new Vector2(0, velocity);
}
public void set_collision_width(int distance)
{
((RectangleShape2D)collision.Shape).Size = new Vector2(distance,
collision.Shape.GetRect().Size.Y);
}
}
|
spring.cs를 위와 같이 수정한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
public override void _Ready()
{
k = 0.015f;
d = 0.05f;
spread = 0.0002f;
passes = 8;
depth = 1000;
bottom = (int)GlobalPosition.Y + depth;
water_polygon = GetNode<Polygon2D>("Polygon2D");
springs = new List<spring>();
spring_number = 10;
distance_between_springs = 100;
scene = ResourceLoader.Load<PackedScene>("res://spring.tscn");
for (int i = 0; i < spring_number; i++)
{
Node2D s = scene.Instantiate<Node2D>();
AddChild(s);
springs.Add(s as spring);
int x_position = distance_between_springs * i + 100;
(s as spring).Initialize(x_position);
(s as spring).set_collision_width(distance_between_springs);
}
wave_position = new Curve2D();
}
|
WaterBody.cs는 set_collision_width() 호출 코드만 추가하면 된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
|
using Godot;
public partial class spring : Node2D
{
public float velocity;
public float force;
public float height;
public float target_height;
public CollisionShape2D collision;
public int index;
public float stone_velocity_factor;
[Signal]
public delegate void SplashEventHandler(int index, float velocity);
// index, stone_velocity_factor, Splash 시그널 추가
public void Initialize(int x_position, int id)
{
Position = new Vector2(x_position, 100);
height = Position.Y;
target_height = Position.Y;
velocity = 0;
index = id;
stone_velocity_factor = 0.02f;
// index, stone_velocity_factor 초기화
collision = GetNode<CollisionShape2D>("Area2D/CollisionShape2D");
}
public void water_update(float spring_constant, float dampening)
{
height = Position.Y;
float x = height - target_height;
float resist = -dampening * velocity;
force = -spring_constant * x + resist;
velocity += force;
Position += new Vector2(0, velocity);
}
public void set_collision_width(int distance)
{
((RectangleShape2D)collision.Shape).Size = new Vector2(distance,
collision.Shape.GetRect().Size.Y);
}
public void OnArea2dBodyEntered(Node2D body)
{
float speed = (body as RigidBody2D).LinearVelocity.Y * stone_velocity_factor;
// We need to disable the spring's collision so that we don't trigger the body enter
// signal more than once.
// Must be deferred as we can't change physics properties on a physics callback.
body.SetDeferred(CollisionShape2D.PropertyName.Disabled, true);
// Signal
EmitSignal(SignalName.Splash, index, speed);
}
}
|
spring.cs 를 위와 같이 수정한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
|
using Godot;
using System.Collections.Generic;
public partial class WaterBody : Node2D
{
public float k;
public float d;
public float spread;
public int passes;
public List<spring> springs;
public int spring_number;
public int distance_between_springs;
public int depth;
public int target_height;
public int bottom;
public PackedScene scene;
public Polygon2D water_polygon;
public Curve2D wave_position;
public override void _Ready()
{
k = 0.015f;
d = 0.05f;
spread = 0.0002f;
passes = 8;
depth = 1000;
bottom = (int)GlobalPosition.Y + depth;
water_polygon = GetNode<Polygon2D>("Polygon2D");
springs = new List<spring>();
spring_number = 10;
distance_between_springs = 100;
scene = ResourceLoader.Load<PackedScene>("res://spring.tscn");
for (int i = 0; i < spring_number; i++)
{
Node2D s = scene.Instantiate<Node2D>();
AddChild(s);
springs.Add(s as spring);
int x_position = distance_between_springs * i + 100;
(s as spring).Initialize(x_position, i);
// Index 추가
(s as spring).set_collision_width(distance_between_springs);
(s as spring).Splash += splash;
// Signal 추가
}
wave_position = new Curve2D();
}
public override void _PhysicsProcess(double delta)
{
//if (Input.IsActionJustPressed("ui_accept"))
// splash(5, 20);
// 테스트용 splash() 호출 삭제
foreach (spring item in springs)
{
item.water_update(k, d);
}
List<float> left_deltas = new List<float>();
List<float> right_deltas = new List<float>();
for (int i = 0; i < springs.Count; i++)
{
left_deltas.Add(0.0f);
right_deltas.Add(0.0f);
}
for (int p = 0; p < passes; p++)
{
for (int i = 0; i < springs.Count; i++)
{
if (i > 0)
{
left_deltas[i] = spread * (springs[i].height - springs[i - 1].height);
springs[i - 1].velocity += left_deltas[i];
}
if (i < springs.Count - 1)
{
right_deltas[i] = spread * (springs[i].height - springs[i + 1].height);
springs[i + 1].velocity += right_deltas[i];
}
}
}
draw_wave();
draw_waterbody();
}
public void splash(int index, float speed)
{
if (index >= 0 && index < springs.Count)
{
springs[index].velocity += speed;
}
}
public void draw_waterbody()
{
List<Vector2> surface_points = new List<Vector2>();
Vector2[] baked_points = wave_position.GetBakedPoints();
for (int i = 0; i < baked_points.Length; i++)
{
surface_points.Add(baked_points[i]);
}
int first_index = 0;
int last_index = surface_points.Count - 1;
surface_points.Add(new Vector2(surface_points[last_index].X, bottom));
surface_points.Add(new Vector2(surface_points[first_index].X, bottom));
water_polygon.Polygon = surface_points.ToArray();
}
public override void _Draw()
{
DrawPolyline(wave_position.Tessellate(), Colors.Blue, 5);
}
public void draw_wave()
{
wave_position.ClearPoints();
Vector2 control0 = new Vector2(-50, 0);
Vector2 control1 = new Vector2(50, 0);
foreach (spring item in springs)
{
wave_position.AddPoint(item.Position, control0, control1);
}
QueueRedraw();
}
}
|
WaterBody.cs를 위와 같이 수정한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
public Node2D collided_with = null;
public void OnArea2dBodyEntered(Node2D body)
{
float speed = (body as RigidBody2D).LinearVelocity.Y * stone_velocity_factor;
// We need to disable the spring's collision so that we don't trigger the body enter
// signal more than once.
// Must be deferred as we can't change physics properties on a physics callback.
//body.SetDeferred(CollisionShape2D.PropertyName.Disabled, true);
if (body == collided_with)
return;
else
collided_with = body;
// Signal
EmitSignal(SignalName.Splash, index, speed);
}
|
spring.cs의 OnArea2dBodyEntered()를 위와 같이 바꾸면 SetDeferred()를 사용할 때 보다 훨씬 반응이 빠르고 자연스러운 파동이 만들어진다.
물의 높이는 spring.cs - Initialize() - Position 변수에서 간단히 변경할 수 있다.
전체 프로젝트 파일
※ 참고
Make a Splash With Dynamic 2D Water Effects
'Godot' 카테고리의 다른 글
[Godot] AnimationPlayer 애니메이션 플레이어 (0) | 2024.02.16 |
---|---|
[Godot] CharacterBody2D Inertial Motion 캐릭터 관성 운동 (0) | 2024.02.15 |
[Godot] Show Collision Shapes at Debug Runtime (0) | 2024.02.10 |
[Godot] Crashes/Freezes When Opening Project Settings (1) | 2023.12.03 |
[Godot] Bezier Spline Curve 베지어 스플라인 곡선 (0) | 2023.10.21 |