Improved logger, added a debug timer, other small stuff, and wrote more user guide

This commit is contained in:
Grayson Riffe (Laptop) 2021-12-04 02:17:33 -06:00
parent f66c3fb4e0
commit 21764d3f98
25 changed files with 326 additions and 121 deletions

View File

@ -61,6 +61,7 @@
<ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>$(ProjectDir)src\include\;$(SolutionDir)NothinFancy\src\include\</AdditionalIncludeDirectories>
<ObjectFileName>$(IntDir)obj\</ObjectFileName>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
@ -90,6 +91,7 @@ if exist "base.nfpack" (copy "base.nfpack" "$(OutDir)assets\")</Command>
<ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>$(ProjectDir)src\include\;$(SolutionDir)NothinFancy\src\include\</AdditionalIncludeDirectories>
<ObjectFileName>$(IntDir)obj\</ObjectFileName>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>

View File

@ -16,6 +16,8 @@ int main(int argc, char* argv[]) {
app.setDefaultState("Main State");
app.run();
delete test;
}
return 0;

View File

@ -6,11 +6,9 @@ void MainState::onEnter() {
camera->setType(currCamType);
ap.load("example.nfpack");
test.create(ap.get("2mats.obj"), nf::Entity::Type::DYNAMIC);
test.setPosition(nf::Vec3(0.0, 1.5, -5.0));
plane.create(ap.get("env.obj"), nf::Entity::Type::MAP);
test.create(ap.get("2mats.obj"), nf::Vec3(0.0, 1.5, -5.0), nf::Entity::Type::DYNAMIC);
plane.create(ap.get("env.obj"), nf::Vec3(0.0, -20.0, 0.0), nf::Entity::Type::MAP);
plane.setScale(20.0);
plane.setPosition(0.0, -20.0, 0.0);
light.create(nf::Vec3(0.0, 20.0, 0.0), nf::Vec3(1.0, 1.0, 1.0));
light2.create(nf::Vec3(-10.0, 20.0, -10.0), nf::Vec3(1.0, 1.0, 1.0));
@ -42,8 +40,7 @@ void MainState::onEnter() {
for (int y = 0; y < 3; y++) {
for (int z = 0; z < 3; z++) {
entities.push_back(new nf::Entity);
entities.back()->create(nf::BaseAssets::cube, nf::Entity::Type::DYNAMIC);
entities.back()->setPosition(nf::Vec3(5.0 + x * 2.05, 1.0 + y * 2.05, -5.0 + z * 2.05));
entities.back()->create(nf::BaseAssets::cube, nf::Vec3(5.0 + x * 2.05, 1.0 + y * 2.05, -5.0 + z * 2.05), nf::Entity::Type::DYNAMIC);
}
}
}
@ -51,7 +48,8 @@ void MainState::onEnter() {
grav = 2.0f;
setGravity(grav);
amb = 0.5f;
amb = 0.1f;
setAmbientLight(amb);
camera->setPosition(-20.0, 7.0, 0.0);
camera->setRotation(85.0, -30.0);
@ -97,8 +95,7 @@ void MainState::update(float deltaTime) {
if (camera->getType() == nf::Camera::Type::FIRST_PERSON && (app->isMouseClicked(NFI_LEFTMOUSE) || app->isMouseHeld(NFI_RIGHTMOUSE))) {
entities.push_back(new nf::Entity);
entities.back()->create(nf::BaseAssets::sphere, nf::Entity::Type::DYNAMIC);
entities.back()->setPosition(camera->getPosition() + camera->getRotation() * 5.0);
entities.back()->create(nf::BaseAssets::sphere, camera->getPosition() + camera->getRotation() * 5.0, nf::Entity::Type::DYNAMIC);
entities.back()->setVelocity(camera->getRotation() * 100.0f);
entities.back()->setMass(1000.0f);
}

View File

@ -62,6 +62,7 @@
<AdditionalIncludeDirectories>$(ProjectDir)src\include\;$(ProjectDir)dep\include\</AdditionalIncludeDirectories>
<ObjectFileName>$(IntDir)obj\</ObjectFileName>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<TreatWarningAsError>true</TreatWarningAsError>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
@ -94,6 +95,7 @@
<AdditionalIncludeDirectories>$(ProjectDir)src\include\;$(ProjectDir)dep\include\</AdditionalIncludeDirectories>
<ObjectFileName>$(IntDir)obj\</ObjectFileName>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<TreatWarningAsError>true</TreatWarningAsError>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>

View File

@ -16,8 +16,8 @@ namespace nf {
m_stateChange(false),
m_stateChangeStarted(false)
{
NFLog("Creating NF application");
NFLog("Width: " + std::to_string(m_currentConfig.width) + ", Height: " + std::to_string(m_currentConfig.height) + ", Fullscreen: " + std::to_string(m_currentConfig.fullscreen) + ", Title: " + m_currentConfig.title);
NFLog("Constructing application");
NFLog("Width: " + std::to_string(m_currentConfig.width) + ", Height: " + std::to_string(m_currentConfig.height) + ", Fullscreen: " + (m_currentConfig.fullscreen ? "true" : "false") + ", Title: " + m_currentConfig.title);
if (getApp(true) != nullptr)
NFError("Cannot create two NF Application objects!");
@ -78,7 +78,9 @@ namespace nf {
}
void Application::run() {
NFLog("Running application...");
#ifdef _DEBUG
Debug::startTimer();
SetThreadDescription(GetCurrentThread(), L"Input Thread");
#endif
if (m_defaultState.empty())
@ -111,6 +113,9 @@ namespace nf {
std::this_thread::sleep_for(std::chrono::milliseconds(5));
}
mainThread.join();
#ifdef _DEBUG
Debug::stopTimer();
#endif
}
bool Application::hasCustomWindowIcon() {
@ -118,10 +123,12 @@ namespace nf {
}
void Application::changeState(const std::string& stateName) {
if (m_stateChange) return;
if (m_states.find(stateName) == m_states.end())
NFError("State \"" + (std::string)stateName + (std::string)"\" doesn't exist!");
m_stateChange = true;
m_nextState = m_states[stateName];
NFLog("Changing to state \"" + stateName + (std::string)"\"");
}
Gamestate* Application::getCurrentState() {
@ -300,7 +307,6 @@ namespace nf {
void Application::quit() {
m_quit = true;
NFLog("Exiting NF application");
}
void Application::runMainGameThread() {
@ -343,12 +349,11 @@ namespace nf {
}
}
}
m_audio->stopAllSounds();
m_currentState->stop();
delete sIntro;
delete m_physics;
delete m_audio;
delete m_renderer;
delete sIntro;
}
void Application::doStateChange() {
@ -445,6 +450,7 @@ namespace nf {
break;
}
case WM_CLOSE: {
NFLog("Exiting NF application");
DestroyWindow(hWnd);
return 0;
}
@ -456,12 +462,5 @@ namespace nf {
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
Application::~Application() {
for (std::pair<std::string, Gamestate*> state : m_states) {
Gamestate* curr = state.second;
delete curr;
}
}
Application* Application::currentApp = nullptr;
}

View File

@ -59,7 +59,6 @@ namespace nf {
unsigned int cubemapCount = 0;
unsigned int buttonCount = 0;
while (packContents.size()) {
size_t startingPos = packContents.find_first_of("#NFASSET ") + 9;
packContents = packContents.substr(9);
size_t endAssetNamePos = packContents.find_first_of('\n');
std::string assetName = packContents.substr(0, endAssetNamePos);
@ -206,7 +205,6 @@ namespace nf {
NFError("Could not find full button set in pack \"" + (std::string)packName + (std::string)"\"!");
while (packContentsOBJ.size()) {
size_t startingPos = packContentsOBJ.find_first_of("#NFASSET ") + 9;
packContentsOBJ = packContentsOBJ.substr(9);
size_t endAssetNamePos = packContentsOBJ.find_first_of('\n');
std::string assetName = packContentsOBJ.substr(0, endAssetNamePos);
@ -223,7 +221,6 @@ namespace nf {
assetContents = packContentsOBJ;
packContentsOBJ = "";
}
size_t assetSize = assetContents.size();
if (extension == "obj") {
AModel* model = new AModel;
@ -236,6 +233,7 @@ namespace nf {
}
}
assetContents = assetContents.substr(assetContents.find("\n") + 1);
size_t assetSize = assetContents.size();
model->data = new char[assetSize];
std::memcpy(model->data, &assetContents[0], assetSize);
model->size = assetSize;

View File

@ -17,7 +17,7 @@ namespace nf {
NFError("Could not initialize COM!");
hr = XAudio2Create(&m_engine);
if (FAILED(hr))
NFError("Could not initialize the audio engine!");
NFError("Could not initialize audio engine!");
#ifdef _DEBUG
XAUDIO2_DEBUG_CONFIGURATION debug = { 0 };
debug.TraceMask = XAUDIO2_LOG_ERRORS | XAUDIO2_LOG_WARNINGS;
@ -25,12 +25,16 @@ namespace nf {
m_engine->SetDebugConfiguration(&debug, 0);
#endif
hr = m_engine->CreateMasteringVoice(&m_masterVoice);
if (hr == HRESULT_FROM_WIN32(ERROR_NOT_FOUND))
if (hr == HRESULT_FROM_WIN32(ERROR_NOT_FOUND)) {
m_isActive = false;
else if (SUCCEEDED(hr))
NFLog("Audio engine not initialized since no audio devices found");
}
else if (SUCCEEDED(hr)) {
m_isActive = true;
NFLog("Initialized audio engine");
}
else
NFError("Could not initialize the audio engine!");
NFError("Could not initialize audio engine!");
m_threadRunning = true;
m_thread = std::thread(&AudioEngine::runAudioThread, this);
}
@ -42,10 +46,11 @@ namespace nf {
return false;
else if (hr == S_OK) {
m_isActive = true;
NFLog("Initialized audio engine");
return true;
}
else {
NFError("Could not initialize audio!");
NFError("Could not initialize audio engine!");
return false;
}
}
@ -62,6 +67,9 @@ namespace nf {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
if (!m_isActive)
return;
DWORD cm;
m_masterVoice->GetChannelMask(&cm);
X3DAUDIO_HANDLE x3d;
@ -192,6 +200,7 @@ namespace nf {
}
AudioEngine::~AudioEngine() {
stopAllSounds();
m_threadRunning = false;
m_thread.join();
m_engine->Release();

View File

@ -24,6 +24,7 @@ namespace nf {
if (physics)
app->getPhysicsEngine()->newScene();
NFTimerLoad;
onEnter();
m_loading = false;

View File

@ -22,10 +22,11 @@ namespace nf {
m_member = true;
}
void Entity::create(Asset* modelAsset, Type type) {
void Entity::create(Asset* modelAsset, const Vec3& position, Type type) {
if (m_constructed)
NFError("Entity already created!");
m_constructed = true;
setPosition(position);
m_type = type;
AModel* model;
if ((model = dynamic_cast<AModel*>(modelAsset)) == nullptr)

View File

@ -140,7 +140,7 @@ namespace nf {
callbacks.close_func = v_close;
callbacks.tell_func = v_tell;
int open = ov_open_callbacks(&memFile, &file, nullptr, 0, callbacks);
ov_open_callbacks(&memFile, &file, nullptr, 0, callbacks);
char* buff = new char[65536 * 1000];
int stream = 0;
@ -164,7 +164,6 @@ namespace nf {
}
size_t Sound::loadWAV(std::string& data) {
unsigned int fileSize = *(unsigned int*)&data[4];
size_t fmtPos;
if ((fmtPos = data.find("fmt")) == std::string::npos)
NFError("WAV not of correct format!");

View File

@ -30,6 +30,7 @@ namespace nf {
m_stepSize(1.0f / 60.0f),
m_accumulator(0.0)
{
NFLog("Initializing physics engine...");
m_err = new PhysicsErrorCallback;
m_foundation = PxCreateFoundation(PX_PHYSICS_VERSION, m_alloc, *m_err);
if (!m_foundation)
@ -54,6 +55,8 @@ namespace nf {
m_dispacher = PxDefaultCpuDispatcherCreate(threads);
m_defaultMat = m_phy->createMaterial(0.5f, 0.5f, 0.0f);
NFLog("Initialized physics engine");
}
void PhysicsEngine::newScene() {

View File

@ -34,6 +34,7 @@ namespace nf {
m_fadeText(true),
m_fadeOutComplete(false)
{
NFLog("Initializing renderer...");
m_hdc = GetDC(m_app->getWindow());
PIXELFORMATDESCRIPTOR pfd = {
sizeof(PIXELFORMATDESCRIPTOR),
@ -123,6 +124,8 @@ namespace nf {
m_quadVAO->finishBufferLayout();
m_quadIB = new IndexBuffer(quadIB, 6);
m_loadingText.create("NFLoadingText", Vec2(0.025f, 0.044f), Vec3(0.7f));
NFLog("Initialized renderer");
}
void Renderer::setFade(bool in, bool out, bool text) {
@ -165,59 +168,57 @@ namespace nf {
glm::mat4 proj = glm::perspective(glm::radians(45.0f), (float)m_app->getConfig().width / (float)m_app->getConfig().height, 0.1f, 1000.0f);
camera->update(m_gBufferShader, m_lightingShader, m_cubemapShader);
//First, fill the gBuffer with entities
m_gBufferShader->setUniform("proj", proj);
m_gBuffer->render(m_lGame, m_gBufferShader);
if (m_lGame.size()) {
//First, fill the gBuffer with entities
m_gBufferShader->setUniform("proj", proj);
m_gBuffer->render(m_lGame, m_gBufferShader);
//Light entities using the gBuffer
size_t lightsRemaining = m_lights.size();
if (!lightsRemaining) {
m_quadVAO->bind();
m_quadIB->bind();
m_lightingShader->bind();
m_gBuffer->bindTextures(m_lightingShader);
glDrawElements(GL_TRIANGLES, m_quadIB->getCount(), GL_UNSIGNED_INT, nullptr);
//Light entities using the gBuffer
size_t lightsRemaining = m_lights.size();
unsigned int drawCount = 0;
do {
size_t currLightsDrawn;
if (lightsRemaining > m_texSlots)
currLightsDrawn = m_texSlots;
else
currLightsDrawn = lightsRemaining;
lightsRemaining -= currLightsDrawn;
m_lightingShader->setUniform("numberOfLights", (int)currLightsDrawn);
if (drawCount == 0)
m_lightingShader->setUniform("isContinued", false);
else {
m_lightingShader->setUniform("isContinued", true);
glBlendFunc(GL_ONE, GL_ONE);
glDepthFunc(GL_LEQUAL);
}
for (unsigned int i = 0; i < currLightsDrawn; i++)
m_lights[i]->bind(m_lightingShader, i);
renderShadowMaps(currLightsDrawn);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glViewport(0, 0, m_app->getConfig().width, m_app->getConfig().height);
m_quadVAO->bind();
m_quadIB->bind();
m_lightingShader->bind();
m_gBuffer->bindTextures(m_lightingShader);
#ifdef _DEBUG
m_lightingShader->validate();
#endif
glDrawElements(GL_TRIANGLES, m_quadIB->getCount(), GL_UNSIGNED_INT, nullptr);
m_lights.erase(m_lights.begin(), m_lights.begin() + currLightsDrawn);
drawCount++;
} while (lightsRemaining > 0);
m_lGame.clear();
m_lights.clear();
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDepthFunc(GL_LESS);
}
unsigned int drawCount = 0;
while (lightsRemaining > 0) {
size_t currLightsDrawn;
if (lightsRemaining > m_texSlots)
currLightsDrawn = m_texSlots;
else
currLightsDrawn = lightsRemaining;
lightsRemaining -= currLightsDrawn;
m_lightingShader->setUniform("numberOfLights", (int)currLightsDrawn);
if(drawCount == 0)
m_lightingShader->setUniform("isContinued", false);
else {
m_lightingShader->setUniform("isContinued", true);
glBlendFunc(GL_ONE, GL_ONE);
glDepthFunc(GL_LEQUAL);
}
for (unsigned int i = 0; i < currLightsDrawn; i++)
m_lights[i]->bind(m_lightingShader, i);
renderShadowMaps(currLightsDrawn);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glViewport(0, 0, m_app->getConfig().width, m_app->getConfig().height);
m_quadVAO->bind();
m_quadIB->bind();
m_lightingShader->bind();
m_gBuffer->bindTextures(m_lightingShader);
glDrawElements(GL_TRIANGLES, m_quadIB->getCount(), GL_UNSIGNED_INT, nullptr);
m_lights.erase(m_lights.begin(), m_lights.begin() + currLightsDrawn);
drawCount++;
}
m_lGame.clear();
m_lights.clear();
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDepthFunc(GL_LESS);
//Draw the cubemap if one is currently set
if (m_cubemap != nullptr) {
m_cubemapShader->setUniform("proj", proj);
m_cubemap->render(m_cubemapShader);
m_cubemap = nullptr;
}
m_cubemap = nullptr;
//Draw UI elements
glDisable(GL_DEPTH_TEST);
@ -297,6 +298,7 @@ namespace nf {
}
void Renderer::loadBaseAssets() {
NFLog("Loading base assets...");
m_baseAP.load("base.nfpack");
const char* gBufferVertex = m_baseAP.get("gBufferVertex.shader")->data;
const char* gBufferFragment = m_baseAP.get("gBufferFragment.shader")->data;
@ -334,6 +336,8 @@ namespace nf {
BaseAssets::cubemap = (ACubemap*)m_baseAP.get("default.cm");
BaseAssets::font = (AFont*)m_baseAP.get("default.ttf");
BaseAssets::button = (AButton*)m_baseAP.get("default.button");
NFLog("Base assets loaded");
}
void Renderer::createShadowMaps() {
@ -371,7 +375,6 @@ namespace nf {
glm::mat4 lightView;
glm::mat4 lightSpaceMat;
bool directionalRendered = false;
unsigned int directionalSlot = 0; //TODO: Test this
glBindFramebuffer(GL_FRAMEBUFFER, m_shadowMapFBO);
for (unsigned int i = 0; i < count; i++) {
Light::Type type = m_lights[i]->getType();

View File

@ -19,47 +19,79 @@ namespace nf {
#ifdef _DEBUG
NFDEBUGINIT;
void Debug::startTimer() {
m_timerStarted = true;
m_initTime = std::chrono::steady_clock::now();
}
void Debug::stopTimer() {
m_timerStarted = false;
}
void Debug::LogImp(const char* in) {
std::chrono::duration<float> time = getCurrentTime();
std::printf("[%.4f] NF Log: %s\n", time.count(), in);
if(m_timerStarted)
printCurrentTime();
std::printf("NF Log: %s\n", in);
}
void Debug::LogImp(const std::string& in) {
std::chrono::duration<float> time = getCurrentTime();
std::printf("[%.4f] NF Log: ", time.count());
if (m_timerStarted)
printCurrentTime();
std::printf("NF Log: ");
std::cout << in << "\n";
}
void Debug::LogImp(int in) {
std::chrono::duration<float> time = getCurrentTime();
std::printf("[%.4f] NF Log: %i\n", time.count(), in);
if (m_timerStarted)
printCurrentTime();
std::printf("NF Log: %i\n", in);
}
void Debug::LogImp(float in) {
std::chrono::duration<float> time = getCurrentTime();
std::printf("[%.4f] NF Log: %.4f\n", time.count(), in);
if (m_timerStarted)
printCurrentTime();
std::printf("NF Log: %.4f\n", in);
}
//TODO: Test every Error in release mode
void Debug::ErrorImp(const char* in, const char* filename, int line) {
std::chrono::duration<float> time = getCurrentTime();
if (m_timerStarted)
printCurrentTime();
static HANDLE cmd = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleTextAttribute(cmd, FOREGROUND_RED);
std::printf("[%.4f] NF Error (%s, %i): %s\n", time.count(), filename, line, in);
std::printf("NF Error (%s, %i): %s\n", filename, line, in);
SetConsoleTextAttribute(cmd, 7);
}
void Debug::ErrorImp(const std::string& in, const char* filename, int line) {
std::chrono::duration<float> time = getCurrentTime();
if (m_timerStarted)
printCurrentTime();
static HANDLE cmd = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleTextAttribute(cmd, FOREGROUND_RED);
std::printf("[%.4f] NF Error (%s, %i): ", time.count(), filename, line);
std::printf("NF Error (%s, %i): ", filename, line);
std::cout << in << "\n";
SetConsoleTextAttribute(cmd, 7);
}
std::chrono::duration<float> Debug::getCurrentTime() {
void Debug::printCurrentTime() {
std::chrono::steady_clock::time_point now = std::chrono::high_resolution_clock::now();
return now - m_initTime;
std::chrono::duration<float> dur = now - m_initTime;
std::printf("[%.4f] ", dur.count());
}
Timer::Timer(const std::string& function, bool onEnter) {
m_funcName = function;
m_initTime = std::chrono::steady_clock::now();
m_loading = onEnter;
if (!m_loading)
NFLog("Started timing \"" + m_funcName + (std::string)"\"");
}
Timer::~Timer() {
std::chrono::duration<float> dur = std::chrono::steady_clock::now() - m_initTime;
if (!m_loading)
NFLog("\"" + m_funcName + (std::string)"\" took " + std::to_string(dur.count()) + (std::string)" seconds.");
else
NFLog("Loading took " + std::to_string(dur.count()) + (std::string)" seconds.");
}
#endif

View File

@ -1,11 +1,8 @@
//This is the main header for the entire engine.
#pragma once
//TODO: Rework this file to only contain functions the frontend will need to access
//Maybe a implementation define here?
//TODO: Prevent including other headers other than this one
#ifndef NFIMPL
#define NFIMPL 1
#define NFIMPL
#endif
#include "nf/Config.h"

View File

@ -177,7 +177,6 @@ namespace nf {
void getMouseDiff(int& x, int& y);
static Application* getApp(bool first = false);
#endif
~Application();
private:
void registerWindowClass();
RECT getWindowRect() const;

View File

@ -53,6 +53,7 @@ namespace nf {
/**
* @brief Creates an entity
* @param modelAsset A model Asset pointer
* @param position Initial position vector
* @param type Type of entity; Defaults to Type::STATIC
*
* This function will initialize an entity by loading its associated model from
@ -61,7 +62,7 @@ namespace nf {
* @warning Calling this function twice before the state exits will result in an
* error. See @ref isConstructed.
*/
void create(Asset* modelAsset, Type type = Type::STATIC);
void create(Asset* modelAsset, const Vec3& position, Type type = Type::STATIC);
/**
* @brief Queries whether or not the entity has been created
* @return If the entity has been created

View File

@ -14,10 +14,10 @@ namespace nf {
class Texture;
/**
* @brief A state NF can be in that includes a collection of objects and user-defined
* @brief An engine state that includes a world, objects, and user-defined
* behavior
*
* Every user-defined state inherits from this class.
* Every state inherits from this class.
*/
class Gamestate {
public:
@ -37,7 +37,7 @@ namespace nf {
* @brief Update function
* @param deltaTime Amount of time the previous frame took to produce in seconds
*
* This function is called every frame. It is called before render.
* This function is called every frame. It is called before @ref render.
*
* The deltaTime parameter's purpose is to create non-frame-dependant gameplay. This
* number should be multiplied with user numbers likes velocities. Doing this will

View File

@ -7,7 +7,7 @@
/**
* @brief Nothin' Fancy namespace
*
* Every class and struct lives inside of this namespace
* Every class and struct lives inside of this namespace.
*
* It could be useful to `using` this namespace:
*
@ -20,7 +20,15 @@ namespace nf {
//Strips __FILE__ down to only the name of the file
#define __FILENAME__ strrchr(__FILE__, '\\') + 1
//Initializes static variables needed for debugging
#define NFDEBUGINIT std::chrono::steady_clock::time_point Debug::m_initTime = std::chrono::high_resolution_clock::now();
#define NFDEBUGINIT std::chrono::steady_clock::time_point Debug::m_initTime = std::chrono::high_resolution_clock::now(); \
bool Debug::m_timerStarted = false
/**
* @defgroup macros Macros
*
* Macros to aid in debugging and developing with NF
*
* @{
*/
/**
* Pauses the engine for a set amount of seconds
*/
@ -43,11 +51,33 @@ namespace nf {
*/
#define NFError(x) {nf::Debug::ErrorImp(x,__FILENAME__, __LINE__);\
__debugbreak();}
/**
* @brief Handles NFLog and NFError calls
*/
/**
* A timer useful for timing functions
*
* To time a function, place this macro at the beginning of it:
*
* ~~~
* void myFunc() {
* NFTimer;
* //Rest of function to be timed...
* } //Prints here
* ~~~
*
* The result will be logged when the scope it's declared in ends.
*/
#define NFTimer nf::Timer _nfTimer(__func__)
/**
* @}
*/
#ifndef NFIMPL
#define NFTimerLoad nf::Timer _nfTimer(__func__, true);
#endif
class Debug {
public:
static void startTimer();
static void stopTimer();
static void LogImp(const char* in);
static void LogImp(const std::string& in);
static void LogImp(int in);
@ -57,8 +87,20 @@ __debugbreak();}
[[noreturn]]
static void ErrorImp(const std::string& in, const char* filename, int line);
private:
static void printCurrentTime();
static std::chrono::steady_clock::time_point m_initTime;
static std::chrono::duration<float> getCurrentTime();
static bool m_timerStarted;
};
class Timer {
public:
Timer(const std::string& function, bool onEnter = false);
~Timer();
private:
std::chrono::steady_clock::time_point m_initTime;
bool m_loading;
std::string m_funcName;
};
#else
#define NFDEBUGINIT
@ -67,6 +109,8 @@ __debugbreak();}
#define NFLog(x)
#define NFError(x) {MessageBox(FindWindow(L"NFClass", NULL), toWide(x).data(), L"NF Engine Error", MB_OK | MB_ICONERROR);\
std::exit(-1);}
#define NFTimer
#define NFTimerLoad
#endif
/**
@ -364,10 +408,11 @@ std::exit(-1);}
float w;
};
#ifndef NFIMPL
const std::wstring toWide(const char* in);
const std::wstring toWide(const std::string& in);
Vec4 degToQuat(const Vec3& in);
#endif
/**
* @brief Writes a stream of bytes as as std::string into a file in a specified location

View File

@ -169,7 +169,7 @@ HTML_HEADER = header.html
HTML_FOOTER = footer.html
HTML_STYLESHEET =
HTML_EXTRA_STYLESHEET = theme.css
HTML_EXTRA_FILES = favicon.png
HTML_EXTRA_FILES = images/favicon.png
HTML_COLORSTYLE_HUE = 30
HTML_COLORSTYLE_SAT = 100
HTML_COLORSTYLE_GAMMA = 80
@ -199,7 +199,7 @@ QHP_SECT_FILTER_ATTRS =
QHG_LOCATION =
GENERATE_ECLIPSEHELP = NO
ECLIPSE_DOC_ID = org.doxygen.Project
DISABLE_INDEX = NO
DISABLE_INDEX = YES
GENERATE_TREEVIEW = YES
FULL_SIDEBAR = NO
ENUM_VALUES_PER_LINE = 4

BIN
docs/images/applifetime.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

BIN
docs/images/blankapp.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -4,11 +4,12 @@
<navindex>
<tab type="mainpage" visible="yes" title=""/>
<tab type="pages" visible="yes" title="" intro=""/>
<tab type="modules" visible="yes" title="" intro=""/>
<tab type="modules" visible="no" title="" intro=""/>
<tab type="namespaces" visible="no" title="API">
<tab type="namespacelist" visible="yes" title="API" intro="Here, you can find a list of every useful class to use in NF."/>
<tab type="namespacemembers" visible="no" title="" intro=""/>
<tab type="namespacelist" visible="yes" title="API" intro="Here, you can find a list of every useful class to use in NF."/>
<tab type="namespacemembers" visible="no" title="" intro=""/>
</tab>
<tab type="user" url="@ref macros" title="Macros" intro=""/>
<tab type="concepts" visible="yes" title="">
</tab>
<tab type="interfaces" visible="yes" title="">
@ -35,7 +36,7 @@
<tab type="filelist" visible="no" title="" intro=""/>
<tab type="globals" visible="no" title="" intro=""/>
</tab>
<tab type="examples" visible="yes" title="" intro=""/>
<tab type="examples" visible="yes" title="Snippets" intro="Here, you can find a list of snippets that showcase different features of the engine."/>
</navindex>
<!-- Layout definition for a class page -->

View File

@ -6,13 +6,127 @@ This tutorial aims to teach the basics of the engine and how to use it.
First, follow the steps on the @ref install page. Once the template MSVC project is setup,
you can begin here.
@section nfArch NF Engine Architecture
@section nfArch Engine Architecture
An NF app is made up of a set of states represented by the nf::Gamestate class. When
an nf::Application is running, it is either running a state or loading one. Below is an
image that describes what the main thread of an NF app would be typically doing in
the program's lifetime.
@image html applifetime.png "The lifetime of a typical NF app" width=20%
Using the engine's architecture, you might not even write any functions that are called
from `main`. Most of the code that programs the engine's behavior should be called in
your state's [update function](@ref nf::Gamestate::update).
To allow a translate unit to use the engine, you must include `NothinFancy.h`. This
header contains every class and function.
@section createConfig Creating a Config
@todo The tutorial page
The first step to creating an app is creating an nf::Config. A config describes how the
engine should display on the screen. nf::Config has these fields:
- `width` - The width of the window if `fullscreen` is set to `false`
- `height` - The height of the window if `fullscreen` is set to `false`
- `fullscreen` - `true` sets the display to the size of the monitor the app is
- `title` - The title of the window shown on the caption bar and taskbar
opened on
To create a 1280 by 720 window with a title of "NF Example", you would write:
~~~cpp
nf::Config conf;
conf.width = 1280;
conf.height = 720;
conf.fullscreen = false;
conf.title = "NF Example";
//Or...
nf::Config conf2{ 1280, 720, false, "NF Example" };
~~~
We then pass this config to an nf::Application
@section createApp Creating and Configuring an Application
The nf::Application class represents an instance of the engine. This is the point where
you will attach your states and run the engine.
@note In a program and on a single machine, there can only be one single instance of
this class at a time. Attempting to create mulitple will result in an error.
The constructor takes in the nf::Config we previously created:
~~~cpp
nf::Application app(conf);
~~~
Constructing an application doesn't do much. It merely allows you to access the member
functions to setup your application.
Here are some functions you might want to call at this point:
- [setWindowIcon](@ref nf::Application::setWindowIcon) - Sets the icon of the window
- [setWindowCursor](@ref nf::Application::setWindowCursor) - Sets the cursor's image
when it is visible and inside the window
And here are the functions you **must** call before an app can run:
- [addState](@ref nf::Application::addState) - Adds a state to an app so that it can
access it later by a user-defined string identifier
- [setDefaultState](@ref nf::Application::setDefaultState) - Sets the state to load
after the logo state exits
Once these functions have been called, the app can be run:
~~~cpp
CustomGamestate* customState = new CustomGamestate; //Inherits nf::Gamestate
app.addState(customState, "State 1"); //"State One" is this state's identifier.
app.setDefaultState("State 1"); //Will error without this
app.run(); //Blocks until exit
~~~
@section createGamestate Creating a Gamestate
To create a gamestate, you must create a class that publicly inherits nf::Gamestate
and overrides its four virtual functions:
- [onEnter](@ref nf::Gamestate::onEnter) - Called when the state is loading; Where member
objects are initialized
- [update](@ref nf::Gamestate::update) - Called every frame after loading is complete;
Where custom behavior is defined
- [render](@ref nf::Gamestate::render) - Called after update; Selects what to render
- [onExit](@ref nf::Gamestate::onExit) - Called when the state is unloaded
A gamestate class might look something like this:
~~~cpp
class CustomGamestate : public nf::Gamestate {
public:
void onEnter() override;
void update(float deltaTime) override; //Note the parameter
void render(nf::Renderer& renderer) override;
void onExit() override;
//Implementations somewhere else...
private:
//Member objects here...
};
~~~
Once an app has been setup and run with an empty state, you should be met with a black screen
after the logo state:
@image html blankapp.png "A blank gamestate" width=50%
Congratulations! You now have a basic NF app running. Now we can add objects to our world.
@section createEntities Adding 3D Objects
@section createUI Creating a UI
@ -20,4 +134,4 @@ you can begin here.
@todo Lighting page?
@section packaging Packaging Your Game
@section packaging Packaging Your App

View File

@ -314,7 +314,7 @@ pre.fragment {
}
div.fragment {
padding: 0 0 1px 0; /*Fixed: last line underline overlap border*/
padding: 5px 0 5px 5px; /*Fixed: last line underline overlap border*/
margin: 4px 8px 4px 2px;
background-color: #FDFCFB;
border: 1px solid #E5D5C4;
@ -322,9 +322,9 @@ div.fragment {
div.line {
font-family: monospace, fixed;
font-size: 13px;
font-size: 15px;
min-height: 13px;
line-height: 1.0;
line-height: 1.1;
text-wrap: unrestricted;
white-space: -moz-pre-wrap; /* Moz */
white-space: -pre-wrap; /* Opera 4-6 */