Pong-SFML-Game
Sau một vài tuần trải nghiệm C++ từ sách Book-Esenthel-2D-Basics thì tôi thấy làm game 2D sẽ phù hợp với kiến thức hiện tại. Ngoài ra Esenthel Engine thì có khá ít tài liệu nên tiếp phải dùng một library khác đó là SFML.
Introduction¶
Tôi đã học SFML được vài lần nhưng đã bỏ cuộc khi thấy quá nhàm chán khi tạo các game 2D theo sách. Tuy nhiên cuối cùng tôi vẫn phải quay lại với nó vì sẽ càng chán hơn nếu học C++ mà không có thực hành gì cả. Tôi sẽ cố gắng hoàn thành quyển sách này, vì đây là sách dạy C++ dựa trên làm game
Trong game này tôi sẽ học rõ hơn về OOP
Ý tưởng của Gane sẽ là các thanh có khả năng di chuyển hướng dọc hoặc ngang và phản ứng lại với quả banh khi chạm vào.
- Thanh ngang có khả năng đi trái hoặc phải
- Có speed
- Có toạ độ
Tạo class¶
Tạo một class Bat có các chức năng như di chuyển, lấy vị trí
được gọi là Constructor functions
Header cho Bat¶
Tạo một header Bat.h
#pragma once
#include <SFML/Graphics.hpp>
//pragma once có nghĩa là chỉ đọc một lần từ compiler
using namespace sf; // viết tắt cho sf::function
// nhưng tốt nhất sử dụng sf:: để cho nhớ rõ function của từng thư viện
class Bat
{
private: // trong private là thuộc tính, ko gọi ra
sf::Vector2f m_Position;// Tạo vector dạng float
sf::RectangleShape m_Shape; // Tạo Shape hình chữ nhật
float m_speed{ 1000.f };
bool m_MovingRight{ false };
bool m_MovingLeft{ false };
public: // Giới thiệu các functions sử dụng trong class Bat, gọi là constructor functions
Bat(float startX, float startY); // Class bat sẽ tạo va variable gồm 2 variable là float
sf::FloatRect getPosition();
sf::RectangleShape getShape();
void moveLeft();// thực hiện các lệnh bên trong void
void moveRight();
void stopLeft();
void stopRight();
void update(sf::Time dt);
};
Definition cho header Bat¶
Tạo một cpp file là Bat.cpp
tương ứng cho header đã tạo Bat.h
. Trong header này đã có constructor tức là khai báo cho các objects được tạo từ class Bat này.
Đó là trong public: Bat(float x, float y)
nên trong cpp
file cần phải định nghĩa vai trò của các arguement float x, float y
Bat::Bat(float startX, float startY)// đây là constructor
{
m_Position.x = startX;// gán giá trị m_Position trong private bằng arguments của X và Y khi tạo một object từ class Bat
m_Position.y = startY; // vì m_Position thuộc class Vector2f nên có (x,y)
m_Shape.setSize(sf::Vector2f(50.f, 5.f));
m_Shape.setPosition(m_Position);
}
Ngoài ra còn có các function
khác trong class bat
này cần được phải định nghĩa như moveLeft()
#include "Bat.h"
Bat::Bat(float startX, float startY)// đây là constructor
{
m_Position.x = startX;// gán giá trị m_Position trong private bằng arguments của X và Y khi tạo một object từ class Bat
m_Position.y = startY; // vì m_Position thuộc class Vector2f nên có (x,y)
m_Shape.setSize(sf::Vector2f(50.f, 5.f));
m_Shape.setPosition(m_Position);
}
sf::FloatRect Bat::getPosition()
{
return m_Shape.getGlobalBounds();// cần phải cho ra 4 vị trí của Rect
}
sf::RectangleShape Bat::getShape()
{
return m_Shape;
}
void Bat::moveLeft()
{
m_MovingLeft = true;
}
void Bat::moveRight()
{
m_MovingRight = true;
}
void Bat::stopLeft()
{
m_MovingLeft = false;
}
void Bat::stopRight()
{
m_MovingRight = false;
}
void Bat::update(sf::Time dt)
{
if (m_MovingLeft) {
m_Position.x = m_Position.x - m_speed * dt.asSeconds();
}
if (m_MovingRight) {
m_Position.x += m_speed * dt.asSeconds();
}
m_Shape.setPosition(m_Position);
}
Check Main.cpp¶
File này sẽ kiểm soát toàn bộ các functions chính trong game, chính là game loop. Bây giờ sẽ sử dụng class Bat
vừa tạo để có nhân vật là thanh hình chữ nhật, có khả năng di chuyển trái/phải
#include "Bat.h"
#include <sstream>
#include <cstdlib>
#include <SFML/Graphics.hpp>
int main() {
sf::VideoMode vm(1280, 720);
sf::RenderWindow window(vm, "Pong Game", sf::Style::Default);
int score = 0;
int lives = 3;
// Sử dụng class Bat vừa tạo để tạo một object tên là bat
Bat bat(1280 / 2, 720 - 20);// khai báo 2 arguments là toạ độ x và y
// Tạo các text từ SF
sf::Text hud;
sf::Font font;
font.loadFromFile("fonts/DS-DIGI.TTF");
hud.setFont(font);
// Make it nice and big
hud.setCharacterSize(75);
// Choose a color
hud.setFillColor(Color::White);
hud.setPosition(20, 20);
// Sử dụng clock để xác định thời gian trong game
sf::Clock clock;
// Tạo một game loop để update các hoạt động trong game
while (window.isOpen())
{
// Update các input từ keyboard
sf::Event event; // tạo các event cho game
while (window.pollEvent(event)) {
if (event.type == sf::Event::Closed)window.close();
}
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Escape))window.close();
// Update từ phản hồi của game
if (Keyboard::isKeyPressed(Keyboard::Right)) {
bat.moveRight();
}
else {
bat.stopRight();
}
if (Keyboard::isKeyPressed(Keyboard::Left)) {
bat.moveLeft();
}
else {
bat.stopLeft();
}
// Update time, text in game
Time dt = clock.restart();
bat.update(dt);
std::stringstream ss;
ss << "Score: " << score << " Lives: " << lives;
// Sử dụng stringstream để gán cả text và value vào một variable ss
// Sau đó dùng sf::Text để print text ra game, nếu ko thì ra console
hud.setString(ss.str()); // Sử dụng setString, chú ý ss phải sử dụng .str()
// Draw
window.clear();
window.draw(hud);
window.draw(bat.getShape());
window.display();
}
return 0;
}
Hiện tại thì game chỉ có thể cho Pong đi qua lại trái /phải.
Ball class¶
Đây là class để tạo quả banh có khả năng phản hồi, xác định va chạm collision
và tính điểm.
Cũng tương tự như Bat class
, chỉ cần sử dụng Graphics
class của SFML là đủ để vẽ các hình.
Các điểm chung như sau:
- Trong private:
- Có vị trí X, Y
- Có khai báo Shape
- Có thêm Direction X và Direction Y vì Ball có thể văng theo hệ toạ độ 2D
- Trong public:
- function getposition
- getShape()
- getVelocity()
- rebound() phản hồi của ball khi va chạm
- update() vị trí
#include "Bat.h"
Bat::Bat(float startX, float startY)// đây là constructor
{
m_Position.x = startX;// gán giá trị m_Position trong private bằng arguments của X và Y khi tạo một object từ class Bat
m_Position.y = startY; // vì m_Position thuộc class Vector2f nên có (x,y)
m_Shape.setSize(sf::Vector2f(100.f, 10.f));
m_Shape.setPosition(m_Position);
}
sf::FloatRect Bat::getPosition()
{
return m_Shape.getGlobalBounds();// cần phải cho ra 4 vị trí của Rect
}
sf::RectangleShape Bat::getShape()
{
return m_Shape;
}
void Bat::moveLeft()
{
m_MovingLeft = true;
}
void Bat::moveRight()
{
m_MovingRight = true;
}
void Bat::stopLeft()
{
m_MovingLeft = false;
}
void Bat::stopRight()
{
m_MovingRight = false;
}
void Bat::update(sf::Time dt)
{
if (m_MovingLeft) {
m_Position.x = m_Position.x - m_speed * dt.asSeconds();
}
if (m_MovingRight) {
m_Position.x += m_speed * dt.asSeconds();
}
m_Shape.setPosition(m_Position);
}
main.cpp update Ball¶
Bây giờ áp dụng class ball vào cho main game
#include "Bat.h"
#include "Ball.h"
#include <sstream>
#include <cstdlib>
#include <SFML/Graphics.hpp>
int main() {
sf::VideoMode vm(1280, 720);
sf::RenderWindow window(vm, "Pong Game", sf::Style::Default);
int score = 0;
int lives = 3;
// Sử dụng class Bat vừa tạo để tạo một object tên là bat
Bat bat(1280 / 2, 720 - 20);// khai báo 2 arguments là toạ độ x và y
Ball ball(1280 / 2, 0); // class ball tạo tại top màn hình
// Tạo các text từ SF
sf::Text hud;
sf::Font font;
font.loadFromFile("fonts/DS-DIGI.TTF");
hud.setFont(font);
// Make it nice and big
hud.setCharacterSize(50);
// Choose a color
hud.setFillColor(Color::White);
hud.setPosition(20, 20);
// Sử dụng clock để xác định thời gian trong game
sf::Clock clock;
// Tạo một game loop để update các hoạt động trong game
while (window.isOpen())
{
// Update các input từ keyboard
sf::Event event; // tạo các event cho game
while (window.pollEvent(event)) {
if (event.type == sf::Event::Closed)window.close();
}
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Escape))window.close();
// Update từ phản hồi của game
if (Keyboard::isKeyPressed(Keyboard::Right)) {
bat.moveRight();
}
else {
bat.stopRight();
}
if (Keyboard::isKeyPressed(Keyboard::Left)) {
bat.moveLeft();
}
else {
bat.stopLeft();
}
//Phản hồi, di chuyển ball
// Nếu rơi ra ngoài
if (ball.getPosition().top > window.getSize().y) {
lives--; // trừ 1 mạng
ball.reboundBottom();// reset lại vị trí ball
//Check nếu lives bị âm thì reset lại game
if (lives < 1) {
score = 0;
lives = 3;
}
}
// Nếu chạm vào top, được phản hồi và tăng điểm
if (ball.getPosition().top <0 ) {
ball.reboundBatOrTop();
score++;
}
// Nếu bạn vào bat
if (ball.getPosition().intersects(bat.getPosition())) {
ball.reboundBatOrTop();
}
// Nếu chạm vào thành
if (ball.getPosition().left <=0 || (ball.getPosition().left+ball.getShape().getSize().x >window.getSize().x)) {
ball.reboundSides();
}
// Update time, text in game
Time dt = clock.restart();
bat.update(dt);
ball.update(dt);
// reset bat position if out of bound
if (bat.getPosition().left + bat.getShape().getSize().x <=0) {
bat.newPosition(window.getSize().x);
}
if (bat.getPosition().left >= window.getSize().x) {
bat.newPosition(0.f);
}
std::stringstream ss;
ss << "Score: " << score << " Lives: " << lives;
// Sử dụng stringstream để gán cả text và value vào một variable ss
// Sau đó dùng sf::Text để print text ra game, nếu ko thì ra console
hud.setString(ss.str()); // Sử dụng setString, chú ý ss phải sử dụng .str()
// Draw
window.clear();
window.draw(hud);
window.draw(bat.getShape());
window.draw(ball.getShape());
window.display();
}
return 0;
}
Sản phẩm cuối cùng như sau:
Backlinks¶
The following pages link to this page:
Created : Mar 15, 2022
Recent Posts
- 2024-11-02: BUỔI 10 - Phân tích thị trường
- 2024-11-02: BUỔI 11 - Phân tích thị trường
- 2024-11-02: BUỔI 12 - Phân tích sóng tăng
- 2024-11-02: BUỔI 13 - Phân tích hỏi đáp
- 2024-11-02: BUỔI 14 - Yếu tố kiểm soát
- 2024-11-02: BUỔI 15 - Hỏi đáp
- 2024-11-01: BUỔI 6 - Ôn lại và bổ sung
- 2024-11-01: BUỔI 7 - Chiến thuật Trend
- 2024-11-01: BUỔI 8 - Công thức điểm vào lệnh
- 2024-11-01: K2023 - BUỔI 9 - Quy trình vào lệnh