Skip to content

Learn SDL 2

Tôi đã sử dụng Raylib, SFML, và hôm nay là SDL. Tôi đã mua 1 khoá học về SDL2 C++ programming trên Udemy để tiếp tục học cách làm game và kết hợp học C++.

2 Get-to-Class

Tạo một class có các functions là set input và display

#include <iostream>
using namespace std;

class Point2D
{
public:
    Point2D();
    ~Point2D();
    void Set(int dx, int dy);
    void Display();

---
title: Learn-SDL2
date: 2022-04-11
tags: 

  - template
---
Created date: 2022-04-11


## Learn-SDL2




private:
    int mX{};
    int mY{};
};

Point2D::Point2D()
{
}

Point2D::~Point2D()
{
}

void Point2D::Set(int dx, int dy)
{
    mX = dx;
    mY = dy;
}

void Point2D::Display()
{
    cout << "X: " << mX << ", Y: " << mY << endl;
}

int main()
{
    Point2D point1; // Sử dụng class Point2D vừa tạo
    Point2D point2;


    point1.Set(10, 20);
    point1.Display();

    point2.Set(100, 200);
    point2.Display();

    return 0;
}

2.1 Private and Publics

Sử dụng private để các function không thể access được như các function trong publics. ví dụ như point1.mX thì sẽ gây ra lỗi.

2.2 Encapsulation - vật chứa

Bởi vì các private variable thì hidden bởi các class, nó không thể truy cập được, nó chỉ có thể Display nếu chúng ta tạo các functions để display nó như Display()

2.3 Data members

cout << "Size of Point1 is:" << sizeof(point1) << endl;
Sử dụng sizeof để biết size của nó

2.4 Methods

Đây còn gọi là các functions ví dụ như

int Point2D::GetX()
{
    return this->mX;
}

2.5 Struct vs Class

  • Struct khi khai báo luôn là public
  • Class cần phải khai báo public hay private nếu không toàn bộ đều là private

Tạo một Struct để get input và Display

struct StructPoint2D
{
    void Set(int dx, int dy);//khai báo function Set cho StructPoint2D
    void Display();

    int mX; // Sử dụng Struct thì không có Private, tất cả đều Public
    int mY;

};

Định nghĩa functions

void StructPoint2D::Set(int dx, int dy) // định nghĩa Struct Set
{
    mX = dx;
    mY = dy;
}

void StructPoint2D::Display()
{
    cout << "Struct X= " << mX << " Y= " << mY << endl;
}

2.6 Constructor

Name của một class được gọi là constructor

class Point2D
{
public:
    Point2D(); // Name của class được gọi là constructor
    ~Point2D();

Một class chỉ có 1 constructor, đây là nơi để khai báo thuộc tính thường được sử dụng của class này, được định nghĩa như sau

Point2D::Point2D()
{
    mX = 0;
    mY = 0;
}

Lưu ý các mX và mY là các variables trong private. Vì vậy thay vì sử dụng function Set(x, y) thì bây giờ chỉ cần sử dụng constructor để khai báo là được rồi. Khi tạo các variable dựa theo class thì nó sẽ có các tính năng cơ bản nhất trong constructor

void Point2D::Set(int dx, int dy)
{
    mX = dx;
    mY = dy;
}

Bây giờ khi khai báo thì không cần dùng function Set nữa mà sử dụng như sau

    Point2D point1(10, 20); // Sử dụng class Point2D vừa tạo
    point1.Display();

Cũng có thể khai báo là point1 = {10,20};

2.7 Initializer lists

Khi khai báo constructor nếu cần có các giá trị mặc định thì có thể khai báo cùng 1 hàng

Point2D::Point2D(int x, int y):mX(x),mY(y)
{
}

Tính năng này hữu ích khi sử dụng reference, nó sẽ không bị lỗi trong các class. Nếu chỉ khai báo trong body thì reference chưa tồn tại vì vậy cần dùng initilizer.

Cần chú ý đến thứ tự của mX và mY khi được khai báo trong private

2.8 Delegate constructor - Uỷ quyền

2.9 Destructor

Nếu cần free các resources trong class thì cần sử dụng destructor

2.10 Class scope

2.11 Explicit constructor

Có thể tạo nhiều constructor với cùng tên method trong một class. Ví dụ như

class Point2D
{
public:
    Point2D(int x, int y); // Name của class được gọi là constructor
    Point2D(float x, float y); // constructor thứ 2 với cùng tên nhưng khác argument
    void Set(int dx, int dy);
    void Display();
    int GetX();
    int GetY();

Để gán sử dụng đúng constructor thì khai báo explicit trước method đó

2.12 Call methods on objects

Để gọi một method từ object vừa được tạo theo class thì sử dụng dấu chấm .
VD như khai báo

    Point2D point1(10, 20); // Sử dụng class Point2D vừa tạo
    Point2D point2 = { 100,200 }; // cách khai báo khác là {x,y}

    point1.Display();
    point2.Display();

Sử dụng new Constructor

    Point2D* pPoint = new Point2D(); // Point2D nhưng thuộc pointer
    pPoint->Display(); // vì pPoint là pointer thì phải dùng ->, nếu không phải để là (*pPoint).Display()

2.13 Objects with objects in 2 functions

class Point2D
{
public:
    Point2D() {}; //constructor nhưng k có argument
    Point2D(int x, int y) :mX{ x }, mY{ y }// Name của class được gọi là constructor
    {
    }
    ~Point2D() {};
    void Display() 
    {
        cout << "Class X: " << mX << ", Y: " << mY << endl;
    }
    void SetValue(int dx, int dy)// methods thì không thể dùng initializer như constructor được
    {
        mX = dx;
        mY = dy;
    }

private:
    int mX{}; //{} để initialize variable này
    int mY{};
};

class secondPoint2D
{
public:
    secondPoint2D() :myPoint2D(20, 10)//constructor này dựa theo dữ liệu của constructor Point2D
    {

    }; 
    ~secondPoint2D() {};

    void SetValue(int dx, int dy)// methods thì không thể dùng initializer như constructor được
    {
        mX = dx;
        mY = dy;
    }
    void Display()
    {
        cout << "Class X: " << mX << ", Y: " << mY << endl;
    }

private:
    int mX{}; //{} để initialize variable này
    int mY{};
    Point2D myPoint2D{};
};

2.14 Array of objects

Ví dụ như khai báo một array từ class MyClass myClass[5]; nhưng sẽ gây lỗi vì không hiểu đây là 1 array. Cần phải khai báo mảng #include <vector>
Bây giờ thì khai báo myClass thuộc một mảng Vector có dạng là MyClass vector<MyClass> myClass[5];

2.15 Copy constructor

Có thể tạo 1 function độc lập và dùng Class là argument, sau đó có thể truy vấn các functions trong class đó

void MyFunc(MyClass myClass) // 1 function độc lập sử dụng MyClass, nhưng không thuộc Class này
{
    myClass.Display();
}

2.16 Static keywords

Khi static được đứng trước variables hoặc một function thì giá trị của nó không thể thay đổi. Giá trị nó tính lifetime trong suốt chương trình, vì vậy nó chỉ được gọi duy nhất 1 lần.
static có ích khi cần lưu lại kết quả của lần chạy đầu tiên
static có thể bên trong hoặc ngoài functions

2.17 Constant

constant của function

int GetIntValue() const
    {
        return this->mValue;
    }

2.18 inline methods

In fact, all the functions defined inside the class are implicitly inline

2.19 friend keyword

Khi sử dụng friend trước một variable trong private của một class thì cho phép class đó sử dụng các variable private

class Node {
private:
    int key;
    Node* next;
    /* Other members of Node Class */

    // Now class  LinkedList can
    // access private members of Node
    friend class LinkedList;
};

Ví dụ như code sau của header file

class Vector2D
{
public:
    Vector2D() :Vector2D(0, 0) {}
    Vector2D(float x, float y) :mX(x), mY(y) {}

    void SetX(float x) { mX = x; }
    void SetY(float y) { mY = y; }
    float GetX() { return mX; }
    float GetY() { return mY; }

