Вторник, 19 Марта 2024, 05:24

Приветствую Вас Гость

[ Новые сообщения · Игроделы · Правила · Поиск ]
  • Страница 1 из 27
  • 1
  • 2
  • 3
  • 26
  • 27
  • »
Форум игроделов » Записи участника » BADCOIQ [528]
Результаты поиска
BADCOIQДата: Среда, 05 Июля 2023, 20:05 | Сообщение # 1 | Тема: Разработка движка (детальное описание для новичков) [C++]
Отец-основатель BADCOIQ Corporation © 2010
Сейчас нет на сайте
Нужно где-то хранить размеры, например, ширину и высоту. И ещё надо хранить 4 значения обозначающих прямоугольную область.
Нужны
Код

bqPoint
bqRect


Куда их сунуть? Где написать?
Добавлю новый .h файл
inc/badcoiq/common/bqBasicTypes.h
Напишу туда.
Код

template<typename T>
class bqPoint_t
{
public:
    bqPoint_t() {}
    bqPoint_t(T X, T Y) : x(X), y(Y) {}

    T x = 0;
    T y = 0;
};

using bqPoint = bqPoint_t<int32_t>;
using bqPointf = bqPoint_t<float>;

template<typename T>
class bqRect_t
{
public:
    bqRect_t() {}
    bqRect_t(T Left, T Top, T Right, T Bottom) :
  left(Left),
  top(Top),
  right(Right),
  bottom(Bottom)
    {}

    T left = 0;
    T top = 0;
    T right = 0;
    T bottom = 0;
};

using bqRect = bqRect_t<int32_t>;
using bqRectf = bqRect_t<float>;


Окно

При создании окна ничего указывать не надо. Просто Призовём окно. Все настройки будем делать методами.
Окну нужен коллбэк, оно будет вызывать методы когда с окном что-то случиться.

inc/badcoiq/system/bqWindow.h
Код

// Когда с окном что-то происходит, оно будет вызывать эти коллбэки
// При создании окна мы обязаны послать указатель на коллбэк
class bqWindowCallback
{
public:
    bqWindowCallback() {}
    virtual ~bqWindowCallback() {}

    // Когда переключились к этому окну
    virtual void OnActivate(bqWindow*) {}
    // Когда переключились к другому окну
    virtual void OnDeactivate(bqWindow*) {}

    // После изменении размера
    virtual void OnSize(bqWindow*) {}
    // Пока изменяем размер
    virtual void OnSizing(bqWindow*) {} // when resizing

    // Свернули окно
    virtual void OnMinimize(bqWindow*) {}
    // Развернули на весь монитор
    virtual void OnMaximize(bqWindow*) {}
    // Восстановили (из Maximized и Minimized в обычное)
    virtual void OnRestore(bqWindow*) {}

    // Когда окно перерисовывается
    virtual void OnDraw(bqWindow*) {}

    // Когда двигается
    virtual void OnMove(bqWindow*) {}

    // Когда кликается кнопка закрытия
    virtual void OnClose(bqWindow*) {}
};


Окна для разных ОС имеют что-то общее.
Нужно сунуть общие данные в структуру.
Код

struct bqWindowCommonData
{
    // тут хранится указатель на коллбэк
    bqWindowCallback* m_cb = 0;

    // размер рамки
    bqPoint m_borderSize;

    // минимальный размер окна
    bqPoint m_sizeMinimum;
    
    // текущий размер окна (клиентской области)
    bqPoint m_sizeCurrent;

    // окно видимо или нет
    bool m_isVisible = false;

    // данные специфичные для ОС
    void* m_implementation = 0;
};


Данные специфичные для ОС могут понадобиться. Надо добавить ещё одну структуру, которая видна только для Windows платформы.

Надо добавить отдельный .h файл, для Windows данных, так как нужно подключать windows.h, а лишний раз это делать не нужно (я за скорость компиляции).
inc/badcoiq/system/bqWindowWin32.h
Код

#ifdef BQ_PLATFORM_WINDOWS
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>

struct bqWindowWin32
{
    wchar_t m_className[20];
    HWND m_hWnd = 0;
    HRAWINPUT m_rawInputData[0xff];
    LONG m_style = 0;
    LONG m_stylePreFullscreen = 0;
    WINDOWPLACEMENT m_wndPlcmnt;
};
#else
#error Для Windows
#endif


m_rawInputData для ввода, m_stylePreFullscreen и m_wndPlcmnt понядобятся для fullscreen

Нужны ОС данные? Подключай bqWindowWin32.h и делай так
Код

bqWindowWin32* WindowOSData = (bqWindowWin32*)myWindow->GetData()->m_implementation;


Теперь класс окна. Возвращаюсь в bqWindow.h
Код

// Окно
class bqWindow
{
    bqWindowCommonData m_data;
public:
    BQ_PLACEMENT_ALLOCATOR(bqWindow);
    bqWindow(bqWindowCallback* cb);
    ~bqWindow();

    // Установить заголовок
    void SetTitle(const char*);

    // Скрыть\показать
    void SetVisible(bool v);

    // Узнать, видимо ли окно.
    // Можно скипать рисование если окно например свёрнуто.
    bool IsVisible() { return m_data.m_isVisible; }

    // Развернуть в полный экран
    void Maximize();
    // Свернуть
    void Minimize();
    // Восстановить оконное состояние (ну типа оконное, не полный экран)
    void Restore();

    // Установить\убрать рамку
    void SetBorderless(bool);

    // Установить\убрать возможность изменять размер
    void SetNoResize(bool);

    // Показать\скрыть кнопку "свернуть окно"
    void SetNoMinimize(bool);

    // Тут, указатели для того чтобы получить их и сохранить где-то, и
    //  не вызывать эти методы каждый раз, так как в таком случае потребуется
    //    вызывать постоянно.
    // Получить минимальный размер окна.
    bqPoint* GetSizeMinimum() { return &m_data.m_sizeMinimum; }
    // Получить размер рамки
    bqPoint* GetBorderSize() { return &m_data.m_borderSize; }

    // Получить данные
    bqWindowCommonData* GetData() { return &m_data; }
    
    // Установить позицию и размер
    void SetPositionAndSize(int x, int y, int sx, int sy);
};


Реализация
src/badcoiq/system/bqWindow.cpp

Код

#include "badcoiq.h"

#include "badcoiq/system/bqWindow.h"

#ifdef BQ_PLATFORM_WINDOWS
#include "badcoiq/system/bqWindowWin32.h"
LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);

// Нахождение текущего размера окна (клиентской области)
void bqWindowWin32_findCurrentSize(bqWindow* w, bqWindowWin32* w32)
{
    RECT rc;
    GetClientRect(w32->m_hWnd, &rc);
    w->GetData()->m_sizeCurrent.x = rc.right - rc.left;
    w->GetData()->m_sizeCurrent.y = rc.bottom - rc.top;
}
#endif


Окно создаётся в конструкторе
Код

bqWindow::bqWindow(bqWindowCallback* cb)
{
#ifdef BQ_PLATFORM_WINDOWS
    // Выделю память для ОС данных
    bqWindowWin32* w32 = (bqWindowWin32*)bqMemory::malloc(sizeof(bqWindowWin32));
    // Сохраню адрес
    m_data.m_implementation = w32;

    // Обычные WinAPI действия
    WNDCLASSEXW wcex;
    wcex.cbSize = sizeof(WNDCLASSEX);
    wcex.style = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc = WndProc;
    wcex.cbClsExtra = 0;
    wcex.cbWndExtra = 0;
    wcex.hInstance = GetModuleHandle(0);
    wcex.hIcon = 0;
    wcex.hCursor = LoadCursor(0, IDC_ARROW);
    wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wcex.lpszMenuName = 0;

    // нужен уникальный lpszClassName
    static int windowCount = 0;
    wsprintf(w32->m_className, L"w%i", windowCount++);
    wcex.lpszClassName = w32->m_className;
    wcex.hIconSm = 0;
    RegisterClassExW(&wcex);

    w32->m_hWnd = CreateWindowExW(
        0,
        w32->m_className,
        L"bqWindow",
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        800,
        600,
        0,
        0,
        wcex.hInstance,
        this
    );

    // сохраню стиль окна
    w32->m_style = GetWindowLongPtr(w32->m_hWnd, GWL_STYLE);

    // получаю размер рамки
    int padding = GetSystemMetrics(SM_CXPADDEDBORDER);
    m_data.m_borderSize.x = GetSystemMetrics(SM_CXFRAME) + padding;
    m_data.m_borderSize.y = (GetSystemMetrics(SM_CYFRAME) + GetSystemMetrics(SM_CYCAPTION) + padding);
   
   bqWindowWin32_findCurrentSize(this, w32);

    SetWindowLongPtr(w32->m_hWnd, GWL_USERDATA, reinterpret_cast<LONG_PTR>(this));
#endif

    m_data.m_cb = cb;
}


Уничтожение окна
Код

bqWindow::~bqWindow()
{
    if (m_data.m_implementation)
    {
#ifdef BQ_PLATFORM_WINDOWS
        bqWindowWin32* w32 = (bqWindowWin32*)m_data.m_implementation;
        if (w32->m_hWnd)
        {
            DestroyWindow((HWND)w32->m_hWnd);
            UnregisterClass(w32->m_className, GetModuleHandle(0));
        }
#endif

        bqMemory::free(m_data.m_implementation);
    }
}


Остальные методы
Код

void bqWindow::SetTitle(const char* s)
{
    BQ_ASSERT_ST(s);
    BQ_ASSERT_ST(m_data.m_implementation);
#ifdef BQ_PLATFORM_WINDOWS
    bqWindowWin32* w32 = (bqWindowWin32*)m_data.m_implementation;
    if (w32->m_hWnd)
        SetWindowTextA(w32->m_hWnd, s);
#endif
}

void bqWindow::SetVisible(bool v)
{
    BQ_ASSERT_ST(m_data.m_implementation);
    m_data.m_isVisible = v;
#ifdef BQ_PLATFORM_WINDOWS
    bqWindowWin32* w32 = (bqWindowWin32*)m_data.m_implementation;
    if (w32->m_hWnd)
        ShowWindow(w32->m_hWnd, v ? SW_SHOW : SW_HIDE);
#endif
}

void bqWindow::Maximize()
{
    BQ_ASSERT_ST(m_data.m_implementation);
    m_data.m_isVisible = true;
#ifdef BQ_PLATFORM_WINDOWS
    bqWindowWin32* w32 = (bqWindowWin32*)m_data.m_implementation;
    ShowWindow(w32->m_hWnd, SW_MAXIMIZE);
#endif
}

void bqWindow::Minimize()
{
    BQ_ASSERT_ST(m_data.m_implementation);
    m_data.m_isVisible = false;
#ifdef BQ_PLATFORM_WINDOWS
    bqWindowWin32* w32 = (bqWindowWin32*)m_data.m_implementation;
    ShowWindow(w32->m_hWnd, SW_MINIMIZE);
#endif
}

void bqWindow::Restore()
{
    BQ_ASSERT_ST(m_data.m_implementation);
    m_data.m_isVisible = true;
#ifdef BQ_PLATFORM_WINDOWS
    bqWindowWin32* w32 = (bqWindowWin32*)m_data.m_implementation;
    ShowWindow(w32->m_hWnd, SW_RESTORE);
#endif
}

