반응형

2D 안개 효과를 만들어 보자.

 

ParallaxBackground를 추가하고 Layer를 높은 숫자로 바꾼다.

 

ParallaxLayer를 추가하고 Mirroring을 뷰포트와 동일하게 설정한다.

 

ColorRect를 추가하고 Size를 뷰포트와 동일하게 설정한다.

 

ColorRect에 ShaderMaterial을 추가하고 Shader를 생성한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
shader_type canvas_item;
//render_mode unshaded; // optional
 
// Noise texture
uniform sampler2D noise_texture: repeat_enable, filter_nearest;
// Fog density
uniform float density: hint_range(0.01.0= 0.25;
// Fog speed
uniform vec2 speed = vec2(0.020.01);
 
// Called for every pixel the material is visible on
void fragment() {
    // Make the fog slowly move
    vec2 uv = UV + speed * TIME;
    // Sample the noise texture
    float noise = texture(noise_texture, uv).r;
    // Convert the noise from the (0.0, 1.0) range to the (-1.0, 1.0) range
    // and clamp it between 0.0 and 1.0 again
    float fog = clamp(noise * 2.0 - 1.00.01.0);
    // Apply the fog effect
    COLOR.a *= fog * density;
}
 

 

셰이더는 위와 같이 작성한다.

 

Shader Parameters - Noise Texture에 Noise Texture 2D를 추가한다.

 

사이즈는 뷰포트와 동일하게, Seamless 옵션 체크, Noise - FastNoiseLite를 추가한다.

 

적당한 주인공 스프라이트를 추가한다.

 

여러가지 옵션을 변경하며 적당한 안개 효과를 찾는다.

 

float fog = clamp(noise * 2.0 - 1.0, 0.0, 1.0);

필요하다면 셰이더 코드에서 2.0 이라는 수치를 적당히 변경해 보자.

 

※ 참고

2D fog overlay

 

반응형
Posted by J-sean
:

[Godot] Path2D PathFollow2D

Godot 2024. 2. 22. 22:10 |
반응형

정해진 길을 따라 움직이는 오브젝트를 만들어 보자.

 

Path2D를 추가하고 툴 버튼을 이용해 path를 그린다.

 

PathFollow2D, Sprite2D를 추가한다. PathFollow2D는 Path2D의 자식 노드이어야 한다.

 

Node2D에 스크립트를 추가한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using Godot;
 
public partial class Control : Node2D
{
    public PathFollow2D follower;
 
    // Called when the node enters the scene tree for the first time.
    public override void _Ready()
    {
        follower = GetChild<Path2D>(0).GetChild<PathFollow2D>(0);
    }
 
    // Called every frame. 'delta' is the elapsed time since the previous frame.
    public override void _Process(double delta)
    {
        follower.ProgressRatio += (float)delta;
    }
}
 

 

위와 같이 코드를 작성한다.

 

실행하면 스프라이트가 Path를 따라 움직인다.

 

반응형
Posted by J-sean
:
반응형

2D 씬에 3D 오브젝트를 표현해 보자.

 

2D 씬에 스프라이트를 생성하고 Texture는 ViewportTexture를 지정한다.

 

SubViewport를 생성한다.

 

3D 씬에 Torus, Camera, Light를 생성하고 적당히 배치해 저장한다.

 

저장한 3D 씬을 SubViewport 자식 노드로 추가한다.

 

스프라이트 Texture - Viewport Path에 SubviewPort를 지정한다.

 

위와 같이 3D 카메라에 보이는 화면이 텍스쳐로 표현된다.

 

Transparent BG 옵션을 선택하면 배경이 투명해진다.

 

3D 오브젝트에 스크립트를 추가해 움직임을 적용하면 스프라이트에 반영된다.

 

반응형
Posted by J-sean
:

[Godot] Line2D 선 그리기

Godot 2024. 2. 21. 18:02 |
반응형

간단한 선을 그려보자.

 

Line2D 노드 두 개와 스크립트를 추가한다.

 

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
using Godot;
using System.Collections.Generic;
 
public partial class Control : Node2D
{
    // Called when the node enters the scene tree for the first time.
    public override void _Ready()
    {
        DrawCircle();
        DrawPentagram();
    }
 
    // 원 그리기
    public async void DrawCircle()
    {
        Line2D line = GetNode<Line2D>("Line2D");
        //line.Closed = true;
        // Closed를 여기서 변경하면 원이 그려지는 방식이 달라진다.
        line.Width = 2;
        line.DefaultColor = Colors.Red;
        line.Position = new Vector2(200200);
 
        float radius = 100;
        float angle;
 
        for (int i = 0; i < 360; i++)
        {
            angle = Mathf.DegToRad(i);
            line.AddPoint(CalcCirclePoint(radius, angle));
            await ToSignal(GetTree().CreateTimer(0.01), SceneTreeTimer.SignalName.Timeout);
        }
 
        line.Closed = true;
    }
 
    // 원 포인트 계산
    public Vector2 CalcCirclePoint(float radius, float angle)
    {
        float x = Mathf.Cos(angle);
        float y = Mathf.Sin(angle);
 
        return new Vector2(x * radius, y * radius);
    }
 
    // 별 그리기
    public async void DrawPentagram()
    {
        Line2D line = GetNode<Line2D>("Line2D2");
        line.Width = 2;
        line.DefaultColor = Colors.Blue;
        line.Position = new Vector2(200200);
 
        List<Vector2> points = new List<Vector2>();
        points.Add(new Vector2(6080));
        points.Add(new Vector2(0-100));
        points.Add(new Vector2(-6080));
        points.Add(new Vector2(95-25));
        points.Add(new Vector2(-95-25));
        //points.Add(new Vector2(60, 80));
        // 마지막에 Closed 프로퍼티에 true를 대입하기 때문에 별의 시작점을 다시 추가할
        // 필요는 없다.
 
        for (int i = 0; i < points.Count; i++)
        {
            line.AddPoint(points[i]);
            await ToSignal(GetTree().CreateTimer(1), SceneTreeTimer.SignalName.Timeout);
        }
 
        line.Closed = true;
    }
}
 

 

위와 같이 코드를 작성한다.

 

 

※ 참고

Line2D

Custom drawing in 2D

 

반응형
Posted by J-sean
:
반응형

AnimatedSprite나 AnimationPlayer를 사용하지 않고 Sprite만으로도 간단한 애니메이션을 만들 수 있다.

 

Sprite2D를 추가하고 Texture를 지정한다. Hframes는 8, Vframes는 7로 조절한다.

 

Scale을 적절히 조절하고 스크립트도 추가한다.

 

char_blue.png
0.02MB

 

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
using Godot;
 
public partial class Control : Sprite2D
{
    public override async void _Process(double delta)
    {
        if (Input.IsActionJustPressed("ui_accept"))
        {
            // 점프 애니메이션
            for (int i = 32; i < 40; i++)
            {
                await ToSignal(GetTree().CreateTimer(0.2),
                    SceneTreeTimer.SignalName.Timeout);
                Frame = i;
            }
        }
 
        if (Input.IsActionJustPressed("ui_cancel"))
        {
            // 전체 애니메이션
            for (int v = 0; v < Vframes; v++)
                for (int h = 0; h < Hframes; h++)
                {
                    await ToSignal(GetTree().CreateTimer(0.2),
                        SceneTreeTimer.SignalName.Timeout);
                    FrameCoords = new Vector2I(h, v);
                }
        }
    }
}
 

 

위와 같은 코드를 작성한다.

엔터키를 누르면 점프, ESC키를 누르면 전체 애니메이션이 플레이 된다. 애니메이션이 플레이 되는 동안 키를 여러번 동시에 누르면 애니메이션이 겹친다.

 

 

반응형
Posted by J-sean
:
반응형

 

2023.10.18 - [Godot] - [Godot] 2D Splash Dynamic Wave 자연스러운 물결 파동 2. 구현

 

위 링크를 참고해 만들어진 물에 돌을 떨어뜨려 보자.

 

Stone씬을 만든다.

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)를 작성한다.

 

