-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathshmup-example.cpp
202 lines (172 loc) · 7.14 KB
/
shmup-example.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
#include <iostream>
#include <unordered_set>
#include <cstdlib>
#include "shapegame.hpp"
using namespace shapegame;
// unused variable for giving the player a shadow
const Color SHADOW_COLOR = Color(0, 0, 0, 0.5);
const int ENEMY_WIDTH = 40;
const int ENEMY_HEIGHT = 40;
const int BULLET_WIDTH = 5;
const int BULLET_HEIGHT = 30;
const int SCREEN_WIDTH = 400;
const int SCREEN_HEIGHT = 600;
const int KILL_DOWN = 700; // the point at wich things should kill themselves below the screen
const int KILL_UP = -30; // the point at wich things should kill themselves above the screen
const int ENEMY_SPEED = 100;
const int BULLET_SPEED = 30;
const int PLAYER_WIDTH = 50;
const int PLAYER_HEIGHT = 50;
const int PLAYER_SHADOW_OFFSET_Y = 5;
const int PLAYER_SHADOW_OFFSET_X = 10;
const int BOTTOM_MARGIN = 10;
//enemy spawn frequency in millisecionds
const int ENEMY_SPAWN_FREQUENCY_MS = 1000;
/* Forward declaration so that enemyList can contain Enemies, and
* Enemy class can reference global enemyList */
class Enemy;
// enemy list for collision handling (not handled by engine)
std::unordered_set<Enemy*> enemyList;
// enemy class extends rectangle class
class Enemy : public Rectangle {
// Public Enemy
public:
// enemy constructor initilizes rectangle base class
Enemy(Position pos) : Rectangle(ENEMY_WIDTH, ENEMY_HEIGHT, pos, Color::RED) {
// adds its self to the enemy list for ad-hock (not handled by engine)
// collision handling
enemyList.insert(this);
}
// override update callback. Called every frame
void update() override {
// move the enemy down (ENEMY_SPEED * delta) (frame rate independant movement)
// on the y axsis every frame
this->translate(0, ENEMY_SPEED * Globals::dt);
/* If this enemy is below the screen */
if (this->pos.y > KILL_DOWN) {
/* kill its self */
this->kill();
// and remove from enemy list
enemyList.erase(this);
}
}
};
// Bullet class extends shapegame::Rectangle
class Bullet : public Rectangle {
public:
/* A constructor for our bullet class that takes a Position as an argument
* It then initilizes the rectangle that will be representing the bullet
* with a width, height, position, and color */
Bullet(Position pos) : Rectangle(BULLET_WIDTH, BULLET_HEIGHT, pos, Color::WHITE) {}
/* Override the update method. This will be called every frame by the
* engine once the object instance is added to the scene */
void update() override {
/* Every frame move up pixels */
this->translate(0, -BULLET_SPEED);
/* If this bullet is above the screen */
if (this->pos.y < -KILL_UP) {
/* This kill its self */
this->kill();
}
// loop through every enemy in the enemyList
// to check to see if this bullet is colliding
for (Enemy* enemy : enemyList) {
if ( // if is collision with enemy
// this is collision checking code.
// Deciphering it is left as an exercise to the reader
this->pos.y < enemy->pos.y + ENEMY_HEIGHT &&
this->pos.y + BULLET_HEIGHT > enemy->pos.y &&
this->pos.x + BULLET_WIDTH > enemy->pos.x &&
this->pos.x < enemy->pos.x + ENEMY_WIDTH
) {
// if enemy and bullet are colliding,
// kill this (bullet)
this->kill();
// remove colliding enemy from enemyList
enemyList.erase(enemy);
// kill that enemy
enemy->kill();
// leave the for-loop (no need to do any more processesing)
break;
}
}
}
};
/* Create a player class which extends
* TriangleIsosceles built in class. In order to hook into the callbacks
* of the engine you need to create a class that overrides the callback
* methods
*/
class Player : public MultiShape {
public:
/* Construct the player with a triangle 100px wide, 100px tall
* at position 100, 100, and with a color of green
*/
Player() : MultiShape() { // creates a multishape with a default position of 0,0
// add a shadow to the player. Note that the positions of the shapes
// being added to the multishape are NOT RELATIVE. They are all
// absolute positions. Also notice how we are adding the shadow
// FIRST. Before the player. This is because the z order of
// objects is defined by the order in which they were added
// i.e. objects added more recently will be added on top of
// objects added before them
this->addShape(std::make_unique<TriangleIsosceles>(PLAYER_WIDTH, PLAYER_HEIGHT, Position(-PLAYER_SHADOW_OFFSET_X, PLAYER_SHADOW_OFFSET_Y), SHADOW_COLOR));
// this adds the player shape. Note that Position() by default
// has the x and y values set to 0s when no arguments are provided
this->addShape(std::make_unique<TriangleIsosceles>(PLAYER_WIDTH, PLAYER_HEIGHT, Position(), Color::GREEN));
}
/* Override the update method. This method is called every frame */
void update() override {
/* Set the X position of the player to the X position of the mouse */
this->setPosition(Position(Input::Mouse::getPos().x, SCREEN_HEIGHT - BOTTOM_MARGIN));
/* Move the player over half the players width in pixels so the mouse is in the middle */
/* Origin is the corner by default */
this->translate(-PLAYER_WIDTH / 2, 0);
//TriangleIsosceles
//TriangleIsosceles(PLAYER_WIDTH, PLAYER_HEIGHT, Position(100, 100), Color::GREEN) {}
}
/* override the mouse click handler to recieve callbacks when
* mouse clicks happen
*/
void onMouseClick(Input::Mouse::Btn btn, Input::Action action) override {
/* check to see if the left mouse button is down */
if (btn == Input::Mouse::Btn::LEFT && action == Input::Action::DOWN) {
/* if it is down then shoot */
/* A lot of things are happening here. Were creating an instance of
* bullet using make_unique
* Were adding that bullet to the scene.
* Notice that we are getting the scene by accessing the game
* instance via the inst() static method of the Game object.
* Then notice that scene->addChild returns a reference to the
* child that was added. We can use this reference to perform
* operations on the object even though it is already added to the
* scene. This is why we can move it around via the translate
* method call below*/
Object* bullet = Game::inst().scene->addChild(std::make_unique<Bullet>(this->pos));
/* Move the bullet so it looks like its coming out of the tip of
* the player */
bullet->translate(25, -40);
}
}
};
int main() {
/* Create a new Game object with window width, height, and title */
Game game(SCREEN_WIDTH, SCREEN_HEIGHT, "SHMUP");
// Set the background color of the scene
game.scene->setBackgroundColor(Color::BLUE);
//[> Add a convenience key handler that closes the window when escape is pressed <]
game.scene->addChild(std::make_unique<DebugKeyHandler>());
// Add a new player to the scene
game.scene->addChild(std::make_unique<Player>());
// Add a timer to the scene
game.scene->addChild(std::make_unique<Timer>(ENEMY_SPAWN_FREQUENCY_MS, true, true, []() {
// Add a new enemy every time the timer times out
int enemySpawnX = (rand() % (SCREEN_WIDTH - ENEMY_WIDTH));
Game::inst().scene->addChild(std::make_unique<Enemy>(Position(enemySpawnX, -(ENEMY_HEIGHT + 20))));
}
));
// finally starting the game
game.run();
// exercise for the reader: give all object shadows (HINT: Make them MultiShapes)
// and also make all movement 'frame rate independent'
}