void bqWindow::SetBorderless(bool v)
{
    BQ_ASSERT_ST(m_data.m_implementation);

#ifdef BQ_PLATFORM_WINDOWS
    bqWindowWin32* w32 = (bqWindowWin32*)m_data.m_implementation;
    if (v)
        SetWindowLongPtr(w32->m_hWnd, GWL_STYLE, WS_POPUP);
    else
        SetWindowLongPtr(w32->m_hWnd, GWL_STYLE, w32->m_style);
    SetWindowPos(w32->m_hWnd, 0, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
    ShowWindow(w32->m_hWnd, SW_NORMAL);
#endif
}

void bqWindow::SetNoResize(bool v)
{
    BQ_ASSERT_ST(m_data.m_implementation);

#ifdef BQ_PLATFORM_WINDOWS
    bqWindowWin32* w32 = (bqWindowWin32*)m_data.m_implementation;
    if (v)
    {
        w32->m_style &= ~WS_THICKFRAME;
        w32->m_style &= ~WS_MAXIMIZEBOX;
    }
    else
        w32->m_style |= WS_THICKFRAME | WS_MAXIMIZEBOX;
    SetWindowLongPtr(w32->m_hWnd, GWL_STYLE, w32->m_style);
    SetWindowPos(w32->m_hWnd, 0, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
    ShowWindow(w32->m_hWnd, SW_NORMAL);
#endif
}

void bqWindow::SetNoMinimize(bool v)
{
    BQ_ASSERT_ST(m_data.m_implementation);

#ifdef BQ_PLATFORM_WINDOWS
    bqWindowWin32* w32 = (bqWindowWin32*)m_data.m_implementation;
    if (v)
    {
        w32->m_style &= ~WS_MINIMIZEBOX;
    }
    else
        w32->m_style |= WS_MINIMIZEBOX;
    SetWindowLongPtr(w32->m_hWnd, GWL_STYLE, w32->m_style);
    SetWindowPos(w32->m_hWnd, 0, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
    ShowWindow(w32->m_hWnd, SW_NORMAL);
#endif
}

void bqWindow::SetPositionAndSize(int x, int y, int sx, int sy)
{
    BQ_ASSERT_ST(m_data.m_implementation);
#ifdef BQ_PLATFORM_WINDOWS
    bqWindowWin32* w32 = (bqWindowWin32*)m_data.m_implementation;

    SetWindowPos(w32->m_hWnd, 0, x, y, sx, sy, 0);
    bqWindowWin32_findCurrentSize(this, w32);
#endif
}


Оконная процедура
Код

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    int wmId = LOWORD(wParam);
    bqWindow* pW = reinterpret_cast<bqWindow*>(GetWindowLongPtr(hWnd, GWL_USERDATA));

    switch (message)
    {
    case WM_ACTIVATE:
    {
        switch (wmId)
        {
        case WA_ACTIVE:
        case WA_CLICKACTIVE:
        {
            if (pW)
                pW->GetData()->m_cb->OnActivate(pW);
            return DefWindowProc(hWnd, message, wParam, lParam);
        }break;
        case WA_INACTIVE:
            if (pW)
                pW->GetData()->m_cb->OnDeactivate(pW);
            break;
        }
        break;
    }
    case WM_GETMINMAXINFO:
    {
        if (pW)
        {
            LPMINMAXINFO lpMMI = (LPMINMAXINFO)lParam;
            lpMMI->ptMinTrackSize.x = pW->GetSizeMinimum()->x;
            lpMMI->ptMinTrackSize.y = pW->GetSizeMinimum()->y;
        }
    }
    break;
    case WM_SIZE:
    {
        if (pW)
        {
            int wmId = LOWORD(wParam);
            switch (wmId)
            {
            case SIZE_MINIMIZED:
                pW->GetData()->m_cb->OnMinimize(pW);
                break;
            case SIZE_RESTORED:
                pW->GetData()->m_cb->OnRestore(pW);
                break;
            case SIZE_MAXIMIZED:
                pW->GetData()->m_cb->OnMaximize(pW);
                break;
            }

            bqWindowWin32_findCurrentSize(pW, (bqWindowWin32*)pW->GetData()->m_implementation);

            pW->GetData()->m_cb->OnSize(pW);
        }

    }return DefWindowProc(hWnd, message, wParam, lParam);
    case WM_SIZING:
    {
        if (pW)
        {
            bqWindowWin32_findCurrentSize(pW, (bqWindowWin32*)pW->GetData()->m_implementation);

            pW->GetData()->m_cb->OnSizing(pW);
        }
    }break;

    case WM_ERASEBKGND:
    case WM_PAINT:
    {
        if (pW)
            pW->GetData()->m_cb->OnDraw(pW);
    }break;
    case WM_MOVE:
    {
        if (pW)
            pW->GetData()->m_cb->OnMove(pW);
    }break;
   
    case WM_CLOSE:
        if (pW)
            pW->GetData()->m_cb->OnClose(pW);
        return 0;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return DefWindowProc(hWnd, message, wParam, lParam);
}


Update метод у bqFramework
Код

void bqFramework::Update()
{
    BQ_ASSERT_ST(g_framework);

#ifdef BQ_PLATFORM_WINDOWS
    // без этого окно не будет реагировать
    MSG msg;
    while (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE))
    {
  GetMessage(&msg, NULL, 0, 0);
  TranslateMessage(&msg);
  DispatchMessage(&msg);
    }
#endif
}


Создание окна у bqFramework
Код

bqWindow* bqFramework::SummonWindow(bqWindowCallback* cb)
{
    BQ_ASSERT_ST(g_framework);
    BQ_ASSERT_ST(cb);
    return new bqWindow(cb);
}


Всё. Тестовая программа
Код

#include "badcoiq.h"
#include "badcoiq/system/bqWindow.h"

BQ_LINK_LIBRARY("badcoiq");

bool g_run = true;

class bqFrameworkCallbackCB : public bqFrameworkCallback
{
public:
    bqFrameworkCallbackCB() {}
    virtual ~bqFrameworkCallbackCB() {}
    virtual void OnMessage() {}
};

class bqWindowCallbackCB : public bqWindowCallback
{
public:
    bqWindowCallbackCB() {}
    virtual ~bqWindowCallbackCB() {}

    virtual void OnClose(bqWindow*)
    {
        printf("Q\n");
        g_run = false;
    }
};

int main()
{
    bqFrameworkCallbackCB fcb;
    bqFramework::Start(&fcb);

    bqWindowCallbackCB wcb;
    auto window = bqFramework::SummonWindow(&wcb);
    if (window)
    {
  window->SetPositionAndSize(10, 10, 300, 200);
        window->SetVisible(true);

        while (g_run)
        {
            bqFramework::Update();
        }

        delete window;
    }
    
    return EXIT_SUCCESS;
}


Надо сделать Fullscreen


github

Добавлено (06 Июля 2023, 18:45)
---------------------------------------------
Delta time
Вычисление Delta Time реализую в bqFramework::Update

Код

static clock_t then = 0;
clock_t now = clock();

g_framework->m_deltaTime = (float)(now - then) / CLOCKS_PER_SEC;
then = now;


для clock_t надо подключить <time.h>

User Data
Бывает объектам нужно добавить поля GetUserData SetUserData
Добавлю базовый класс.

inc/badcoiq/common/bqUserData.h
Код

// Если для какого-то класса нужен user data, то можно наследовать этот класс
class bqUserData
{
    void* m_data = 0;
public:
    bqUserData() {}
    virtual ~bqUserData() {}

    virtual void* GetUserData() { return m_data; }
    virtual void SetUserData(void* d) { m_data = d; }
};


github

Добавлено (07 Июля 2023, 22:40)
---------------------------------------------
Математика

Хочу использовать заранее вычисленные значения для sin cos atan2 и т.д.
Нецелесообразно вычислять большой диапазон. Функции принимают значения например от -1 до +1, от -Пи до +Пи и т.д.

Добавлю файл
inc/badcoiq/math/bqMath.h

Код

class bqMath
{
public:
    static float DegToRad(float degrees);
    static double DegToRad(double degrees);
    static float RadToDeg(float radians);
    static double RadToDeg(double radians);

    static float Abs(float);
    static double Abs(double);
    static int32_t Abs(int32_t);

    // Expect value form -1 to +1
    static float Acos(float);
    static double Acos(double);

    // Expect value form -1 to +1
    static float Asin(float);
    static double Asin(double);

    // Expect value form -2 to +2
    static float Atan(float);
    static double Atan(double);

    // Expect value form -1 to +1
    static float Atan2(float y, float x);
    static double Atan2(double y, double x);

    static float Clamp(float, float mn, float mx);
    static double Clamp(double, double mn, double mx);

    // Expect value from -PI to +PI
    // It will do this
    // if (v > 3.141f) v = 3.141f;
    // if (v < -3.141f) v = -3.141f;
    static float Cos(float);
    static double Cos(double);
    static float Sin(float);
    static double Sin(double);
    static float Tan(float);
    static double Tan(double);

    // wikipedia:
    // Imprecise method, which does not guarantee v = v1 when t = 1, due to floating-point arithmetic error.
    // This method is monotonic. This form may be used when the hardware has a native fused multiply-add instruction.
    //
    // типа, Lerp1 не гарантирует что x станет равным y (...Lerp2 тоже)
    //  ... имеет 1 умножение. Lerp2 имеет 2 умножения.
    static float Lerp1(float x, float y, float t);
    static double Lerp1(double x, double y, double t);

    // wikipedia:
    // Precise method, which guarantees v = v1 when t = 1. This method is monotonic only when v0 * v1 < 0.
    // Lerping between same values might not produce the same value
    static float Lerp2(float x, float y, float t);
    static double Lerp2(double x, double y, double t);

    // Находится ли точка в прямоугольной области
    static bool PointInRect(const bqPoint&, const bqRect&);
};


Опять класс со статическими методами(...очень удобно).
Постепенно будут добавлены множество других методов когда появятся вектор, матрица, кватернион.

Некоторая реализация
src/badcoiq/math/bqMath.cpp
Код

#include "badcoiq.h"
#include "badcoiq/math/bqMath.h"

#include "math_private.h"

// Сгенерированные результаты функций:

static float g_acosf[] = {
#include "acosf.inl"
};
static double g_acos[] = {
#include "acos.inl"
};
...


Как работает
Код

float bqMath::Cos(float v)
{
    if (v < -3.141f) v = -3.141f;
    if (v < 0.f) v = bqMath::Abs(v);
    if (v > 3.141f) v = 3.141f;

    uint32_t ind = (uint32_t)roundf(v * 1000.0f);

    if (ind < 3143)
  return g_cosf[ind];
    return 0;
}


Пока не особо тестировал. Я замутил это ради скорости.

Вектр

2D, 3D и 4D вектора. Правильнее называть 2х компонентный и т.д.
Используется в том числе как координата.

2D использует 2 значения - x и y
3D - x y z
4D - x y z w

В векторе не должно быть других данных. Его размер в байтах должен быть равен размеру компонентов в сумме.

Вот типичный 4D вектр


В bqForward.h добавил
Код

template<typename T>
class bqVec2_t;
template<typename T>
class bqVec3_t;
template<typename T>
class bqVec4_t;
template<typename T>
class bqMatrix4_t;

using bqVec2  = bqVec2_t<bqReal>;
using bqVec2f = bqVec2_t<float>;
using bqVec2i = bqVec2_t<int32_t>;
using bqVec3  = bqVec3_t<bqReal>;
using bqVec3f = bqVec3_t<float>;
using bqVec3i = bqVec3_t<int32_t>;
using bqVec4  = bqVec4_t<bqReal>;
using bqVec4f = bqVec4_t<float>;
using bqVec4i = bqVec4_t<int32_t>;
using bqMat4  = bqMatrix4_t<bqReal>;


так-же некоторые функции для векторов добавил в класс bqMath, чтобы не нагружать bqVector.h

Матрица
Матрица для 3D мира это четыре 4D вектора

Код

vec4 matrix[4];

Ещё называют матрица 4на4

Есть матрица 3на3
Матрица 3на3 является как бы частью матрицы 4на4
Та часть что 3 на 3 это базис. Он отвечает за вращение.
Раньше делали игры на матрицах 3на3. ХЗ как сейчас, везде 4 на 4.
Необходима позиция, например, объект на сцене перемещается изменяя позицию. В матрице 4на4 указывается эта позиция. А при использовании матрицы 3на3 видимо посылали позицию отдельно, я честно не изучал это.

Напримр мы имеем
Код

vec4 matrix[4];


четырёх компонентный вектр, четыре раза

матрица выглядит так
Код

[x][y][z][w]
[x][y][z][w]
[x][y][z][w]
[x][y][z][w]


и в памяти он должен занимать это так-же, вот почему вектр должен НЕ содержать ничего лишнего.

Вот эта часть (помечено единичками) отвечает за положение точки в пространстве, ориентация, всмысле вращение относительно центра, так-же там находится так называемый масштаб
Код

[1][1][1][0]
[1][1][1][0]
[1][1][1][0]
[0][0][0][0]

то есть
Код

matrix[0]
matrix[1]
matrix[2]


Эта часть отвечает за позицию в 3D мире. Как бы, сначала повернули (значениями выше), потом отодвинули на нужное место.
Код

[0][0][0][0]
[0][0][0][0]
[0][0][0][0]
[1][1][1][0]

то есть
Код

matrix[3]


Ещё в некоторых не очень популярных реализациях устанавливают значение позиции в эти места
Код

[0][0][0][1]
[0][0][0][1]
[0][0][0][1]
[0][0][0][0]

то есть
Код

matrix[0].w
matrix[1].w
matrix[2].w

неудобно. Используем первый вариант.

За масштаб отвечают эти значения
Код

[1][0][0][0]
[0][1][0][0]
[0][0][1][0]
[0][0][0][0]

Если поставить 2 то моделька увеличиться\координата сдвинеться.
Если 0.5 то уменьшится\сдвинеться в обратную сторону.
Если там будут нули и мы рисуем модель то модельки не будет видно.

В углу должна стоять единица, для математической магии.
Код

[0][0][0][0]
[0][0][0][0]
[0][0][0][0]
[0][0][0][1]


Простая матрица должна выглядеть вот так
Код

[1][0][0][0]
[0][1][0][0]
[0][0][1][0]
[0][0][0][1]


Для рисования объекта в 3D пространстве нужна матрица этого объекта.
Её принято называть World матрица.
Достаточно минимальной матрицы
Код

[1][0][0][0]
[0][1][0][0]
[0][0][1][0]
[0][0][0][1]


Для того чтобы вращать объект, масштабировать и перемещать, можно создать отдельные матрицы
Код

Matrix4x4 Scale = Matrix4x4(
1,0,0,0,
0,1,0,0,
0,0,1,0,
0,0,0,1
);

Matrix4x4 Rotation = CreateRotationMatrix(углы поворота);

Matrix4x4 Position = Matrix4x4(
0,0,0,0,
0,0,0,0,
0,0,0,0,
x,y,z,1
);

Matrix4x4 World = Position * Rotation * Scale;
// справа на лево - масштабируем, вращаем, и устанавливаем на позицию.


Матрица нужна для преобразования 3D координаты...
например позиция [132.25][2556.23][1.52]
...в 2D координаты куда идёт рисование (напрямую на экран, или в текстуру)...назавём "холст"
а координатная система холста от -1 до +1

Позиция умножается на матрицу - получается нужное значение.

Нужно создать удобный класс.
Код

// кратко
template<typename T>
class bqMatrix4_t
{
public:
    bqMatrix4_t() { Identity(); }
    
    bqMatrix4_t(const bqQuaternion& q)
    {
  Identity();
  SetRotation(q);
    }
    
    void Identity() {
  auto* p = this->Data();
  p[0] = static_cast<T>(1.f);
  p[1] = static_cast<T>(0.f);
  p[2] = static_cast<T>(0.f);
  p[3] = static_cast<T>(0.f);

  p[4] = static_cast<T>(0.f);
  p[5] = static_cast<T>(1.f);
  p[6] = static_cast<T>(0.f);
  p[7] = static_cast<T>(0.f);

  p[8] = static_cast<T>(0.f);
  p[9] = static_cast<T>(0.f);
  p[10] = static_cast<T>(1.f);
  p[11] = static_cast<T>(0.f);

  p[12] = static_cast<T>(0.f);
  p[13] = static_cast<T>(0.f);
  p[14] = static_cast<T>(0.f);
  p[15] = static_cast<T>(1.f);
    }
    
    void SetRotation(const bqQuaternion& q)
    {
  T d = q.Length();
  T s = 2.0f / d;
  T xs = q.x * s, ys = q.y * s, zs = q.z * s;
  T wx = q.w * xs, wy = q.w * ys, wz = q.w * zs;
  T xx = q.x * xs, xy = q.x * ys, xz = q.x * zs;
  T yy = q.y * ys, yz = q.y * zs, zz = q.z * zs;
  Set(
   1.0f - (yy + zz), xy - wz, xz + wy,
   xy + wz, 1.0f - (xx + zz), yz - wx,
   xz - wy, yz + wx, 1.0f - (xx + yy));
    }

.......
    
    bqVec4_t<T> m_data[4];
    T* Data() { return &m_data[0].x; }
};


Кватернион

Что такое кватернион не объяснить, ибо это высшая математика, комплексное число. Читайте википедию.

Простыми словами, кватернион хранит вращение, наверно более корректно - ориентацию.
Указываем углы, получаем кватернион, преобразуем в матрицу вращения.
Вращать объект кватернионом можно в любое направление.
Главное я покажу в будущем, во "Вращение объектов"

В коде кватернион это четыре float
Код

class bqQuaternion
{
public:
    bqQuaternion() {
  Identity();
    }
...
void Set(float _x, float _y, float _z)
    {
  _x *= 0.5f;
  _y *= 0.5f;
  _z *= 0.5f;
  float c1 = cos(_x);
  float c2 = cos(_y);
  float c3 = cos(_z);
  float s1 = sin(_x);
  float s2 = sin(_y);
  float s3 = sin(_z);
  w = (c1 * c2 * c3) + (s1 * s2 * s3);
  x = (s1 * c2 * c3) - (c1 * s2 * s3);
  y = (c1 * s2 * c3) + (s1 * c2 * s3);
  z = (c1 * c2 * s3) - (s1 * s2 * c3);
    }
...
    float x, y, z, w;
    float* Data() { return &x; }
};


Добавлено (07 Июля 2023, 22:49)
---------------------------------------------
Кватернионы можно интерполировать. Обычно такая функция называется Slerp

на форуме тупизм, невозможно продолжить публиковать сообщения пока кто-то другой не напишет
статейки пишутся в текстовый документ, будут опубликованы позже, тут или где-то ещё.

github


"Do you think we can fly?
Well, I do."


Сообщение отредактировал BADCOIQ - Суббота, 08 Июля 2023, 15:12
BADCOIQДата: Вторник, 04 Июля 2023, 19:46 | Сообщение # 2 | Тема: Разработка движка (детальное описание для новичков) [C++]
Отец-основатель BADCOIQ Corporation © 2010
Сейчас нет на сайте
Цитата afq ()
этот форум стал каким-то токсичным

Это везде так. Было и будет.
Пока одни ноют, люди пишут. В итоге, когда у одного человека написано, у другого нихера нет кроме накопленного флуда.

Эти сообщения уже давно пролетают мимо.

Некоторые потом заходят с другого аккаунта пишут "Чувак ты крут."


"Do you think we can fly?
Well, I do."
BADCOIQДата: Вторник, 04 Июля 2023, 18:46 | Сообщение # 3 | Тема: Разработка движка (детальное описание для новичков) [C++]
Отец-основатель BADCOIQ Corporation © 2010
Сейчас нет на сайте
Цитата Storm54 ()
А то тут PhysX

И хрен с ними.

Механика - элементарно. Я говорил про раздел физики.
Математикой владеем.


"Do you think we can fly?
Well, I do."
BADCOIQДата: Вторник, 04 Июля 2023, 18:23 | Сообщение # 4 | Тема: Разработка движка (детальное описание для новичков) [C++]
Отец-основатель BADCOIQ Corporation © 2010
Сейчас нет на сайте
afq, абсолютно согласен.

Игрострой это не только сюжет, дизайн и т.д. Это ещё и технологии, то что скрыто под капотом.

Ну вот если вспомнить детство, во что играли, денди, сега, кто-то играл на более древних консолях. Я лично всегда задавался вопросом "как сделано то, как сделано это".

Когда ковыряешься в этих вещах, приходят мысли "с этой вещью можно сделать то-то". Изучаешь всё это, и рамки, границы возможностей расширяются.

Моя граница упёрлась в физику. Всё сделано по списку, осталось только она. Я хочу переписать всё, документируя что возможно, и написать свою физику. И я опишу начальные шаги, и все свои ошибки, и 100% всё сделаю, с оптимизациями. Книжку прочитал, оказывается это элементарщина. Будет интересно реализовать.

Как только будет реализовано (всё по списку), буду делать игрульку. Ресурсы есть. Главное не напрягаться и заниматься тем что нравится.


"Do you think we can fly?
Well, I do."
BADCOIQДата: Понедельник, 03 Июля 2023, 20:14 | Сообщение # 5 | Тема: Разработка движка (детальное описание для новичков) [C++]
Отец-основатель BADCOIQ Corporation © 2010
Сейчас нет на сайте
Все те действия что были описаны выше НЕОБХОДИМЫ.
Ошибки надо показывать, для этого нужна своя строка (ибо херня из STL однажды может дать 0, где нибудь в конвертации юникода). Строку нужно написать чтобы удовлетворяла, например чтобы тупо можно было добавить число. Для строки нужны функции для работы с памятью.

Это минимум который должен быть.

Перед тем как продолжить, надо добавить ещё один .h файл
inc/badcoiq/common/bqForward.h

Там будут forward declarations.
Код

class bqWindow;
class bqWindowCallback;


bqForward.h так-же должен быть указан в badcoiq.h

Что делает forward declarations?
Если мы делаем класс и в нём есть объект
Код

class CClass{
...
    MyObject m_object;
};

То компилятору нужно знать сколько памяти выделить для этого объекта. Компилятор должен видеть описание MyObject.
Можно вставить #include, но в итоге время компиляции увеличится. Давно я делал ДВИЖЁК где все include были в одном файле. Это плохой подход.

Нужно использовать указатель.
Код

class MyObject; //forward declaration
class CClass{
...
    MyObject* m_object = 0;
};

Компилятор знает размер указателя и проблем не будет. Нужно всего лишь вставить forward declaration - добавить class ИмяКласса.

В будущем все классы которые не будут входить в минимальный набор (указанный в badcoiq.h) будут добавляться в forward declaration.

Теперь ДВИЖЁК

Framework

Будет внутренний класс, в котором будут находиться все объекты необходимые для работы ДВИЖКА.
Управление фреймворком будет происходить через класс со статическими методами

+
inc/badcoiq/framework/bqFramework.h

Код

// Предполагаю что фреймворк должен посылать какие-то сообщения.
// Я это ещё не реализовал
class bqFrameworkCallback
{
public:
    bqFrameworkCallback() {}
    virtual ~bqFrameworkCallback() {}

    virtual void OnMessage() = 0;
};

// API для фреймворка.
class bqFramework
{
public:
    static void Start(bqFrameworkCallback*);
    static void Stop();
    static void Update();

    static float* GetDeltaTime();
    static bqWindow* SummonWindow(bqWindowCallback*);
};


Когда нам нужно запустить ДВИЖЁК, вызываем Start. Он выделит память для внутреннего класса. Stop для завершения работы, он освободит ту память.
Update - например, он вычислит DeltaTime, обновит состояния ввода, GUI, окна.
Delta Time - время между двумя вызывами Update. Update надо вызывать каждую итерацию главного цикла. Соответственно, это время затраченное на одну итерацию(так-же можно сказать время на рисование 1го кадра).
SummonWindow - создать окно. Имя CreateWindow занято WinAPI, как и многие другие. Я решил использовать слово Summon(призвать) вместо Create(создать).

Теперь реализация.
src/badcoiq/framework/bqFramework.cpp

Класс который будет хранить всё необходимое.
Код

class bqFrameworkImpl
{
public:
    bqFrameworkImpl() {}
    ~bqFrameworkImpl() {}

    float m_deltaTime = 0.f;
    bqFrameworkCallback* m_frameworkCallback = 0;
    
    BQ_PLACEMENT_ALLOCATOR(bqFrameworkImpl);
};


Добавлю глобальную переменную.
Код

bqFrameworkImpl* g_framework = 0;


Реализовываю статические методы bqFramework
Код

// Выделяю память
void bqFramework::Start(bqFrameworkCallback* cb)
{
    BQ_ASSERT_ST(cb);
    BQ_ASSERT_ST(!g_framework);
    if (!g_framework)
    {
  g_framework = new bqFrameworkImpl();
    }
}

// Освобождаю
void bqFramework::Stop()
{
    BQ_ASSERT_ST(g_framework);
    if (g_framework)
    {
  delete g_framework;
  g_framework = 0;
    }
}

void bqFramework::Update()
{
    BQ_ASSERT_ST(g_framework);
}

float* bqFramework::GetDeltaTime()
{
    BQ_ASSERT_ST(g_framework);
    return &g_framework->m_deltaTime;
}

bqWindow* bqFramework::SummonWindow(bqWindowCallback* cb)
{
    BQ_ASSERT_ST(g_framework);
    BQ_ASSERT_ST(cb);
    return 0;
}


Есть шанс что Stop не будет вызван. Надо добавить класс который при закрытии программы в деструкторе вызовет Stop
Код

class bqFrameworkDestroyer
{
public:
    bqFrameworkDestroyer() {}
    ~bqFrameworkDestroyer()
    {
  if (g_framework)
   bqFramework::Stop();
    }
};
// При закрытии программы этот объект будет уничтожен - будет вызван деструктор
//   в котором будет вызван bqFramework::Stop();
bqFrameworkDestroyer g_frameworkDestroyer;


Ну вот и всё.
Далее необходимо создать окно, и реализовать Update

github


"Do you think we can fly?
Well, I do."
BADCOIQДата: Воскресенье, 02 Июля 2023, 15:56 | Сообщение # 6 | Тема: Разработка движка (детальное описание для новичков) [C++]
Отец-основатель BADCOIQ Corporation © 2010
Сейчас нет на сайте
Строки

В своём assert я хотел иметь возможность использовать дефолтную Visual Studio функцию _wassert, но она требует wchar_t строку.
Когда-то я замарочился и сделал 100% рабочий конвертер UTF-8\16\32
Я подумал и решил, что не нужен мне сам конвертер. Мне нужна UTF-32 строка.
Эта строка должна иметь методы для конвертации.

Эту строку лучше использовать для текста который мы будем рисовать.

Для внутреннего использования необходимы более простые строки.
Начну с них.

Полностью описывать классы будет очень долго. В коде есть комментарии.
В общем всё элементарно.
Можно объяснить основные принципы.

Класс строки содержит массив символов + 1 символ для завершающего нуля.
При создании объекта надо заранее выделить немного памяти.
При добавлении символов, если необходимо, перевыделяем память.
Код

void _reallocate(size_t new_allocated)
{
    // выделение новой памяти, копирование содержимого, освобождение старой, сохранение нового указателя
    char_type* new_data = (char_type*)bqMemory::malloc(new_allocated * sizeof(char_type));
    if (m_data)
    {
  memcpy(new_data, m_data, m_size * sizeof(char_type));
  bqMemory::free(m_data);
    }
    else
    {
  memset(new_data, 0, new_allocated);
    }
    m_data = new_data;
    m_allocated = new_allocated;
}


Главное реализовать функцию push_back.
Все остальные функции (append assign operator=) и конструкторы в итоге будут вызывать push_back.
Код

template<typename other_type>
bqString_base(const other_type* str)
{
    _reallocate(m_allocated);
    assign(str); // просто присвоение
}
...
// присвоить содержимое другой строки
template<typename other_type>
void assign(other_type str)
{
    clear();
    if (str)
  append(str);
}
...
template<typename other_type>
void append(const other_type* str)
{
    append(str, bqStringLen(str));
}
...
// добавить в конец
template<typename other_type>
void append(const other_type* str, size_t l)
{
    for (size_t i = 0; i < l; ++i)
    {
  push_back((char_type)str[i]);
    }
}
...
// добавить в конец символ
void push_back(char_type c)
{
    size_t new_size = m_size + 1u;
    if ((new_size + 1u) > m_allocated)
  _reallocate((new_size + 1u) + (1 + (uint32_t)(m_size * 0.5f)));
    m_data[m_size] = c;
    m_size = new_size;
    m_data[m_size] = 0;
}


Очень необходимо реализовать добавление чисел. Например float:
Код

void append(float f)
{
    char buf[32];
    int nob = sprintf_s(buf, 32, "%.4f", f);
    if (nob)
    {
  for (int i = 0; i < nob; ++i)
  {
   push_back((char_type)buf[i]);
  }
    }
}


Полный код шаблонной строки не влезает в сообщение.
Реализовано куча полезных методов.


В конце были созданы type alias: bqStringW и bqStringA

Теперь Unicode строка.
Класс огромный, и будет иметь реализацию в .cpp файле.
Используется тип char32_t


Теперь реализация
src/badcoiq/string/bqString.cpp

Для конвертации в UTF-8 и UTF-16 LE я использую таблицу, заранее вычисленную.
Код

struct slUnicodeCharNode
{
    uint32_t m_utf8; // код для UTF-8
    uint32_t m_utf16;// код для UTF-16
};
static slUnicodeCharNode g_UnicodeChars[0x32000] =
{
#include "UnicodeChars.inl" // коды здесь
};


Добавление char* и wchar_t* строк. По сути конвертация UTF-8 и UTF-16 в UTF-32
Код

// UTF-8 to UTF-32
void bqString::append(const char* str)
{
    size_t len = bqStringLen(str);

    unsigned char c1 = 0;
    unsigned char c2 = 0;
    unsigned char c3 = 0;
    unsigned char c4 = 0;
    for (size_t i = 0; i < len; ++i)
    {
  c1 = str[i];

  if (c1 <= 0x7F)
  {
   push_back((char32_t)c1);
  }
  else
  {
   if ((c1 & 0xE0) == 0xC0) //2 bytes
   {
    ++i;
    if (i < len)
    {
     c2 = str[i];
     if ((c2 & 0xC0) == 0x80)
     {
      char16_t wch = (c1 & 0x1F) << 6;
      wch |= (c2 & 0x3F);
      push_back((char32_t)wch);
     }
    }
   }
   else if ((c1 & 0xF0) == 0xE0) //3
   {
    ++i;
    if (i < len)
    {
     c2 = str[i];
     if ((c2 & 0xC0) == 0x80)
     {
      ++i;
      if (i < len)
      {
       c3 = str[i];
       if ((c3 & 0xC0) == 0x80)
       {
        char16_t wch = (c1 & 0xF) << 12;
        wch |= (c2 & 0x3F) << 6;
        wch |= (c3 & 0x3F);
        push_back((char32_t)wch);
       }
      }
     }
    }
   }
   else if ((c1 & 0xF8) == 0xF0) //4
   {
    ++i;
    if (i < len)
    {
     c2 = str[i];
     if ((c2 & 0xC0) == 0x80)
     {
      ++i;
      if (i < len)
      {
       c3 = str[i];
       if ((c3 & 0xC0) == 0x80)
       {
        ++i;
        if (i < len)
        {
         c4 = str[i];
         if ((c4 & 0xC0) == 0x80)
         {
                              uint32_t u = (c1 & 0x7) << 18;
                              u |= (c2 & 0x3F) << 12;
                              u |= (c3 & 0x3F) << 6;
                              u |= (c4 & 0x3F);

                              push_back((char32_t)u);
         }
        }
       }
      }
     }
    }
   }
  }
    }
}

// UTF-16 to UTF-32
void bqString::append(const wchar_t* str)
{
    size_t len = bqStringLen(str);
    for (size_t i = 0u; i < len; ++i)
    {
  char16_t ch16 = str[i];
  if (ch16 < 0x80)
  {
   push_back((char32_t)ch16);
  }
  else if (ch16 < 0x800)
  {
   push_back((char32_t)ch16); //????
  }
  else if ((ch16 & 0xFC00) == 0xD800)
  {
   ++i;
   if (i < len)
   {
    char16_t ch16_2 = str[i];
    if ((ch16_2 & 0xFC00) == 0xDC00)
    {
     uint32_t u = (ch16 - 0xD800) * 0x400;
     u += (ch16_2 - 0xDC00) + 0x10000;

     push_back((char32_t)u);
    }
   }
  }
  else
  {
   push_back((char32_t)ch16);
  }
    }
}


Конвертация в UTF-8 и UTF-16
Код

// для to_utf8 to_utf16.
union UC
{
    uint8_t bytes[4];
    uint16_t shorts[2];
    uint32_t integer;
};
...
void bqString::to_utf8(bqStringA& stra) const
{
    stra.clear();
    UC uc;
    auto ut = &g_UnicodeChars[0];
    for (size_t i = 0; i < m_size; ++i)
    {
  char32_t c = m_data[i];
  if (c >= 0x32000)
   c = '?';

  uc.integer = ut[c].m_utf8;

  if (uc.bytes[3]) stra.push_back(uc.bytes[3]);
  if (uc.bytes[2]) stra.push_back(uc.bytes[2]);
  if (uc.bytes[1]) stra.push_back(uc.bytes[1]);
  if (uc.bytes[0]) stra.push_back(uc.bytes[0]);
    }
}

void bqString::to_utf16(bqStringW& strw) const
{
    strw.clear();
    UC uc;
    auto ut = &g_UnicodeChars[0];
    for (size_t i = 0; i < m_size; ++i)
    {
  char32_t c = m_data[i];
  if (c >= 0x32000)
   c = '?';

  uc.integer = ut[c].m_utf16;

  if (uc.shorts[1]) strw.push_back(uc.shorts[1]);
  if (uc.shorts[0]) strw.push_back(uc.shorts[0]);
    }
}


Полный код не влезает в сообщение. В общем-то там много дублирования простой строки. Ещё есть дополнительные методы типа to_upper.

github

Добавлено (02 Июля 2023, 17:51)
---------------------------------------------
Stacktracer
Stacktracer покажет текущее место выполнения программы.

Например:

Код

C:\Code\badcoiq\src\badcoiq\system\bqStacktracer.cpp(190):'bqStackTracerImpl::GetStackTrace'
C:\Code\badcoiq\src\badcoiq\system\bqStacktracer.cpp(200):'bqStackTracer::Print'
C:\Code\badcoiqConsoleApplication1\badcoiqConsoleApplication1.cpp(9):'main'
D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl(78):'invoke_main'
D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl(288):'__scrt_common_main_seh'
D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl(331):'__scrt_common_main'
D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_main.cpp(17):'mainCRTStartup'
(null)(0):'BaseThreadInitThunk'
(null)(0):'RtlGetAppContainerNamedObjectPath'
(null)(0):'RtlGetAppContainerNamedObjectPath'
(null)(0):'(unknown)'


Обычно используется во время ошибки.

Почему-то корректно работает только со статическими библиотеками.

inc/badcoiq/system/bqStacktracer.h

Код

class bqStackTracer
{
public:
    static void Print();
};


Реализация
src/badcoiq/system/bqStacktracer.cpp

Код взят из Havok
Постарался чуть изменить. В общем ничего особенного нет в плане авторского права, одни вызовы системных функций.

Грузим системные библиотеки, получаем указатели на функции, вызываем эти функции.
Детали слишком специфичны для ОС, не буду их описывать. Если почитать MSDN то всё станет понятно.

Код

#include "badcoiq.h"

#ifdef BQ_PLATFORM_WINDOWS
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <dbghelp.h>
typedef BOOL(__stdcall* tSymInitialize)(HANDLE hProcess, PSTR UserSearchPath, BOOL fInvadeProcess);
typedef BOOL(__stdcall* tSymCleanup)(HANDLE hProcess);
typedef DWORD(__stdcall* tSymGetOptions)();
typedef DWORD(__stdcall* tSymSetOptions)(DWORD SymOptions);
typedef BOOL(__stdcall* tSymFromAddr)(HANDLE hProcess, DWORD64 dwAddr, PDWORD64 pdwDisplacement, PSYMBOL_INFO Symbol);
typedef BOOL(__stdcall* tSymGetLineFromAddr64)(HANDLE hProcess, DWORD64 dwAddr, PDWORD64 pdwDisplacement, PIMAGEHLP_LINE64 Line);
typedef void(__stdcall* tRtlCaptureContext)(PCONTEXT context);
typedef USHORT(__stdcall* tRtlCaptureStackBackTrace)(ULONG FramesToSkip, ULONG FramesToCapture, PVOID* BackTrace, PULONG BackTraceHash);
#endif

class bqStackTracerImpl
{
    void _init();
public:
    bqStackTracerImpl();
    ~bqStackTracerImpl();

    void DumpStackTrace(const size_t* trace, int numtrace);
    int  GetStackTrace(size_t* trace, int maxtrace, int framesToSkip = 0);

    CRITICAL_SECTION m_lock;
    bool m_lockInitialized = false;
    bool m_initialized = false;
    HINSTANCE m_DbgHelpDll = 0;
    HINSTANCE m_Kernel32Dll = 0;
    tSymInitialize m_SymInitialize = 0;
    tSymCleanup m_SymCleanup = 0;
    tSymGetOptions m_SymGetOptions = 0;
    tSymSetOptions m_SymSetOptions = 0;
    tSymFromAddr m_SymFromAddr = 0;
    tSymGetLineFromAddr64 m_SymGetLineFromAddr64 = 0;
    tRtlCaptureContext m_RtlCaptureContext = 0;
    tRtlCaptureStackBackTrace m_RtlCaptureStackBackTrace = 0;
};

#define LOAD_FUNCTION(A) if(1) { m_##A = (t##A) GetProcAddress(m_DbgHelpDll, #A); } else
#define LOAD_FUNCTION_WARN(A) if(1) { m_##A = (t##A) GetProcAddress(m_DbgHelpDll, #A); NATIVE_WARN_IF( m_##A == NULL, "Could not load symbol " #A " from dbghelp.dll, version too old, but will continue without it."); } else

bqStackTracerImpl::bqStackTracerImpl()
{
    InitializeCriticalSection(&m_lock);
    m_lockInitialized = true;
}

bqStackTracerImpl::~bqStackTracerImpl()
{
    m_lockInitialized = false;
    DeleteCriticalSection(&m_lock);
    m_SymCleanup(GetCurrentProcess());
    if (m_DbgHelpDll)
  FreeLibrary(m_DbgHelpDll);
    if (m_Kernel32Dll)
  FreeLibrary(m_Kernel32Dll);
}

void bqStackTracerImpl::_init()
{
    if (!m_initialized)
    {
  EnterCriticalSection(&m_lock);
  if (!m_initialized)
  {
   m_DbgHelpDll = LoadLibraryA("dbghelp.dll");
   if (m_DbgHelpDll)
   {
    m_Kernel32Dll = LoadLibraryA("kernel32.dll");
    if (m_Kernel32Dll)
    {
     LOAD_FUNCTION(SymInitialize);
     LOAD_FUNCTION(SymCleanup);
     LOAD_FUNCTION(SymGetOptions);
     LOAD_FUNCTION(SymSetOptions);
     LOAD_FUNCTION(SymFromAddr);
     LOAD_FUNCTION(SymGetLineFromAddr64);

     m_RtlCaptureContext = (tRtlCaptureContext)GetProcAddress(m_Kernel32Dll, "RtlCaptureContext");
     m_RtlCaptureStackBackTrace = (tRtlCaptureStackBackTrace)GetProcAddress(m_Kernel32Dll, "RtlCaptureStackBackTrace");

     DWORD symOptions = m_SymGetOptions();
     symOptions |= SYMOPT_LOAD_LINES | SYMOPT_DEBUG | SYMOPT_DEFERRED_LOADS;
     m_SymSetOptions(symOptions);

     UINT prevErrMode = GetErrorMode();
     SetErrorMode(SEM_FAILCRITICALERRORS);
     BOOL initsymbols = m_SymInitialize(GetCurrentProcess(), NULL, TRUE);
     if (initsymbols == TRUE)
     {
      SetErrorMode(prevErrMode);
      m_initialized = true;
     }
    }
   }
  }
  LeaveCriticalSection(&m_lock);
    }

    if (!m_initialized)
    {
  if(m_DbgHelpDll)
   FreeLibrary(m_DbgHelpDll);
  if(m_Kernel32Dll)
   FreeLibrary(m_Kernel32Dll);
  m_DbgHelpDll = 0;
  m_Kernel32Dll = 0;
    }
}

void bqStackTracerImpl::DumpStackTrace(const size_t* trace, int maxtrace)
{
    _init();

    SYMBOL_INFO_PACKAGE sym;
    sym.si.SizeOfStruct = sizeof(sym.si);
    sym.si.MaxNameLen = sizeof(sym.name);

    HANDLE curproc = GetCurrentProcess();

    for (int i = 0; i < maxtrace; ++i)
    {
  uint64_t symaddress = trace[i];

  DWORD64 displacement = 0;
  BOOL ok_symbol = m_SymFromAddr(
   curproc,
   DWORD64(symaddress),
   &displacement,
   &sym.si);

  if (!ok_symbol)
   ::strcpy_s(sym.si.Name, 2000, "(unknown)");
  IMAGEHLP_LINE64 line;
  ::memset(&line, 0, sizeof(line));
  line.SizeOfStruct = sizeof(line);

  m_SymGetLineFromAddr64(
   curproc,
   DWORD64(symaddress),
   &displacement,
   &line);

  char trace[2048];
  _snprintf_s(trace, 2048, "%s(%i):'%s'\n", line.FileName, line.LineNumber, sym.si.Name);
  printf(trace);
  //bqLog::Print("%s\n", trace);
    }
}

int bqStackTracerImpl::GetStackTrace(size_t* trace, int maxtrace, int framesToSkip)
{
    _init();
    if (m_RtlCaptureStackBackTrace)
  return m_RtlCaptureStackBackTrace(framesToSkip, maxtrace, (PVOID*)trace, NULL);
    return 0;
}

static bqStackTracerImpl g_stackTracer;

void bqStackTracer::Print()
{
    size_t t[100];
    g_stackTracer.DumpStackTrace(t, g_stackTracer.GetStackTrace(t, 100, 0));
}



Сама реализация находится в отдельном классе (bqStackTracerImpl). Пока чисто Windows версия.
Имеем глобальный объект (g_stackTracer). Статический метод(bqStackTracer::Print) использует его.

Можно создать тестовую программу:
Код

// не забыть указать inc и libs папки в параметрах проекта
#include "badcoiq.h"

BQ_LINK_LIBRARY("badcoiq");

int main()
{
    bqStackTracer::Print();
    
    return EXIT_SUCCESS;
}


github

Добавлено (02 Июля 2023, 19:25)
---------------------------------------------
Logging

Надо выводить текстовую информацию, ошибки и предупреждения.
Нужно сделать так чтобы можно было указать свою функцию для вывода. Например, чтобы выводить в файл, или в игровую консоль.

+
inc/badcoiq/common/bqLog.h

Код

// Класс для вывода текстового сообщения
// Можно установить коллбэки
class bqLog
{
public:
    static void Print(const char* s, ...);
    static void Print(const wchar_t* s, ...);

    static void PrintInfo(const char* s, ...);
    static void PrintInfo(const wchar_t* s, ...);

    static void PrintWarning(const char* s, ...);
    static void PrintWarning(const wchar_t* s, ...);

    static void PrintError(const char* s, ...);
    static void PrintError(const wchar_t* s, ...);

    static void SetCallbackA(void(*)(const char*));
    static void SetCallbackW(void(*)(const wchar_t*));
};


реализация
src/badcoiq/common/bqLog.cpp

Дефолтные колбэки
Код

#include <stdarg.h>

void bqLogDefaultCallbackA(const char* s)
{
    printf(s);
}

void bqLogDefaultCallbackW(const wchar_t* s)
{
    wprintf(s);
}


Класс который будет всё выполнять
Код

class bqLogImpl
{
public:
    bqLogImpl()
    {
  m_cba = bqLogDefaultCallbackA;
  m_cbw = bqLogDefaultCallbackW;
    }

    void vprintf(const char* s, va_list vl);
    void vwprintf(const wchar_t* s, va_list vl);

    void(*m_cba)(const char*);
    void(*m_cbw)(const wchar_t*);
};


Пишем в буфер и вызываем коллбэки
Код

void bqLogImpl::vprintf(const char* s, va_list vl)
{
    char buffer[0xFFFF];
    vsnprintf_s(buffer, 0xFFFF, s, vl);
    m_cba(buffer);
}

void bqLogImpl::vwprintf(const wchar_t* s, va_list vl)
{
    wchar_t buffer[0xFFFF];
    vswprintf_s(buffer, 0xFFFF, s, vl);
    m_cbw(buffer);
}


Добавляю глобальный объект
Код

static bqLogImpl g_log;


Теперь класс bqLog
Код

void bqLog::Print(const char* s, ...)
{
    va_list vl;
    va_start(vl, s);
    g_log.vprintf(s, vl);
    va_end(vl);
}

void bqLog::Print(const wchar_t* s, ...)
{
    va_list vl;
    va_start(vl, s);
    g_log.vwprintf(s, vl);
    va_end(vl);
}

void bqLog::PrintInfo(const char* s, ...)
{
    Print("%s", "Info: ");

    va_list vl;
    va_start(vl, s);
    g_log.vprintf(s, vl);
    va_end(vl);
}

void bqLog::PrintInfo(const wchar_t* s, ...)
{
    Print("%s", "Info: ");
    va_list vl;
    va_start(vl, s);
    g_log.vwprintf(s, vl);
    va_end(vl);
}

void bqLog::PrintWarning(const char* s, ...)
{
    Print("%s", "Warning: ");
    va_list vl;
    va_start(vl, s);
    g_log.vprintf(s, vl);
    va_end(vl);
}

void bqLog::PrintWarning(const wchar_t* s, ...)
{
    Print("%s", "Warning: ");
    va_list vl;
    va_start(vl, s);
    g_log.vwprintf(s, vl);
    va_end(vl);
}

void bqLog::PrintError(const char* s, ...)
{
    Print("%s", "Error: ");
    va_list vl;
    va_start(vl, s);
    g_log.vprintf(s, vl);
    va_end(vl);
}

void bqLog::PrintError(const wchar_t* s, ...)
{
    Print("%s", "Error: ");
    va_list vl;
    va_start(vl, s);
    g_log.vwprintf(s, vl);
    va_end(vl);
}

void bqLog::SetCallbackA(void(*cb)(const char*))
{
    if (cb)
  g_log.m_cba = cb;
    else
  g_log.m_cba = bqLogDefaultCallbackA;
}

void bqLog::SetCallbackW(void(*cb)(const wchar_t*))
{
    if (cb)
  g_log.m_cbw = cb;
    else
  g_log.m_cbw = bqLogDefaultCallbackW;
}


Однажды потребовалось вывести wchar_t*, проще было добавить W версию методов.

github


"Do you think we can fly?
Well, I do."
BADCOIQДата: Воскресенье, 02 Июля 2023, 06:58 | Сообщение # 7 | Тема: Разработка движка (детальное описание для новичков) [C++]
Отец-основатель BADCOIQ Corporation © 2010
Сейчас нет на сайте
Макросы

Добавлю файл, который будет подключать стандартные файлы, и в котором будут макросы.
Что такое макрос и для чего они нужны.

Макросы это то что создаётся используя слово define

Код

#define ABC


Они могут быть в виде функций.

Их используют чтобы добиться мультиплатформенного кода, для краткости написания кода и для много чего.
Вставляешь макрос, и на его месте будет то что было справа от него.

Новый файл будет иметь имя

inc/badcoiq/common/bqDefines.h

Туда добавляю

Код

#include <stddef.h> // NULL, size_t
#include <stdlib.h>
#include <assert.h>
#include <stdint.h> // int32_t uint32_t и другие
#include <ctype.h>  // isspace
#include <utility>  // std::move


Возможно ненужная вещь. Пережиток прошлого и всё такое. Многие библиотеки используют это.
`inline` не гарантирует что функция будет inline. Предположу что "не гарантирует" если функция написана в .c .cpp файлах(или файл инклюдится в них), ведь такие функции не встраиваемые, по умолчанию.
Вместо inline теперь надо писать BQ_FORCEINLINE
Код

#ifdef _MSC_VER
#define BQ_FORCEINLINE __forceinline
#else
#define BQ_FORCEINLINE inline
#endif


Для координат в 3D, матриц и т.д. надо дать возможность указать что использовать, double или float.
Такому типу обычно дают имя `real`
Код

#define bqReal double

Можно было написать using bqReal = double; но ведь это файл с макросами + Visual Studio окрашивает макросы в особый цвет.

Определяем платформу
Код

#if defined(WIN32) | defined(_WIN64) | defined(_WIN32)
#define BQ_PLATFORM_WINDOWS
#else
#error Только для Windows
#endif


Для использования Си (функций из .c файлов) внутри C++
Код

#ifdef BQ_PLATFORM_WINDOWS
#define BQ_CDECL _cdecl
#else
#error Только для Windows
#endif


Свой макрос для дебага
Код

#ifdef _DEBUG
#define BQ_DEBUG
#endif


Упаковка 4х байтов в один uint32_t
// uint32_t magic = BQ_MAKEFOURCC('‰', 'P', 'N', 'G');
Код

#define BQ_MAKEFOURCC( ch0, ch1, ch2, ch3 )\
    ((uint32_t)(uint8_t)(ch0)|((uint32_t)(uint8_t)(ch1)<<8)|\
    ((uint32_t)(uint8_t)(ch2)<<16)|((uint32_t)(uint8_t)(ch3)<<24))


Надо узнать, является ли конфигурация 64бит
Код

#ifdef BQ_PLATFORM_WINDOWS
#if defined _WIN64 || defined __x86_64__
#define BQ_BIT_64
#endif
#else
#error Только для Windows
#endif


Для показа имени файла, строки и функции
// printf("%s %i %s\n", BQ_FILE, BQ_LINE, BQ_FUNCTION);
Код

#ifdef BQ_PLATFORM_WINDOWS
#define BQ_FILE __FILE__
#define BQ_LINE __LINE__
#define BQ_FUNCTION __FUNCTION__
#else
#error Только для Windows
#endif


Для быстрого поключения .lib файлов в Visual Studio
Код

#ifdef _MSC_VER

// Версия компилятора Visual Studio
#if _MSC_VER >= 1930 && _MSC_VER <= 1936
#define BQ_LINK_LIBRARY_CMP "_v143"
#elif _MSC_VER >= 1920 && _MSC_VER <= 1929
#define BQ_LINK_LIBRARY_CMP "_v142"
#elif _MSC_VER >= 1910 && _MSC_VER <= 1916
#error Не проверено.
#define BQ_LINK_LIBRARY_CMP "_v141"
#endif

#ifdef BQ_BIT_64
#define BQ_LINK_LIBRARY_ARCH "_x64"
#else
#define BQ_LINK_LIBRARY_ARCH "_x86"
#endif

#ifdef BQ_DEBUG
#define BQ_LINK_LIBRARY_CONF "_Debug"
#else
#define BQ_LINK_LIBRARY_CONF "_Release"
#endif

// Использование : BQ_LINK_LIBRARY("libname")
#define BQ_LINK_LIBRARY(n) \
    __pragma(comment(lib, n BQ_LINK_LIBRARY_CMP BQ_LINK_LIBRARY_ARCH BQ_LINK_LIBRARY_CONF ".lib"))

#endif


Полезная вещь. Например имеем 0xAABBCCDD
Можно получить 0xAABB и 0xCCDD
Код

#define BQ_LO32(l) ((uint16_t)(((uint32_t)(l)) & 0xffff))
#define BQ_HI32(l) ((uint16_t)((((uint32_t)(l)) >> 16) & 0xffff))





Подключаю файл в главном .h файле
Код

#include "badcoiq/common/bqDefines.h"


github

Добавлено (02 Июля 2023, 09:14)
---------------------------------------------
Память

Я всегда делал DLL библиотеки, и требовалось чтобы выделение и освобождение памяти происходило в одном месте.
Если в DLL память была выделена используя new, а в EXE она будет delete, то может так случиться что эти new и delete будут из разных мест (различные Run Time).
От этого произойдёт ошибка и программа упадёт.
Решение проблемы - создать место которое будет выделять\освобождать память.

Возможно для статических библиотек это не проблема. Но я планирую использовать .dll как плагины.
Поэтому надо это создать и использовать только эти функции.

inc/badcoiq/common/bqMemory.h

Простой класс.
Код

class bqMemory
{
public:
    static void* malloc(size_t size);
    static void* calloc(size_t size);
    static void  free(void*);
    static void* realloc(void*, size_t size);
};


Его реализация

src/badcoiq/common/bqMemory.cpp

Код

#include "badcoiq.h"

#ifdef BQ_PLATFORM_WINDOWS
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
// Windows функции для работы с памятью требуют Process Heap
// Ничего страшного в глобальной переменной нет.
static HANDLE g_processHeap = GetProcessHeap();
#endif

void* bqMemory::malloc(size_t size)
{
#ifdef BQ_PLATFORM_WINDOWS
    return HeapAlloc(g_processHeap, 0, size);
#else
    return ::malloc(size);
#endif
}

void* bqMemory::calloc(size_t size)
{
#ifdef BQ_PLATFORM_WINDOWS
    return HeapAlloc(g_processHeap, HEAP_ZERO_MEMORY, size);
#else
    return ::calloc(1, size);
#endif
}

void bqMemory::free(void* ptr)
{
#ifdef BQ_PLATFORM_WINDOWS
    HeapFree(g_processHeap, 0, ptr);
#else
    ::free(ptr);
#endif
}

void* bqMemory::realloc(void* ptr, size_t size)
{
#ifdef BQ_PLATFORM_WINDOWS
    return HeapReAlloc(g_processHeap, HEAP_ZERO_MEMORY, ptr, size);
#else
    return ::realloc(ptr, size);
#endif
}

Вызываем функции Windows напрямую.

Возвращаюсь в bqMemory.h

Данные функции неудобно использовать для замены new и delete
Потому что для объектов классов нужно вызывать конструктор и деструктор.
Добавлю две функции которые будут выделять\освобождать память и вызывать конструктор\деструктор.
Код

template<typename _type, typename... Args>
_type* bqCreate(Args&&... args)
{
    _type* ptr = (_type*)bqMemory::malloc(sizeof(_type));
    if (ptr)
  ::new(ptr) _type(std::forward<Args>(args)...);// вызов конструктора
    return ptr;
}

template<typename _type>
void bqDestroy(_type* ptr)
{
    assert(ptr);
    ptr->~_type();// вызов деструктора
    bqMemory::free(ptr);
}

Для шаблонных функций force inline не нужен, так как они все получаются встраиваемыми.

Используется так
Код

MyClass* o = bqCreate<MyClass>(param1, param2, param3);
if(o)
    bqDestroy(o);


Тоже не очень удобно.
Можно добавить placement new\delete. Это для классов.
Код

#define BQ_PLACEMENT_ALLOCATOR(c) void* operator new(std::size_t count){ return (c*)bqMemory::malloc(count);}\
void operator delete(void* ptr){if (ptr) bqDestroy(ptr);}


Используется так
Код

class MyClass
{
public:
    MyClass(){
  printf("C1\n");
    }

    MyClass(int a, int b, int c){
  printf("C2\n");
    }

    ~MyClass(){
  printf("D\n");
    }

    BQ_PLACEMENT_ALLOCATOR(MyClass);
};

void f()
{
    /*MyClass* o = bqCreate<MyClass>();
    if (o)
  bqDestroy(o);*/

    MyClass* o = new MyClass(1,2,3);
    if (o)
  delete o;
}


github

Добавлено (02 Июля 2023, 11:22)
---------------------------------------------
DLL

Изначально был план дать возможность использовать библиотеку как DLL.
Добавил функции для работы с DLL.
Потом отказался от поддержки DLL версии, так как много возни.
Но сами функции пригодятся в будущем.

Новый include файл

inc/badcoiq/system/bqDLL.h

Подобно bqMemory, это класс со статическими методами.

Код

// надо завести свои имена для хэндла и указателя на функцию
typedef void* bqDLLHandle;   // HMODULE в Windows
typedef void* bqDLLFunction;

class bqDLL
{
public:

    // Загрузить динамическую библиотеку
    // Вернётся указатель если загрузилось.
    // При завершении работы надо вызвать free и передать bqDLLHandle
    static bqDLLHandle load(const char* libraryName);

    // Выгрузить библиотеку
    static void free(bqDLLHandle library);

    // Получить функцию из библиотеки
    // Вернёт NULL если была какая-то ошибка.
    static bqDLLFunction get_proc(bqDLLHandle library, const char* functionName);
};


Реализация
src/badcoiq/system/bqDLL.cpp

Код

#include "badcoiq.h"

#ifdef BQ_PLATFORM_WINDOWS
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#endif

bqDLLHandle bqDLL::load(const char* libraryName)
{
    BQ_ASSERT_ST(libraryName);
#ifdef BQ_PLATFORM_WINDOWS
    return (bqDLLHandle)LoadLibraryA(libraryName);
#else
#error Для Windows
#endif
}

void bqDLL::free(bqDLLHandle library)
{
    BQ_ASSERT_ST(library);
#ifdef BQ_PLATFORM_WINDOWS
    FreeLibrary((HMODULE)library);
#else
#error Для Windows
#endif
}

bqDLLFunction bqDLL::get_proc(bqDLLHandle library, const char* functionName)
{
    BQ_ASSERT_ST(library);
    BQ_ASSERT_ST(functionName);
#ifdef BQ_PLATFORM_WINDOWS
    return (bqDLLFunction)GetProcAddress((HMODULE)library, functionName);
#else
#error Для Windows
#endif
}

Просто обёртка для функций ОС. Более сложной штуки и не надо.

В данном месте начинает использоваться свой assert.

github

Добавлено (02 Июля 2023, 13:02)
---------------------------------------------
Assert

assert выполняет проверку на истинность
Это макрос, который содержит if. Если что-то плохо, то выполняется код в теле этого if.

Код

#ifdef BQ_DEBUG
#define BQ_ASSERT(expression) if((expression) == 0){bq::internal::OnAssert(#expression, BQ_FILE, BQ_LINE, 0);}
#define BQ_ASSERT_ST(expression) if((expression) == 0){bq::internal::OnAssert(#expression, BQ_FILE, BQ_LINE, bq::internal::flag_onAssert_useDefaultSystemWindow | bq::internal::flag_onAssert_useStackTrace);}
#else
#define BQ_ASSERT(expression) ((void)0)
#define BQ_ASSERT_ST(expression) ((void)0)
#endif


Если ошибка произойдёт, то в функцию передадутся выражение используемое в if (#expression), имя файла и строка где находится BQ_ASSERT.

assert работает только в debug режиме. Его нужно добавлять в код везде где можно, в релизной версии там будет пустота.

BQ_ASSERT вызвет функцию bq::internal::OnAssert()
Эта функция должна быть реализована в библиотеке.
Она должна использовать указатель на функцию - дефолтная OnAssert
Дефолтную функцию можно изменить, послав свой указатель функцией SetOnAssert.
...этот код должен быть выше макросов BQ_ASSERT
Код

// Скрою в неймспейсах вспомогательные функции
namespace bq
{
    namespace internal
    {
        enum flag_onAssert
        {
            // использовать системное окно которое показывается как при использовании assert()
            flag_onAssert_useDefaultSystemWindow = 0x1,

            // использовать stack tracer
            flag_onAssert_useStackTrace = 0x2,
        };

        // макросы BQ_ASSERT будут вызывать эту функцию
        void BQ_CDECL OnAssert(const char* message, const char* file, uint32_t line, uint32_t flags = 0);
  
  // установить свою функцию
        void BQ_CDECL SetOnAssert(void(*)(const char* message, const char* file, uint32_t line, uint32_t flags));
    }
}


Теперь реализация
src/badcoiq/common/bqAssert.cpp

Код

#include "badcoiq.h"

#ifdef BQ_PLATFORM_WINDOWS
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#endif

// Дефолтная функция для assert
// Надо раздокументировать когда будут готовы log, stacktracer и string
void bqOnAssert_default(const char* message, const char* file, uint32_t line, uint32_t flags)
{
#ifdef BQ_DEBUG
    if ((flags & bq::internal::flag_onAssert_useStackTrace) == bq::internal::flag_onAssert_useStackTrace)
    {
        //bqStackTracer::Print();
    }

    if ((flags & bq::internal::flag_onAssert_useDefaultSystemWindow) == bq::internal::flag_onAssert_useDefaultSystemWindow)
    {
        //bqString str_msg(message);
        //bqString str_file(file);

        //bqStringW str1;
        //str_msg.to_utf16(str1);

        //bqStringW str2;
        //str_file.to_utf16(str2);

#ifdef BQ_PLATFORM_WINDOWS
        //_wassert((const wchar_t*)str1.m_data, (const wchar_t*)str2.m_data, line);
#endif
    }
    else
    {
        //printf("%s %s %i\n", message, file, line);
       //bqlLog::PrintError("%s %s %i\n", message, file, line);
#ifdef BQ_PLATFORM_WINDOWS
        DebugBreak();
#endif
    }
#endif
}

// Указатель на функцию. Можно будет изменять функцию OnAssert используя функцию SetOnAssert
static void(*g_onAssert)(const char* message, const char* file, uint32_t line, uint32_t flags) = bqOnAssert_default;

namespace bq
{
    namespace internal
    {
        // просто вызываем g_onAssert
        void BQ_CDECL OnAssert(const char* message, const char* file, uint32_t line, uint32_t flags)
        {
            g_onAssert(message, file, line, flags);
        }
    
        // Изменяем g_onAssert. Или устанавливаем дефолтную функцию.
        void BQ_CDECL SetOnAssert(void(*f)(const char* message, const char* file, uint32_t line, uint32_t flags))
        {
            g_onAssert = f ? f : bqOnAssert_default;
        }
    }
}



github


"Do you think we can fly?
Well, I do."


Сообщение отредактировал BADCOIQ - Воскресенье, 02 Июля 2023, 09:35
BADCOIQДата: Суббота, 01 Июля 2023, 19:58 | Сообщение # 8 | Тема: Разработка движка (детальное описание для новичков) [C++]
Отец-основатель BADCOIQ Corporation © 2010
Сейчас нет на сайте
Начало

Что необходимо сделать чтобы получился ДВИЖОК?
Сначала надо освоить создание библиотек.
Как только получится вызвать функцию из библиотеки, сразу придёт понимание что такое этот ваш ДВИЖОК - это просто библиотека, или их набор.

Библиотеки могут быть динамическими или статическими.
Динамическую библиотеку можно использовать во множестве языков программирования. Она имеет вид отдельного файла.

Статические библиотеки это набор кодов, которые становятся частью программы, они вшиваются в исполняемый файл.
Эти библиотеки нельзя использовать в иных ЯП. Наверно.

Здесь, я буду использовать статические библиотеки. Почему:
- всё быстро компилируется
- их проще подключить (для Visual Studio)

Минус статической библиотеки в том что нужно использовать конкретную версию компилятора, это касается VS, на GCC их не писал.
Поэтому при начале работы, лучше скомпилировать библиотеки тем компилятором что есть.

Мысли о коде
Я не имею опыт современного C++. Даже не знаю есть ли C++23 и далее, и вообще что там было после C++17, даже не интересно.
Что-то увижу полезное, приму. Но никаких концептов, модулей, жёсткого STL и т.д.

* Запрещено использовать STL в API.
Необходимо написать свои вещи.

* Общий стиль кода должен быть простым в стиле СИ.
Имена функций и пользовательских типов должны иметь префикс, в моём случае я буду использовать `bq`.
Сишные префиксы гораздо удобнее использования namespace.

* Вообще namespace даёт кучу неудобностей, поэтому запрещено использовать его в API. Допускается его использование для внутренних вещей.
Код

namespace bq
{
    namespace internal
    {
    }
}


* Фигурные скобки тел функций и пользовательских типов должны быть на уникальной строке.
* Тела if for и прочих могут находится на той-же строке что и инструкция.

* Имена для API нужно писать в CamelCase. Код проще редактировать, не нужно тратить время на ввод симола `_`
Получается, имена будут выглядить так: bqPolygonMesh

* Использование имён_такого_типа допускается только в классах которые схожи с классами в STL.

* В коде лучше использовать сишные\сырые массивы. Ненужно использовать динамические если без них не обойтись.

* Допускается использование макросов, но не сложных.
Например, запрещено использовать макросы для генерации функций
Код

DECLARE_FUNC(One)
DECLARE_FUNC(Two)
DECLARE_FUNC(Three)


где на выходе мы получаем
Код

что_то_там_One_что_то_там
что_то_там_Two_что_то_там
что_то_там_Three_что_то_там


* Использовать строки char* в API или свой класс bqString.
Использовать wchar_t* для открытия файла не имеет смысла. char* работает подобно UTF-8.

* Использование uint8_t и подобных имён для стандартных типов.

* Запрещено кидать исключения.

* В пользовательских типах, имена переменных должны иметь префикс 'm_',
имена открытых методов должны начинаться с Большой буквы,
имена закрытых методов должны начинаться с символа `_`.

* Наследование используется только для классов.

Мысль об архитектуре
Не нужно писать супер класс который всё сможет сделать.
Лучше написать простой класс, потом поверх него чуть сложнее, потом ещё сложнее если надо. Типа слой за слоем.
И эти сложные слои должны находиться на стороне программы.
Например, работа со сценой, рисование теней и т.д. Всё должно делаться на стороне программы.

Создаваемые вещи должны быть максимально независимыми. Если делаем рендер с уникальными технологиями, то более простые рендеры всё равно должны работать.

Шаблоны проектирования под сомнением. Их не будет до той поры пока я не осознаю их пользу.

Внутри основной библиотеки будет главный класс Framework. Он будет содержать все необходимые вещи, будет инициализировать библиотеки, иметь загрузчики ресурсов и т.д.
Для простоты будет глобальный указатель на объект данного класса. Простая глобальная переменная. И ненужны никакие заумные вещи.

В общем, главное простота, а не выпендрёж.

Для простых вещей будут использоваться классы со статическими методами.

Структура проекта
Я использую Visual Studio 2022. Буду использовать его возможности.

Папки:
`build` - там лежат файлы проектов
`inc` - #include файлы движка
`libs` - туда прилетят скомпилированные библиотеки
`src` - тут лежат исходные коды движка и сторонних библиотек.

По ходу прогресса появятся папки
`data` - ресурсы для демки
`bin32\bin64` - демка и инструмены
`tools` - исходные коды для инструментов

Создание проекта Visual Studio
Я всегда создаю пустые (Empty) проекты, и настраиваю их.
Некоторые проекты я просто копирую в проводнике, редактирую в Notepad и кидаю в Visual Studio.



После создания проекта, надо добавить какой нибудь файл с исходным кодом.
В папке 'src' надо создать папку для исходного кода движка.
Далее создать текстовый файл и переименовать его установив расширение .cpp
Далее кидаем этот файл в Visual Studio.



Это надо сделать, потому что Visual Studio не показывает C/C++ параметры если в проекте нет файла с исходным кодом.

Содержимое данного файла


В каждом файле (.h .c .c++ .cxx и т.д.) должна быть указана лицензия.
Так-же практически в каждом файле (.c .cpp) потребуется минимальный набор include файлов.
Все эти файлы указаны в `badcoiq.h`

Надо создать badcoiq.h
Он будет находится в папке `inc`.
Просто копирую badcoiq.cpp и изменю расширение.



Теперь настрою параметры проекта.



Параметр Target Name. В результате имя библиотеки получиться типа такое

badcoiq_v143_x86_Debug.lib

Укажу include папку



Для Release укажу оптимизацию



Можно изменить Floating Point Model
Fast даст чуть скорости.



Указываю NODEFAULTLIB
Возможно они и так будут игнориться, но укажу на всякий случай.



Всё, настройка завершена.

Далее нажимаю в редакторе правой кнопкой по badcoiq.h и выбираю Go To Document
Для быстроты я привык нажимать просто Ctrl+Shift+G


Откроется badcoiq.h и можно приступить к его редактированию.

В данном моменте там должна быть лицензия и include guard

Код

#pragma once
#ifndef __BQ_BADCOIQ_H__
#define __BQ_BADCOIQ_H__

#endif


Указываю #pragma once тоже, первее include guard

Всё, можно строить.

В папке libs будут такие файлы



В Visual Studio можно включить кнопку для построения. Правой кнопкой по GUI и выбираем Build.
Добавлю кнопку Compile



Осталось отредактировать .gitignore и сделать коммит

Код


build/badcoiq/.vs
build/badcoiq/x64
build/badcoiq/Debug
build/badcoiq/Release
*.idb
*.pdb
*.lib



github


"Do you think we can fly?
Well, I do."


Сообщение отредактировал BADCOIQ - Суббота, 01 Июля 2023, 20:14
BADCOIQДата: Суббота, 01 Июля 2023, 17:02 | Сообщение # 9 | Тема: Разработка движка (детальное описание для новичков) [C++]
Отец-основатель BADCOIQ Corporation © 2010
Сейчас нет на сайте
Цитата graveman ()
Вектор

Мне по вектору, нужен такой, который методом clear просто сделает

Код

m_size = 0;


а не освободит память совсем.


"Do you think we can fly?
Well, I do."
BADCOIQДата: Суббота, 01 Июля 2023, 16:36 | Сообщение # 10 | Тема: Разработка движка (детальное описание для новичков) [C++]
Отец-основатель BADCOIQ Corporation © 2010
Сейчас нет на сайте
Цитата graveman ()
Ссылку пока не оставлю

Дай мне посмотреть.


"Do you think we can fly?
Well, I do."
BADCOIQДата: Суббота, 01 Июля 2023, 16:07 | Сообщение # 11 | Тема: Разработка движка (детальное описание для новичков) [C++]
Отец-основатель BADCOIQ Corporation © 2010
Сейчас нет на сайте
Все сообщения здесь
https://github.com/badcoiq/badcoiq/wiki

Хочу написать библиотеку которую буду использовать как для игр так и для простого и не очень ПО.
В данной теме буду публиковать детальное описание своим действиям.

Главная цель это показать действия тем людям кто хочет написать своё и не знает с чего начать.
Как говорят, кто ничего в жизни не добился, идут в учителя. Но тем не менее я считаю себя достаточно опытным.

Всё уже написано по миллиону раз. Это будет финальная итерация.

Код и архитектура не претендует на эталон. Но я напишу определённые моменты к которым я пришёл, и я считаю их истинно верными.

Репозиторий находится здесь https://github.com/badcoiq/badcoiq

Какой результат я ожидаю? Хочу сделать минимальный набор функций и набор редакторов (написанные на этой библиотеке).
Именно набор а не одну единственную программу всё-в-одном.
Графика будет в последнюю очередь, я её даже не включил в список дел.

Ниже будут ссылки на сообщения в данной теме.
В каждом сообщении будет находится ссылка на репозиторий той версии когда было закончено написание сообщения.
Всё написано последовательно, от старого к новому.
Если в списке чего-то нет, это не значит что этого не будет вообще.



Необходимо время для того чтобы я написал Начало...


"Do you think we can fly?
Well, I do."


Сообщение отредактировал BADCOIQ - Вторник, 11 Июля 2023, 17:34
BADCOIQДата: Пятница, 27 Июля 2018, 17:08 | Сообщение # 12 | Тема: Скролл текста с плавным появлением и исчезновением
Отец-основатель BADCOIQ Corporation © 2010
Сейчас нет на сайте
Поверх экрана налепить прямоугольник с полупрозрачной текстурой.


Если например фон не черный а прозрачный то можно было бы сделать шейдерами.


"Do you think we can fly?
Well, I do."


Сообщение отредактировал BADCOIQ - Пятница, 27 Июля 2018, 17:12
BADCOIQДата: Четверг, 26 Июля 2018, 16:09 | Сообщение # 13 | Тема: Вершинный шейдер
Отец-основатель BADCOIQ Corporation © 2010
Сейчас нет на сайте
Меши так и перемещаются.

Матрица модели(точнее объекта который визуализирует модель) (так называемая мировая матрица) это есть матрица перемещения * матрица вращения * матрица масштаба, и её надо обновлять постоянно как что-то изменилось (изменили масштаб, ориентацию или позицию).

На CPU нужно вычислить WVP, потому что на GPU эти вычисления будут идти на каждую вершину, а вершин очень много.
Код

Matrix4  W = GetWorldMatrix();
Matrix4  V = GetViewMatrix();
Matrix4  P = GetProjectionMatrix();
Matrix4  WVP = P * V * W;
посылаем в шейдер WVP


Шейдер
Код

#version 330 core
layout(location = 0) in vec3 lay_position;
void main(){
    gl_Position = WVP * vec4( lay_position, 1.0f ); // перемещаем текущую вершину на нужную позицию
}


"Do you think we can fly?
Well, I do."


Сообщение отредактировал BADCOIQ - Четверг, 26 Июля 2018, 16:17
BADCOIQДата: Воскресенье, 22 Июля 2018, 02:27 | Сообщение # 14 | Тема: воспроизведение игры в окне редактора движка
Отец-основатель BADCOIQ Corporation © 2010
Сейчас нет на сайте
Цитата afq ()
Как так делают? Видел в unity, что в редакторе можно запустить проект игры и играть в самом редакторе, то же самое видел и unreal engine, как такое можно сделать, не пойму?


Представь, в редакторе, есть камера, через которую человек видит виртуальный мир. Есть другие объекты на сцене, дорога, стена. У всех объектов есть параметры, позиция, физические параметры. Что мешает сделать кнопку, по нажатии на которую, инициализировался бы физический движок, активировалась нужная камера, включались бы нужные кнопки ввода?
Все данные о виртуальном мире сохраняются, скрипты доступны и они как бы переходят в рабочий режим.

Если часть игры вынесена в другие модули (.dll .so), то для правильной работы создают общие методы, интерфейсы. А значит, каждый модуль должен реализовывать их (например, инициализация, завершение работы).

Цитата afq ()
DivES, есть функция void (*loop)(); она получает ссылку на функцию с помощью dlsym, но она не получает её, потому что если сделать такую проверку, то ничего не будет.


При загрузки библиотеки нужно проверить, загружена ли она. Если загружена то
Указатель должен быть инициализирован нулём или nullptr.
Если получить адрес на функцию не удалось, то указатель так и останется нулевым. Значит, была допущена ошибка либо при экспорте функции, либо при получении указателя на неё (ошибка в имени, ошибка в объявлении указателя).

При загрузки библиотеки лучше сразу проверить на наличие все функции. Если какой-то нет то ошибка, старая .dll .so. Если всё есть, то проверки можно убрать, или использовать какой-нибудь флаг (библиотека была загружена или нет).


"Do you think we can fly?
Well, I do."
BADCOIQДата: Суббота, 21 Июля 2018, 00:26 | Сообщение # 15 | Тема: Нужны художники, 3D художники, фантазёры, сценаристы +
Отец-основатель BADCOIQ Corporation © 2010
Сейчас нет на сайте
Буду(или будем) делать игру по моей идее (первый вариант).

Главная мысль которая у меня была в тот момент когда я её придумывал, сделать игру для геймпада, чтобы в развалочку сидя/лёжа на чём-то, нажимать минимум кнопок, чтобы было легко по умолчанию, чтобы игра расслабляла.

Протестировал тени и освещение.


Осталось сделать только освещение от частиц. Думал, сделаю вершинным светом, но, думаю проще рендерить частицы и накладывать сверху, как освещение для героя.


"Do you think we can fly?
Well, I do."


Сообщение отредактировал BADCOIQ - Суббота, 21 Июля 2018, 12:07
BADCOIQДата: Вторник, 17 Июля 2018, 20:37 | Сообщение # 16 | Тема: Нужны художники, 3D художники, фантазёры, сценаристы +
Отец-основатель BADCOIQ Corporation © 2010
Сейчас нет на сайте
Примерно должен быть такой вид.

(Спрайты вырезал из Blood Omen: Legacy of Kain)

На счёт эффектов - их нет. Они будут реализованы особенным образом, когда уровни будут нарисованы.


"Do you think we can fly?
Well, I do."


Сообщение отредактировал BADCOIQ - Вторник, 17 Июля 2018, 20:38
BADCOIQДата: Вторник, 17 Июля 2018, 16:49 | Сообщение # 17 | Тема: Нужны художники, 3D художники, фантазёры, сценаристы +
Отец-основатель BADCOIQ Corporation © 2010
Сейчас нет на сайте
Цитата Kxarog ()
1 никому нельзя верить и вы не исключение, где гарантия что вы не кинете людей?
2 идеи как таковой нет, и вы ссылаетесь на то что решит группа(если соберется), а если не будет единогласного мнения? что тогда?
3 группа не соберется полностью (допустим присоединится один только музыкант) что тогда? заставите всю остальную работу его делать? сами будете делать?
4 как будете контролировать группу? энтузиазм слишком быстро заканчивается тоесть к примеру через неделю пол группы скажет "ой все я устал, я ухожу" что делать остальным которые отнеслись к делу серьезно? на свалку только зря время потратили?
5 что будет если команда все таки соберется вы создали проект но он провалился? команда разбегается? создавать новый?
6 возможно эгоистичный вопрос, но почему поровну? к примеру если художник создал намного больший объем работы чем скажем музыкант, получается музыкант схалявил, а художник в пролете, разве это справедливо?


мда

хватит держать людей за идиотов (я объяснять не буду, но вы это делаете, задумайтесь и поймёте)

______

Я вам больше не отвечу.

Это какой-то треш.

Будут посты только по теме.


"Do you think we can fly?
Well, I do."


Сообщение отредактировал BADCOIQ - Вторник, 17 Июля 2018, 16:54
BADCOIQДата: Вторник, 17 Июля 2018, 00:58 | Сообщение # 18 | Тема: Нужны художники, 3D художники, фантазёры, сценаристы +
Отец-основатель BADCOIQ Corporation © 2010
Сейчас нет на сайте
У вас одно желание - услышать оправдания от человека.

Люди бывают разные, такова жизнь.

Как я и писал, господа, не обращайте на меня внимания, я не достоин вас. Именно вас я и отталкиваю ибо знаю суть форумных жителей. Я пришел не к вам, а к обычным игроделам ЛЮБИТЕЛЯМ которые не треплятся на форуме, потешая своё чувство собственной важности, набивая 2000+ сообщений и т.д.

Вот, вы прекратите делать из людей, не подс***вших вам, не подлизавших, идиотов.
Это моё ощущение, видение быта в интернет сообществах, и это норма, к сожалению.
Просто я принципиальный, не имею стадного инстинкта, гнобить слабых, льстить `важным особам` и т.д.

Есть идея, нужны люди которым будет интересно сообща делать что-то, от этого у всех будет стимул работать, особенно у меня. Раскрывать даже некоторые свои карты, естественно, нет желания, ибо крыс сейчас полно. Вести диспуты, подобные этим, не вызывает у меня интереса, а только наводит на тоску.

Пожалуйста, если вы так хотите, пофилософствовать о смыслах написанного и ненаписанного, создайте отдельную тему в этой категории -> флейм Поверьте, не все люди имеют такое же мировозрение как у вас. Им безразлично то, на что вы обращаете внимание. Вас что-то не устраивает - игнорируйте, делов-то.

______________
Столько мусора, кроме первых 2х сообщений. Всё нормально я объясняю. Просто нужны не тормозящие люди.

Добавлено (17 Июля 2240, час икс)
---------------------------------------------
Днём запилю демку с 2д графикой.


"Do you think we can fly?
Well, I do."


Сообщение отредактировал BADCOIQ - Вторник, 17 Июля 2018, 00:59
BADCOIQДата: Понедельник, 16 Июля 2018, 22:50 | Сообщение # 19 | Тема: Нужны художники, 3D художники, фантазёры, сценаристы +
Отец-основатель BADCOIQ Corporation © 2010
Сейчас нет на сайте
Я никого не заставляю. Есть желающие - добро пожаловать. Как я написал, у меня есть идеи, и мне нужна помощь в их проработке, быть может, кто-то хочет реализовать что-то иное. Так то я отвечаю за код (непонимаю, зачем я должен всё на пальцах объяснять, по первому посту это можно было понять). Ну, если никого не будет, ничего страшного, буду таки сам делать.

Если и нанимать то только квалифицированных профессионалов. Какое тогда удовольствие от разработки? От процесса? Лучше сам буду, да пару иностранцев, пакистанцев, непальцев найду.

Прошу задавать вопросы, ответы которых ещё более проясняют ситуацию. Быть может я что-то не написал, не выразил какую-то мысль. Объясню, дополню. Только давайте без вечных вопросов (зачем, почему бы не и т.д.).

______________________

>показать почему идея крутая.
Вместе с командой создадим свою крутую идею. Главное - не тормозить.

Добавлено (16 Июля 2018, 22:50)
---------------------------------------------

Цитата TLT ()
А движок больше трёхмерный... Или имеется в виду двухмерный (фиксированный) вид на трёхмерную сцену?


Демонстрацию не сделал пока. Но есть спрайты с анимацией, 2D камера.
Будет именно в двух плоскостях. Всё остальное рисуется и собирается в редакторе.


"Do you think we can fly?
Well, I do."


Сообщение отредактировал BADCOIQ - Понедельник, 16 Июля 2018, 22:45
BADCOIQДата: Понедельник, 16 Июля 2018, 21:30 | Сообщение # 20 | Тема: Нужны художники, 3D художники, фантазёры, сценаристы +
Отец-основатель BADCOIQ Corporation © 2010
Сейчас нет на сайте
Цитата ДанилаСтержнев ()
Как можно работать за веру в то, чего нет?

Суть не в том что есть а что нет, а в том чтобы найти людей с желанием что-то делать.

Ищу "художников", 2d, 3d, аудио, в программировании и т.д. Бизнесменов прошу не беспокоить.

Цитата ДанилаСтержнев ()
Ровно - это кажется украинский город. Точно прибыль по нему будете делить?

" по ровну." - %) придирки одни к мелочным ошибкам, Дартаньяны блин кругом.

просьба не флудить.


"Do you think we can fly?
Well, I do."


Сообщение отредактировал BADCOIQ - Понедельник, 16 Июля 2018, 21:35
Форум игроделов » Записи участника » BADCOIQ [528]
  • Страница 1 из 27
  • 1
  • 2
  • 3
  • 26
  • 27
  • »
Поиск:

Все права сохранены. GcUp.ru © 2008-2024 Рейтинг