기존에는 Game클래스 내에서 Obstacle들을 배열로 만들어서 구현했다.
이번에는 별도로 에이전트를 만들어서 그 에이전트 내에서 각종 게임상에 출몰하는 아이템들을 만들려고 한다.
여러가지 아이템들을 스폰시키는 items라는 클래스를 만들 것이다. 선언부는 다음과 같다.
#pragma once #ifndef __items__ #define __items__ #include "raylib.h" #include "coin.h" #include <vector> #include "bird.h" class Items { private: Items() {} static Items* instance; public: static Items* GetInstance() { if (instance != nullptr) { return instance; }else { instance = new Items(); return instance; } } public: void Init(); void Update(Bird* _player); void Draw(); ~Items(); void SetTimer(); private: // 텍스처 로드하기 // NOTE: Textures MUST be loaded after Window initialization (OpenGL context is required) Texture2D CoinSprite; // Texture loading; std::vector<Coin> coins; Sound fxCoin; // Load WAV audio file bool canSpawn; int timer; }; #endif
아이템 에이전트는 싱글톤으로 만들었다. 굳이 싱글톤으로 만들 필요가 있는지 의문이긴하나, 혹시 나중에 코드가 커졌을 떄에 아이템 에이전트가 2개가 되면 안되니 싱글톤으로 만들었다.
앞으로 사용할 코인스프라이트등의 텍스쳐나, 사운드는 아이템 에이전트(Items 클래스)에 저장한 후, 여기에서 불러올 것이다. 그리고 포인터로 리소스를 불러오면 리소스의 중복을 막을 수 있을 것이다.
아이템을 스폰할 때에는 장애물이 스폰되고 나서 일정한 시간이 지난 후에야 스폰되게 만들었다.
그래서 canSpawn변수와 timer 변수를 만들었다1).
일단 전체 파일을 게시한다.
#include "items.h" #include <stdio.h> Items* Items::instance; void Items::Init() { CoinSprite = LoadTexture("Assets/coinspritesheets.png"); // Texture loading fxCoin = LoadSound("Assets/Coin01.wav"); coins.clear(); canSpawn = true; timer = 0; } void Items::SetTimer() { canSpawn = false; timer = 0; } void Items::Update(Bird* _player) { // 파이프를 지나갈 떄 까지는 코인 스폰을 시키지 않는다. if (!canSpawn) { timer++; if (timer > 55) // 파이프가 100픽셀이고 스피드가 2이므로 50프레임이 지나야 파이프와 겹치지 않는다. 넉넉하게 55프레임으로 하였다. { canSpawn = true; timer = 0; } }else { int spawn = GetRandomValue(0, 1000); // 코인이 발생할 확률 if (spawn < 2 && canSpawn == true) // 코인이 발생할 확률을 1000분의 10 즉 1%로 설정함 { Coin coin; coin.Init(&CoinSprite); coins.push_back(coin); } } // 각 코인별로 로직을 실행한다. if (coins.size() > 0) { for (unsigned int i = 0;i < coins.size();i++) { coins[i].Update(); // 개별 코인 업데이트 함수 // 코인과 플레이어간의 상호작용 Rectangle birdRect = _player->GetPosition(); // 플레이어 위치 확인 if (coins[i].GetCollision(birdRect)) // 코인과 플레이어가 부딪히는지 확인 { _player->SetAddScore(3); // 플레이어 점수를 올림. 여기에서는 3점을 올리기로 함 PlaySound(fxCoin); // 코인 획득 사운드 coins.erase(coins.begin() + i); // 코인이 플레이어와 부딪히면 배열에서 제거 }else { // 코인을 배열에서 제거하기 if (coins[i].GetPosition() <= 0) coins.erase(coins.begin() + i); // 코인이 왼쪽 끝에 가면 배열에서 제거 } } } } void Items::Draw() { if (coins.size() >0 ) { for (unsigned int i = 0;i< coins.size();i++) { coins[i].Draw(); } } } Items::~Items() { UnloadTexture(CoinSprite); // Texture unloading UnloadSound(fxCoin); // Sound unloading }
헤더파일에는
static Items* instance;
이렇게 하여 instance를 선언(declare)만 하였는데, 선언만 해서는 instance가 무엇인지 알지 못한다.
따라서 cpp파일의 맨 앞에는 그 instance에 대하여 정의(define)해야만 한다. cpp파일에는 맨 앞에
Items* Items::instance;
라고 하여 instance를 정의하여야 한다. 이렇게 하지 않으면 정의가 없으므로 컴파일러가
C++: Undefined reference to instance in (어쩌구)
라는 에러를 내뿜는다.
public으로 선언한 타이머를 정의해줬다. 게임클라스에서 장애물을 만들고 나서는 일정한 시간동안 아이템을 스폰하지 못하게 해주는 것이다. SetTimer()는 game.cpp에서 장애물이 만들어지고 나서 바로 호출하면 된다.
game.cpp의 일부분은 다음과 같다.
// 장애물 로직 void Game::ObstacleLogic() { // 장애물 로직 시작 // 타이머를 만족하면 새로운 파이프 장애물을 만든다. timer++; if (timer >= maxTimer) { timer = 0; maxTimer = GetRandomValue(120, 240); Obstacle newPipe; newPipe.Init(&UpPipeImage, &DownPipeImage); pipes.push_back(newPipe); items->SetTimer(); } // 뒤에는 생략
// 파이프를 지나갈 떄 까지는 코인 스폰을 시키지 않는다. if (!canSpawn) { timer++; if (timer > 55) // 파이프가 100픽셀이고 스피드가 2이므로 50프레임이 지나야 파이프와 겹치지 않는다. 넉넉하게 55프레임으로 하였다. { canSpawn = true; timer = 0; } }else { int spawn = GetRandomValue(0, 1000); // 코인이 발생할 확률 if (spawn < 2 && canSpawn == true) // 코인이 발생할 확률을 1000분의 10 즉 1%로 설정함 { Coin coin; coin.Init(&CoinSprite); coins.push_back(coin); } }
(가) 장애물 이후 스폰 타이밍 간격
우리는 장애물 파이프를 너비 100으로 설정하였다. 그리고 스피드를 2로 하였다. 따라서 50프레임이 지나면 장애물이 전부 보이게 된다. 그 이후에 비로소 코인등의 아이템을 스폰할 것이다. 따라서 55프레임에 적당히 5를 더하여 55프레임을 지나면 비로소 코인이 스폰하게 하였다.
(나) 코인 스폰 확률
그리고 코인 확률은 약 1000분의 2로 하였다. 이렇게 하면 적당히 코인이 발생하였다.
코인클래스는 스프라이트로 애니메이션 구현하기에서 대충 만들어 보았으므로 여기서는 생략한다.
장애물을 배열로 저장한 것과 마찬가지로 코인도 vector를 이용하여 동적 배열에 저장하였다.
(다) 코인에 스프라이트의 주소값을 인자로 주기
각 코인마다 스프라이트 리소스를 로딩해 줄 필요는 없다. 각 코인은 하나의 리소스만 공유하면 된다. 따라서 주소 값을 인자로 넘겨줬다.
coin.Init(&CoinSprite);
// 각 코인별로 로직을 실행한다. if (coins.size() > 0) { for (unsigned int i = 0;i < coins.size();i++) { coins[i].Update(); // 개별 코인 업데이트 함수 // 코인과 플레이어간의 상호작용 Rectangle birdRect = _player->GetPosition(); // 플레이어 위치 확인 if (coins[i].GetCollision(birdRect)) // 코인과 플레이어가 부딪히는지 확인 { _player->SetAddScore(3); // 플레이어 점수를 올림. 여기에서는 3점을 올리기로 함 PlaySound(fxCoin); // 코인 획득 사운드 coins.erase(coins.begin() + i); // 코인이 플레이어와 부딪히면 배열에서 제거 }else { // 코인을 배열에서 제거하기 if (coins[i].GetPosition() <= 0) coins.erase(coins.begin() + i); // 코인이 왼쪽 끝에 가면 배열에서 제거 } } }
코인이 하나라도 있다면 코인을 왼쪽으로 움직이게 하는 등 각 코인별로 업데이트 메서드를 실행한다.
우리가 Update()를 실행할 때 인자로 플레이어의 주소값을 가져온 것을 기억하자.
플레이어의 주소값을 통하여 플레이어의 위치를 불러왔다(GetPosition).
만약 코인이 플레이어와 부딪히면 플레이어의 점수를 일정량만큼 올리고 코인을 제거하게 하였다. 코인이 왼쪽 끝에 가도 코인을 배열에서 제거하였다.
이미 장애물 코드에서도 해본 것이지만 여기에서 한번 더 리마인드 해보자. 다음과 같이 CheckCollisionRecs()를 이용하면 충돌 값을 반환해 줄 수 있다.
coin.cpp의 일부분이다.
bool Coin::GetCollision(Rectangle bRect) { Rectangle rect = {pos.x, pos.y, (float)coinImage->width/SPRITE_NUMBERS, (float)coinImage->height}; return CheckCollisionRecs(bRect, rect); }
이제 단순히 Items클래스를 Game클래스에서 호출하면 된다.
변수로 private영역에 items클래스를 포인터로 선언하였다.
// Items 에이전트 Items* items;
game클래스의 Init()메서드에는 다음과 같이 아이템 에이전트를 초기화하였다.
// 아이템 에이전트 인스턴스 및 초기화 items = Items::GetInstance(); items->Init();
나머지는 그냥 각각의 Update()메서드와 Draw()메서드에
아이템의 Update()메서드와 Draw()메서드를 넣으면 된다.
다만 아이템의 Update()메서드는 플레이어의 주소값을 인자로 받으므로 다음과 같이 불러줘야 할 것이다.
items->Update(&bird);
이렇게 하면 이제 다양한 아이템을 만들 수 있다.
아이템을 스폰시키는 에이전트 만들기가 지금까지 만든 전체 소스파일이다.
다음에는 상속을 이용하여 다른 아이템 만들기를 해볼 것이다.