Bazald's adapation of DXTank (written by by Jon Voigt) to Zenilib.
Set up the Development Environment if you haven't already.
Follow the rest of the generic Start Guide.
class Play_State : public Gamestate_Base { public: private: };This will blindly accept the defaults given in Gamestate_Base.
to 'zenilib/textures/', creating the directory 'textures/' as needed.
class Game_Object { public: Game_Object(const Point2f &position_, const Vector3f &size_, const float &theta_ = 0.0f, const float &speed_ = 0.0f) : m_position(position_), m_size(size_), m_theta(theta_), m_speed(speed_) { } private: Point2f m_position; // Upper left corner Vector3f m_size; // (width, height, 0.0f) float m_theta; float m_speed; };Note that 'Vector3f's, 3D vectors, are used to represent 2D size and velocity. This is okay. So long as the third value is always ignored, they can be treated like 2D vectors. Non-3D vectors have such limited functionality and usefulness that they are not provided.
class Tank : public Game_Object { public: Tank(const Point2f &position_, const Vector3f &size_, const float &theta_) : Game_Object(position_, size_, theta_) { } };Instances of Tank can now be defined in Play_State. But what can we do with them?
// in class Game_Object {...} public: virtual void render() const = 0; // pure virtual function call protected: void render(const string &texture, const Color &filter = Color()) const { // Use a helper defined in Zeni/EZ2D.h render_image( texture, // which texture to use m_position, // upper-left corner Point2f(m_position.x + m_size.i, m_position.y + m_size.j), // lower-right corner m_theta, // rotation in radians 1.0f, // scaling factor Point2f(m_position.x + 0.5f * m_size.i, m_position.y + 0.5f * m_size.j), // point to rotate & scale about false, // whether or not to horizontally flip the texture filter); // what Color to "paint" the texture } // ... // in class Tank {...} public: void render() const { Game_Object::render("tank"); }
class Play_State : public Gamestate_Base { public: Play_State() : m_tank(Point2f(0.0f, 0.0f), Vector3f(64.0f, 64.0f, 0.0f), pi * 1.5f) { } private: void render() { m_tank.render(); } Tank m_tank; };
class Play_State : public Gamestate_Base { public: Play_State() : m_tank(Point2f(0.0f, 0.0f), Vector3f(64.0f, 64.0f, 0.0f), pi * 1.5f), m_forward(false), m_turn_left(false), m_backward(false), m_turn_right(false) { } private: void on_key(const SDL_KeyboardEvent &event) { switch(event.keysym.sym) { case SDLK_ESCAPE: get_Game().pop_state(); break; case SDLK_w: m_forward = event.type == SDL_KEYDOWN; break; case SDLK_a: m_turn_left = event.type == SDL_KEYDOWN; break; case SDLK_s: m_backward = event.type == SDL_KEYDOWN; break; case SDLK_d: m_turn_right = event.type == SDL_KEYDOWN; break; default: break; } } void render() { m_tank.render(); } Tank m_tank; bool m_forward; bool m_turn_left; bool m_backward; bool m_turn_right; };
// in class Game_Object {...} public: void turn_left(const float &theta_) { m_theta += theta_; } void move_forward(const float &move_) { m_position.x += move_ * cos(m_theta); m_position.y += move_ * -sin(m_theta); } // ... // in class Play_State {...} private: Time m_time_passed; void perform_logic() { const Time m_current_time; const float time_step = m_current_time.get_seconds_since(m_time_passed); m_time_passed = m_current_time; // without a multiplier, this will rotate a full turn after ~6.28s m_tank.turn_left((m_turn_left - m_turn_right) * time_step); // without the '100.0f', it would move at ~1px/s m_tank.move_forward((m_forward - m_backward) * time_step * 100.0f); }
<--
to 'zenilib/textures/'.
class Bullet : public Game_Object { public: Bullet(const Point2f &position_, const Vector3f &size_, const float &theta_) : Game_Object(position_, size_, theta_) { } void render() const { Game_Object::render("bullet"); } };See how easy this was because of our base class, shared with Tank? It is still a bit verbose, but we basically 'find-replace-all'ed 'Tank' for 'Bullet' and 'tank' for 'bullet'.
// in class Game_Object {...} public: const Point2f & get_position() const {return m_position;} const Vector3f & get_size() const {return m_size;} const float & get_theta() const {return m_theta;} const float get_radius() const { return 0.5f * m_size.magnitude(); } // in class Tank {...} public: Bullet * fire() const { const float radius = 1.2f * get_radius(); const Vector3f bullet_size(8.0f, 8.0f, 0.0f); const Point2f position(get_position().x + 0.5f * (get_size().i - bullet_size.i) + radius * cos(get_theta()), get_position().y + 0.5f * (get_size().j - bullet_size.j) + radius * -sin(get_theta())); return new Bullet(position, bullet_size, get_theta()); } // in the initializer list for Play_State::Play_State() , m_fire(false) // in the switch statement in Play_State::on_key(...) {...} case SDLK_SPACE: m_fire = event.type == SDL_KEYDOWN; break; // in Play_State::perform_logic() {...} if(m_fire) { m_fire = false; m_bullets.push_back(m_tank.fire()); } // in class Play_State {...} private: bool m_fire; list<Bullet *> m_bullets; public: ~Play_State() { for(list<Bullet *>::iterator it = m_bullets.begin(); it != m_bullets.end(); ++it) delete *it; }
// in Play_State::render() {...} for(list<Bullet *>::const_iterator it = m_bullets.begin(); it != m_bullets.end(); ++it) (*it)->render();
// in Play_State::perform_logic() {...} for(list<Bullet *>::iterator it = m_bullets.begin(); it != m_bullets.end(); ++it) (*it)->move_forward(time_step * 200.0f);
// in Play_State::perform_logic() {...} for(list<Bullet *>::iterator it = m_bullets.begin(); it != m_bullets.end();) { const Point2f &p = (*it)->get_position(); if(p.x < -10.0f || p.x > 10.0f + get_Video().get_screen_width() || p.y < -10.0f || p.y > 10.0f + get_Video().get_screen_height()) { delete *it; it = m_bullets.erase(it); } else ++it; }
to 'zenilib/textures/'.
// in class Game_Object {...} public: bool collide(const Game_Object &rhs) const { const float dist_x = m_position.x - rhs.m_position.x + 0.5f * (m_size.i - rhs.m_size.i); const float dist_y = m_position.y - rhs.m_position.y + 0.5f * (m_size.j - rhs.m_size.j); const float dist = sqrt(dist_x * dist_x + dist_y * dist_y); return dist < get_radius() + rhs.get_radius(); } // in the initializer list for Tank::Tank(...) , m_exploded(false) // in class Tank {...} private: bool m_exploded; public: bool has_exploded() {return m_exploded;} void collide(const list<Bullet *> &bullets) { if(!m_exploded) for(list<Bullet *>::const_iterator it = bullets.begin(); it != bullets.end(); ++it) if((*it)->collide(*this)) { m_exploded = true; break; } } // in Tank::render() {...} if(m_exploded) render_image("boom", get_position(), Point2f(get_position().x + get_size().i, get_position().y + get_size().j)); // in Play_State::perform_logic() {...} m_tank.collide(m_bullets); // and change m_tank.*(...) to if(!m_tank.has_exploded()) { m_tank.turn_left((m_turn_left - m_turn_right) * time_step); m_tank.move_forward((m_forward - m_backward) * time_step * 100.0f); if(m_fire) { m_fire = false; m_bullets.push_back(m_tank.fire()); } }
// in class Play_State {...} private: Tank m_enemy; // in the initializer list for Play_State::Play_State() , m_enemy(Point2f(400.0f, 400.0f), Vector3f(64.0f, 64.0f, 0.0f), pi * 0.75f) // in Play_State::perform_logic() {...} m_enemy.collide(m_bullets); // in Play_State::render() {...} m_enemy.render();
// in Play_State {...} private: bool m_enemy_forward; bool m_enemy_turn_left; bool m_enemy_backward; bool m_enemy_turn_right; bool m_enemy_fire; // in the initializer list for Play_State::Play_State() {...} , m_enemy_forward(false) , m_enemy_turn_left(false) , m_enemy_backward(false) , m_enemy_turn_right(false) , m_enemy_fire(false) // in the switch statement in Play_State::on_key(...) {...} case SDLK_UP: m_enemy_forward = event.type == SDL_KEYDOWN; break; case SDLK_LEFT: m_enemy_turn_left = event.type == SDL_KEYDOWN; break; case SDLK_DOWN: m_enemy_backward = event.type == SDL_KEYDOWN; break; case SDLK_RIGHT: m_enemy_turn_right = event.type == SDL_KEYDOWN; break; case SDLK_RETURN: m_enemy_fire = event.type == SDL_KEYDOWN; break; // in Play_State::perform_logic(...) {...} if(!m_enemy.has_exploded()) { m_enemy.turn_left((m_enemy_turn_left - m_enemy_turn_right) * time_step); m_enemy.move_forward((m_enemy_forward - m_enemy_backward) * time_step * 100.0f); if(m_enemy_fire) { m_enemy_fire = false; m_bullets.push_back(m_enemy.fire()); } }Note that it would probably be better to move the block just added in perform_logic into the class itself, as a generic 'step' function dependent on the 'time_step' value obtained in perform_logic. That way, a bit of duplicate code could be removed from perform_logic.
to 'zenilib/textures/'.
class Title_State_Custom : public Title_State<Play_State> { public: Title_State_Custom() : Title_State<Play_State>("") { } void render() { Title_State<Play_State>::render(); render_image("logo", Point2f(200.0f, 25.0f), Point2f(600.0f, 225.0f)); } };
// in class Play_State {...} private: Color m_prev_clear_color; // in the initializer list for Play_State::Play_State() {...} , m_prev_clear_color(get_Video().get_clear_color()) // in Play_State::Play_State() {...} get_Video().set_clear_color(Color(1.0f, 0.26f, 0.13f, 0.0f)); // in Play_State::~Play_State() {...} get_Video().set_clear_color(m_prev_clear_color);
// in Play_State {...} private: Chronometer<Time> m_explosion; // in Play_State::perform_logic() {...} if(m_tank.has_exploded() || m_enemy.has_exploded()) if(m_explosion.running()) { if(m_explosion.seconds() > 3.0f) get_Game().pop_state(); } else m_explosion.start();
// at the very beginning of Play_State::render() {...} get_Video().set_2d(make_pair(Point2f(0.0f, 0.0f), Point2f(800.0f, 600.0f))); /** The following is optional because we aren't using the mouse in Play_State **/ // in Play_State {...} private: Projector2D m_projector; // in the initalizer list of Play_State::Play_State() , m_projector(make_pair(Point2f(0.0f, 0.0f), Point2f(800.0f, 600.0f))) // finally, note that the Projector classes can do conversions between // screen space and your virtual screen size (e.g. 800x600) for you (and your Widgets)