눈팅하는 게임개발자 블로그
스플래툰 승리/패배 판정 시스템 본문
예전 게임에 만들어서 적용해보려던 것이 있었는데 그래픽에 대한 지식이 없어서 포기했었던 적이 있었다.
스플래툰의 승리/패배 판정 시스템이다.
스플래툰의 랭크 배틀이 아닌 일반 배틀의 기본 룰은
1. 주어진 무기를 활용해(물총, 롤러, 저격총, 물바가지 등) 적 잉클링들을 잡거나 맵을 칠한다.
2. 끝난 시점에 더 많은 맵의 면적을 자신의 팀 색상으로 칠한 팀이 승리한다.
여기서 승패를 가르기 위한 각 팀의 스코어를 집계하기 위한 방법을 생각해보자면
그저 맵의 모든 부분의 (static한)mesh에 닿아있는 페인트의 면적 숫자 자체를 스코어로서 기록하여
기존 다른 게임의 스코어를 집계하는 것 처럼 할 수 있다.
실제 스플래툰에서도 포인트를 따로 계산하는 걸 보아서는 이런 방식으로 스코어를 집계하는 것일 수도 있다.
하지만 이는 그저 맵에 페인트된 색상의 비율을 찾는 것이므로 다른 방법으로도 스코어를 집계할 수 있다.
게임이 끝난 시점에 맵 전체를 도면처럼 하나의 quad로 펼쳐서 색상 정보를 담고.
어떤 팀의 색상이 화면에서 더 높은 비중을 차지하는가를 계산하면 된다.
실제 게임 종료 화면에서도 해당 맵을 탑뷰에서 내려다보는 것처럼 보여주면서 승패를 판정한다.
결국 핵심 로직은 펼쳐진 스크린의 pixel 정보들을 읽어내어 양 팀 색상의 비중을 얻어내면 되는 것이다.
간단하게 두 개의 quad(Red, Blue)를 선언하고 해당 렌더링 씬의 픽셀 정보들을 glReadPixels함수로 읽어내서
점수를 합산하는 방법으로 시도해보자.
glm::mat4 projection = glm::perspective(glm::radians(45.0f), (float)SCR_WIDTH / (float)SCR_HEIGHT, 1.0f, 100.0f);
glm::mat4 view = camera.GetViewMatrix();;
glm::mat4 model = glm::mat4(1.0f);
redQuadShader.use();
redQuadShader.setMat4("projection", projection);
redQuadShader.setMat4("view", view);
model = glm::translate(model, glm::vec3(-1.0f, 0.0f, 0.0f));
redQuadShader.setMat4("model", model);
renderQuad();
blueQuadShader.use();
blueQuadShader.setMat4("projection", projection);
blueQuadShader.setMat4("view", view);
model = glm::mat4(1.0f);
model = glm::scale(model, glm::vec3(0.9f, 0.9f, 0.9f));
model = glm::translate(model, glm::vec3(1.0f, 0.0f, 0.0f));
blueQuadShader.setMat4("model", model);
renderQuad();
아무것도 없는 씬에 간단하게 quad 두개를 렌더링한다.
하나는 붉은색 quad로 x축으로 -1.0만큼 이동한 상태로 렌더링되고,
다른 하나는 파란색 quad로 크기를 0.9로 스케일링하고, x축으로 1.0만큼 이동한 상태로 렌더링된다.
여기서 glReadPixels함수를 사용해서 Red, blue의 픽셀을 읽어내서 점수를 집계한다.
void getScore()
{
int rScore = 0, bScore = 0;
//GLfloat pixelData[4 * 800 * 600]; // RGBA의 4개값 * 화면가로길이 * 화면세로길이
GLfloat* pixelData = new GLfloat[4 * 800 * 600];
glReadPixels(0, 0, SCR_WIDTH, SCR_HEIGHT, GL_RGBA, GL_FLOAT, pixelData);
for (size_t i = 0; i < 4 * 800 * 600; i += 4) // 루프가 200만번 정도 돈다.
{
if (pixelData[i] == 1.0f) // RGBA의 R값이 1.0일 때, 픽셀이 빨간색일 때.
{
rScore++;
}
else if (pixelData[i + 2] == 1.0f) // RGBA의 B 값이 1.0일 때, 픽셀이 파란색일 때.
{
bScore++;
}
}
cout << "Red Team : " << rScore << endl;
cout << "Blue Team : " << bScore << endl;
delete[] pixelData;
}
그냥 배열로 읽어내면 4 * 800 * 600 이 대충 200만에 육박하기 때문에 stack overflow.
동적 할당으로 읽어서 실행해본 결과는 다음과 같다.
10 * 10 비율의 redQuad의 픽셀 갯수가 84100개.
9 * 9 비율의 blueQuad의 픽셀 갯수가 67860개.
대충 맞아 떨어진다.
하지만 이걸로는 탑뷰에서 제대로 볼 수 있는 부분에 칠해진 페인트만 점수에 합계될 뿐이다.
탑뷰에서 보이지 않는 벽면에 칠해진 페인트 또한 점수에 합계되어야 하는데.
탑뷰에서 제대로 볼 수 없는 벽면에 대한 정보를 얻기 위해 framebuffer를 활용할 수 있다.
탑 뷰에서 확인할 수 없는 부분을 따로 볼 수 있도록 렌더링하여(탑 뷰에서 보이지 않는 벽면을 따로 렌더링)
framebuffer에 해당 렌더링 장면의 색상 정보를 저장하고,
해당 framebuffer를 바인딩한 후에 glReadPixels함수로 페인트 점수를 합산하면 될 것이다.
이를 실행하기 위한 씬을 우선 만들자.
glm::mat4 projection = glm::perspective(glm::radians(45.0f), (float)SCR_WIDTH / (float)SCR_HEIGHT, 1.0f, 100.0f);
glm::mat4 view = camera.GetViewMatrix();;
glm::mat4 model = glm::mat4(1.0f);
redQuadShader.use();
redQuadShader.setMat4("projection", projection);
redQuadShader.setMat4("view", view);
model = glm::translate(model, glm::vec3(-1.0f, 0.0f, 0.0f));
redQuadShader.setMat4("model", model);
renderQuad();
model = glm::mat4(1.0f);
model = glm::translate(model, glm::vec3(-2.0f, 0.0f, 1.0f));
model = glm::rotate(model, glm::radians(90.0f), glm::vec3(0.0f, 1.0f, 0.0f));
redQuadShader.setMat4("model", model);
renderQuad();
blueQuadShader.use();
blueQuadShader.setMat4("projection", projection);
blueQuadShader.setMat4("view", view);
model = glm::mat4(1.0f);
model = glm::scale(model, glm::vec3(0.9f, 0.9f, 0.9f));
model = glm::translate(model, glm::vec3(1.0f, 0.0f, 0.0f));
blueQuadShader.setMat4("model", model);
renderQuad();
model = glm::mat4(1.0f);
model = glm::scale(model, glm::vec3(0.9f, 0.9f, 0.9f));
model = glm::translate(model, glm::vec3(2.0f, 0.0f, 1.0f));
model = glm::rotate(model, glm::radians(-90.0f), glm::vec3(0.0f, 1.0f, 0.0f));
blueQuadShader.setMat4("model", model);
renderQuad();
각 quad들의 양쪽에 같은 크기, 같은 색상의 quad를 하나씩 더 붙여준다.
현재 카메라로 보았을 때는 양 쪽 벽면이 어느정도 화면에 확실히 보이지만
페인트가 칠해진 정확한 면적이 픽셀 상의 숫자(갯수)로 나타나고 있지는 않다.
이부분은 그냥 안보이는 것으로 하고 적당히 무시한다.
여기서 양 옆 두 개의 quad를 framebuffer를 바인딩한 상태로 렌더링해서 색상 정보를 framebuffer에 담아야 한다.
이를 위한 framebuffer를 두 개 생성한다.
unsigned int framebuffer1; // glReadPixels함수를 사용할 때
unsigned int framebuffer2; // 이 변수들이 필요하므로 전역 변수로 선언한다.
glGenFramebuffers(1, &framebuffer1);
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer1);
unsigned int textureColorbuffer1;
glGenTextures(1, &textureColorbuffer1);
glBindTexture(GL_TEXTURE_2D, textureColorbuffer1);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureColorbuffer1, 0);
unsigned int rbo1;
glGenRenderbuffers(1, &rbo1);
glBindRenderbuffer(GL_RENDERBUFFER, rbo1);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, SCR_WIDTH, SCR_HEIGHT);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo1);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
cout << "ERRORR:FRAMEBUFFER:: Framebuffer is not complete!" << endl;
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glGenFramebuffers(1, &framebuffer2);
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer2);
unsigned int textureColorbuffer2;
glGenTextures(1, &textureColorbuffer2);
glBindTexture(GL_TEXTURE_2D, textureColorbuffer2);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureColorbuffer2, 0);
unsigned int rbo2;
glGenRenderbuffers(1, &rbo2);
glBindRenderbuffer(GL_RENDERBUFFER, rbo2);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, SCR_WIDTH, SCR_HEIGHT);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo2);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
cout << "ERRORR:FRAMEBUFFER:: Framebuffer is not complete!" << endl;
glBindFramebuffer(GL_FRAMEBUFFER, 0);
해당 framebuffer들에 렌더링되어야 할 화면은 다음과 같다.
glm::mat4 view = camera.GetViewMatrix();
view = glm::rotate(view, glm::radians(90.0f), glm::vec3(0.0f, 1.0f, 0.0f));
최초의 quad 2개는 전혀 보이지 않고 옆 벽면에 세워진 파란색 quad만이 렌더링된다.
이런 식으로 반대 편의 빨간색 quad도 렌더링한다.
각 프레임버퍼에 렌더링하는 코드는 다음과 같다.
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer2);
glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glm::mat4 projection = glm::perspective(glm::radians(45.0f), (float)SCR_WIDTH / (float)SCR_HEIGHT, 1.0f, 100.0f);
glm::mat4 view = camera.GetViewMatrix();
view = glm::rotate(view, glm::radians(90.0f), glm::vec3(0.0f, 1.0f, 0.0f));
renderScene(redQuadShader, blueQuadShader, projection, view);
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer1);
glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
projection = glm::perspective(glm::radians(45.0f), (float)SCR_WIDTH / (float)SCR_HEIGHT, 1.0f, 100.0f);
view = camera.GetViewMatrix();
view = glm::rotate(view, glm::radians(-90.0f), glm::vec3(0.0f, 1.0f, 0.0f));
renderScene(redQuadShader, blueQuadShader, projection, view);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
camera.Position = glm::vec3(0.0f, 0.0f, 5.0f); // 원래 위치로
projection = glm::perspective(glm::radians(45.0f), (float)SCR_WIDTH / (float)SCR_HEIGHT, 1.0f, 100.0f);
view = camera.GetViewMatrix();
renderScene(redQuadShader, blueQuadShader, projection, view);
이제 이를 P키를 눌렀을 때 각각의 score를 계산할 수 있도록 한다.
if (glfwGetKey(window, GLFW_KEY_P) == GLFW_PRESS)
{
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer1);
getScore();
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer2);
getScore();
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
위의 코드처럼 각 framebuffer를 바인드하고 getScore() 함수를 실행해보면 결과는 다음과 같이 나온다.
framebuffer1이 바인드 되었을 때(파란색 쿼드만 렌더링했을 때)의 blue team 스코어만이 책정되고
framebuffer2가 바인드 되었을 때(빨간색 쿼드만 렌더링했을 때)의 red team 스코어만이 책정되었다.
해당 score 값이 최초의 score 값보다 높아진 것은 카메라의 위치가 해당 quad들에 더 가깝기 때문에
quad가 화면에 더 큰 비율로 나타났기 때문이다.
이 값들을 적당한 비율로 조정하고(대충 절반으로 하고.), 최초의 값과 합치면
blue 색상의 픽셀 갯수는 67,860 + 163,200 / 2 = 149,460
red 색상의 픽셀 갯수는 84,100 + 192,800 / 2 = 180,500
스코어의 대략적인 비율도 알 수 있다.
해당 씬에서는 blue quad를 0.9만큼 스케일링했으므로 결과는 당연히 red quad가 더 클 것이고.
red team의 승리가 된다.
'공부한거 > OpenGL' 카테고리의 다른 글
OpenGL Advanced Lighting 6-8 Deferred Shading (0) | 2020.12.14 |
---|---|
OpenGL Advanced Lighting 6-7 Bloom (0) | 2020.12.14 |
OpenGL Advanced Lighting 6-6 HDR (0) | 2020.12.14 |
OpenGL Advanced Lighting 6-5 Parallax Mapping (0) | 2020.12.14 |
OpenGL Advanced Lighting 6-4 Normal Mapping (0) | 2020.12.13 |