반응형

유니티 Input System Package 사용방법.

 

Workflow - Actions

대부분의 경우 권장되는 방법이다.

Responding to Actions

각 입력에 대해 반응하는 방법이다.

Method Description
InputAction.WasPerformedThisFrame() True if the InputAction.phase of the action has, at any point during the current frame, changed to Performed.
InputAction.WasCompletedThisFrame() True if the InputAction.phase of the action has, at any point during the current frame, changed away from Performed to any other phase. This can be useful for Button actions or Value actions with interactions like Press or Hold when you want to know the frame the interaction stops being performed. For actions with the default Interaction, this method will always return false for Value and Pass-Through actions (since the phase stays in Started for Value actions and stays in Performed for Pass-Through).
InputAction.IsPressed() True if the level of actuation on the action has crossed the press point and did not yet fall to or below the release threshold.
InputAction.WasPressedThisFrame() True if the level of actuation on the action has, at any point during the current frame, reached or gone above the press point.
InputAction.WasReleasedThisFrame() True if the level of actuation on the action has, at any point during the current frame, gone from being at or above the press point to at or below the release threshold.

 

void Update()
{
    if (isDead)
        return;

    if (jumpAction.WasPerformedThisFrame() && jumpCount < 2)
    {
        // 버튼이 눌릴때 한 번
        jumpCount++;
        playerRigidbody.linearVelocity = Vector2.zero;
        playerRigidbody.AddForce(new Vector2(0, jumpForce));
        playerAudio.Play();
    }
    else if (jumpAction.WasCompletedThisFrame() && playerRigidbody.linearVelocity.y > 0)
    {
        // 버튼이 떨어질때 한 번
        playerRigidbody.linearVelocity = playerRigidbody.linearVelocity * 0.5f;
    }

    animator.SetBool("Grounded", isGrounded);
}

예제 코드

 

Workflow - Actions and PlayerInput

Callback 함수를 사용하는 방법이다.

Callback 함수를 연결하는 방법은 아래를 참고한다.

 

Player Input 컴포넌트에 Input Action Asset을 연결한다.

 

Project-wide Input Action을 Player Input에 사용 하는건 권장되지 않는 방법이지만 여기서는 사용 방법을 보이기 위해 그냥 사용했다. Behavior는 Invoke Unity Events로 변경하고, Events - Player - Move(CallbackContext)에 'Player' 오브젝트가 연결되어 있는데 # 아이콘이 마치 스크립트인것 같지만 실제로는 Callback 함수가 정의된 스크립트를 가지는 Player 게임 오브젝트이다.

 

이 Player 게임 오브젝트에 PlayerController라는 스크립트가 추가되어 있고 그 스크립트에 OnMove()가 존재한다. 오른쪽에 PlayerController.OnMove를 선택해 주었다. (이 예에선 Player 게임 오브젝트가 Player Input Asset도 가지고 있고 PlayerController 스크립트도 가지고 있는 것이다)

 

PlayerController 스크립트에 OnMove()가 정의 되어 있다.

 

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

raylib로 간단한 파티클 시스템을 만들어 보자.

 

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
#include "raylib.h"
 
const int screenWidth = 640;
const int screenHeight = 480;
 
Texture2D texture;
 
class Particles {
private:
    Vector2 position;
    Vector2 velocity;
public:
    Particles() {
        Reset();
    }
 
    void Draw_particle() {
        //DrawPixel(position.x, position.y, WHITE);
        //DrawCircle(position.x, position.y, 10, WHITE);
        DrawTexture(texture, position.x, position.y, WHITE);
        position.x += velocity.x;
        position.y += velocity.y;
        if (position.x > screenWidth || position.y > screenHeight)
            Reset();
    }
 
    void Reset() {
        position.x = GetRandomValue(-screenWidth * 0.4f, screenWidth);
        position.y = GetRandomValue(-screenHeight * 20);
        float max = 10000.0f;
        velocity.x = (GetRandomValue(1, (int)max) / max) * 0.5f;
        velocity.y = (GetRandomValue(1, (int)max) / max) + 0.8f;
    }
};
 
int main(void)
{
    //SetConfigFlags(FLAG_WINDOW_TRANSPARENT | FLAG_WINDOW_UNDECORATED);
    InitWindow(screenWidth, screenHeight, "raylib example");
    // InitWindow() 실행시 내부에서 SetRandomSeed()를 time(NULL)로 초기화 한다.
    // particle 생성시 GetRandomValue()를 사용하므로 InitWindow() 이후에 생성한다.
    SetTargetFPS(60);
 
    texture = LoadTexture("snow.png");
    // NOTE: Textures MUST be loaded after Window initialization (OpenGL context is required)
 
    const int number_particles = 400;
    Particles particles[number_particles];
 
    while (!WindowShouldClose())
    {
        BeginDrawing();
 
        ClearBackground(BLACK);
 
        for (int i = 0; i < number_particles; i++)
            particles[i].Draw_particle();
 
        DrawFPS(00);
 
        EndDrawing();
    }
 
    UnloadTexture(texture);
 
    CloseWindow();
 
    return 0;
}
 

 

특별한 설정 없이도 png 파일의 알파 채널을 인식한다.

소스를 입력하고 빌드한다.

 

snow.png
0.00MB

 

 

DrawPixel(position.x, position.y, WHITE) 실행

 

DrawTexture(texture, position.x, position.y, WHITE) 실행


