C++ Ball Physics Tutorial 2 - Framework

In the last part we installed SFML for our project and made sure it worked. In this part we will create a small framework. One obvious class that we need is the Ball class. We will be using a class called World which will be responsible for all of the updates, and a class called WorldRenderer which will show us all of the updates on the screen.

 

Ball.h

Let's begin with the class Ball. Create Ball.h and enter this:

#ifndef BALL_H
#define BALL_H

#include <SFML/Graphics.hpp>

class Ball {

public:
	Ball(float radius);
	~Ball();

	void update(float deltatime);
	void draw(sf::RenderWindow& window);

	void setPosition(float x, float y);
	void setVelocity(float x, float y);
	void setAcceleration(float x, float y);
	void setDragged(bool dragged);

	sf::Vector2f getVelocity() const;
	sf::Vector2f getAcceleration() const;
	float getMass() const;
	float getDragged() const;
	float getRadius() const;
	sf::Vector2f getPosition() const;

private:
	sf::Vector2f velocity;
	sf::Vector2f acceleration;
	sf::CircleShape circleShape;

	bool dragged;
	float mass;
};

#endif

The include line, if you're not familiar with SFML, makes it possible for us to use SFML graphics. Our balls are going to depend on the SFML objects CircleShape and Vector2f, as you see in the private section:

	sf::Vector2f velocity;
	sf::Vector2f acceleration;
	sf::CircleShape circleShape;

Notice that we have a velocity and acceleration vector, but no position vector. This is because sf::CircleShape has a position vector that we will take advantage of when we create the functions setPosition and getPosition. There's also a bool named dragged and a float named mass. The variable dragged will be used to check if a ball is being dragged, i.e a user clicked on it and is about to give it some speed. The variable mass will simply be the balls mass, it is needed because the balls velocities is depending on the masses.

The constructor takes a float as an argument, which will determine the balls radius. The mass will also be determined based on the radius in the constructor. We got an update function,

	void update(float deltatime);

which we will leave empty until the next part. In this part, our goal is to display non moving balls with a framework. The function below, 


 
	void draw(sf::RenderWindow& window);

 

takes a reference of sf::RenderWindow. This is the function we will use in WorldRenderer later to display the balls. The rest of the functions in Ball.h is just setters and getters, which are pretty self explanatory.

 

Ball.cpp

Declared functions and variable doesn't do much on its own. Create Ball.cpp and enter this:

#include "Ball.h"

Ball::Ball(float radius) : dragged(false), mass(radius * 20.f), circleShape(sf::CircleShape(radius)) {
	circleShape.setFillColor(sf::Color(rand() % 255, rand() % 255, rand() % 255));
	circleShape.setOrigin(radius, radius);
}

Ball::~Ball() {}

void Ball::update(float deltatime) {

}

void Ball::draw(sf::RenderWindow & window) {
	window.draw(circleShape);
}

void Ball::setPosition(float x, float y) {
	this->circleShape.setPosition(sf::Vector2f(x, y));
}

float Ball::getRadius() const {
	return circleShape.getRadius();
}

sf::Vector2f Ball::getPosition() const {
	return circleShape.getPosition();
}

void Ball::setVelocity(float x, float y) {
	this->velocity = sf::Vector2f(x, y);
}

void Ball::setAcceleration(float x, float y) {
	this->acceleration = sf::Vector2f(x, y);
}

void Ball::setDragged(bool dragged) {
	this->dragged = dragged;
}

float Ball::getMass() const {
	return mass;
}

float Ball::getDragged() const {
	return dragged;
}

sf::Vector2f Ball::getVelocity() const {
	return velocity;
}

sf::Vector2f Ball::getAcceleration() const {
	return acceleration;
}

This file mostly contains setters and getters. In the constructor, the bool dragged gets instantiated to false and mass to radius * 20.f. Feel free to experience with the float 20. We also instantiate circleShape to sf::CircleShape(radius). In the actual body of the constructor, the balls is given a random color:

        circleShape.setFillColor(sf::Color(rand() % 255, rand() % 255, rand() % 255));

We also make sure the origin is in the balls center:

    circleShape.setOrigin(radius, radius);

We leave the function update empty for now, and put one line in draw:

void Ball::draw(sf::RenderWindow & window) {
	window.draw(circleShape);
}

That is everything we need to do to display the balls later. More about RenderWindow soon. Moving on to setPosition, it takes two arguments, floats x and y. These are used to set the position of the circleShape variable. sf::CircleShape actually takes a vector2f as an argument, so we create one and instantiate it with our two floats:

void Ball::setPosition(float x, float y) {
	this->circleShape.setPosition(sf::Vector2f(x, y));
}

The rest of Ball.cpp is just setters and getters, those are pretty self explanatory. 

 

World.h

Create a file and name it World.h.

#ifndef WORLD_H
#define WORLD_H
#include "Ball.h"

class World {
public:
	World();
	~World();

	void initBalls(size_t size);
	void update(float deltatime);

	void ballCollision();

	bool dragBall(sf::Vector2f point);
	void setDraggedVelocity(float x, float y);

	Ball* getDraggedBall() const;
	std::vector<Ball> getBalls() const;

private:
	std::vector<Ball> balls;

	Ball* draggedBall;
};

#endif

World includes Ball.h and iostream. It has two private variables, a vector for our balls and a pointer that will point to a ball that is being dragged. We got a function called initBalls which takes a size_t as an argument. This function will generate a requested amount of balls with different radius and colors. The update function will go through every balls and see if any collision occurs, and update every balls position and velocity. But that will be done in the next part. The next three functions, ballCollision, dragBall and setDraggedVelocity will also be left empty for now. 

ballCollision however, will contain the logic for collisions. dragBall will take the mouse position as an input and use this to check if a click is touching a ball. setDraggedVelocity will be called when the user releases a dragged ball and give the ball some velocity. The next two functions are getters for the pointer draggedBall and the vector which contains the balls.

 

World.cpp

#include "World.h"

World::World() {
	draggedBall = nullptr;
	initBalls(10);
}

World::~World() {
	delete draggedBall;
}

void World::initBalls(size_t size) {
	for (size_t i = 0; i < size; ++i) {

		float x = (float)(rand() % 1000);
		float y = (float)(rand() % 600);
		float radius = (float)(15 + (rand() % (40 - 16)));

		Ball ball = Ball(radius);
		ball.setPosition(x, y);

		balls.push_back(ball);
	}
}

void World::update(float deltatime) {

}

void World::ballCollision() {

}

bool World::dragBall(sf::Vector2f point) {

	return false;
}

void World::setDraggedVelocity(float x, float y) {

}

Ball * World::getDraggedBall() const {
	return draggedBall;
}

std::vector<Ball> World::getBalls() const {
	return balls;
}

The constructor of World.cpp instantiate draggedBall to a nullptr, we don't drag a ball to begin with. After that, it generates ten balls by calling initBalls(10). Play around with that number. In the deconstructor we delete draggedBall.

initBalls contains a for loop, and every time it runs it generates a random position and radius for a ball, then it stores that new ball in the vector. The number 1000 and 600 here:

		float x = (float)(rand() % 1000);
		float y = (float)(rand() % 600);

is just the maximum x and y to make sure our balls don't escape the window. The window will have the width 1000 and height 600. 15 is the minimum radius, and 40 is the maximum:

		float radius = (float)(15 + (rand() % (40 - 16)));

The formula used for this is (max + rand() % (max - min + 1)).

update is empty, ballCollision is empty, dragBall return false for the moment, setDraggedVelocity is empty, getDraggedBall return draggedBall and getBalls return balls.

 

WorldRenderer.h

Time to program the renderer. This will be the only complete class in this part.

#ifndef WORLDRENDERER_H
#define WORLDRENDERER_H
#include <SFML/Graphics.hpp>
#include "World.h"

class WorldRenderer {

public:
	WorldRenderer(World& world);
	~WorldRenderer();

	void render(sf::RenderWindow& window) const;
	void renderBalls(sf::RenderWindow& window) const;

private:
	World& world;
};

#endif

It has a private reference to a World. The constructor takes World& as an argument. It only contains two functions, render and renderBalls, and both take sf::RenderWindow& as arguments. They are also both of type const, because we are not going to change anything in here, just display the balls.

 

WorldRenderer.cpp

#include "WorldRenderer.h"

WorldRenderer::WorldRenderer(World& world) : world(world) {}

WorldRenderer::~WorldRenderer() {}

void WorldRenderer::render(sf::RenderWindow& window) const {
	renderBalls(window);
}

void WorldRenderer::renderBalls(sf::RenderWindow& window) const {
	for (Ball ball : world.getBalls()) {
		ball.draw(window);
	}
}

The constructor instantiate the world variable. The render function just calls renderBalls. We could just call renderBalls directly, but if you want to build more on this project, maybe add some rectangles, it's easier to handle it this way. renderBalls loops through every balls in the vector from the world and calls its draw function.

 

Main.cpp

#include <iostream>
#include <SFML/Graphics.hpp>
#include "WorldRenderer.h"
#include "World.h"

int main() {
	sf::RenderWindow window(sf::VideoMode(1000, 600), "Ball Physics Simulator");

	srand((unsigned)(time(NULL)));

	World world;
	WorldRenderer worldRenderer(world);

	while (window.isOpen()) {
		sf::Event event;

		while (window.pollEvent(event)) {
			if (event.type == sf::Event::Closed) {
				window.close();
			}
		}

		window.clear(sf::Color::White);
		worldRenderer.render(window);

		window.display();

	}

	return 0;
}

First line creates a window for us with the width 1000 and height 600. The next line, 

	srand((unsigned)(time(NULL)));

makes our random more random. Then we create a World object, and pass that in a WorldRenderers constructor that we also create. The next line:

	while (window.isOpen()) {

will act as our "game loop". We will do some changes here in the next part. First thing we do in that loop is to poll events. If we didn't close the window, the three next lines will run:

		window.clear(sf::Color::White);
		worldRenderer.render(window);
		window.display();

which will set the background color to white, render the world and display it.

If you run this code you should see balls with different colors and size. See you in the next part!

Enjoyed this article? Give the teacher an apple.

cookie

0

Author

authors profile photo

Articles with similar tags

Comments