In my 2D OpenGL Game Project in C++ I am now trying to read the "quantity of enemies" from a file and then render these enemies. Each enemy consisting of 2 triangles with texture (2D sprite).
I could implement rendering with an array declaration for the struct holding individual "enemy" data (e.g. position of each enemy), but if I try to use malloc to dynamically allocate memory according to the number I am reading from the file, nothing is rendered.
What am I doing wrong? I really would like to use malloc as the number of enemies will vary and I wouldn't like to work with a fixed-size array.
About the code below: basically one class called "gameEnemies" holds a struct called "Enemy"; each time you create an object of class "gameEnemies" the object should allocate enough memory to create as many enemies as per what is defined in the external file.
This is the struct with declaration of fixed-size array - this one is working.
struct Enemy {
Point3D center; //object attributes
Vector3D direction; //gets updated with current direction after rotations to show where object is facing at
Vector3D collDir; //which direction collided
float deltaX;
float deltaY;
float prevDeltaX;
float prevDeltaY;
float deltaA;
float prevDeltaA;
float speed;
bool collided;
int qtyCollided; //with how many other objects it collided
bool alive;
//matrices below passed as uniforms to shader
//rotation
Transform4D rotateTransf{ 1.0f, 0.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f, 0.0f };
//moves to world position after being drawn with center at origin (0,0)
Transform4D modelTransf{ 1.0f, 0.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f, 0.0f };
//translation of texture (animation), passed as uniform to shader
Transform4D transformTex{ 1.0f, 0.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f, 0.0f };
}enemies[MAX_TMX_ENEMIES];
This is the struct which is not working - same as above however declaring the pointer instead.
struct Enemy {
Point3D center; //object attributes
Vector3D direction; //gets updated with current direction after rotations to show where object is facing at
Vector3D collDir; //which direction collided
float deltaX;
float deltaY;
float prevDeltaX;
float prevDeltaY;
float deltaA;
float prevDeltaA;
float speed;
bool collided;
int qtyCollided; //with how many other objects it collided
bool alive;
//matrices below passed as uniforms to shader
//rotation
Transform4D rotateTransf{ 1.0f, 0.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f, 0.0f };
//moves to world position after being drawn with center at origin (0,0)
Transform4D modelTransf{ 1.0f, 0.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f, 0.0f };
//translation of texture (animation), passed as uniform to shader
Transform4D transformTex{ 1.0f, 0.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f, 0.0f };
}*enemies;
Below is the loop that allocates memory plus initializes value - seems to work as I can access data and I get no errors at compile time nor at running time:
enemies = (Enemy*)malloc(sizeof(Enemy) * totalObj);
for (int j = 0; j < totalObj; j++) //loop objects in the group
{
enemies[j].center = mapObj.getObjects(i)[j].center;
enemies[j].direction = Vector3D{1.0f, 0.0f, 0.0f};
enemies[j].deltaX = 0.0f;
enemies[j].deltaY = 0.0f;
enemies[j].prevDeltaX = 0.0f;
enemies[j].prevDeltaY = 0.0f;
enemies[j].deltaA = 0.0f;
enemies[j].prevDeltaA = 0.0f;
enemies[j].speed = 6.0f;
enemies[j].collided = false;
enemies[j].collDir = Vector3D{ 0.0f, 0.0f, 0.f };
enemies[j].qtyCollided = 0;
enemies[j].alive = true;
enemies[j].modelTransf.SetTranslation(Point3D{ enemies[j].center.x, enemies[j].center.y, 0.0f }); //initial translation to world position
enemies[j].rotateTransf.SetRotationZ(enemies[j].deltaA); //initial rotation = 0
enemies[j].transformTex.SetTranslation(Point3D{ 0.0f, 0.0f, 0.0f }); //no transform on texture upon object creation
}
Below is the loop to initialize values when the array declaration is used - only change is that the malloc is not preset:
/*EXCLUDED: enemies = (Enemy*)malloc(sizeof(Enemy) * totalObj);*/
for (int j = 0; j < totalObj; j++) //loop objects in the group
{
enemies[j].center = mapObj.getObjects(i)[j].center;
enemies[j].direction = Vector3D{1.0f, 0.0f, 0.0f};
enemies[j].deltaX = 0.0f;
enemies[j].deltaY = 0.0f;
enemies[j].prevDeltaX = 0.0f;
enemies[j].prevDeltaY = 0.0f;
enemies[j].deltaA = 0.0f;
enemies[j].prevDeltaA = 0.0f;
enemies[j].speed = 6.0f;
enemies[j].collided = false;
enemies[j].collDir = Vector3D{ 0.0f, 0.0f, 0.f };
enemies[j].qtyCollided = 0;
enemies[j].alive = true;
enemies[j].modelTransf.SetTranslation(Point3D{ enemies[j].center.x, enemies[j].center.y, 0.0f }); //initial translation to world position
enemies[j].rotateTransf.SetRotationZ(enemies[j].deltaA); //initial rotation = 0
enemies[j].transformTex.SetTranslation(Point3D{ 0.0f, 0.0f, 0.0f }); //no transform on texture upon object creation
}
Below the common render function: works well with array, doesn't render a single object if I use malloc:
void render()
{
// shader to use
objShader.use();
objShader.setVec4("orthoTex", orthoTex); //common to all instances
//for texture
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glBindTexture(GL_TEXTURE_2D, texture);
glBindVertexArray(VAO);
for(int i = 0; i< totalEnemies; i++)
{
//pass relevant uniforms to shader before drawing each instance
objShader.setVec4("transfTex", enemies[i].transformTex);
objShader.setVec4("modelTransf", enemies[i].modelTransf);
objShader.setVec4("rotateTransf", enemies[i].rotateTransf);
//draw
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
}
glBindVertexArray(0);
}
The struct Enemy is inside a struct called "gameEnemies" - not sure if this changes anything but just in case. This is because all the "Enemies" will share common attributes like the OpenGL buffers, vertices, shader, etc.
struct gameEnemies {
protected:
//arrays with vertex data and indices
float vertices[16]; //holds vertex + texture coordinates for VBO (order: obj.x, obj.y, tex.x, tex.y, obj.x...)
unsigned int indices[6]; //holds triangle indices for the EBO
//the OpenGL buffers
unsigned int VBO, VAO, EBO;
//object shader
Shader objShader;
//texture ID
unsigned int texture;
float width;
float height;
int totalEnemies;
int enemyMapPos; //position in the MAP OBJECT (which group) where enemies of specicif "label" for this handler are
//transform texture coordinates to world (png file) coordinates (0,0 //bottom left to 1,1//top right)
Transform4D orthoTex;
struct Enemy {
Point3D center; //object attributes
Vector3D direction; //gets updated with current direction after rotations to show where object is facing at
Vector3D collDir; //which direction collided
float deltaX;
float deltaY;
float prevDeltaX;
etc...
Using malloc
in C++ is, except in very special cases, wrong.
malloc
allocates memory, but does not construct any object in it. Trying to use the memory as if you had created instances of Enemy
in them causes undefined behavior.
You need to use new[]
. It allocates memory and constructs the objects:
enemies = new Enemy[totalObj];
You destruct these objects and free the memory later with delete[] enemies
.
However, you should not use manual dynamic memory management. Use std::vector
instead:
std::vector<Enemy> enemies;
enemies.resize(totalObj);
It is also very unclear why you initialize some of the members in the class definition with default member initializers, but others in an external loop.
Either use default member initializers for all members or write a proper constructor for your struct
doing all the initialization.
Your current approach is confusing and less efficient because the memory must be iterated twice.
It is also dangerous, because if you forget to set one of the uninitialized members when after constructing the Enemy
s, then you will have undefined behavior when using it.