실제 실행하면 텍스쳐가 많이 깜빡인다. (아래 동영상 참고)

DrawPixel() 을 실행할 때는 크게 눈에 띄지 않지만 DrawTexture() 를 실행하면 눈에 띈다.

 

윈도우 사이즈를 크게 할수록 깜빡이는 현상이 줄어드는거 같다. 아니면 snow.png 파일을 LoadImage()로 로드하고 ImageResize()로 크게 바꾼 다음 LoadTextureFromImage()로 로드하면 깜빡임이 약간 줄어드는거 같다. (ImageResize()로 이미지 확대가 핵심)

Vector2로 정의된 위치값을 사용하는 DrawTextureV()를 이용해도 차이가 없다.

정확한 원인을 모르겠다.

 

 

RenerTexture2D를 사용하니까 조금 덜 깜빡이는거 같기도 한데.. 아닌거 같다.

더보기

 

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
#include "raylib.h"
 
const int screenWidth = 640;
const int screenHeight = 480;
 
Texture2D texture;
 
class Particles {
private:
    Vector2 position;
    Vector2 velocity;
public:
    Particles() {
        Reset();
    }
 
    void Draw_particle() {
        //DrawPixel(position.x, position.y, WHITE);
        //DrawCircle(position.x, position.y, 10, WHITE);
        DrawTexture(texture, position.x, position.y, WHITE);
        position.x += velocity.x;
        position.y += velocity.y;
        if (position.x > screenWidth || position.y > screenHeight)
            Reset();
    }
 
    void Reset() {
        position.x = GetRandomValue(-screenWidth * 0.4f, screenWidth);
        position.y = GetRandomValue(-screenHeight * 20);
        float max = 10000.0f;
        velocity.x = (GetRandomValue(1, (int)max) / max) * 0.5f;
        velocity.y = (GetRandomValue(1, (int)max) / max) + 0.8f;
    }
};
 
int main(void)
{
    //SetConfigFlags(FLAG_WINDOW_TRANSPARENT | FLAG_WINDOW_UNDECORATED);
    InitWindow(screenWidth, screenHeight, "raylib example");
    // InitWindow() 실행시 내부에서 SetRandomSeed()를 time(NULL)로 초기화 한다.
    // particle 생성시 GetRandomValue()를 사용하므로 InitWindow() 이후에 생성한다.
    SetTargetFPS(60);
 
    RenderTexture2D target = LoadRenderTexture(screenWidth, screenHeight);
 
    texture = LoadTexture("snow.png");
    // NOTE: Textures MUST be loaded after Window initialization (OpenGL context is required)
 
    const int number_particles = 400;
    Particles particles[number_particles];
 
    while (!WindowShouldClose())
    {
        BeginTextureMode(target);
        {
            ClearBackground(BLACK);
 
            for (int i = 0; i < number_particles; i++)
                particles[i].Draw_particle();
 
            DrawFPS(00);
        }
        EndTextureMode();
 
        BeginDrawing();
        {
            //ClearBackground(BLACK);
            //DrawTexture(target.texture, 0, 0, WHITE); // 위아래가 뒤집혀 보인다.
            DrawTextureRec(target.texture, { 00, screenWidth, -screenHeight }, { 00 }, WHITE);
            // OpenGL의 좌표 체계가 다르기 때문에 위아래를 뒤집어야 제대로 표시된다.
        }
        EndDrawing();
    }
 
    UnloadTexture(texture);
    UnloadRenderTexture(target);
 
    CloseWindow();
 
    return 0;
}
 

 

 

Raylib 파티클 예제에 나오는 것 처럼 구조체를 이용해 봐도 별 차이가 없다.

Raylib Particles  

더보기

 

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
#include "raylib.h"
 
const int screenWidth = 640;
const int screenHeight = 480;
 
Texture2D texture;
 
typedef struct {
    Vector2 position;
    Vector2 velocity;
} s_particle;
 
const int number_particles = 400;
s_particle particles[number_particles];
 
void spawn_particle(s_particle* pt) {
    pt->position.x = GetRandomValue(-screenWidth * 0.4f, screenWidth);
    pt->position.y = GetRandomValue(-screenHeight * 20);
    float max = 10000.0f;
    pt->velocity.x = (GetRandomValue(1, (int)max) / max) * 0.5f;
    pt->velocity.y = (GetRandomValue(1, (int)max) / max) + 0.8f;
}
 
void draw_particle(s_particle pt[]) {
    for (int i = 0; i < number_particles; i++) {
        s_particle* particle = &pt[i];
        particle->position.x += particle->velocity.x;
        particle->position.y += particle->velocity.y;
 
        if (particle->position.x > screenWidth || particle->position.y > screenHeight)
            spawn_particle(particle);
 
        DrawTexture(texture, particle->position.x, particle->position.y, WHITE);
    }
}
 
int main(void)
{
    //SetConfigFlags(FLAG_WINDOW_TRANSPARENT | FLAG_WINDOW_UNDECORATED);
    InitWindow(screenWidth, screenHeight, "raylib example");
    // InitWindow() 실행시 내부에서 SetRandomSeed()를 time(NULL)로 초기화 한다.
    // particle 생성시 GetRandomValue()를 사용하므로 InitWindow() 이후에 생성한다.
    SetTargetFPS(60);
 
    texture = LoadTexture("snow.png");
    // NOTE: Textures MUST be loaded after Window initialization (OpenGL context is required)
 
    for (int i = 0; i < number_particles; i++) {
        spawn_particle(&particles[i]);
    }
 
    while (!WindowShouldClose())
    {
        BeginDrawing();
        {
            ClearBackground(BLACK);
 
            draw_particle(particles);
 
            DrawFPS(00);
        }
        EndDrawing();
    }
 
    UnloadTexture(texture);
 
    CloseWindow();
 
    return 0;
}
 

 

