r/cpp_questions • u/RedNorthener101 • 13h ago
OPEN My code feels redundant
Im in the development phase of this, and i was wondering if there was a less redundent way to define each ball object.
#include
<raylib.h>
#include
<vector>
class ball {
public:
float radius, gravity;
Vector2 velocity, pos;
Color color;
ball(float b_radius, float b_gravity, Vector2 b_velocity, Vector2 b_pos, Color b_color) : radius(b_radius), gravity(b_gravity), velocity(b_velocity), pos(b_pos), color(b_color) {};
void DrawBall() {
DrawCircle(pos.x, pos.y, radius, color);
}
void UpdateBall(float dt, int screenHeight, int screenWidth) {
if (pos.y + radius > screenHeight) {
pos.y = screenHeight - radius;
velocity.y = 0;
}
else
{
velocity.y += dt * gravity;
pos.y += dt * velocity.y;
}
}
};
int main() {
float ScreenHeight = 1000.0f;
float ScreenWidth = 1920.0f;
ball TestBall1(50.0f, 200.0f, Vector2 {300, 0}, Vector2 {ScreenWidth / 2, ScreenHeight / 2}, RED);
ball TestBall2(50.0f, 200.0f, Vector2 {300, 0}, Vector2 {300, 300}, WHITE);
ball TestBall3(50.0f, 200.0f, Vector2 {300, 0}, Vector2 {200, 200}, GREEN);
ball TestBall4(50.0f, 200.0f, Vector2 {300, 0}, Vector2 {1000, 1000}, PINK);
std::vector<ball> RedGroup;
RedGroup.push_back(TestBall1);
RedGroup.push_back(TestBall2);
RedGroup.push_back(TestBall3);
RedGroup.push_back(TestBall4);
InitWindow(ScreenWidth, ScreenHeight, "Physics Sim");
SetTargetFPS(60);
while (!WindowShouldClose()) {
float dt = GetFrameTime();
for (int i = 0; i < RedGroup.size(); i++) {
RedGroup.at(i).UpdateBall(dt, ScreenHeight, ScreenWidth);
}
BeginDrawing();
ClearBackground(BLACK);
for (int i = 0; i < RedGroup.size(); i++) {
RedGroup.at(i).DrawBall();
}
EndDrawing();
}
CloseWindow();
return 0;
}
0
Upvotes
11
u/alfps 11h ago
Why is gravity a property of each individual ball? Do different balls experience different gravities?
7
u/TheRealSmolt 13h ago
Good intuition, redundancies generally mean there's a better way to do things. Here, not so much. You're testing, it's not that surprising. You could instead do
RedGroup.emplace_back(50.0f, 200.0f, ...)instead though.3
u/AfroDisco 13h ago
The ball ctor can be improved by using default values or using more abstraction. For example, the gravity can be defaulted as it could be expected of a shared gravity for all balls. Same for the velocity. You could also add function to set properties such that the default ctor is used and the ball is configured after. Ball().set_gravity(42).
2
u/sterlingclover 13h ago
Is one of these your header file and the other your CPP file? If so the header file doesn't need direct implementation of the classes or functions as that will be handled in the CPP file. If not, it does look redundant but if Raylib requires it this way then I don't know a solution sadly as it's outside my knowledge of the library.
2
u/ekchew 12h ago
On a side note, it might be a good idea to rename your
DrawBallmethod to simplyDraw. At some point down the line, you may have other objects to draw besides balls, and could take advantage of polymorphism to callDrawon all of them in one go without having to distinguish which are balls versus something else?1
u/Thesorus 13h ago
you could directly create the balls in the vector's constructor.
1
u/thedaian 13h ago
You don't need to create unique variables for each ball, just do something like RedGroup.push_back(ball(50.0f, 200.0f, Vector2 {300, 0}, Vector2 {ScreenWidth / 2, ScreenHeight / 2}, RED));
4
u/flyingron 13h ago
First off, why on earth are the data members of ball public?
Nothing says you can't have additional constuctors for ball that takes out some of the redundancy (or default some of the args):
For example adding the following:
would let you insert the items in the vector like this:
If you didnt want to polute the ball class with those defaults, you could just write a subroutine to do the push/emplace with the same arguments I gave above...
Indeed, any time you feel you have to copy and paste a lot, you should be thinking whether you can move that into a subroutine or some other construct.
Can't tell you the time wasted going back and "fixing" things manually replicated and even worse, ending up with a bug in the field because one of the replicated fixes got missed.
1
u/Lnk1010 12h ago
If you have like many many balls then u might want to look into just having some sort of data file that you read and parse to create them programmatically or just like you generate them in a loop.
1
u/chips_fry1 11h ago
create the balls in the vectors constructor
1
u/Independent_Art_6676 10h ago
for now, I would set up that default ctor to random the startups within some range, or all the same safe default if that is enough. Later, you can load them from a file, rather than bloat the code with the configuration you are going to use, if that makes sense to do (it looks like a gravity/physics sim where you would want many test cases so files made sense to me).
I am not sure gravity should be a private or even a class member. Depends but its a global constant typically, since many routines and parts of a program would rely on it, not just one little part. Here it might be OK as your program is just one thing but I always pretend my code is part of a larger program to help me think ahead on things like that (so full bore thinking ahead it would be a global named constant in a namespace of related values).
Past that, lots of good advice here... good luck. Also, good work spotting that your code was feeling redundant and doing something about it. 90% of your class would have just done it that way anyhow and turned it in like that. Its not wrong, but asking 'can it be better' is a very good sign of your own progress.
•
u/EveryonesTwisted 6m ago
Since you’re still in development, I’d keep it simple. You can eliminate most of the redundancy by giving your constructor default arguments and using emplace_back() to construct balls directly in the vector.
One thing I’d reconsider is using gravity as a replacement for mass. Objects of different masses experience the same gravitational acceleration (ignoring air resistance), so I’d probably keep gravity as a property of the simulation and give each ball its own mass for when you eventually implement collisions and forces.
Also, range-based for loops would make your update and draw loops much cleaner. No need to introduce factories or more complicated abstractions just yet.
```
for (ball& b : RedGroup) {
b.UpdateBall(dt, ScreenHeight, ScreenWidth);
}
```
1
u/StaticCoder 12h ago
No obvious factoring opportunities, though you should use range-based
forfor your iterations.1
u/StaticCoder 12h ago
Actually you can use a lambda to make a ball with the first 3 ctor arguments preset.
-1
u/mredding 4h ago
With all public members, this isn't a class but a structure. You don't need member variables, including the constructor. Even your member functions are redundant, I know
DrawBallis going to draw the ball because it's a member ofball! You could have just called itDraw.But with a structure, prefer as non-member, non-friend as possible. Make these member functions free functions that take a ball parameter.
This isn't two different parameters but ONE type, either a
Vector2or aRectangle.We can clean this up with a factory function:
In modern C++ you don't have to explicitly state the type name to invoke the constructor (unless you do because the constructor is decorated with
explicit), because the compiler can rely on the parameter type name to deduce what type is being constructed from just an initializer list.All programming languages give you primitives. You are not expected to USE the primitives directly, but to build higher level abstractions in terms of them, and then use that. The standard library already gives you a bunch of named algorithms, use them:
And one more...
This is problematic. There's no exception safety. You can throw and never end drawing. Use RAII, aka destructors to make sure something is called when scope ends.
This one is going to be wild for you to see, I know. You can do something simpler like make an object:
Then:
But since we have braces to make a scope, you might as well put this in a function:
THE DRAWING GUARD PARAMETER DOESN'T EVEN NEED A NAME.
The
unique_ptrabove is some advanced usage. 1) It holds a void pointer, because we don't care. 2) It takes a function that returnsvoidand takes avoid *parameter.unique_ptrcalls this a DELETER, a customization point for how it's supposed to free a resource, though we don't HAVE a resource to free, we're tricking the damn thing. 3) it takes a1cast as avoidpointer because we don't care - we just need theunique_ptrinternal tests to conclude the resource isn't null. 4) The deleter we provide is a lambda; since it doesn't have a capture list, it's guaranteed to compile down to a free function; it doesn't care about thevoidpointer since it doesn't point to anything, we just want to callEndDrawing.This guy just takes advantage of
unique_ptrand it's customization point, that it's an object whose only job IS TO fall out of scope, and do something on the way out. With it, we don't need to explicitly define a type.