Godot
[Godot] 2D Splash Dynamic Wave 자연스러운 물결 파동 1. 준비
J-sean
2023. 10. 14. 12:22
반응형
자연스러운 2D 물결 파동을 만들어 보자.
우선 저항이 없는 완전 탄성 스프링 운동을 시뮬레이션 해 보자.
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
|
using Godot;
public partial class spring : Node2D
{
public float velocity;
public float force;
public float height;
public float target_height;
public float k;
public override void _Ready()
{
target_height = Position.Y + 200;
// 목표 위치
k = 0.0015f;
// 스프링 탄성 계수. 탄성 계수가 클수록 빠르게
// 움직인다. 운동 거리와는 상관 없다.
}
public override void _PhysicsProcess(double delta)
{
water_update(k);
}
public void water_update(float spring_constant)
{
height = Position.Y;
float x = height - target_height;
// 변위. 스프링 운동시 실제 이동 거리는 -x ~ +x 다.
force = -spring_constant * x;
// 훅의 법칙(Hooke's Law) F = -kx
// 스프링의 복원력과 변형량 사이에는 비례관계가 성립한다.
// F = ma, a = -kx/m
// -k/m = c(상수), 가속도 a는 x에 비례한다.
velocity += force;
// 속도에 힘을 누적시킨다.
// 이동거리(s) = 속도(v) X 시간(t)
// 매 순간의 속도를 모두 더하면 총 이동거리를 알 수 있다.
// 이 예에서는 아래 Position 값이 매 순간의 총 이동거리다.
Position += new Vector2(0, velocity);
// 스프링은 목표 높이(Position.Y + 200)를 중심으로 변위 x만큼
// (-x ~ +x) 왕복 운동한다. 목표 높이가 스프링을 늘리거나
// 압축하지 않은 평상시 위치다.
// 예) 초기 위치(Position)의 Y값이 100 이면, 300(100+200)이
// 평상시 위치고 100(300-200)에서 500(300+200)사이의 왕복
// 운동을 한다.
}
}
|
추가한 스크립트에 스프링 운동을 표현할 코드를 작성한다.
비탄성 스프링 운동을 시뮬레이션 해 보자.
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
|
using Godot;
public partial class spring : Node2D
{
public float velocity;
public float force;
public float height;
public float target_height;
public float k;
public float d;
public override void _Ready()
{
target_height = Position.Y + 200;
// 목표 위치
k = 0.0015f;
// 스프링 탄성 계수. 탄성 계수가 클수록 빠르게
// 움직인다. 운동 거리와는 상관 없다.
d = 0.005f;
// 저항 계수
}
public override void _PhysicsProcess(double delta)
{
water_update(k, d);
}
public void water_update(float spring_constant, float dampening)
{
height = Position.Y;
float x = height - target_height;
// 변위. 스프링 운동시 실제 이동 거리는 -x ~ +x 다.
float resist = -dampening * velocity;
// 속도에 비례하는 저항값을 계산한다.
force = -spring_constant * x + resist;
// 훅의 법칙(Hooke's Law) F = -kx
// 스프링의 복원력과 변형량 사이에는 비례관계가 성립한다.
// F = ma, a = -kx/m
// -k/m = c(상수), 가속도 a는 x에 비례한다.
// 그리고 저항을 더해주면 힘이 점점 감소한다.
velocity += force;
// 속도에 힘을 누적시킨다.
// 이동거리(s) = 속도(v) X 시간(t)
// 매 순간의 속도를 모두 더하면 총 이동거리를 알 수 있다.
// 이 예에서는 아래 Position 값이 매 순간의 총 이동거리다.
Position += new Vector2(0, velocity);
// 스프링은 목표 높이(Position.Y + 200)를 중심으로 변위 x만큼
// (-x ~ +x) 왕복 운동한다. 목표 높이가 스프링을 늘리거나
// 압축하지 않은 평상시 위치다.
// 예) 초기 위치(Position)의 Y값이 100 이면, 300(100+200)이
// 평상시 위치고 100(300-200)에서 500(300+200)사이의 왕복
// 운동을 한다.
}
}
|
저항이 추가된 코드를 작성한다.
스프링을 여러개 만들고 서로 영향을 주고 받게 해 보자.
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
|
using Godot;
public partial class spring : Node2D
{
public float velocity;
public float force;
public float height;
public float target_height;
public void Initialize()
{
height = Position.Y;
target_height = Position.Y;
velocity = 0;
}
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);
}
}
|
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
|
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 override void _Ready()
{
k = 0.015f;
d = 0.05f;
spread = 0.0002f;
passes = 8;
springs= new List<spring>();
foreach(spring item in GetChildren())
{
springs.Add(item);
item.Initialize();
}
//splash(2, 10);
}
public override void _PhysicsProcess(double delta)
{
if (Input.IsActionJustPressed("ui_accept"))
splash(2, 10);
// 엔터키를 누르면 물결 효과가 진행된다.
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);
// 모든 차이를 0으로 초기화.
}
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];
// 높이 차이와 파동 전달 정도(spread)를 곱하여 왼쪽 스프링 속도에 누적시킨다.
}
if (i < springs.Count - 1)
{
right_deltas[i] = spread * (springs[i].height - springs[i+1].height);
springs[i+1].velocity += right_deltas[i];
// 높이 차이와 파동 전달 정도(spread)를 곱하여 오른쪽 스프링 속도에 누적시킨다.
}
}
}
}
public void splash(int index, float speed)
{
if(index >= 0 && index < springs.Count)
{
springs[index].velocity += speed;
}
// index번 스프링에 속도를 지정하여 물에 돌을 던진 것 같은 효과를 준다.
}
}
|
이제 스프링을 스크립트에서 인스턴스화 하고 초기화 되도록 해 보자.
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
|
using Godot;
public partial class spring : Node2D
{
public float velocity;
public float force;
public float height;
public float target_height;
public void Initialize(int x_position)
{
Position = new Vector2(x_position, 100);
// 인수로 받은 x 좌표와 y=100으로 위치를 정한다.
height = Position.Y;
target_height = Position.Y;
velocity = 0;
}
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);
}
}
|
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
|
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 PackedScene scene;
public override void _Ready()
{
k = 0.015f;
d = 0.05f;
spread = 0.0002f;
passes = 8;
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);
// 인스턴스된 스프링에 적당한 위치를 인수로 주고 초기화한다.
}
}
public override void _PhysicsProcess(double delta)
{
if (Input.IsActionJustPressed("ui_accept"))
splash(5, 20);
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];
}
}
}
}
public void splash(int index, float speed)
{
if(index >= 0 && index < springs.Count)
{
springs[index].velocity += speed;
}
}
}
|
WaterBody.cs 는 위와 같이 변경한다.
2023.10.18 - [Godot] - [Godot] 2D Splash Dynamic Wave 자연스러운 물결 파동 2. 구현
※ 참고
Make a Splash With Dynamic 2D Water Effects
반응형