한 가지 확인한 것은 velocity->x, velocity->y의 값을 임의의 소수가 아닌 (1이나 2같은)정수로 바꾸면 깜빡임이 없다.

Raylib 렌더링 함수에서 소수 처리 부분에 문제가 있는게 아닐까 싶다.

 

매 프레임마다 눈 이동 거리를 delta time으로 계산해도 별 의미가 없는거 같다.

더보기

 

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
#include "raylib.h"
 
const int screenWidth = 640;
const int screenHeight = 480;
 
Texture2D texture;
 
typedef struct {
    Vector2 position;
    Vector2 velocity;
} s_particle;
 
const int number_particles = 400;
s_particle particles[number_particles];
 
void spawn_particle(s_particle* pt) {
    pt->position.x = GetRandomValue(-screenWidth * 0.4f, screenWidth);
    pt->position.y = GetRandomValue(-screenHeight * 20);
    //float max = 10000.0f;
    //pt->velocity.x = (float)(GetRandomValue(1, (int)max) / max) * 0.5f;
    //pt->velocity.y = (float)(GetRandomValue(1, (int)max) / max) + 0.8f;
 
    pt->velocity.x = (float)GetRandomValue(030+ 10;
    // 눈이 오른쪽으로 이동하는 속도를 초당 10~40 픽셀로 설정
    pt->velocity.y = (float)GetRandomValue(2080+ 40;
    // 눈이 아래로 떨어지는 속도를 초당 60~120 픽셀로 설정    
}
 
void draw_particle(s_particle pt[], float delta) {
    for (int i = 0; i < number_particles; i++) {
        s_particle* particle = &pt[i];
        particle->position.x += particle->velocity.x * delta;
        particle->position.y += particle->velocity.y * delta;
 
        if (particle->position.x > screenWidth || particle->position.y > screenHeight)
            spawn_particle(particle);
 
        DrawTexture(texture, particle->position.x, particle->position.y, WHITE);
    }
}
 
int main(void)
{
    //SetConfigFlags(FLAG_WINDOW_TRANSPARENT | FLAG_WINDOW_UNDECORATED);
    InitWindow(screenWidth, screenHeight, "raylib example");
    // InitWindow() 실행시 내부에서 SetRandomSeed()를 time(NULL)로 초기화 한다.
    // particle 생성시 GetRandomValue()를 사용하므로 InitWindow() 이후에 생성한다.
    SetTargetFPS(60);
 
    texture = LoadTexture("snow.png");
    // NOTE: Textures MUST be loaded after Window initialization (OpenGL context is required)
 
    for (int i = 0; i < number_particles; i++) {
        spawn_particle(&particles[i]);
    }
 
    while (!WindowShouldClose())
    {
        BeginDrawing();
        {
            ClearBackground(BLACK);
 
            draw_particle(particles, GetFrameTime());
            // GetFrameTime() : Get time in seconds for last frame drawn (delta time)
 
            DrawFPS(00);
        }
        EndDrawing();
    }
 
    UnloadTexture(texture);
 
    CloseWindow();
 
    return 0;
}
 

 

 

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

픽셀 파티클 시스템을 만들어 보자.

 

더보기

 

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
#define SDL_MAIN_USE_CALLBACKS 1
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <stdio.h>
 
SDL_Window* window = NULL;
SDL_Renderer* renderer = NULL;
 
int screen_width = 640;
int screen_height = 480;
 
float speed = 0.001f;
Uint64 start;
Uint64 end;
float fps = 3000;
 
typedef struct {
    float x, y;
    float vx, vy;
    int life;
} s_particle;
 
const int number_particles = 100;
s_particle particles[number_particles];
 
void spawn_particle(s_particle* pt) {
    pt->= SDL_randf() * screen_width;
    pt->= SDL_randf() * screen_height;
    pt->vx = (SDL_randf() * 2 - 1* speed;
    pt->vy = (SDL_randf() * 2 - 1* speed;
    // '* 2 - 1' 의 의미: 음의 방향으로도 움직이기 위해서.
    pt->life = (int)fps + SDL_rand((int)fps * 2);
}
 
void draw_particle(s_particle pt[]) {
    for (int i = 0; i < number_particles; i++) {
        s_particle* particle = &pt[i];
        particle->+= particle->vx;
        particle->+= particle->vy;
        particle->life--;
 
        if (particle->life <= 0)
            spawn_particle(particle);
 
        SDL_SetRenderDrawColor(renderer, 02550, SDL_ALPHA_OPAQUE);
        SDL_RenderPoint(renderer, particle->x, particle->y);
    }
}
 
SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[])
{
    SDL_SetAppMetadata("Example""1.0""sean");
 
    if (!SDL_Init(SDL_INIT_VIDEO)) {
        SDL_Log("Couldn't initialize SDL: %s", SDL_GetError());
        return SDL_APP_FAILURE;
    }
 
    if (!SDL_CreateWindowAndRenderer("examples/renderer/clear", screen_width, screen_height, 0&window, &renderer)) {
        SDL_Log("Couldn't create window/renderer: %s", SDL_GetError());
        return SDL_APP_FAILURE;
    }
 
    for (int i = 0; i < number_particles; i++) {
        spawn_particle(&particles[i]);
    }
 
    return SDL_APP_CONTINUE;
}
 
SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event)
{
    switch (event->type) {
    case SDL_EVENT_QUIT:
        return SDL_APP_SUCCESS;
    case SDL_EVENT_KEY_DOWN:
        printf("Key pressed: %s\n", SDL_GetKeyName(event->key.key));
        if (event->key.key == SDLK_ESCAPE)
            return SDL_APP_SUCCESS;
        break;
    default:
        break;
    }
 
    return SDL_APP_CONTINUE;
}
 
SDL_AppResult SDL_AppIterate(void* appstate)
{
    // FPS 계산. 사용하지는 않음.
    start = SDL_GetPerformanceCounter();
 
    {
        SDL_SetRenderDrawColor(renderer, 000, SDL_ALPHA_OPAQUE);
        SDL_RenderClear(renderer);
 
        draw_particle(particles);
 
        SDL_RenderPresent(renderer);
    }
 
    end = SDL_GetPerformanceCounter();
    fps = 1.0f / ((end - start) / (float)SDL_GetPerformanceFrequency());
 
    return SDL_APP_CONTINUE;
}
 
void SDL_AppQuit(void* appstate, SDL_AppResult result)
{
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);
    SDL_Quit();
}
 

 

 

위 코드에서는 FPS를 설정하지 않고 시스템의 CPU 속도에 맞게 speed 변수를 적당히 조절했다. FPS 설정이 필요하면 아래 링크를 참고한다.

2025.04.22 - [C, C++] - [SDL] Framerate Per Second FPS

 

파티클들이 사방으로 움직인다.

 

이번엔 불꽃놀이 파티클을 만들어 보자.

 

더보기

 

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
#define SDL_MAIN_USE_CALLBACKS 1
 
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <stdio.h>
 
SDL_Window* window = NULL;
SDL_Renderer* renderer = NULL;
 
int screen_width = 640;
int screen_height = 480;
 
float speed = 3.0f;
float fps = 60;
 
typedef struct {
    float x, y;
    float vx, vy;
    SDL_Color color;
    int life;
    bool exist;
} s_particle;
 
const int number_particles = 1000;
s_particle particles[number_particles];
SDL_Point spawn_point;
 
void spawn_particle(s_particle* pt) {
    pt->exist = true;
    pt->= spawn_point.x;
    pt->= spawn_point.y;
 
    float x = (SDL_randf() * 2 - 1);
    float y = (SDL_randf() * 2 - 1);
    float n = SDL_sqrtf(x * x + y * y);
    // 벡터 정규화. 불꽃이 원형으로 퍼져나가기 위해.
    // '* 2 - 1' 의 의미: 음의 방향으로도 움직이기 위해.
    pt->vx = (x / n) * speed * SDL_randf();
    pt->vy = (y / n) * speed * SDL_randf();
 
    pt->color.r = SDL_rand(256);
    pt->color.g = SDL_rand(256);
    pt->color.b = SDL_rand(256);
    pt->life = (int)fps + SDL_rand((int)fps * 0.5f);
}
 
void draw_particle(s_particle pt[]) {
    for (int i = 0; i < number_particles; i++) {
        s_particle* particle = &pt[i];
        if (particle->exist) {
            particle->+= particle->vx;
            particle->+= particle->vy;
            particle->life--;
 
            if (particle->life <= 0) {
                particle->exist = false;
                continue;
            }
            SDL_SetRenderDrawColor(renderer, particle->color.r, particle->color.g, particle->color.b, SDL_ALPHA_OPAQUE);
            SDL_RenderPoint(renderer, particle->x, particle->y);
        }
    }
}
 
SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[])
{
    SDL_SetAppMetadata("Example""1.0""sean");
 
    if (!SDL_Init(SDL_INIT_VIDEO)) {
        SDL_Log("Couldn't initialize SDL: %s", SDL_GetError());
        return SDL_APP_FAILURE;
    }
 
    if (!SDL_CreateWindowAndRenderer("examples/renderer/clear", screen_width, screen_height, 0&window, &renderer)) {
        SDL_Log("Couldn't create window/renderer: %s", SDL_GetError());
        return SDL_APP_FAILURE;
    }
 
    return SDL_APP_CONTINUE;
}
 
SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event)
{
    switch (event->type) {
    case SDL_EVENT_QUIT:
        return SDL_APP_SUCCESS;
    case SDL_EVENT_MOUSE_BUTTON_DOWN:
        if (event->button.button == 1) { // 왼쪽 버튼 클릭. 오른쪽 버튼은 3.
            printf("x: %f, y: %f\n", event->button.x, event->button.y);
            spawn_point.x = event->button.x;
            spawn_point.y = event->button.y;
            for (int i = 0; i < number_particles; i++)
                spawn_particle(&particles[i]);
        }
        break;
    case SDL_EVENT_KEY_DOWN:
        printf("Key pressed: %s\n", SDL_GetKeyName(event->key.key));
        if (event->key.key == SDLK_ESCAPE)
            return SDL_APP_SUCCESS;
        break;
    default:
        break;
    }
 
    return SDL_APP_CONTINUE;
}
 
SDL_AppResult SDL_AppIterate(void* appstate)
{
    SDL_Delay(1000 / fps);
 
    {
        SDL_SetRenderDrawColor(renderer, 000, SDL_ALPHA_OPAQUE);
        SDL_RenderClear(renderer);
 
        draw_particle(particles);
 
        SDL_RenderPresent(renderer);
    }
 
    return SDL_APP_CONTINUE;
}
 