Main씬에 스크립트(Main.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)를 작성한다.

 

 

마우스를 클릭하면 돌이 생성되고 아래로 떨어진다.

 

spring씬에 Area2D와 CollisionShape2D를 추가한다.

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() 호출 코드만 추가하면 된다.

 

 

실행 시 Collision Shape이 보이도록 설정한다.

 

Collision Shape이 생성되고 표시된다.

 

Spring - Area2D - body_entered 시그널을 Spring 스크립트와 연결한다.

 

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(-500);
        Vector2 control1 = new Vector2(500);
 
        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 변수에서 간단히 변경할 수 있다.

 

Water.zip
10.27MB

전체 프로젝트 파일

 

※ 참고

Make a Splash With Dynamic 2D Water Effects

 

반응형
Posted by J-sean
:
반응형

SDL에 2D 카메라를 만들어 보자.

 

character.bmp
0.00MB
shop.bmp
0.03MB

 

 

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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
#include "SDL.h"
 
class Camera {
public:
    int m_width;
    int m_height;
    int m_cameraBorder;
    int m_target_width;
    int m_target_height;
    float m_x, m_y;
    SDL_Texture* m_targetTexture;
public:
    Camera(SDL_Renderer* renderer, int width, int height, int border, SDL_BlendMode mode) {
        m_width = width;
        m_height = height;
        m_cameraBorder = border;
        m_target_width = width + border * 2;
        m_target_height = height + border * 2;
        m_x = 0;
        m_y = 0;
        m_targetTexture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_TARGET,
            m_target_width, m_target_height);
        SDL_SetTextureBlendMode(m_targetTexture, mode);
    }
    void Draw(SDL_Renderer* renderer, int windowHeight) {
        float pixel_h = (float)windowHeight / m_height;
        float correction_x = (int)m_x - m_x;
        float correction_y = (int)m_y - m_y;
        SDL_Rect dst = { (int)(correction_x * pixel_h - pixel_h * m_cameraBorder),
            (int)(correction_y * pixel_h - pixel_h * m_cameraBorder),
            (int)(m_target_width * pixel_h),
            (int)(m_target_height * pixel_h) };
        SDL_RenderCopy(renderer, m_targetTexture, NULL&dst);
    }
    void Update(int xDirection, int yDirection) {
        m_x += 0.5f * xDirection;
        m_y += 0.5f * yDirection;
    }
    void CameraReset(SDL_Renderer* renderer, int width, int height, int border, SDL_BlendMode mode) {
        m_width = width;
        m_height = height;
        m_cameraBorder = border;
        m_target_width = width + border * 2;
        m_target_height = height + border * 2;
        SDL_DestroyTexture(m_targetTexture);
        m_targetTexture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_TARGET,
            m_target_width, m_target_height);
        SDL_SetTextureBlendMode(m_targetTexture, mode);
    }
    ~Camera() {
        SDL_DestroyTexture(m_targetTexture);
    }
};
 
