I don't know a lot about C++, I actually know more about C# and I've never made a game without a game engine, I only made games in Unity in this case.
But I want to make games with a lot of GameObjects (for those who know Unity), or objects, items, etc. in the screen, like for example Factorio, but I can't do this on Unity, not from what I know at least, I would have to use ECS and Dots, but it doesn't work on my computer at the moment, according to the forum it's something related to shader model. So the only option for me, is to make a game from scratch, without a game engine and then later learn Data Oriented Programming to optimize the game.
My goal is to make a "3D" Galaxy, with all the location being real and not just rendered (like the code that I will show), obviously I don't want 100 bilions of stars, I think that this is too much, but I want a good number, then I will make a "solar system" in some of the stars at runtime. In my reasearch I managed to found this code "https://github.com/beltoforion/Galaxy-Renderer/tree/master", that is Galaxy Renderer, it's based on this article "https://beltoforion.de/en/spiral_galaxy_renderer/?a=spiral_galaxy_renderer", you can change some variables and create different galaxies, but from what I understand from reading the code, there is a Star struct, but it doesn't even have any x, y and z position on the struct, it seems more like a spherical coordinate system, because it has an angle (theta0) and the tilt (tiltAngle), and the Vec3 which has x, y and z floats isn't used in a lot of places, so it doesn't keep stored the position of the stars, they kinda have a fixed movement and are being rendered in the screen. Unless i'm not understanding it right....
So since I want to make a game, I read a book "Game Programming in C++: Creating 3D Games (Game Design) 1st Edition", it uses C++, OpenGl and SDL, then I tried to implement the code from the Galaxy Renderer repository with code from this book, I manage to make it work, but it's not a 3D galaxy and I don't know how to make it one, I don't think that its even possible, because as I said I don't think that the autor of the code uses any sort of Vector3 to store the positions of any star. So I found another link that is a 3D Galaxy, the link is "https://github.com/frozein/VkGalaxy/tree/d5a33109c34213a4e112cf374795bde21eb92189", but he uses Vulkan, which I don't have any knowledge.
The code from the book that I'm using is here "https://github.com/gameprogcpp/code/tree/master", Chapter 11.
Main.cpp
#include "Game.h"
int main(int argc, char** argv)
{
Game game;
bool success = game.Initialize();
if (success)
{
game.RunLoop();
}
game.Shutdown();
return 0;
}
The "Math.h" is from the book, the link is here: https://github.com/gameprogcpp/code/blob/master/Chapter11/Math.h
Game.h
#pragma once
#include <unordered_map>
#include <string>
#include <vector>
#include "Math.h"
#include <SDL_types.h>
class Game
{
public:
Game();
bool Initialize();
void RunLoop();
void Shutdown();
class Renderer* GetRenderer() { return mRenderer; }
enum GameState
{
EGameplay,
EPaused,
EQuit
};
GameState GetState() const { return mGameState; }
void SetState(GameState state) { mGameState = state; }
private:
void UpdateGame();
void GenerateOutput();
void HandleKeyPress(int key);
void ProcessInput();
class Renderer* mRenderer;
Uint32 mTicksCount;
GameState mGameState;
};
Game.cpp
#include "Game.h"
#include "Renderer.h"
#include <SDL/SDL.h>
#include <algorithm>
#include <fstream>
#include <sstream>
#include <rapidjson/document.h>
Game::Game(): mRenderer(nullptr), mGameState(EGameplay){ }
bool Game::Initialize()
{
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) != 0)
{
SDL_Log("Unable to initialize SDL: %s", SDL_GetError());
return false;
}
mRenderer = new Renderer(this);
//This Initialize function is different from the original, see the Renderer.h and Renderer.cpp to understand.
if (!mRenderer->Initialize(1500, 1000, 35000.0))
{
SDL_Log("Failed to initialize renderer");
delete mRenderer;
mRenderer = nullptr;
return false;
}
//Functions to initialize the galaxy and opengl to draw the galaxy
mRenderer->InitGL();
mRenderer->InitSimulation();
mTicksCount = SDL_GetTicks();
return true;
}
void Game::RunLoop()
{
while (mGameState != EQuit)
{
ProcessInput();
UpdateGame();
GenerateOutput();
mRenderer->UpdateGalaxy();
}
}
void Game::Shutdown()
{
if (mRenderer)
{
mRenderer->Shutdown();
}
SDL_Quit();
}
void Game::UpdateGame()
{
while (!SDL_TICKS_PASSED(SDL_GetTicks(), mTicksCount + 16));
float deltaTime = (SDL_GetTicks() - mTicksCount) / 1000.0f;
if (deltaTime > 0.05f)
{
deltaTime = 0.05f;
}
mTicksCount = SDL_GetTicks();
}
void Game::GenerateOutput()
{
mRenderer->DrawGalaxy();
}
void Game::HandleKeyPress(int key)
{
switch (key)
{
case SDLK_ESCAPE:
SetState(Game::EQuit);
break;
case SDLK_KP_PLUS:
mRenderer->ScaleAxis(0.9f);
mRenderer->SetCameraOrientation({ 0,1,0 });
break;
case SDLK_KP_MINUS:
mRenderer->ScaleAxis(1.1f);
mRenderer->SetCameraOrientation({ 0,1,0 });
break;
default:
break;
}
}
void Game::ProcessInput()
{
SDL_Event event;
while (SDL_PollEvent(&event))
{
switch (event.type)
{
case SDL_QUIT:
mGameState = EQuit;
break;
case SDL_KEYDOWN:
if (!event.key.repeat)
{
if (mGameState == EGameplay)
{
HandleKeyPress(event.key.keysym.sym);
}
}
break;
}
}
}
All these hpp files from the Galaxy Renderer can be found here, https://github.com/beltoforion/Galaxy-Renderer/tree/master.
Renderer.h
#pragma once
#include <string>
#include <vector>
#include <unordered_map>
#include <SDL.h>
#include "Math.h"
#include "Galaxy/Galaxy.hpp"
#include "Galaxy/VertexBufferBase.hpp"
#include "Galaxy/VertexBufferLines.hpp"
#include "Galaxy/VertexBufferStars.hpp"
#include "Galaxy/TextBuffer.hpp"
struct DirectionalLight
{
// Direction of light
Vector3 mDirection;
// Diffuse color
Vector3 mDiffuseColor;
// Specular color
Vector3 mSpecColor;
};
class Renderer
{
public:
Renderer(class Game* game);
~Renderer();
bool Initialize(float screenWidth, float screenHeight, float axisLen);
void Shutdown();
void InitGL();
void InitSimulation();
void AdjustCamera();
void DrawGalaxy();
void UpdateGalaxy();
void SetViewMatrix(const Matrix4& view) { mView = view; }
Vector3 Unproject(const Vector3& screenPoint) const;
void GetScreenDirection(Vector3& outStart, Vector3& outDir) const;
float GetScreenWidth() const { return mScreenWidth; }
float GetScreenHeight() const { return mScreenHeight; }
void SetCameraOrientation(const glm::vec3& orientation);
void ScaleAxis(float scale);
private:
class Game* mGame;
Matrix4 mView;
Matrix4 mProjection;
float mScreenWidth;
float mScreenHeight;
SDL_Window* mWindow;
SDL_GLContext mContext;
//GALAXY VARIABLES
enum class DisplayItem : uint32_t
{
NONE = 0,
AXIS = 1 << 1,
STARS = 1 << 2,
PAUSE = 1 << 3,
HELP = 1 << 4,
DENSITY_WAVES = 1 << 5,
VELOCITY = 1 << 6,
DUST = 1 << 7,
H2 = 1 << 8,
FILAMENTS = 1 << 9,
};
enum RenderUpdateHint : uint32_t
{
ruhNONE = 0,
ruhDENSITY_WAVES = 1 << 1,
ruhAXIS = 1 << 2,
ruhSTARS = 1 << 3,
ruhDUST = 1 << 4,
ruhCREATE_VELOCITY_CURVE = 1 << 5,
ruhCREATE_TEXT = 1 << 7
};
uint32_t _flags;
Galaxy _galaxy;
uint32_t _renderUpdateHint;
VertexBufferLines _vertDensityWaves;
VertexBufferLines _vertAxis;
VertexBufferLines _vertVelocityCurve;
VertexBufferStars _vertStars;
std::vector<Galaxy::GalaxyParam> _predefinedGalaxies;
void UpdateDensityWaves();
void UpdateAxis();
void UpdateStars();
void UpdateVelocityCurve();
void AddEllipsisVertices(std::vector<VertexColor>& vert, std::vector<int>& vertIdx, float a, float b, float angle, uint32_t pertNum, float pertAmp, ColorG col) const;
protected:
glm::mat4 _matProjection;
glm::mat4 _matView;
void SetCamera(const glm::vec3& pos, const glm::vec3& lookAt, const glm::vec3& orient);
glm::vec3 _camPos;
glm::vec3 _camLookAt;
glm::vec3 _camOrient;
float _fov;
};
Texture.h and Shader.h are also from the book, their link are these, https://github.com/gameprogcpp/code/blob/master/Chapter11/Texture.h and https://github.com/gameprogcpp/code/blob/master/Chapter11/Shader.h respectivally.
Renderer.cpp
#include "Renderer.h"
#include "Texture.h"
#include <algorithm>
#include "Shader.h"
#include "Game.h"
#include <glew.h>
Renderer::Renderer(Game* game):mGame(game)
, _flags((int)DisplayItem::STARS | (int)DisplayItem::AXIS | (int)DisplayItem::HELP | (int)DisplayItem::DUST | (int)DisplayItem::H2 | (int)DisplayItem::FILAMENTS)
, _galaxy()
, _renderUpdateHint(ruhDENSITY_WAVES | ruhAXIS | ruhSTARS | ruhDUST | ruhCREATE_VELOCITY_CURVE | ruhCREATE_TEXT)
, _vertDensityWaves(2)
, _vertAxis()
, _vertVelocityCurve(1, GL_DYNAMIC_DRAW)
, _vertStars(GL_FUNC_ADD, GL_ONE)
, _fov(0) { }
Renderer::~Renderer() { }
bool Renderer::Initialize(float screenWidth, float screenHeight, float axisLen)
{
_fov = axisLen;
mScreenWidth = screenWidth;
mScreenHeight = screenHeight;
SDL_Init(SDL_INIT_VIDEO);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
mWindow = SDL_CreateWindow("Super Heroes & Villains", 100, 100,
static_cast<int>(mScreenWidth), static_cast<int>(mScreenHeight), SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
if (!mWindow)
{
SDL_Log("Failed to create window: %s", SDL_GetError());
return false;
}
mContext = SDL_GL_CreateContext(mWindow);
glewExperimental = GL_TRUE;
if (glewInit() != GLEW_OK)
{
SDL_Log("Failed to initialize GLEW.");
return false;
}
glGetError();
SDL_SetWindowFullscreen(mWindow, SDL_WINDOW_FULLSCREEN);
return true;
}
void Renderer::Shutdown()
{
SDL_GL_DeleteContext(mContext);
SDL_DestroyWindow(mWindow);
}
void Renderer::AdjustCamera()
{
double l = _fov / 2.0;
double aspect = (double)static_cast<int>(mScreenWidth) / static_cast<int>(mScreenHeight);
_matProjection = glm::ortho(-l * aspect, l * aspect, -l, l, -l, l);
glm::dvec3 camPos(_camPos.x, _camPos.y, _camPos.z);
glm::dvec3 camLookAt(_camLookAt.x, _camLookAt.y, _camLookAt.z);
glm::dvec3 camOrient(_camOrient.x, _camOrient.y, _camOrient.z);
_matView = glm::lookAt(camPos, camLookAt, camOrient);
}
void Renderer::DrawGalaxy()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
AdjustCamera();
if (_flags & (int)DisplayItem::AXIS)
{
_vertAxis.Draw(_matView, _matProjection);
CHECK_GL_ERROR
}
int features = 0;
if (_flags & (int)DisplayItem::STARS)
features |= 1 << 0;
if (_flags & (int)DisplayItem::DUST)
features |= 1 << 1;
if (_flags & (int)DisplayItem::FILAMENTS)
features |= 1 << 2;
if (_flags & (int)DisplayItem::H2)
features |= 1 << 3;
if (features != 0)
{
_vertStars.UpdateShaderVariables(0, _galaxy.GetPertN(), _galaxy.GetPertAmp(), (int)_galaxy.GetDustRenderSize(), features);
_vertStars.Draw(_matView, _matProjection);
}
if (_flags & (int)DisplayItem::DENSITY_WAVES)
{
_vertDensityWaves.Draw(_matView, _matProjection);
}
if (_flags & (int)DisplayItem::VELOCITY)
_vertVelocityCurve.Draw(_matView, _matProjection);
SDL_GL_SwapWindow(mWindow);
}
void Renderer::UpdateGalaxy()
{
if (!(_flags & (int)DisplayItem::PAUSE))
//_time += GalaxyWnd::TimeStepSize;
if ((_renderUpdateHint & ruhDENSITY_WAVES) != 0)
UpdateDensityWaves();
if ((_renderUpdateHint & ruhAXIS) != 0)
UpdateAxis();
if ((_renderUpdateHint & ruhSTARS) != 0)
UpdateStars();
if ((_renderUpdateHint & ruhCREATE_VELOCITY_CURVE) != 0)
UpdateVelocityCurve();
_camOrient = { 0, 1, 0 };
_camPos = { 0, 0, 5000 };
_camLookAt = { 0, 0, 0 };
}
void Renderer::InitGL()
{
glViewport(0, 0, GetScreenWidth(), GetScreenHeight());
_vertDensityWaves.Initialize();
_vertAxis.Initialize();
_vertVelocityCurve.Initialize();
_vertStars.Initialize();
glDisable(GL_DEPTH_TEST);
glClearColor(0.0f, .0f, 0.08f, 0.0f);
SetCameraOrientation({ 0, 1, 0 });
}
void Renderer::InitSimulation()
{
_galaxy.Reset({13000, 4000, 0.0004f, 0.85f, 0.95f, 100000, true, 2, 40, 70, 4000});
}
void Renderer::UpdateDensityWaves()
{
std::vector<VertexColor> vert;
std::vector<int> idx;
int num = 100;
float dr = _galaxy.GetFarFieldRad() / num;
for (int i = 0; i <= num; ++i)
{
float r = dr * (i + 1);
AddEllipsisVertices(vert, idx, r, r * _galaxy.GetExcentricity(r), Helper::RAD_TO_DEG * _galaxy.GetAngularOffset(r), _galaxy.GetPertN(), _galaxy.GetPertAmp(), { 1, 1, 1, 0.2f });
}
int pertNum = 0;
float pertAmp = 0;
auto r = _galaxy.GetCoreRad();
AddEllipsisVertices(vert, idx, r, r, 0, pertNum, pertAmp, { 1, 1, 0, 0.5 });
r = _galaxy.GetRad();
AddEllipsisVertices(vert, idx, r, r, 0, pertNum, pertAmp, { 0, 1, 0, 0.5 });
r = _galaxy.GetFarFieldRad();
AddEllipsisVertices(vert, idx, r, r, 0, pertNum, pertAmp, { 1, 0, 0, 0.5 });
_vertDensityWaves.CreateBuffer(vert, idx, GL_LINE_STRIP);
_renderUpdateHint &= ~ruhDENSITY_WAVES;
}
void Renderer::UpdateAxis()
{
std::vector<VertexColor> vert;
std::vector<int> idx;
GLfloat s = (GLfloat)std::pow(10, (int)(std::log10(_fov / 2)));
GLfloat l = _fov / 100, p = 0;
float r = 0.3, g = 0.3, b = 0.3, a = 0.8;
for (int i = 0; p < _fov; ++i)
{
p += s;
idx.push_back((int)vert.size());
vert.push_back({ p, -l, 0, r, g, b, a });
idx.push_back((int)vert.size());
vert.push_back({ p, l, 0, r, g, b, a });
idx.push_back((int)vert.size());
vert.push_back({ -p, -l, 0, r, g, b, a });
idx.push_back((int)vert.size());
vert.push_back({ -p, 0, 0, r, g, b, a });
idx.push_back((int)vert.size());
vert.push_back({ -l, p, 0, r, g, b, a });
idx.push_back((int)vert.size());
vert.push_back({ 0, p, 0, r, g, b, a });
idx.push_back((int)vert.size());
vert.push_back({ -l, -p, 0, r, g, b, a });
idx.push_back((int)vert.size());
vert.push_back({ 0, -p, 0, r, g, b, a });
}
idx.push_back((int)vert.size());
vert.push_back({ -_fov, 0, 0, r, g, b, a });
idx.push_back((int)vert.size());
vert.push_back({ _fov, 0, 0, r, g, b, a });
idx.push_back((int)vert.size());
vert.push_back({ 0, -_fov, 0, r, g, b, a });
idx.push_back((int)vert.size());
vert.push_back({ 0, _fov, 0, r, g, b, a });
_vertAxis.CreateBuffer(vert, idx, GL_LINES);
s = (GLfloat)std::pow(10, (int)(std::log10(_fov / 2)));
l = _fov / 100, p = 0;
_renderUpdateHint &= ~ruhAXIS;
}
void Renderer::UpdateStars()
{
std::vector<VertexStar> vert;
std::vector<int> idx;
const auto& stars = _galaxy.GetStars();
float a = 1;
ColorG color = { 1, 1, 1, a };
for (int i = 1; i < stars.size(); ++i)
{
const ColorG& col = Helper::ColorFromTemperature(stars[i].temp);
color = { col.r, col.g, col.b, a };
idx.push_back((int)vert.size());
vert.push_back({ stars[i], color });
}
_vertStars.CreateBuffer(vert, idx, GL_POINTS);
_renderUpdateHint &= ~ruhSTARS;
}
void Renderer::UpdateVelocityCurve()
{
const auto& stars = _galaxy.GetStars();
std::vector<VertexColor> vert;
vert.reserve(1000);
std::vector<int> idx;
idx.reserve(1000);
float r = 0, v = 0;
float cr = 0.5, cg = 1, cb = 1, ca = 1;
for (int r = 0; r < _galaxy.GetFarFieldRad(); r += 100)
{
idx.push_back((int)vert.size());
if (_galaxy.HasDarkMatter())
vert.push_back({ (float)r, Helper::VelocityWithDarkMatter((float)r) * 10.f, 0, cr, cg, cb, ca });
else
vert.push_back({ (float)r, Helper::VelocityWithoutDarkMatter((float)r) * 10.f, 0, cr, cg, cb, ca });
}
_vertVelocityCurve.CreateBuffer(vert, idx, GL_POINTS);
_renderUpdateHint &= ~ruhCREATE_VELOCITY_CURVE;
}
void Renderer::AddEllipsisVertices(std::vector<VertexColor>& vert, std::vector<int>& vertIdx, float a, float b, float angle, uint32_t pertNum, float pertAmp, ColorG col) const
{
const int steps = 100;
const float x = 0;
const float y = 0;
float beta = -angle * Helper::DEG_TO_RAD;
float sinbeta = std::sin(beta);
float cosbeta = std::cos(beta);
int firstPointIdx = static_cast<int>(vert.size());
for (int i = 0; i < 360; i += 360 / steps)
{
float alpha = i * Helper::DEG_TO_RAD;
float sinalpha = std::sin(alpha);
float cosalpha = std::cos(alpha);
GLfloat fx = (GLfloat)(x + (a * cosalpha * cosbeta - b * sinalpha * sinbeta));
GLfloat fy = (GLfloat)(y + (a * cosalpha * sinbeta + b * sinalpha * cosbeta));
if (pertNum > 0)
{
fx += (GLfloat)((a / pertAmp) * std::sin(alpha * 2 * pertNum));
fy += (GLfloat)((a / pertAmp) * std::cos(alpha * 2 * pertNum));
}
vertIdx.push_back((int)vert.size());
VertexColor vc = { fx, fy, 0, col.r, col.g, col.b, col.a };
vert.push_back(vc);
}
vertIdx.push_back(firstPointIdx);
vertIdx.push_back(0xFFFF);
}
void Renderer::SetCameraOrientation(const glm::vec3& orient)
{
_camOrient = orient;
AdjustCamera();
}
void Renderer::ScaleAxis(float scale)
{
_fov *= scale;
AdjustCamera();
}
void Renderer::SetCamera(const glm::vec3& pos, const glm::vec3& lookAt, const glm::vec3& orient)
{
_camOrient = orient;
_camPos = pos;
_camLookAt = lookAt;
AdjustCamera();
}
That code produces the default Galaxy from the Galaxy Renderer.