void SDL_AppQuit(void* appstate, SDL_AppResult result)
{
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);
    SDL_Quit();
}
 

 

 

마우스 클릭 지점에서 간단한 불꽃놀이 파티클이 생성된다.

 

이번엔 픽셀이 아닌 비트맵 이미지로 파티클 시스템을 만들어 보자.

snow.bmp
0.00MB

 

snow1.bmp
0.00MB

 

 

더보기

 

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
#define SDL_MAIN_USE_CALLBACKS 1
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <stdio.h>
 
SDL_Window* window = NULL;
SDL_Renderer* renderer = NULL;
 
int screen_width = 640;
int screen_height = 480;
 
float speed = 0.01f;
Uint64 start;
Uint64 end;
float fps = 3000;
 
typedef struct {
    float x, y;
    float vx, vy;
    int life;
} s_particle;
 
const int number_particles = 200;
s_particle particles[number_particles];
 
// 파티클 이미지 텍스쳐와 렉트
SDL_Texture* texture;
SDL_FRect rect;
 
void spawn_particle(s_particle* pt) {
    pt->= SDL_randf() * screen_width;
    pt->= SDL_randf() * screen_height;
    pt->vx = (SDL_randf() * 2 - 1* speed;
    pt->vy = (SDL_randf() * 2 - 1* speed;
    // '* 2 - 1' 의 의미: 음의 방향으로도 움직이기 위해서.
    pt->life = (int)fps + SDL_rand((int)fps * 2);
}
 
void draw_particle(s_particle pt[]) {
    for (int i = 0; i < number_particles; i++) {
        s_particle* particle = &pt[i];
        particle->+= particle->vx;
        particle->+= particle->vy;
        particle->life--;
 
        if (particle->life <= 0)
            spawn_particle(particle);
 
        // 텍스쳐 그리기
        rect.x = particle->- rect.w / 2;
        rect.y = particle->- rect.h / 2;
        SDL_RenderTexture(renderer, texture, NULL&rect);
 
        //SDL_SetRenderDrawColor(renderer, 255, 0, 0, SDL_ALPHA_OPAQUE);
        //SDL_RenderPoint(renderer, particle->x, particle->y);
    }
}
 
SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[])
{
    SDL_SetAppMetadata("Example""1.0""sean");
 
    if (!SDL_Init(SDL_INIT_VIDEO)) {
        SDL_Log("Couldn't initialize SDL: %s", SDL_GetError());
        return SDL_APP_FAILURE;
    }
 
    if (!SDL_CreateWindowAndRenderer("example", screen_width, screen_height, 0&window, &renderer)) {
        SDL_Log("Couldn't create window/renderer: %s", SDL_GetError());
        return SDL_APP_FAILURE;
    }
 
    for (int i = 0; i < number_particles; i++) {
        spawn_particle(&particles[i]);
    }
 
    // 비트맵 로드
    SDL_Surface* bmpSurface = SDL_LoadBMP("snow.bmp");
    if (!bmpSurface) {
        SDL_Log("Couldn't load BMP: %s", SDL_GetError());
        return SDL_APP_FAILURE;
    }
 
    // 컬러키(투명) 설정
    SDL_SetSurfaceColorKey(bmpSurface, true, SDL_MapSurfaceRGB(bmpSurface, 0x000x000x00));
    //SDL_SetSurfaceColorKey(bmpSurface, true, SDL_MapRGB(SDL_GetPixelFormatDetails(bmpSurface->format), NULL, 0x00, 0x00, 0x00));
    rect = { 00, (float)bmpSurface->w, (float)bmpSurface->h };
    texture = SDL_CreateTextureFromSurface(renderer, bmpSurface);
 
    SDL_DestroySurface(bmpSurface);
 
    return SDL_APP_CONTINUE;
}
 
SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event)
{
    switch (event->type) {
    case SDL_EVENT_QUIT:
        return SDL_APP_SUCCESS;
    case SDL_EVENT_KEY_DOWN:
        printf("Key pressed: %s\n", SDL_GetKeyName(event->key.key));
        if (event->key.key == SDLK_ESCAPE)
            return SDL_APP_SUCCESS;
        break;
    default:
        break;
    }
 
    return SDL_APP_CONTINUE;
}
 
SDL_AppResult SDL_AppIterate(void* appstate)
{
    // FPS 계산. 사용하지는 않음.
    start = SDL_GetPerformanceCounter();
 
    {
        SDL_SetRenderDrawColor(renderer, 000, SDL_ALPHA_OPAQUE);
        SDL_RenderClear(renderer);
 
        draw_particle(particles);
 
        SDL_RenderPresent(renderer);
    }
 
    end = SDL_GetPerformanceCounter();
    fps = 1.0f / ((end - start) / (float)SDL_GetPerformanceFrequency());
 
    return SDL_APP_CONTINUE;
}
 
void SDL_AppQuit(void* appstate, SDL_AppResult result)
{
    SDL_DestroyTexture(texture);
 
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);
    SDL_Quit();
}
 

 

 

알파채널이 없는 비트맵이라 조금 어색하지만 눈이 사방으로 흩날린다.

 

 

이번엔 진짜 눈처럼 내리는 환경을 만들어 보자.

 

더보기

 

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
#define SDL_MAIN_USE_CALLBACKS 1
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <stdio.h>
 
SDL_Window* window = NULL;
SDL_Renderer* renderer = NULL;
 
int screen_width = 640;
int screen_height = 480;
 
Uint64 start;
Uint64 end;
float fps;
 
typedef struct {
    float x, y;
    float vx, vy;
} s_particle;
 
const int number_particles = 400;
s_particle particles[number_particles];
 
// 파티클 이미지 텍스쳐와 렉트
SDL_Texture* texture;
SDL_FRect rect;
 
void spawn_particle(s_particle* pt) {
    pt->= (SDL_randf() * 2 - 1* screen_width * 10;
    pt->= SDL_randf() * screen_height * -2;
    // 넓은 범위에서 눈이 생성되게 해서 실행 초기에 눈이 쏟아져 내리는걸 방지.
    pt->vx = SDL_randf() * 0.5f; // 눈은 오른쪽으로만 흩날린다.
    pt->vy = SDL_randf() + 0.8f;
}
 
void draw_particle(s_particle pt[]) {
    for (int i = 0; i < number_particles; i++) {
        s_particle* particle = &pt[i];
        particle->+= particle->vx;
        particle->+= particle->vy;
        // 눈이 화면 왼쪽에서도 바람에 날려 올 수 있도록 생존 범위 조정.
        if (particle->> screen_width || particle->< -screen_width || particle->> screen_height)
            spawn_particle(particle);
 
        // 텍스쳐 그리기
        rect.x = particle->- rect.w / 2;
        rect.y = particle->- rect.h / 2;
        SDL_RenderTexture(renderer, texture, NULL&rect);
 
        //SDL_SetRenderDrawColor(renderer, 255, 0, 0, SDL_ALPHA_OPAQUE);
        //SDL_RenderPoint(renderer, particle->x, particle->y);
    }
}
 
SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[])
{
    SDL_SetAppMetadata("Example""1.0""sean");
 
    if (!SDL_Init(SDL_INIT_VIDEO)) {
        SDL_Log("Couldn't initialize SDL: %s", SDL_GetError());
        return SDL_APP_FAILURE;
    }
 
    SDL_SetHint(SDL_HINT_RENDER_VSYNC, "1");
    // A variable controlling whether updates to the SDL screen surface should be synchronized
    // with the vertical refresh, to avoid tearing.
 
    if (!SDL_CreateWindowAndRenderer("example", screen_width, screen_height, 0&window, &renderer)) {
        SDL_Log("Couldn't create window/renderer: %s", SDL_GetError());
        return SDL_APP_FAILURE;
    }
 
    for (int i = 0; i < number_particles; i++) {
        spawn_particle(&particles[i]);
    }
 
    // 비트맵 로드
    SDL_Surface* bmpSurface = SDL_LoadBMP("snow.bmp");
    if (!bmpSurface) {
        SDL_Log("Couldn't load BMP: %s", SDL_GetError());
        return SDL_APP_FAILURE;
    }
 
    // 컬러키(투명) 설정
    SDL_SetSurfaceColorKey(bmpSurface, true, SDL_MapSurfaceRGB(bmpSurface, 0x000x000x00));
    //SDL_SetSurfaceColorKey(bmpSurface, true, SDL_MapRGB(SDL_GetPixelFormatDetails(bmpSurface->format), NULL, 0x00, 0x00, 0x00));
    rect = { 00, (float)bmpSurface->w, (float)bmpSurface->h };
    texture = SDL_CreateTextureFromSurface(renderer, bmpSurface);
 
    SDL_DestroySurface(bmpSurface);
 
    return SDL_APP_CONTINUE;
}
 
SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event)
{
    switch (event->type) {
    case SDL_EVENT_QUIT:
        return SDL_APP_SUCCESS;
    case SDL_EVENT_KEY_DOWN:
        printf("Key pressed: %s\n", SDL_GetKeyName(event->key.key));
        if (event->key.key == SDLK_ESCAPE)
            return SDL_APP_SUCCESS;
        break;
    default:
        break;
    }
 
    return SDL_APP_CONTINUE;
}
 
SDL_AppResult SDL_AppIterate(void* appstate)
{
    // FPS 계산. 사용하지는 않음.
    start = SDL_GetPerformanceCounter();
 
    {
        SDL_SetRenderDrawColor(renderer, 000, SDL_ALPHA_OPAQUE);
        SDL_RenderClear(renderer);
 
        draw_particle(particles);
 
        SDL_RenderPresent(renderer);
    }
 
    end = SDL_GetPerformanceCounter();
    fps = 1.0f / ((end - start) / (float)SDL_GetPerformanceFrequency());
 
    return SDL_APP_CONTINUE;
}
 
void SDL_AppQuit(void* appstate, SDL_AppResult result)
{
    SDL_DestroyTexture(texture);
 
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);
    SDL_Quit();
}
 

 

 

실제 프로그램을 실행하면 훨씬 자연스럽게 눈이 내린다.

 

이번엔 배경이 투명한 윈도우를 만들어 보자.

 

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
...
SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[])
{
    ...
    if (!SDL_CreateWindowAndRenderer("example", screen_width, screen_height, SDL_WINDOW_TRANSPARENT | SDL_WINDOW_BORDERLESS, &window, &renderer)) {
        SDL_Log("Couldn't create window/renderer: %s", SDL_GetError());
        return SDL_APP_FAILURE;
    }
    ...
}
...
SDL_AppResult SDL_AppIterate(void* appstate)
{
    start = SDL_GetPerformanceCounter();
 
    {
        //SDL_SetRenderDrawColor(renderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
        SDL_RenderClear(renderer);
 
        draw_particle(particles);
 
        SDL_RenderPresent(renderer);
    }
    ...
}
...
 

 

윈도우 생성시 SDL_WINDOW_TRANSPARENT |  SDL_WINDOW_BOARDERLESS 옵선을 주고 SDL_SetRenderDrawColor() 호출을 삭제하면 투명한 배경의 윈도우를 만들 수 있다.

 

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
...
SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[])
{
    ...    
    if (!SDL_CreateWindowAndRenderer("example", screen_width, screen_height, SDL_WINDOW_TRANSPARENT | SDL_WINDOW_BORDERLESS | SDL_WINDOW_FULLSCREEN, &window, &renderer)) {
        SDL_Log("Couldn't create window/renderer: %s", SDL_GetError());
        return SDL_APP_FAILURE;
    }
 
    SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
    ...
}
...
SDL_AppResult SDL_AppIterate(void* appstate)
{
    start = SDL_GetPerformanceCounter();
 
    {
        SDL_SetRenderDrawColor(renderer, 0000);
        SDL_RenderClear(renderer);
 
        draw_particle(particles);
 
        SDL_RenderPresent(renderer);
    }
    ...
}
...
 

 

아니면 위 코드와 같이 바꿔도 투명한 배경의 윈도우를 만들 수 있다. 위 코드의 경우 SDL_SetRendererDrawColor()의 r, g, b, a 값을 바꾸면 반투명하면서 색이 있는 배경의 윈도우를 만들 수 있다.

예) SDL_SetRendererDrawColor(renderer, 0, 0, 0, 128); // 약간 어두운 투명 배경

   SDL_SetRendererDrawColor(renderer, 128, 0, 0, 128); // 약간 붉은 투명 배경

 

윈도우의 배경이 투명해서 데스크탑에서 눈이 내리는것 처럼 보인다.

 

만약 전체 화면으로 만들고 싶다면 아래 내용을 추가(변경)하면 된다.

1. #include <Windows.h>

2. int screen_width =  GetSystemMetrics(SM_CXSCREEN);
   int screen_height = GetSystemMetrics(SM_CYSCREEN);

3. SDL_CreateWindowAndRenderer("example", screen_width, screen_height, SDL_WINDOW_TRANSPARENT | SDL_WINDOW_BORDERLESS | SDL_WINDOW_FULLSCREEN, &window, &renderer)

 

※ 참고

2025.07.23 - [SDL, raylib] - [SDL] Particle 파티클 (Snow) 2

 

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

AudioSwitcher를 이용해 시스템 볼륨을 조정해 보자.

 

AudioSwitcher Nuget Package를 설치한다.

 

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
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
using System.Runtime.InteropServices;
using AudioSwitcher.AudioApi.CoreAudio;
 
namespace ConsoleApp1
{
    class Program
    {
        [DllImport("kernel32.dll")]
        static extern IntPtr GetConsoleWindow();
 
        [DllImport("user32.dll")]
        static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
 