class Sprite {
public:
    int width;
    int height;
    float m_destX, m_destY;
    SDL_Surface* surface;
    SDL_Texture* texture;
public:
    Sprite(SDL_Renderer* renderer, const char* filename) {
        surface = SDL_LoadBMP(filename);
        width = surface->w;
        height = surface->h;
        m_destX = 0;
        m_destY = 0;
        texture = SDL_CreateTextureFromSurface(renderer, surface);
        SDL_FreeSurface(surface);
    }
    void DrawSprite(SDL_Renderer* renderer, Camera* camera, int x, int y) {
        SDL_Rect destRect = { (int)(m_destX - camera->m_x) + camera->m_cameraBorder + x,
                (int)(m_destY - camera->m_y) + camera->m_cameraBorder + y,
                width, height };
 
        SDL_RenderCopy(renderer, texture, NULL&destRect);
    }
    ~Sprite() {
        SDL_DestroyTexture(texture);
    }
};
 
int main(int argc, char** argv) {
    const int Window_Width = 640;
    const int Window_Height = 480;
 
    SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS);
    SDL_Window* window = SDL_CreateWindow("SDL Simple Camera", SDL_WINDOWPOS_CENTERED,
        SDL_WINDOWPOS_CENTERED, Window_Width, Window_Height, SDL_WINDOW_RESIZABLE);
    SDL_assert(window);
 
    SDL_SetHint(SDL_HINT_RENDER_BATCHING, "1");
    SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED |
        SDL_RENDERER_PRESENTVSYNC);
    SDL_assert(renderer);
 
    // Init
    Camera camera = Camera(renderer, 1601201, SDL_BLENDMODE_BLEND);
    SDL_assert(camera.m_targetTexture);
    Sprite shopSprite = Sprite(renderer, "shop.bmp");
    Sprite characterSprite = Sprite(renderer, "character.bmp");
 
    int xDirection = 0;
    int yDirection = 0;
 
    SDL_Event event;
    bool quit = false;
 
    while (!quit) {
        while (SDL_PollEvent(&event)) {
            switch (event.type) {
            case SDL_QUIT:
                quit = true;
                break;
            case SDL_KEYDOWN:
                if (event.key.keysym.sym == SDLK_ESCAPE)
                    quit = true;
                if (event.key.keysym.sym == SDLK_LEFT)
                    xDirection = -1;
                if (event.key.keysym.sym == SDLK_RIGHT)
                    xDirection = 1;
                if (event.key.keysym.sym == SDLK_UP)
                    yDirection = -1;
                if (event.key.keysym.sym == SDLK_DOWN)
                    yDirection = 1;
                if (event.key.keysym.sym == SDLK_r)
                    camera.CameraReset(renderer, 1601201, SDL_BLENDMODE_BLEND);
                if (event.key.keysym.sym == SDLK_e)
                    camera.CameraReset(renderer, 6404801, SDL_BLENDMODE_BLEND);
                break;
 
            default:
                xDirection = 0;
                yDirection = 0;
                break;
            }
        }
        camera.Update(xDirection, yDirection);
 
        // Camera target
        SDL_SetRenderTarget(renderer, camera.m_targetTexture);
        SDL_SetRenderDrawColor(renderer, 0000xFF);
        SDL_RenderClear(renderer);
 
        // Draw Sprite
        shopSprite.DrawSprite(renderer, &camera, 00);
        characterSprite.DrawSprite(renderer, &camera, 11666);
 
        // Screen target
        SDL_SetRenderTarget(renderer, NULL);
        SDL_SetRenderDrawColor(renderer, 0x000xFF0x000xFF);
        SDL_RenderClear(renderer);
 
        camera.Draw(renderer, Window_Height);
 
        SDL_RenderPresent(renderer);
    }
 
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);
    SDL_Quit();
 
    return 0;
}
 

 

코드를 입력하고 빌드한다.

 

실행하면 shop과 character 스프라이트가 표시된다.

 

r, e 키로 해상도를 바꿀 수 있다.

 

방향키로 카메라를 움직일 수 있다.

 

 

※ 참고

Pixel perfect camera in SDL2

 

반응형
Posted by J-sean
:
반응형

 

2023.10.14 - [Godot] - [Godot] 2D Splash Dynamic Wave 자연스러운 물결 파동 1. 준비

 

위 링크를 참고해 준비가 끝난 씬에 Polygon2D를 이용해 물 모양을 추가해 보자.

 

WaterBody에 Polygon2D를 추가한다.

 

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
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 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");
        // 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);
        }
    }
 
    public override void _PhysicsProcess(double delta)
    {
        if (Input.IsActionJustPressed("ui_accept"))
            splash(520);
 
        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_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>();
        foreach (spring item in springs)
        {
            surface_points.Add(item.Position);
        }
        // 모든 스프링의 위치를 리스트에 추가한다.
 
        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();
        // 폴리곤을 구성하는 포인트 배열을 지정한다.
    }
}
 

 

 

WaterBody.cs 파일은 위와 같이 수정한다.

 

게임을 실행하고 엔터키를 누르면 폴리곤이 그려지고 스프링과 함께 물결처럼 운동한다.

 

좀 더 부드러운 물결을 만들어 보자.

 

WaterBody 씬에서 Polygon2D - Inspector - Visibility - Show Behind Parent 옵션을 체크한다.

 

 

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
138
139
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;
    // 부드러운 물결 곡선의 포인트를 저장할 Curve2D 변수
 
    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);
        }
 
        wave_position = new Curve2D();
        // 물결 곡선 Curve2D 초기화
    }
 
    public override void _PhysicsProcess(double delta)
    {
        if (Input.IsActionJustPressed("ui_accept"))
            splash(520);
 
        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_waterbody();
        draw_wave();
        // 부드러운 물결 그리기 함수 호출.
    }
 
    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>();
        foreach (spring item in springs)
        {
            surface_points.Add(item.Position);
        }
 
        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(-500);
        Vector2 control1 = new Vector2(500);
        // 수평 컨트롤
 
        foreach (spring item in springs)
        {
            wave_position.AddPoint(item.Position, control0, control1);
            // 스프링 위치와 컨트롤을 곡선 포인트로 추가한다.
        }
 
        QueueRedraw();
        // _Draw() 호출
    }
}
 

 

 

WaterBody.cs 는 위와같이 변경한다.

 

spring 씬에서 Sprite2D - Inspector - Visibility - Visible 옵션을 해제한다.

 

Main 씬에서 실행한다.

 

폴리곤 위에 파란 물결이 부드러운 곡선으로 표시된다.

 

 

하지만 자세히 보면 폴리곤은 여전히 거칠다.

 

폴리곤을 부드럽게 바꿔보자.

 

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
138
139
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);
        }
 
        wave_position = new Curve2D();
    }
 
    public override void _PhysicsProcess(double delta)
    {
        if (Input.IsActionJustPressed("ui_accept"))
            splash(520);
 
        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();
        // draw_wave()가 먼저 호출되어야 한다.
    }
 
    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>();
        //foreach (spring item in springs)
        //{
        //    surface_points.Add(item.Position);
        //}
        Vector2[] baked_points = wave_position.GetBakedPoints();
        for (int i = 0; i < baked_points.Length; i++)
        {
            surface_points.Add(baked_points[i]);
        }
        // wave_position으로 만든 곡선의 포인트를 폴리곤 포인트로 사용하기 위해
        // 위와 같이 수정한다.
 
        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(-500);
        Vector2 control1 = new Vector2(500);
 
        foreach (spring item in springs)
        {
            wave_position.AddPoint(item.Position, control0, control1);
        }
 
        QueueRedraw();
    }
}
 

 

 

WaterBody.cs를 위와같이 변경한다.

 

폴리곤도 부드럽게 바뀌었다.

 

2024.02.11 - [Godot] - [Godot] 2D Splash Dynamic Wave 자연스러운 물결 파동 3. 응용

 

※ 참고

Beziers, curves, and paths

Make a Splash With Dynamic 2D Water Effects

 

반응형
Posted by J-sean
: