I have shot some bugs : )
Unfortunately i can't make a screenshot fast enough while bullet is still on screen.
It's pretty boring because the bugs don't move, but it works to shoot them.
pbivens67 said:
then what is a data structure?
The structs i use in the code below.
Notice how easy it becomes to deal with many bugs, as one more bug is just one more element in the std::vector of bugs. I could use an array of bugs too, or a list, etc.
It's straight forward. Most work with writing this actually was the SetupLevel() function, which sets up all the data.
After that, the data lives in memory, not in magic numbers scattered across copy pasted functions.
I write my functions just once, which i can do because i use data structures.
So you should not write multiple similar functions, just because the data differs. Data and functionality should be separate things.
You want to write one generic function, and all the permutations, e.g. two bugs having a different position, should be expressed by having different data.
So you can just loop over the data, processing it all with the same function. It's less code, and it can scale up to increasing complexity.
There should be no difference of having 2 bugs or 100 bugs, for you as the programmer. You never want to copy paste a line of code 100 times just because you now have 100 bugs.
I hope it helps…
{
// this code runs every frame, basically in my rendering loop
// i will use static variables to preserve state between frames, you would use global variables instead
// and i have to use lambdas insead normal functions, so i can write functions inside functions. you would just use normal functions instead
// to try the game quickly, you would only need to paste all the code as is in your rendering loop too, and replacing drawing and input
// the very first thing we do, we make us some DATA STRUCTURES:
struct Box
{
float x, y, w, h;
};
struct Bug : Box
{
Box posSize; // our box gives us position and size of a bug
bool alive = true;
};
struct Bullet
{
Box posSize; // yay, we can use the same data structure here again! Isn't that nice?
float velX, velY;
};
struct Player
{
Box posSize;
};
/* note: We could spare a lot typing of 'posSize', if we would use Box as a base class, e.g.
struct Bug : Box
{
bool alive = true;
};
Then we could write just bug.x = 0 instead bug.posSize.x = 0. But that's a matter of taste.
*/
// that's all we need, now let's bundle it together to make a game out of it
struct Game
{
std::vector<Bug> bugs; // we'll use many bugs!
Bullet bullet; // but only one bullet for now
Player player;
Box screen = {0,0,0.640f,0.480f}; // i make my sscreen very small, so i can find it easily with my usual projection
void SetupLevel (int resX, int resY)
{
Box bugArea = screen;
bugArea.h *= 0.75f;
bugArea.y += screen.h * 0.25f;
Box bugSize;
bugSize.w = .5f * bugArea.w / float(resX);
bugSize.h = .5f * bugArea.h / float(resY);
for (int y=0; y<resY; y++)
for (int x=0; x<resX; x++)
{
float tx = (float(x) + .25f) / float(resX);
float ty = (float(y) + .25f) / float(resY);
Bug bug;
bug.posSize = bugSize;
bug.posSize.x = bugArea.x + tx * bugArea.w;
bug.posSize.y = bugArea.y + ty * bugArea.h;
bugs.push_back(bug);
}
player.posSize = bugSize;
player.posSize.x = (screen.w - bugSize.w) * 0.5f;
player.posSize.y = 0;
bullet.posSize.w = bugSize.w * 0.1f;
bullet.posSize.h = bugSize.w * 0.5f;
bullet.posSize.x = 0;
bullet.posSize.y = screen.h;
};
void Init ()
{
SetupLevel (8, 5);
}
bool IsBulletLaunced ()
{
return (bullet.posSize.y < screen.h);
}
void PlayerInput (Application &app, float timestep)
{
ImGui::Text("timestep %f", timestep);
float speed = screen.w * 0.5f;
if (app.KeyDown(Application::KEY_LBUTTON))
{
player.posSize.x = max(player.posSize.x - speed * timestep, screen.x);
}
if (app.KeyDown(Application::KEY_RBUTTON))
{
player.posSize.x = min(player.posSize.x + speed * timestep, screen.x + screen.w - player.posSize.w);
}
if (app.KeyDown(Application::KEY_MBUTTON))
{
if (!IsBulletLaunced()) // can only shoot if bullet left the screen
{
bullet.posSize.x = player.posSize.x + (player.posSize.w - bullet.posSize.w) * 0.5f;
bullet.posSize.y = player.posSize.y + player.posSize.h;
}
}
}
void UpdatePhysics (float timestep)
{
float speed = screen.w * 0.75f;
bullet.posSize.y += speed * timestep;
// todo: animate bugs
// collsion detection
// we have some generic function for the collision test
auto AABoxIntersection = [] ( float b1minX, float b1minY, float b1maxX, float b1maxY,
float b2minX, float b2minY, float b2maxX, float b2maxY )
{
if (b1maxX < b2minX || b1minX > b2maxX) return false;
if (b1maxY < b2minY || b1minY > b2maxY) return false;
return true;
};
// plus some tooling function for our Box convention
auto Intersection = [&] (Box a, Box b)
{
return AABoxIntersection ( a.x, a.y, a.x+a.w, a.y+a.h,
b.x, b.y, b.x+b.w, b.y+b.h );
};
// the loop to collide bullet with all bugs
if (IsBulletLaunced()) for (Bug &bug : bugs)
{
if (!bug.alive)
continue;
if (Intersection(bullet.posSize, bug.posSize))
{
bug.alive = false; // kill bug
bullet.posSize.y = screen.h; // kill bullet
break;
}
}
}
void Render ()
{
auto RenderBox = [](Box box, float r, float g, float b)
{
vec2 p0(box.x, box.y);
vec2 p1 = p0 + vec2(box.w, box.h);
RenderAABBox (p0, p1, r,g,b);
};
RenderBox (screen, 1,1,1); // white screen border
for (Bug &bug : bugs) // render bugs as blue squares
{
if (bug.alive) RenderBox (bug.posSize, 0,0.5f,1);
}
if (IsBulletLaunced())
RenderBox (bullet.posSize, 1,0.5f,0); // orange bullet
RenderBox (player.posSize, 0,1,0); // green player
}
};
// create the game and initialize it once
static Game game;
static bool firstRun = true;
if (firstRun) // do this only once
{
firstRun = false;
game.Init();
}
// sync to realtime
static auto prevTime = std::chrono::system_clock::now();
auto curTime = std::chrono::system_clock::now();
std::chrono::duration<float> deltaTime = curTime - prevTime;
prevTime = curTime;
// game loop
game.PlayerInput (application, deltaTime.count());
game.UpdatePhysics (deltaTime.count());
game.Render();
}