        static void Main(string[] args)
        {
            const int SW_HIDE = 0// 창 숨기기
            const int SW_SHOW = 1// 창 보이기
 
            IntPtr handle = GetConsoleWindow();
            //ShowWindow(handle, SW_HIDE);
 
            CoreAudioDevice defaultPlaybackDevice = new CoreAudioController().DefaultPlaybackDevice;
            Console.WriteLine("Current Volume: " + defaultPlaybackDevice.Volume);
 
            while (true)
            {
                if (defaultPlaybackDevice.Volume > 20// 볼륨이 20 보다 크다면
                {
                    while (defaultPlaybackDevice.Volume > 20// 볼륨이 20 보다 크지 않을때 까지 무한 루프
                    {
                        defaultPlaybackDevice.Volume--// 볼륨 1 감소
                        Console.WriteLine("Current Volume: " + defaultPlaybackDevice.Volume);
                        System.Threading.Thread.Sleep(1000); // 매 1초 확인
                    }
                }
 
                System.Threading.Thread.Sleep(10000); // 매 10초 확인
            }
        }
    }
}
 

 

소스를 입력하고 빌드한다.

 

프로그램을 실행하면 시스템 볼륨이 20 이하일 때까지 1초마다 1씩 감소한다.

 

처음 35였던 볼륨이 20이 되었다.

 

2022.01.07 - [C#] - C# AudioSwitcher System Audio/Sound Volume Control - 시스템 오디오/사운드 볼륨 컨트롤 2

 

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

예전 영화에서 종종 볼 수 있던 레이저 침입 감지 시스템은 간단한 작동 원리에도 불구하고 레이저를 이용 한 방식이 뭔가 있어 보이는지 아직까지도 영화나 드라마에서 심심치 않게 볼 수 있다. 작동 원리가 간단한 만큼 저렴한 부품으로도 비슷하게 만들어 볼 수 있다.


침입 감지 시스템을 회피하기 위한 Catherine Zeta-Jones의 피나는 노력 (영화 Entrapment)


The Big Bang Theory


약 $0.4 짜리 레이저 모듈. 5V로 작동하고 출력 5mW, 파장 650nm의 붉은색 레이저를 발생 시킨다.


Red 

625 - 740nm

Orange

590 - 625nm

Yellow

565 - 590nm

Green

520 - 565nm

Cyan

500 - 520nm

Blue

435 - 500nm

Violet

380 - 435nm


약 $0.7 짜리 레이저 수신 모듈. 5V로 작동하고, 레이저가 수신 될때는 HIGH 시그널을, 수신 되지 않을때는 LOW 시그널을 출력 한다. 수신하려는 레이저 외, 태양이나 다른 강한 빛이 없는 실내용.



레이저 리시버 센서는 방향에 주의 한다. 반대로 연결하면 뜨거울 정도로 열이 발생 한다.


위 다이어그램과 같이 구성 한다.


레이저 모듈에서 레이저가 발생되고 수신 모듈에서 이 레이저를 감지하면 HIGH 시그널을 출력 한다.


중간에 장애물이 생겨 레이저를 수신하지 못하게 되면 수신 모듈에서 LOW 시그널을 출력하고 아두이노는 디지털 4번 핀으로 HIGH를 출력해 Buzzer를 작동 시킨다.



실제 부품의 연결 방식은 다이어그램과 다르지만 기본 구성은 동일 하다. 


소스를 컴파일하고 아두이노에 업로드 한다.


제작 과정 및 테스트


Serial Monitor에도 적의 침입 기록이 남는다.


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

It describes how to draw the simple solar system that is composed of the Sun, the Earth and the Moon.

OpenGL로 태양, 지구, 달로 구성된 간단한 태양계 그리기

 

1 day = 10 frames

1 month = 30 days (300 frames)

1 year = 12 months (3,600 frames)

 

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
#include <gl/glut.h>
#include <iostream>
 
GLfloat EarthRotation = 0.0f;
GLfloat MoonRevolution = 0.0f;
GLfloat EarthRevolution = 0.0f;
 
GLfloat FramePerYear = 3600.0f;
GLint delay = 10;
 
GLint Year = 2020;
GLint Month = 1;
GLint Day = 1;
 
void MyDisplay();
void MyTimer(int value);
 
int main(int argc, char** argv)
{
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
    glutCreateWindow("OpenGL Solar System");
 
    gluLookAt(
        0.01.00.0,    // Eye
        0.00.00.0,    // Center
        1.00.01.0    // Up
    );
    // gluLookAt creates a viewing matrix derived from an eye point, a reference point indicating
    // the center of the scene, and an UP vector.
 
    glutTimerFunc(delay, MyTimer, 1);
    glutDisplayFunc(MyDisplay);
    
    glutMainLoop();
 
    return 0;
}
 
void MyDisplay()
{
    glClear(GL_COLOR_BUFFER_BIT);
    
    // The Sun
    glutWireSphere(0.44020);
    
    // The Earth
    glPushMatrix();
    glRotatef(EarthRevolution, 0.0f1.0f0.0f); // Earth's revolution
    glTranslatef(0.7f0.0f0.0f);
    
    glRotatef(-EarthRevolution, 0.0f1.0f0.0f);    // Earth의 Revolution에 의한 Earth의 회전 삭제.
    glRotatef(EarthRotation, 0.0f1.0f0.0f);    // Earth's rotation
    glutWireSphere(0.12010);
 
        // The Moon
        glPushMatrix();
        glRotatef(-EarthRotation, 0.0f1.0f0.0f);    // Earth의 Rotation에 의한 Moon의 회전 삭제.
        glRotatef(MoonRevolution, 0.0f1.0f0.0f); // Moon's revolution
        glTranslatef(0.2f0.0f0.0f);
        glutWireSphere(0.03105);
        glPopMatrix();
 
    glPopMatrix();
    
    glutSwapBuffers();
}
 
void MyTimer(int value)
{
    EarthRotation += (360.0f * 360.0f/ FramePerYear;    // 지구 자전: (360도 * 360일) / FramePerYear(3600) = 36도
    if (EarthRotation >= 360.0f) {
        EarthRotation = 0.0f;
 
        std::cout << Year << "-" << Month << "-" << Day << std::endl;
        // 날짜 출력 코드가 계산 코드(Day++, Month++, Year++)보다 먼저 처리 되야 제대로 표시 된다.
 
        Day++;
        if (Day > 30)
            Day = 1;
    }
 
    MoonRevolution += (360.0f * 12.0f/ FramePerYear;
    // 달의 공전 주기는 약27일 이지만 30일(1달)로 설정.
    // Rotation은 Revolution 과 같은 주기로 따로 계산할 필요 없음.
    // Revolution 처리 만으로 항상 달의 같은면이 지구를 바라 봄.
    if (MoonRevolution >= 360.0f) {
        MoonRevolution = 0.0f;
 
        Month++;
        if (Month > 12)
            Month = 1;
    }
 
    EarthRevolution += 360.0f / FramePerYear;    // 1 프레임에 0.1도 회전. 3600 프레임이 1년.
    if (EarthRevolution >= 360.0f) {
        EarthRevolution = 0.0f;
 
        Year++;
    }
    
    glutPostRedisplay();
    glutTimerFunc(delay, MyTimer, 1);
}
 

 

Run the program and see how it works.

 

 

반응형
Posted by J-sean
: