티스토리 뷰

* 인프런에 있는 "홍정모의 게임 만들기 연습 문제 패키지" 강의를 바탕으로 작성된 글입니다.

 

1. 탱크 예제에 포탄 발사 사운드와 탱크 이동소리 넣어보기

* 강의에서 제공되는 "fmod_cpp_context.cpp" 소스코드를 Sound 클래스로 만들었습니다.

#pragma once

#include "fmod.hpp"
#include <string>

constexpr int SOUND_CNT = 32;
constexpr int CHANNEL_CNT = 16;

namespace shyplants
{
	class Sound
	{
	private:
		FMOD::System     *system;
		FMOD::Sound      *sound[SOUND_CNT];
		FMOD::Channel    *channel[CHANNEL_CNT];
		FMOD_RESULT       result;
		unsigned int      version;
		void             *extradriverdata;
		bool             bPlay;

	public:
		Sound()
		{}

		int Init()
		{
			system = nullptr;
			for (int i = 0; i < SOUND_CNT; ++i) sound[i] = nullptr;
			for (int i = 0; i < CHANNEL_CNT; ++i) channel[i] = nullptr;
			extradriverdata = nullptr;
			bPlay = false;

			result = FMOD::System_Create(&system);
			if (result != FMOD_OK) return -1;

			result = system->getVersion(&version);
			if (result != FMOD_OK) return -1;

			result = system->init(CHANNEL_CNT, FMOD_INIT_NORMAL, extradriverdata);
			if (result != FMOD_OK) return -1;
		}

		void sound_order_check(int sound_order)
		{
			assert(sound_order >= 0);
			assert(sound_order < SOUND_CNT);
		}

		void channel_order_check(int channel_order)
		{
			assert(channel_order >= 0);
			assert(channel_order < CHANNEL_CNT);
		}

		int createSound(const std::string& title, int mode, int sound_order)
		{
			sound_order_check(sound_order);

			result = system->createSound(title.c_str(), mode, 0, &sound[sound_order]);
			
			return result != FMOD_OK ? -1 : 0;
		}

		int playSound(int sound_order, int channel_order)
		{
			sound_order_check(sound_order);
			channel_order_check(channel_order);

			result = system->playSound(sound[sound_order], 0, false, &channel[channel_order]);
			
			return result != FMOD_OK ? -1 : 0;
		}

		int stopChannel(int channel_order)
		{
			channel_order_check(channel_order);

			result = channel[channel_order]->stop();

			return result != FMOD_OK ? -1 : 0;
		}

		bool isPlaying(int channel_order)
		{
			channel_order_check(channel_order);

			result = channel[channel_order]->isPlaying(&bPlay);

			return bPlay;
		}

	};
}

Sound 클래스를 활용하여 기존 탱크예제에 사운드 추가

#pragma once

#include "Game2D.h"
#include "Sound.h"
#include <list>

namespace shyplants
{
	class MyTank
	{
	public:
		vec2 center = vec2(0.0f, 0.0f);

		void draw()
		{
			beginTransformation();
			{
				translate(center);
				drawFilledBox(Colors::green, 0.25f, 0.1f); // body
				translate(-0.02f, 0.1f);
				drawFilledBox(Colors::blue, 0.15f, 0.09f); // turret
				translate(0.15f, 0.0f);
				drawFilledBox(Colors::red, 0.15f, 0.03f);  // barrel
			}
			endTransformation();
		}
	};

	class MyBullet
	{
	public:
		vec2 center = vec2(0.0f, 0.0f);
		vec2 velocity = vec2(0.0f, 0.0f);

		void draw()
		{
			beginTransformation();
			translate(center);
			drawFilledRegularConvexPolygon(Colors::yellow, 0.02f, 8);
			drawWiredRegularConvexPolygon(Colors::gray, 0.02f, 8);
			endTransformation();
		}

		void update(const float& dt)
		{
			center += velocity * dt;
		}
	};

	class TankExample : public Game2D
	{
	public:
		MyTank tank;
		MyBullet *bullet = nullptr;
		std::list<MyBullet*> bulletList;
		Sound* soundSys;
		bool isPlaying;

		enum ESound
		{
			MOVE,
			SHOOT,
		};

		enum EChannel
		{
			CH_0,
			CH_1,
		};

		float dy[4] = { 0, 0, -0.5f, +0.5f };
		float dx[4] = { +0.5f, -0.5f, 0, 0 };


	public:
		TankExample()
			: Game2D("This is my digital canvas!", 1024, 768, false, 2)
		{
			soundSys = new Sound;
			soundSys->Init();
			soundSys->createSound("move.wav", FMOD_LOOP_OFF, 0);
			soundSys->createSound("shoot.ogg", FMOD_LOOP_OFF, 1);
			isPlaying = false;
		}

		~TankExample()
		{
			if (bullet != nullptr) delete bullet;	
		}

		void update() override
		{
			// move tank
			bool arrowKeyPressed[4], isMove = false;
			for (int dir = 0; dir < 4; ++dir)  // {RIGHT, LEFT, DOWN, UP}
			{
				arrowKeyPressed[dir] = isKeyPressed(262 + dir);
				if (arrowKeyPressed[dir])
				{
					isMove = true;
					tank.center.x += dx[dir] * getTimeStep();
					tank.center.y += dy[dir] * getTimeStep();
				}
			}


			if (isMove && !soundSys->isPlaying(EChannel::CH_0))
			{
				soundSys->playSound(ESound::MOVE, EChannel::CH_0);
			}

			if (!isMove)
			{
				soundSys->stopChannel(EChannel::CH_0);
			}

			// shoot a cannon ball
			if (isKeyPressedAndReleased(GLFW_KEY_SPACE))
			{
				bulletList.push_back(new MyBullet);	
				bulletList.back()->center = tank.center;
				bulletList.back()->center.x += 0.2f;
				bulletList.back()->center.y += 0.1f;
				bulletList.back()->velocity = vec2(2.0f, 0.0f);

				soundSys->playSound(ESound::SHOOT, EChannel::CH_1);
			}

			tank.draw();

			// 총알 리스트를 순회하며 업데이트, 드로우 함수 호출
			for (auto iter = bulletList.begin(); iter != bulletList.end(); iter++)
			{
				(*iter)->update(getTimeStep());
				(*iter)->draw();
			}
			
			// 총알 리스트를 순회하며 화면 밖에 나간 총알 메모리 해제 (메모리 누수 방지)
			for (auto iter = bulletList.begin(); iter != bulletList.end();)
			{
				/* 
					list 컨테이너의 erase함수는 삭제된 다음 원소를 반환한다.
					따라서 Iterator 중복증가를 방지하기 위해 분리시킨다.
				*/
				if ((*iter)->center.x > 2.0f) {
					delete *iter;
					iter = bulletList.erase(iter); 
				}
				else {
					iter++;
				}
			}
		}
	};
}

 

댓글