- 명령과 행위 자체를 객체로 만듬
- 최대한 추상화 해서 변경 사항이 생기더라도 메인 무한루프를 가지는 메인 함수에서는 변경할 것이 없게끔 해야 좋다.
전체 코드
#pragma once
#include "Game2D.h"
#include <map>
namespace jm
{
class Actor
{
public:
virtual void moveUp(float dt) = 0;
virtual void moveLeft(float dt) = 0;
virtual void moveRight(float dt) = 0;
virtual void moveDown(float dt) = 0;
};
class Command
{
public:
virtual ~Command() {}
virtual void execute(Actor& actor, float dt) = 0;
};
class UpCommand : public Command
{
public:
virtual void execute(Actor& actor, float dt) override
{
actor.moveUp(dt);
}
};
class LeftCommand : public Command
{
public:
virtual void execute(Actor& actor, float dt) override
{
actor.moveLeft(dt);
}
};
class RightCommand : public Command
{
public:
virtual void execute(Actor& actor, float dt) override
{
actor.moveRight(dt);
}
};
class DownCommand : public Command
{
public:
virtual void execute(Actor& actor, float dt) override
{
actor.moveDown(dt);
}
};
class MyTank : public Actor
{
public:
vec2 center = vec2(0.0f, 0.0f);
//vec2 direction = vec2(1.0f, 0.0f, 0.0f);
void moveUp(float dt) override
{
center.y += 0.5f * dt;
}
void moveLeft(float dt) override
{
center.x -= 0.5f * dt;
}
void moveRight(float dt) override
{
center.x += 0.5f * dt;
}
void moveDown(float dt) override
{
center.y -= 0.5f * dt;
}
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 InputHandler
{
public:
Command * button_up = nullptr;
Command* button_left = nullptr;
Command* button_right = nullptr;
Command* button_down = nullptr;
/*std::map<int, Command *> key_command_map;*/
InputHandler()
{
button_up = new UpCommand;
button_left = new LeftCommand;
button_right = new RightCommand;
button_down = new DownCommand;
}
void handleInput(Game2D & game, Actor & actor, float dt)
{
if (game.isKeyPressed(GLFW_KEY_UP)) button_up->execute(actor, dt);
if (game.isKeyPressed(GLFW_KEY_LEFT)) button_left->execute(actor, dt);
if (game.isKeyPressed(GLFW_KEY_RIGHT)) button_right->execute(actor, dt);
if (game.isKeyPressed(GLFW_KEY_DOWN)) button_down->execute(actor, dt);
/*for (auto & m : key_command_map)
{
if (game.isKeyPressed(m.first)) m.second->execute(actor, dt);
}*/
}
};
class TankExample : public Game2D
{
public:
MyTank tank;
InputHandler input_handler;
public:
TankExample()
: Game2D("This is my digital canvas!", 1024, 768, false, 2)
{
//key mapping
/*input_handler.key_command_map[GLFW_KEY_UP] = new UpCommand;
input_handler.key_command_map[GLFW_KEY_LEFT] = new LeftCommand;
input_handler.key_command_map[GLFW_KEY_DOWN] = new RightCommand;*/
}
~TankExample()
{
}
void update() override
{
// move tank
/*if (isKeyPressed(GLFW_KEY_LEFT)) tank.center.x -= 0.5f * getTimeStep();
if (isKeyPressed(GLFW_KEY_RIGHT)) tank.center.x += 0.5f * getTimeStep();
if (isKeyPressed(GLFW_KEY_UP)) tank.center.y += 0.5f * getTimeStep();
if (isKeyPressed(GLFW_KEY_DOWN)) tank.center.y -= 0.5f * getTimeStep();*/
input_handler.handleInput(*this, tank, getTimeStep());
// rendering
tank.draw();
}
};
}
Actor Class
class Actor
{
public:
virtual void moveUp(float dt) = 0;
virtual void moveLeft(float dt) = 0;
virtual void moveRight(float dt) = 0;
virtual void moveDown(float dt) = 0;
};
Actor 들이 모두 기본적으로 가지는 기능 들만 묶어서 class Actor로 만들고 이를 각각 상속받게 한다.
ex) 탱크, 자전거 등
탱크 클래스, 자전거 클래스 등등 모두 Actor를 상속하는 자식클래스이다.
Actor들이 기본적으로 가지는 기능
ex) moveUp(), moveDown(), stop(), moveLeft(), moveRight 등
순수 가상 함수로 만들어 자식 클래스들이 각각 오버라이딩 하도록 강제한다.
공통적인 기능이라도 그 내용은 어떤 종류의 액터냐에 따라 다르기 때문
예를 들어 탱크와 비행기 둘 다 moveUp이라는 기능을 가지지만 얼만큼 y 좌표로 움직일지 등등 내용은 각자 다 다르다.
Command Class
class Command
{
public:
virtual ~Command() {}
virtual void execute(Actor& actor, float dt) = 0;
};
가상 소멸자를 쓴다.
자식 Command 들은 각자 소멸자를 호출하도록 설계
무언가를 수행하는 일을 한다.
UpCommand, DownCommand 등등 수행하는 일도 종류가 다양하다.
각자 어떤 일을 수행할지는 execute 함수 를 오버라이딩 하여 내용을 다르게 하면 됨(본인은 방향키마다 다르게 함)
순수 가상함수이므로 반드시 오버라이딩 해야 함
다형성 및 추상화
virtual void execute(Actor& actor, float dt) = 0;
어떠한 종류의 자식 Actor 들이 들어오던지 부모인 Actor 하나로 다 참조할 수 있도록.
어떠한 종류의 자식 Actor 들이 들어오던지 이 Command를 수행할 수 있도록.
Actor& actor 변수 하나에 Tank, AirPlane 객체 다 참조 가능.
UpCommand Class 등
class UpCommand : public Command
{
public:
virtual void execute(Actor& actor, float dt) override
{
actor.moveUp(dt);
}
};
Command 상속
어떤 일을 수행할 것인지 부모인 Command 클래스의 execute 함수 를 오버라이딩 한다.
UpCommand 클래스는 Actor의 moveUp 기능을 수행하는 클래스
즉, 위 코드에서,,,???Command 클래스는 Actor의 move??? 기능을 수행하는 클래스
어떤 종류의 Actor이냐에 따라 다른 move???이 호출될 것이다.
Actor& actor 에 탱크가 들어오면 탱크만의 move???이 들어올 것.
MyTank Class
class MyTank : public Actor
{
public:
vec2 center = vec2(0.0f, 0.0f);
//vec2 direction = vec2(1.0f, 0.0f, 0.0f);
void moveUp(float dt) override
{
center.y += 0.5f * dt;
}
void moveLeft(float dt) override
{
center.x -= 0.5f * dt;
}
void moveRight(float dt) override
{
center.x += 0.5f * dt;
}
void moveDown(float dt) override
{
center.y -= 0.5f * dt;
}
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();
}
};
Actor 상속
모든 Acotr들이 가지는 기본적인 기능을 탱크(액터)에 맞게 오버라이딩 한다.
InputHandler Class
class InputHandler
{
public:
Command * button_up = nullptr;
Command* button_left = nullptr;
Command* button_right = nullptr;
Command* button_down = nullptr;
/*std::map<int, Command *> key_command_map;*/
InputHandler()
{
button_up = new UpCommand;
button_left = new LeftCommand;
button_right = new RightCommand;
button_down = new DownCommand;
}
void handleInput(Game2D & game, Actor & actor, float dt)
{
if (game.isKeyPressed(GLFW_KEY_UP)) button_up->execute(actor, dt);
if (game.isKeyPressed(GLFW_KEY_LEFT)) button_left->execute(actor, dt);
if (game.isKeyPressed(GLFW_KEY_RIGHT)) button_right->execute(actor, dt);
if (game.isKeyPressed(GLFW_KEY_DOWN)) button_down->execute(actor, dt);
/*for (auto & m : key_command_map)
{
if (game.isKeyPressed(m.first)) m.second->execute(actor, dt);
}*/
}
};
키보드 입력을 감지하는 클래스
키보드 입력 또한 update()에서 안받게 따로 추상화하여 뺐다.
update()에선 InputHandler의 void handleInput(Game2D & game, Actor & actor, float dt) 함수만 실행한다.
Game2D & game
isKeyPressed 함수를 사용해야 하기 때문에
Actor & actor
어떤 종류의 액터를 기능하게 할지
button_up
Command 타입인 부모형 포인터.
Command의 자식인 UpCommand 객체를 참조한다.
button_up에 무엇이 들어갈지는 그때 그때 바뀔 수 있다. 게임 진행 중에도.
예를 들어 플레이어가 술에 취해서 위 아래가 뒤집히는 연출을 하고 싶다면 button_up = new DownCommand, button_down = new UpCommand 이렇게 뒤집을 수 있겠지.
void handleInput(Game2D & game, Actor & actor, float dt)
키보드 입력에 따라 어떤 Actor를 어떤 기능을 시킬지.
button_up->execute(actor, dt);
execute은 가상함수이므로 button_up이 참조하는 자식 객체인 UpCommand의 execute가 실행된다.
UpCommand의 execute는 moveUp을 호출하므로
해당 Actor이 오버라이딩 한 moveUp이 호출된다.

TankExample
class TankExample : public Game2D
{
public:
MyTank tank;
InputHandler input_handler;
public:
TankExample()
: Game2D("This is my digital canvas!", 1024, 768, false, 2)
{
//key mapping
/*input_handler.key_command_map[GLFW_KEY_UP] = new UpCommand;
input_handler.key_command_map[GLFW_KEY_LEFT] = new LeftCommand;
input_handler.key_command_map[GLFW_KEY_DOWN] = new RightCommand;*/
}
~TankExample()
{
}
void update() override
{
input_handler.handleInput(*this, tank, getTimeStep());
// rendering
tank.draw();
}
};
멤버함수 update() 안 input_handler.handleInput(*this, tank, getTimeStep());
이 함수 안에서 키보드 입력에 따른 함수 처리를 다 해줄 것.
*this
부모인 Game2D 타입으로 TankExample 객체를 참조
키보드 입력을 받는 함수를 쓰기 위해.
tank
탱크 Actor.
중요한 메인 루프 함수인 update()는 수정할게 생기더라도 건드릴 일이 없게 해야한다.
만약 자동차로 변경하고 싶다면 input_handler.handleInput(*this, car, getTimeStep()) 처럼 바꿔주면 됨
추가)
대부분은 개발자가 정해서 커맨드 키를 정해두진 않고 사용자가 변경할 수 있게끔 코드를 짜거나(c++에서는 기본적으로 미리 std::map에 키보드 번호와 Command의 자식들을 종류별로 맵핑시켜놓고 for문 돌려 실행하는 방법도 있다.) txt파일로 값을 읽어오도록(경험한 바로는 피파온라인4의 그래픽 설정, 배틀필드2042의 모든 설정) 한다.
'공부 일지 > C++' 카테고리의 다른 글
[C++] 3.1 공 튕기기 시뮬레이션 (0) | 2023.10.03 |
---|---|
[C++] 2.6 싱글톤 패턴 singleton pattern (0) | 2023.09.20 |
[C++] 2.4 공장 패턴 factory pattern (0) | 2023.09.16 |
[C++] 2.3 다형성(std::vector, auto, new&delete, 스마트포인터) (0) | 2023.09.16 |
[C++] 2.2 상속 (0) | 2023.09.16 |
- 명령과 행위 자체를 객체로 만듬
- 최대한 추상화 해서 변경 사항이 생기더라도 메인 무한루프를 가지는 메인 함수에서는 변경할 것이 없게끔 해야 좋다.
전체 코드
#pragma once
#include "Game2D.h"
#include <map>
namespace jm
{
class Actor
{
public:
virtual void moveUp(float dt) = 0;
virtual void moveLeft(float dt) = 0;
virtual void moveRight(float dt) = 0;
virtual void moveDown(float dt) = 0;
};
class Command
{
public:
virtual ~Command() {}
virtual void execute(Actor& actor, float dt) = 0;
};
class UpCommand : public Command
{
public:
virtual void execute(Actor& actor, float dt) override
{
actor.moveUp(dt);
}
};
class LeftCommand : public Command
{
public:
virtual void execute(Actor& actor, float dt) override
{
actor.moveLeft(dt);
}
};
class RightCommand : public Command
{
public:
virtual void execute(Actor& actor, float dt) override
{
actor.moveRight(dt);
}
};
class DownCommand : public Command
{
public:
virtual void execute(Actor& actor, float dt) override
{
actor.moveDown(dt);
}
};
class MyTank : public Actor
{
public:
vec2 center = vec2(0.0f, 0.0f);
//vec2 direction = vec2(1.0f, 0.0f, 0.0f);
void moveUp(float dt) override
{
center.y += 0.5f * dt;
}
void moveLeft(float dt) override
{
center.x -= 0.5f * dt;
}
void moveRight(float dt) override
{
center.x += 0.5f * dt;
}
void moveDown(float dt) override
{
center.y -= 0.5f * dt;
}
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 InputHandler
{
public:
Command * button_up = nullptr;
Command* button_left = nullptr;
Command* button_right = nullptr;
Command* button_down = nullptr;
/*std::map<int, Command *> key_command_map;*/
InputHandler()
{
button_up = new UpCommand;
button_left = new LeftCommand;
button_right = new RightCommand;
button_down = new DownCommand;
}
void handleInput(Game2D & game, Actor & actor, float dt)
{
if (game.isKeyPressed(GLFW_KEY_UP)) button_up->execute(actor, dt);
if (game.isKeyPressed(GLFW_KEY_LEFT)) button_left->execute(actor, dt);
if (game.isKeyPressed(GLFW_KEY_RIGHT)) button_right->execute(actor, dt);
if (game.isKeyPressed(GLFW_KEY_DOWN)) button_down->execute(actor, dt);
/*for (auto & m : key_command_map)
{
if (game.isKeyPressed(m.first)) m.second->execute(actor, dt);
}*/
}
};
class TankExample : public Game2D
{
public:
MyTank tank;
InputHandler input_handler;
public:
TankExample()
: Game2D("This is my digital canvas!", 1024, 768, false, 2)
{
//key mapping
/*input_handler.key_command_map[GLFW_KEY_UP] = new UpCommand;
input_handler.key_command_map[GLFW_KEY_LEFT] = new LeftCommand;
input_handler.key_command_map[GLFW_KEY_DOWN] = new RightCommand;*/
}
~TankExample()
{
}
void update() override
{
// move tank
/*if (isKeyPressed(GLFW_KEY_LEFT)) tank.center.x -= 0.5f * getTimeStep();
if (isKeyPressed(GLFW_KEY_RIGHT)) tank.center.x += 0.5f * getTimeStep();
if (isKeyPressed(GLFW_KEY_UP)) tank.center.y += 0.5f * getTimeStep();
if (isKeyPressed(GLFW_KEY_DOWN)) tank.center.y -= 0.5f * getTimeStep();*/
input_handler.handleInput(*this, tank, getTimeStep());
// rendering
tank.draw();
}
};
}
Actor Class
class Actor
{
public:
virtual void moveUp(float dt) = 0;
virtual void moveLeft(float dt) = 0;
virtual void moveRight(float dt) = 0;
virtual void moveDown(float dt) = 0;
};
Actor 들이 모두 기본적으로 가지는 기능 들만 묶어서 class Actor로 만들고 이를 각각 상속받게 한다.
ex) 탱크, 자전거 등
탱크 클래스, 자전거 클래스 등등 모두 Actor를 상속하는 자식클래스이다.
Actor들이 기본적으로 가지는 기능
ex) moveUp(), moveDown(), stop(), moveLeft(), moveRight 등
순수 가상 함수로 만들어 자식 클래스들이 각각 오버라이딩 하도록 강제한다.
공통적인 기능이라도 그 내용은 어떤 종류의 액터냐에 따라 다르기 때문
예를 들어 탱크와 비행기 둘 다 moveUp이라는 기능을 가지지만 얼만큼 y 좌표로 움직일지 등등 내용은 각자 다 다르다.
Command Class
class Command
{
public:
virtual ~Command() {}
virtual void execute(Actor& actor, float dt) = 0;
};
가상 소멸자를 쓴다.
자식 Command 들은 각자 소멸자를 호출하도록 설계
무언가를 수행하는 일을 한다.
UpCommand, DownCommand 등등 수행하는 일도 종류가 다양하다.
각자 어떤 일을 수행할지는 execute 함수 를 오버라이딩 하여 내용을 다르게 하면 됨(본인은 방향키마다 다르게 함)
순수 가상함수이므로 반드시 오버라이딩 해야 함
다형성 및 추상화
virtual void execute(Actor& actor, float dt) = 0;
어떠한 종류의 자식 Actor 들이 들어오던지 부모인 Actor 하나로 다 참조할 수 있도록.
어떠한 종류의 자식 Actor 들이 들어오던지 이 Command를 수행할 수 있도록.
Actor& actor 변수 하나에 Tank, AirPlane 객체 다 참조 가능.
UpCommand Class 등
class UpCommand : public Command
{
public:
virtual void execute(Actor& actor, float dt) override
{
actor.moveUp(dt);
}
};
Command 상속
어떤 일을 수행할 것인지 부모인 Command 클래스의 execute 함수 를 오버라이딩 한다.
UpCommand 클래스는 Actor의 moveUp 기능을 수행하는 클래스
즉, 위 코드에서,,,???Command 클래스는 Actor의 move??? 기능을 수행하는 클래스
어떤 종류의 Actor이냐에 따라 다른 move???이 호출될 것이다.
Actor& actor 에 탱크가 들어오면 탱크만의 move???이 들어올 것.
MyTank Class
class MyTank : public Actor
{
public:
vec2 center = vec2(0.0f, 0.0f);
//vec2 direction = vec2(1.0f, 0.0f, 0.0f);
void moveUp(float dt) override
{
center.y += 0.5f * dt;
}
void moveLeft(float dt) override
{
center.x -= 0.5f * dt;
}
void moveRight(float dt) override
{
center.x += 0.5f * dt;
}
void moveDown(float dt) override
{
center.y -= 0.5f * dt;
}
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();
}
};
Actor 상속
모든 Acotr들이 가지는 기본적인 기능을 탱크(액터)에 맞게 오버라이딩 한다.
InputHandler Class
class InputHandler
{
public:
Command * button_up = nullptr;
Command* button_left = nullptr;
Command* button_right = nullptr;
Command* button_down = nullptr;
/*std::map<int, Command *> key_command_map;*/
InputHandler()
{
button_up = new UpCommand;
button_left = new LeftCommand;
button_right = new RightCommand;
button_down = new DownCommand;
}
void handleInput(Game2D & game, Actor & actor, float dt)
{
if (game.isKeyPressed(GLFW_KEY_UP)) button_up->execute(actor, dt);
if (game.isKeyPressed(GLFW_KEY_LEFT)) button_left->execute(actor, dt);
if (game.isKeyPressed(GLFW_KEY_RIGHT)) button_right->execute(actor, dt);
if (game.isKeyPressed(GLFW_KEY_DOWN)) button_down->execute(actor, dt);
/*for (auto & m : key_command_map)
{
if (game.isKeyPressed(m.first)) m.second->execute(actor, dt);
}*/
}
};
키보드 입력을 감지하는 클래스
키보드 입력 또한 update()에서 안받게 따로 추상화하여 뺐다.
update()에선 InputHandler의 void handleInput(Game2D & game, Actor & actor, float dt) 함수만 실행한다.
Game2D & game
isKeyPressed 함수를 사용해야 하기 때문에
Actor & actor
어떤 종류의 액터를 기능하게 할지
button_up
Command 타입인 부모형 포인터.
Command의 자식인 UpCommand 객체를 참조한다.
button_up에 무엇이 들어갈지는 그때 그때 바뀔 수 있다. 게임 진행 중에도.
예를 들어 플레이어가 술에 취해서 위 아래가 뒤집히는 연출을 하고 싶다면 button_up = new DownCommand, button_down = new UpCommand 이렇게 뒤집을 수 있겠지.
void handleInput(Game2D & game, Actor & actor, float dt)
키보드 입력에 따라 어떤 Actor를 어떤 기능을 시킬지.
button_up->execute(actor, dt);
execute은 가상함수이므로 button_up이 참조하는 자식 객체인 UpCommand의 execute가 실행된다.
UpCommand의 execute는 moveUp을 호출하므로
해당 Actor이 오버라이딩 한 moveUp이 호출된다.

TankExample
class TankExample : public Game2D
{
public:
MyTank tank;
InputHandler input_handler;
public:
TankExample()
: Game2D("This is my digital canvas!", 1024, 768, false, 2)
{
//key mapping
/*input_handler.key_command_map[GLFW_KEY_UP] = new UpCommand;
input_handler.key_command_map[GLFW_KEY_LEFT] = new LeftCommand;
input_handler.key_command_map[GLFW_KEY_DOWN] = new RightCommand;*/
}
~TankExample()
{
}
void update() override
{
input_handler.handleInput(*this, tank, getTimeStep());
// rendering
tank.draw();
}
};
멤버함수 update() 안 input_handler.handleInput(*this, tank, getTimeStep());
이 함수 안에서 키보드 입력에 따른 함수 처리를 다 해줄 것.
*this
부모인 Game2D 타입으로 TankExample 객체를 참조
키보드 입력을 받는 함수를 쓰기 위해.
tank
탱크 Actor.
중요한 메인 루프 함수인 update()는 수정할게 생기더라도 건드릴 일이 없게 해야한다.
만약 자동차로 변경하고 싶다면 input_handler.handleInput(*this, car, getTimeStep()) 처럼 바꿔주면 됨
추가)
대부분은 개발자가 정해서 커맨드 키를 정해두진 않고 사용자가 변경할 수 있게끔 코드를 짜거나(c++에서는 기본적으로 미리 std::map에 키보드 번호와 Command의 자식들을 종류별로 맵핑시켜놓고 for문 돌려 실행하는 방법도 있다.) txt파일로 값을 읽어오도록(경험한 바로는 피파온라인4의 그래픽 설정, 배틀필드2042의 모든 설정) 한다.
'공부 일지 > C++' 카테고리의 다른 글
[C++] 3.1 공 튕기기 시뮬레이션 (0) | 2023.10.03 |
---|---|
[C++] 2.6 싱글톤 패턴 singleton pattern (0) | 2023.09.20 |
[C++] 2.4 공장 패턴 factory pattern (0) | 2023.09.16 |
[C++] 2.3 다형성(std::vector, auto, new&delete, 스마트포인터) (0) | 2023.09.16 |
[C++] 2.2 상속 (0) | 2023.09.16 |