    friend ostream& operator <<(ostream& consoleOut, const Vector2D& vec); // sử dụng friend để có thể truy cập được private variables
private:
    float mX, mY;
};

Trong cpp definition file

#include "Vector2D.h"

ostream& operator<<(ostream& consoleOut, const Vector2D& vec)
{
    cout << "X: " << vec.mX << ", Y: " << vec.mY << endl;
}

3 Math in C++

3.1 Comparing floats

Vì các floats có rất nhiều số sau dấu phẩy. Ví dụ không thể so sánh 3.14==PI được. Có thể tạo một function đơn giản để so sánh hai giá trị floats.
Sử dụng thêm cmath lib để tính toán

3.2 Negating a vector

Sử dụng unary overloading operator.
Như trong class Vector2D mà tôi tự tạo thì sẽ có float x và float y. Trong python thì chúng ta có thể sử dụng vector1+ vector2 một cách đơn giản, nó sẽ tự x1+x1 và y1 + y2 để tạo ra vector mới. Nhưng C++ thì chưa hiểu functions này. Vì vậy cần sử dụng unary overloading operator.

Khi sử dụng overide operator, conpiler sẽ dịch ra như sau
p1+p2 tương đương p1.operator+(p2). Tức là operator+() là một function của class, còn p2 là một argument của function này.

Xem ví dụ sau

#pragma once
#include <iostream>
using namespace std;

class Vector2D
{
public:
    Vector2D() :Vector2D(0, 0) {}
    Vector2D(float x, float y) :mX(x), mY(y) {}

    void SetX(float x) { mX = x; }
    void SetY(float y) { mY = y; }
    float GetX() { return mX; }
    float GetY() { return mY; }

    friend ostream& operator <<(ostream& consoleOut, const Vector2D& vec); // sử dụng friend để có thể truy cập được private variables
    bool operator==(const Vector2D& vec2) const;
    bool operator!=(const Vector2D& vec2) const;

    Vector2D operator-() const; // tạo một operator based on class Vector2 ngay trong class này
    // cần phải sử dụng const để nó không thay đổi variable bên trong function
private:
    float mX, mY;
};

Một lưu ý nhỏ trong class này là const member function. Đoạn code dưới đây giải thích ý nghĩa sử dụng const cho functions.

const đặt cuối function tức là promise sẽ KHÔNG đổi values của this*

class Fred {
public:
  void inspect() const;   // This member promises NOT to change *this
  void mutate();          // This member function might change *this
};
void userCode(Fred& changeable, const Fred& unchangeable)
{
  changeable.inspect();   // Okay: doesn't change a changeable object
  changeable.mutate();    // Okay: changes a changeable object
  unchangeable.inspect(); // Okay: doesn't change an unchangeable object
  unchangeable.mutate();  // ERROR: attempt to change unchangeable object
}

Một ví dụ khác

#include <cassert> // dùng thay cho ifdefine
#include <cmath>

Vector2D Vector2D::operator/(float scale) const
{
    assert(fabsf(scale) > EPSILON); // kiểm trả scale không phải là zero, để không chia cho 0
    return Vector2D(this->mX/scale, this->mX/scale);
}

Trong ví dụ này có sử dụng thêm assert, đây là dùng để test điều kiện trước khi thực hiện các lệnh phía sau. Điều này khá giống với một hàm if ??
Điểm khác biệt với ifassert chỉ sử dụng khi debug để biết lỗi hay không. Còn khi release sẽ không check nó, giúp build nhanh hơn so với hàm if.

Một điều lưu ý khác là vị trí của operator, nó có thể là x*n nhưng cũng có thể là n*x

Vector2D Vector2D::operator*(float scale) const
{
    return Vector2D(scale*this->mX, scale*this->mY);
}

Vector2D operator*(float scalar, const Vector2D& vec)
{
    return vec * scalar;
}

4 SDL2

4.1 SDL window

Khi cài SDL2 từ vcpkg cần phải manually link lib với linker của VS2019.
`$(VcpkgRoot)\installed$(VcpkgTriplet)\debug\lib\manual-link\SDL2maind.lib

Ngoài ra trong main() cũng cần khai báo như sau, vì SDL main() function trùng với nó

int main(int argc, char *args[])
{
    // Your code here
}

Hoặc phải dùng #define SDL_MAIN_HANDLED hàng đầu tiên của cpp có liên quan đến SDL2. Trong main function cần bổ sung thêm
SDL_SetMainReady();

4.2 Screen and Surface SDL

In PixelFormat của Surface

The following pages link to this page:



Created : Apr 4, 2022