Notice: Function _load_textdomain_just_in_time was called incorrectly. Translation loading for the antispam-bee domain was triggered too early. This is usually an indicator for some code in the plugin or theme running too early. Translations should be loaded at the init action or later. Please see Debugging in WordPress for more information. (This message was added in version 6.7.0.) in /home/ruwritingagame/public_html/wp-includes/functions.php on line 6121
Глава 13. Базовые классы 2 – Игра в Написание Игры

Глава 13. Базовые классы 2

Windows

1. Запускаем VS. Открываем C:\CPP\a998engine\p_windows\p_windows.sln


MyColor класс

Причина держать цвет в отдельном классе в том, что он будет нужен в ТРЕХ разных форматах:

  • как массив 4-х byte integers (от 0 до 255, как это хранится в файлах)
  • как массив 4-х floats (от 0.0 до 1.0, как требуется OpenGL-ом)
  • и как 32-bit integer (4 байта, по одному на каждый канал)

Любое изменение в любом из форматов должно отражаться в двух остальных.

2. Под xEngine добавим новый header (.h) файл MyColor.h

Location – C:\CPP\engine

Код:

#pragma once
#include "platform.h"

class MyColor
{
private:
    float rgba[4] = { 0,0,0,0 };
    union {
        myUint32 uint32value = 0;
        myUint8  channel[4]; //4 channels
    } RGBA;
public:
    float* forGL() { return rgba; };
    static void cloneIntToFloat(MyColor* pCl);
    static void cloneFloatToInt(MyColor* pCl);
    void setRGBA(float* rgba) { return setRGBA(this, rgba); };
    static void setRGBA(MyColor* pCl, float* rgba);
    void setRGBA(float r, float g, float b, float a) { return setRGBA(this, r, g, b, a); };
    static void setRGBA(MyColor* pCl, float r, float g, float b, float a);
    void setRGBA(int R, int G, int B, int A) { return setRGBA(this, R, G, B, A); };
    static void setRGBA(MyColor* pCl, int R, int G, int B, int A);
    void setRGBA(unsigned char* RGBA) { return setRGBA(this, RGBA); };
    static void setRGBA(MyColor* pCl, unsigned char* RGBA);
    void setUint32(unsigned int RGBA) { return setUint32(this, RGBA); };
    static void setUint32(MyColor* pCl, unsigned int RGBA);
    bool isZero() { return (RGBA.uint32value == 0); };
    bool isSet() { return (RGBA.uint32value != 0); };
    void clear() { setUint32(0); };
    unsigned int getUint32() { return (unsigned int)RGBA.uint32value; };
};

  • Заметим: у нас тут 2 собственных типа переменных: myUint32 and myUint8.
  • Мы не можем использовать “нормальные” _int32 или _int8 потому что они Windows-specific, в Андроиде они представлены по-другому.

3. Откроем platform.h и заменим код на:

#pragma once
#include <glad/glad.h>
#include <stdio.h>

typedef unsigned _int64 myUint64;
typedef unsigned _int32 myUint32;
typedef unsigned _int16 myUint16;
typedef unsigned _int8  myUint8;

void mylog(const char* _Format, ...);
void mySwapBuffers();
void myPollEvents();
int myFopen_s(FILE** pFile, const char* filePath, const char* mode);


4. Под xEngine добавим новый C++ File (.cpp) MyColor.cpp

Location – C:\CPP\engine

Код:

#include "MyColor.h"

float chanIntToFloat = 1.0f / 255;

void MyColor::cloneIntToFloat(MyColor* pCl) {
	for (int i = 0; i < 4; i++)
		pCl->rgba[i] = chanIntToFloat * pCl->RGBA.channel[i];
}
void MyColor::cloneFloatToInt(MyColor* pCl) {
	for (int i = 0; i < 4; i++)
		pCl->RGBA.channel[i] = (int)(pCl->rgba[i] * 255);
}
void MyColor::setRGBA(MyColor* pCl, float* rgba) {
	for (int i = 0; i < 4; i++)
		pCl->rgba[i] = rgba[i];
	cloneFloatToInt(pCl);
}
void MyColor::setRGBA(MyColor* pCl, float r, float g, float b, float a) {
	pCl->rgba[0] = r;
	pCl->rgba[1] = g;
	pCl->rgba[2] = b;
	pCl->rgba[3] = a;
	cloneFloatToInt(pCl);
}
void MyColor::setRGBA(MyColor* pCl, int R, int G, int B, int A) {
	pCl->RGBA.channel[0] = R;
	pCl->RGBA.channel[1] = G;
	pCl->RGBA.channel[2] = B;
	pCl->RGBA.channel[3] = A;
	cloneIntToFloat(pCl);
}
void MyColor::setRGBA(MyColor* pCl, unsigned char* RGBA) {
	for (int i = 0; i < 4; i++)
		pCl->RGBA.channel[i] = RGBA[i];
	cloneIntToFloat(pCl);
}
void MyColor::setUint32(MyColor* pCl, unsigned int RGBA) {
	pCl->RGBA.uint32value = RGBA;
	cloneIntToFloat(pCl);
}


Material класс

“Material” в нашем случае – это инструкция КАК рендрить, каким шейдером, какую текстуру или цвет, и т.д. Например, “Рисовать зеленую поверхность ucolor_flat шейдером” – это один Material. “Рисовать красную поверхность тем же шейдером” – уже другой. ЧТО рендрить – это отдельный вопрос. Пока у нас не так много что держать в этом классе, но потом будет больше.

5. Под xEngine добавим новый header (.h) файл Material.h

Location – C:\CPP\engine

Код:

#pragma once
#include "MyColor.h"

class Material
{
public:
	int shaderN = -1;
	int primitiveType = GL_TRIANGLES;
	MyColor uColor;
	int uTex0 = -1;
};


6. Под xEngine добавим новый C++ (.cpp) файл Material.cpp

Location – C:\CPP\engine

Пока оставим его пустым.


И теперь мы готовы к ключевому классу нашего движка, который будет представлять ЧТО рендрить. Также в нем будет и Material, так что он будет знать ЧТО рендрить и КАК. Назовем его DrawJob.

Каждый game subject будет иметь 1 или несколько DrawJobs. Например, хотим мы нарисовать синий кубик с картинкой на одной стороне. Значит, у subject-а будет 2 DrawJobs:

  • 5-сторонний меш со ссылкой на цвет и с шейдером ucolor_flat
  • 1-сторонний меш со ссылкой на текстуру и с шейдером tex_flat

Класс TheGame будет сканировать массив game subject-ов и их DrawJobs и будет их исполнять.

Каждый DrawJob должен иметь ссылки на задействованные массивы данных, в нашем примере – на один vertex_buffer VBO (Vertex Buffer Object, который мы сгенерировали из массива вертексов). Каждый элемент этого VBO состоит из 5 floats (а могло быть и 2 VBO: в одном xyz координаты, а в другом текстурные – tUV).

Также DrawJob должен иметь информацию как эти данные интерпретировать и использовать: для каждой задействованной переменной (таких как aPos или aTuv) он должен указать где ее искать (в каком VBO и ее позицию/offset/смещение внутри этого VBO). Для этого понадобится новая структура, назовем ее AttribRef (for Attribute Reference).

Сами VBO будут храниться отдельно (в GPU). В DrawJob же AttribRefs, если быть точнее) нам будет нужен только номер – VBO id.


DrawJob class

7. Под xEngine добавим новый header (.h) файл DrawJob.h

Location – C:\CPP\engine

Код:

#pragma once
#include "Material.h"
#include <vector>

struct AttribRef //attribute reference/description
{
	unsigned int glVBOid = 0; //buffer object id
	int offset = 0; //variable's offset inside of VBO's element
	int stride = 0; //Buffer's element size in bytes
};

class DrawJob
{
public:
	Material mt;
	int pointsN = 0; //N of points to draw
	unsigned int glVAOid = 0; //will hold data stream attributes mapping/positions

	//common attributes
	AttribRef aPos;
	AttribRef aTuv;

	//static arrays (vectors) of all loaded DrawJobs, VBO ids
	static std::vector<DrawJob*> drawJobs;
	static std::vector<unsigned int> buffersIds;
public:
	DrawJob();
	virtual ~DrawJob(); //destructor
	static int cleanUp();
	static int newBufferId();
	int buildVAO() { return buildVAOforShader(this, mt.shaderN); };
	static int buildVAOforShader(DrawJob* pDJ, int shaderN);
	static int attachAttribute(int varLocationInShader, int attributeSizeInFloats, AttribRef* pAttribRef);

	virtual int setDesirableOffsets(int* pStride, int shaderN) { return setDesirableOffsetsBasic(this, pStride, shaderN); };
	static int setDesirableOffsetsBasic(DrawJob* pDJ, int* pStride, int shaderN);

	int execute(float* uMVP, Material* pMt) { return executeDrawJob(this, uMVP, pMt); };
	static int executeDrawJob(DrawJob* pDJ, float* uMVP, Material* pMt);
};


8. Под xEngine добавим новый C++ (.cpp) файл DrawJob.cpp

Location – C:\CPP\engine

Код:

#include "DrawJob.h"
#include "platform.h"
#include "utils.h"
#include "Shader.h"
#include "Texture.h"

//static arrays (vectors) of all loaded DrawJobs, VBO ids
std::vector<DrawJob*> DrawJob::drawJobs;
std::vector<unsigned int> DrawJob::buffersIds;

DrawJob::DrawJob() {
	drawJobs.push_back(this);
}
DrawJob::~DrawJob() {
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
	if (glVAOid > 0)
		glDeleteVertexArrays(1, &glVAOid);
}
int DrawJob::newBufferId() {
	unsigned int bufferId;
	glGenBuffers(1, &bufferId);
	buffersIds.push_back(bufferId);
	return (int)bufferId;
}

int DrawJob::setDesirableOffsetsBasic(DrawJob* pDJ, int* pStride, int shaderN) {
	//sets desirable offsets and stride according to given shader needs
	//assuming that we have 1 single VBO
	Shader* pSh = Shader::shaders.at(shaderN);
	int stride = 0;
	pDJ->aPos.offset = 0; //attribute o_aPos, always 0
	stride += sizeof(float) * 3; //aPos size - 3 floats (x,y,z)
	if (pSh->l_aTuv >= 0) { //attribute TUV (texture coordinates)
		pDJ->aTuv.offset = stride; //attribute TUV (texture coordinates)
		stride += sizeof(float) * 2;
	}
	*pStride = stride;
	return 1;
}
unsigned int activeVBOid;
int DrawJob::buildVAOforShader(DrawJob* pDJ, int shaderN) {
	//delete VAO if exists already
	if (pDJ->glVAOid > 0) {
		glBindBuffer(GL_ARRAY_BUFFER, 0);
		glDeleteVertexArrays(1, &(pDJ->glVAOid));
	}
	glGenVertexArrays(1, &pDJ->glVAOid);
	glBindVertexArray(pDJ->glVAOid);

	//open shader descriptor to access variables locations
	Shader* pShader = Shader::shaders.at(pDJ->mt.shaderN);

	activeVBOid = 0;
	attachAttribute(pShader->l_aPos, 3, &pDJ->aPos);
	attachAttribute(pShader->l_aTuv, 2, &pDJ->aTuv);
	return 1;
}

int DrawJob::attachAttribute(int varLocationInShader, int attributeSizeInFloats, AttribRef* pAR) {
	if (varLocationInShader < 0)
		return 0; //not used in this shader
	if (pAR->glVBOid == 0) {
		mylog("ERROR in DrawJob::attachAttribute, nk such attribute/VBO\n");
		return -1;
	}
	glEnableVertexAttribArray(varLocationInShader);
	if (activeVBOid != pAR->glVBOid) {
		activeVBOid = pAR->glVBOid;
		//attach input stream data
		glBindBuffer(GL_ARRAY_BUFFER, activeVBOid);
	}
	glVertexAttribPointer(varLocationInShader, attributeSizeInFloats, GL_FLOAT, GL_FALSE, pAR->stride, (void*)(long)pAR->offset);
	return 1;
}
int DrawJob::executeDrawJob(DrawJob* pDJ, float* uMVP, Material* pMt) {
	if (pMt == NULL)
		pMt = &(pDJ->mt);
	glBindVertexArray(pDJ->glVAOid);
	Shader* pShader = Shader::shaders.at(pMt->shaderN);
	glUseProgram(pShader->GLid);
	glUniformMatrix4fv(pShader->l_uMVP, 1, GL_FALSE, (const GLfloat*)uMVP);

	//attach textures
	if (pShader->l_uTex0 >= 0) {
		int textureId = Texture::getGLid(pMt->uTex0);
		//pass textureId to shader program
		glActiveTexture(GL_TEXTURE0); // activate the texture unit first before binding texture
		glBindTexture(GL_TEXTURE_2D, textureId);
		// Tell the texture uniform sampler to use this texture in the shader by binding to texture unit 0.    
		glUniform1i(pShader->l_uTex0, 0);
	}
	//other uniforms
	if (pShader->l_uColor >= 0) {
		//float uColor[4] = { 1.f, 0.f, 1.f, 1.f }; //R,G,B, alpha
		glUniform4fv(pShader->l_uColor, 1, pMt->uColor.forGL());
	}
	glDrawArrays(pMt->primitiveType, 0, pDJ->pointsN);
	return 1;
}
int DrawJob::cleanUp() {
	int itemsN = drawJobs.size();
	//delete all drawJobs
	for (int i = 0; i < itemsN; i++) {
		DrawJob* pDJ = drawJobs.at(i);
		delete pDJ;
	}
	drawJobs.clear();
	//delete Buffers
	itemsN = buffersIds.size();
	//delete all buffers
	for (int i = 0; i < itemsN; i++) {
		unsigned int id = buffersIds.at(i);
		glDeleteBuffers(1, &id);
	}
	buffersIds.clear();

	return 1;
}

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


9. Откроем TheGame.cpp и заменим код на:

#include "TheGame.h"
#include "platform.h"
#include "linmath.h"
#include "Texture.h"
#include "Shader.h"
#include "DrawJob.h"

extern std::string filesRoot;

static const struct
{
    float x, y, z, tu, tv;
} vertices[4] =
{
    { -0.5f,  0.5f, 0.f, 0.f, 0.f }, //top-left
    { -0.5f, -0.5f, 0.f, 0.f, 1.f }, //bottom-left
    {  0.5f,  0.5f, 0.f, 1.f, 0.f }, //top-right
    {  0.5f, -0.5f, 0.f, 1.f, 1.f }  //bottom-right
};

DrawJob* pDJ;
float angle_z = 0;

int TheGame::getReady() {
    bExitGame = false;
    Shader::loadShaders();

    pDJ = new DrawJob();
    //define material
    pDJ->mt.shaderN = Shader::spN_flat_tex; 
    pDJ->mt.primitiveType = GL_TRIANGLE_STRIP;
    pDJ->mt.uTex0 = Texture::loadTexture(filesRoot + "/dt/sample_img.png");
    //pDJ->mt.color0.setRGBA(1.f, 0.f, 1.f, 1.f);
    //pDJ->mt.color0.setRGBA(255, 0, 255,255);

    //build and attach data source(s)
    pDJ->pointsN = 4; //number of vertices
    unsigned int glVBOid = pDJ->newBufferId();
    glBindBuffer(GL_ARRAY_BUFFER, glVBOid);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    //describe data source
    int stride = sizeof(float) * 5;
    //attributes references
    AttribRef* pAR;
    pAR = &pDJ->aPos; pAR->offset = 0;                 pAR->glVBOid = glVBOid; pAR->stride = stride;
    pAR = &pDJ->aTuv; pAR->offset = sizeof(float) * 3; pAR->glVBOid = glVBOid; pAR->stride = stride;

    //create and fill vertex attributes array (VAO)
    pDJ->buildVAO();
    //end of data stream setting, now it is stored in VAO
    return 1;
}
int TheGame::drawFrame() {
    myPollEvents();

    mat4x4 m, p, mvp;
    //glClearColor(0.0, 0.0, 0.5, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);
    angle_z += 0.01f;
    mat4x4_identity(m);
    mat4x4_rotate_Z(m, m, angle_z);
    mat4x4_scale_aniso(m, m, 2.0, 1.0, 1.0);
    mat4x4_ortho(p, -screenRatio, screenRatio, -1.f, 1.f, 1.f, -1.f);
    mat4x4_mul(mvp, p, m);

    pDJ->execute((float*)mvp, NULL);
    mySwapBuffers();
    return 1;
}
int TheGame::cleanUp() {
    Texture::cleanUp();
    Shader::cleanUp();
    DrawJob::cleanUp();
    return 1;
}
int TheGame::onScreenResize(int width, int height) {
    if (screenSize[0] == width && screenSize[1] == height)
        return 0;
    screenSize[0] = width;
    screenSize[1] = height;
    screenRatio = (float)width / (float)height;
    glViewport(0, 0, width, height);
    mylog(" screen size %d x %d\n", width, height);
    return 1;
}
int TheGame::run() {
    getReady();
    while (!bExitGame) {
        drawFrame();
    }
    cleanUp();
    return 1;
}


10. Компиляция и запуск – отлично.


Теперь попробуем на

Андроиде

11. Пере-запускаем VS. Открываем C:\CPP\a998engine\p_android\p_android.sln.


12. Под xEndine добавим Existing Item

Идем в C:\CPP\engine

Выбираем

  • DrawJob.cpp
  • DrawJob.h
  • Material.cpp
  • Material.h
  • MyColor.cpp
  • MyColor.h

Add.


Теперь надо добавить Android’s myUint32/8 implementations.

13. Откроем platform.h и заменим код на:

#pragma once

typedef uint32_t myUint64;
typedef uint32_t myUint32;
typedef uint8_t  myUint16;
typedef uint8_t  myUint8;

void mylog(const char* _Format, ...);
void mySwapBuffers();
void myPollEvents();
int myFopen_s(FILE** pFile, const char* filePath, const char* mode);


14. Включаем, разблокируем и подключаем Android, разрешаем debugging.

Компиляция и запуск – отлично.


Leave a Reply

Your email address will not be published. Required fields are marked *