Coding & Programming/C, C++, SFML

[C/C++, SFML] 11. 개체(Shape, Sprite) 충돌(Collision) 감지하기

mainCodes 2021. 4. 5. 10:18

안녕하세요 JollyTree입니다 (•̀ᴗ•́)و


SFML의 sf:Rect<T> 클래스intersects() 메소드는 사각형 영역에 대한 충돌(Collision) 감지를 지원합니다. 2개의 Sprite나 Shape가 존재하면서 서로 영역이 겹친다면 이를 충돌로 인식할 수 있습니다.이번 포스팅은 1:n의 RectangleShape 개체를 화면에 출력하고 1을 플레이어로 가정하여 n개의 개체들과 충돌하는 것을 공유하려고 합니다.

 

0

기본 컨셉은 먼저 MAX_ENEMY_COUNT 20개 만큼 enemies 에 저장합니다. 20개의 적들은 1~25 x 1~25의 랜덤한 크기로 1~500의 랜덤한 위치에서 등장합니다. 사용자는 상/하/좌/우 방향키로 플레이어(rect_player)를 조정하여 위에서 아래로 박스들과의 충돌을 시도합니다.

만약 20개의 적들 중 플레이어(rect_player)와 충돌이 발생하면 해당 적은 개체 목록에서 사라질뿐만 아니라 화면에서도 사라지게됩니다. 모든 개체들과 충돌을 유발하여 남은 개체 수가 0 이되면 프로그램은 종료됩니다.

아래 RectangleShape, CircleShape 개체 대신 Sprite 개체로 바꿔도 충돌 실험은 가능할 것 같은데 스프라이트의 투명색을 인식해서 정확히 경계선 체크가 될지는 확인을 해보아야 할것 같습니다. 만약 SFML이 지원하지 않는다면 직접 개발을 해야 하지 않을까 합니다. 이 부분은 나중에 확인되면 공유할게요.

  //RectangleShape
    vector<RectangleShape> enemies;
    RectangleShape enemy_circle;

 

 

    //CircleShape

    vector<CircleShape> enemies;  
    CircleShape enemy_circle;

 

예제에서는 충돌이 발생하면 rect_player는 그대로 있고 충돌한 적이 사라지게 되어 있습니다. 만약, 플레이어(rect_player)를 사라지게 하고 싶으면 n개의 플레이어(rect_player)의 총 개수를 카운드 해야 할 것 같네요. 목숨이 하나라면 그냥 화면상에서 사라지고 게임을 끝내면 되구요.

 

SFML sf:Rect<T> 클래스 intersects()를 이용 개체 충돌 예제(Example):

 
#include <SFML/Graphics.hpp>
#include <iostream>
 
#define MOVE_PIXEL        5.f
#define MAX_ENEMY_COUNT   20
 
using namespace std;
using namespace sf;
 
float rand_number(int max)
{
    float num = (float)(rand() % max + 1);
 
    return num;
}
 
int main()
{
    Clock clock;
    float interval = 0;
    float rectx_p = 0, recty_p = 0;
    float rectx_n = 50, recty_n = 50;
 
    srand((unsigned int)time(NULL));
 
    RectangleShape rect_player;
    vector<RectangleShape> enemies;
    RectangleShape enemy_rect;
 
    rect_player.setSize(sf::Vector2f(1515));
    rect_player.setOutlineColor(sf::Color::Green);
    rect_player.setOutlineThickness(2);
    rect_player.setPosition(150400);
 
    for (int i = 0; i < MAX_ENEMY_COUNT; i++)
    {
        enemy_rect.setSize(Vector2f(rand_number(25), rand_number(25)));
        enemy_rect.setOutlineColor(sf::Color::Red);
        enemy_rect.setOutlineThickness(5);
        enemy_rect.setPosition(rand_number(500), recty_n);
        Vector2f rpos = enemy_rect.getPosition();
        cout << "rpos.x = " << rpos.x << "rpos.y = " << rpos.y << endl;
        enemies.push_back(enemy_rect);
    }
 
    cout << "프로그램이 시작되었습니다." << endl;
 
    RenderWindow app(VideoMode(504504), "https://maincodes.tistory.com/");
    app.setFramerateLimit(60);
 
    while (app.isOpen())
    {
        float time = clock.getElapsedTime().asSeconds();
        clock.restart();
 
        interval += time;
 
        Event event;
        while (app.pollEvent(event))
        {
 
            if (event.type == Event::EventType::Closed)
            {
                app.close();
                cout << "프로그램이 종료되었습니다." << endl;
            }
 
            if (Keyboard::isKeyPressed(Keyboard::Left))
                rect_player.move(-MOVE_PIXEL, 0);
 
            if (Keyboard::isKeyPressed(Keyboard::Right))
                rect_player.move(MOVE_PIXEL, 0);
 
            if (Keyboard::isKeyPressed(Keyboard::Up))
                rect_player.move(0-MOVE_PIXEL);
 
 
            if (Keyboard::isKeyPressed(Keyboard::Down))
                rect_player.move(0, MOVE_PIXEL);
        }
 
       app.clear(Color::Black);
 
        if ((int)interval % 1 == 0)
        {
            vector< RectangleShape>::iterator iter;
            for (iter = enemies.begin(); iter != enemies.end(); iter++)
            {
                //내려오는 속도를 랜덤하게 설정
                (*iter).move(0, rand_number(3));
 
                Vector2f pos = (*iter).getPosition();
                if (pos.y > 500)
                    (*iter).setPosition(rand_number(400), recty_n);
            }
        }
 
        //상자들에 대한 글로벌 바운드 구하고, 플레이어 상자와 충돌감지
        vector< RectangleShape>::iterator iter;
        for (int i = 0; i < enemies.size(); i++)
        {
            if ((enemies[i].getGlobalBounds()).intersects(rect_player.getGlobalBounds()))
            {
                enemies.erase(enemies.begin() + i);
                cout << i << "번째 상자와 충돌 발생!" << endl;
            }
        }
 
        //적 상자 그리기
        for (iter = enemies.begin(); iter != enemies.end(); iter++)
            app.draw(*iter);
 
        //플레이어 상자 그리기
        app.draw(rect_player);
 
        app.display();
 
        if (enemies.size() == 0)
        {
            cout << "충돌 시험이 끝났습니다. 프로그램을 종료합니다." << endl;
            app.close();
 
            break;
        }
    }
 
    return 0;
}
cs


실행결과(Output):

 

코드 실행 콘솔(좌측)과 게임 화면(우측)

예제는 플레이어(rect_player)가 적 상자들을 따라가서 없애야 하지만 위에서 잠시 설명했듯이 조금 응용하면 적 상자들과의 충돌을 피해는 유명한 닷지 게임(총알 피하기) 형태로도 변경이 가능합니다. 특별히 계획을 가지고 있진 않지만 우주를 배경으로 한 닷지 게임을 스터디 목적으로 직접 제작해 보고 싶은 마음이 듭니다. :) 

 

아래 예제는 RectangleShape 대신 CircleShape로 변경한 예제입니다. 내용은 거의 동일하고요. 사각형이 아닌 원이기 때문에 중간에 setSize() 대신 setRadious() 메소드를 사용한 정도만 차이가 있어요.

 

CircleShape를 이용한 충돌 시험 예제(Example):

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
#include <SFML/Graphics.hpp>
#include <iostream>
 
#define MOVE_PIXEL        5.f
#define MAX_ENEMY_COUNT   20
 
using namespace std;
using namespace sf;
 
float rand_number(int max)
{
    float num = (float)(rand() % max + 1);
 
    return num;
}
 
int main()
{
    Clock clock;
    float interval = 0;
    float rectx_p = 0, recty_p = 0;
    float rectx_n = 50, recty_n = 50;
 
    srand((unsigned int)time(NULL));
 
    RectangleShape rect_player;
    vector<CircleShape> enemies;
    CircleShape enemy_circle;
 
    rect_player.setSize(sf::Vector2f(1515));
    rect_player.setOutlineColor(sf::Color::Green);
    rect_player.setOutlineThickness(2);
    rect_player.setPosition(150400);
 
    for (int i = 0; i < MAX_ENEMY_COUNT; i++)
    {
        enemy_circle.setRadius(rand_number(25));
        enemy_circle.setOutlineColor(sf::Color::Red);
        enemy_circle.setOutlineThickness(5);
        enemy_circle.setPosition(rand_number(500), recty_n);
        Vector2f rpos = enemy_circle.getPosition();
        cout << "rpos.x = " << rpos.x << "rpos.y = " << rpos.y << endl;
        enemies.push_back(enemy_circle);
    }
 
    cout << "프로그램이 시작되었습니다." << endl;
 
    RenderWindow app(VideoMode(504504), "https://maincodes.tistory.com/");
    app.setFramerateLimit(60);
 
    while (app.isOpen())
    {
        float time = clock.getElapsedTime().asSeconds();
        clock.restart();
 
        interval += time;
 
        Event event;
        while (app.pollEvent(event))
        {
 
            if (event.type == Event::EventType::Closed)
            {
                app.close();
                cout << "프로그램이 종료되었습니다." << endl;
            }
 
            if (Keyboard::isKeyPressed(Keyboard::Left))
                rect_player.move(-MOVE_PIXEL, 0);
 
            if (Keyboard::isKeyPressed(Keyboard::Right))
                rect_player.move(MOVE_PIXEL, 0);
 
            if (Keyboard::isKeyPressed(Keyboard::Up))
                rect_player.move(0-MOVE_PIXEL);
 
 
            if (Keyboard::isKeyPressed(Keyboard::Down))
                rect_player.move(0, MOVE_PIXEL);
        }
 
        app.clear(Color::Black);
 
        if ((int)interval % 1 == 0)
        {
            vector< CircleShape>::iterator iter;
            for (iter = enemies.begin(); iter != enemies.end(); iter++)
            {
                //내려오는 속도를 랜덤하게 설정
                (*iter).move(0, rand_number(3));
 
                Vector2f pos = (*iter).getPosition();
                if (pos.y > 500)
                    (*iter).setPosition(rand_number(400), recty_n);
            }
        }
 
        //상자들에 대한 글로벌 바운드 구하고, 플레이어 상자와 충돌감지
        vector<CircleShape>::iterator iter;
        for (int i = 0; i < enemies.size(); i++)
        {
            if ((enemies[i].getGlobalBounds()).intersects(rect_player.getGlobalBounds()))
            {
                enemies.erase(enemies.begin() + i);
                cout << i << "번째 원과 충돌 발생!" << endl;
            }
        }
 
        //적 상자 그리기
        for (iter = enemies.begin(); iter != enemies.end(); iter++)
            app.draw(*iter);
 
        //플레이어 상자 그리기
        app.draw(rect_player);
 
        app.display();
 
        if (enemies.size() == 0)
        {
            cout << "충돌 시험이 끝났습니다. 프로그램을 종료합니다." << endl;
            app.close();
 
            break;
        }
    }
 
    return 0;
}
cs

 

실행결과(Output):

관심있는 분들께 조금이라도 도움이 되었으면 좋겠습니다. 이상 JollyTree였습니다 (•̀ᴗ•́)و