Wrote a lot of the tutorial; Minor changes
This commit is contained in:
parent
21764d3f98
commit
8c67ceb5a4
@ -24,6 +24,9 @@ namespace nf {
|
|||||||
|
|
||||||
if (physics)
|
if (physics)
|
||||||
app->getPhysicsEngine()->newScene();
|
app->getPhysicsEngine()->newScene();
|
||||||
|
|
||||||
|
setAmbientLight(0.1f);
|
||||||
|
|
||||||
NFTimerLoad;
|
NFTimerLoad;
|
||||||
onEnter();
|
onEnter();
|
||||||
|
|
||||||
|
@ -6,11 +6,10 @@
|
|||||||
|
|
||||||
namespace nf {
|
namespace nf {
|
||||||
void IntroGamestate::onEnter() {
|
void IntroGamestate::onEnter() {
|
||||||
NFLog("Intro onEnter!");
|
|
||||||
m_scale = 2.0;
|
m_scale = 2.0;
|
||||||
m_logoTex.create(BaseAssets::logo, Vec2(0.0, 0.0));
|
m_logoTex.create(BaseAssets::logo, Vec2(0.0, 0.0));
|
||||||
m_logoTex.centered(true, true);
|
m_logoTex.centered(true, true);
|
||||||
m_text.create("(c) Grayson Riffe 2021", Vec2(0.01f, 0.025f), Vec3(0.8f));
|
m_text.create("(c) Grayson Riffe 2021 | graysonriffe.com", Vec2(0.01f, 0.025f), Vec3(0.8f));
|
||||||
m_text.setScale(0.6f);
|
m_text.setScale(0.6f);
|
||||||
m_start = std::chrono::steady_clock::now();
|
m_start = std::chrono::steady_clock::now();
|
||||||
}
|
}
|
||||||
@ -37,6 +36,6 @@ namespace nf {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void IntroGamestate::onExit() {
|
void IntroGamestate::onExit() {
|
||||||
NFLog("Intro onExit!");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -89,7 +89,7 @@ namespace nf {
|
|||||||
Timer::~Timer() {
|
Timer::~Timer() {
|
||||||
std::chrono::duration<float> dur = std::chrono::steady_clock::now() - m_initTime;
|
std::chrono::duration<float> dur = std::chrono::steady_clock::now() - m_initTime;
|
||||||
if (!m_loading)
|
if (!m_loading)
|
||||||
NFLog("\"" + m_funcName + (std::string)"\" took " + std::to_string(dur.count()) + (std::string)" seconds.");
|
NFLog("\"" + m_funcName + (std::string)"\" took " + std::to_string(dur.count() * 1000.0f) + (std::string)" ms.");
|
||||||
else
|
else
|
||||||
NFLog("Loading took " + std::to_string(dur.count()) + (std::string)" seconds.");
|
NFLog("Loading took " + std::to_string(dur.count()) + (std::string)" seconds.");
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ namespace nf {
|
|||||||
* texture.
|
* texture.
|
||||||
*
|
*
|
||||||
* @sa @ref customButtons
|
* @sa @ref customButtons
|
||||||
* @ref createUI
|
* @ref createUITut
|
||||||
*/
|
*/
|
||||||
class Button : public UIElement, public NFObject {
|
class Button : public UIElement, public NFObject {
|
||||||
public:
|
public:
|
||||||
|
@ -11,7 +11,8 @@ namespace nf {
|
|||||||
*
|
*
|
||||||
* A cubemap is a cube with a texture on each one of its 6 sides.
|
* A cubemap is a cube with a texture on each one of its 6 sides.
|
||||||
*
|
*
|
||||||
* @sa @ref createCubemap @ref customCubemap
|
* @sa @ref createCubemapTut
|
||||||
|
* @ref customCubemap
|
||||||
*/
|
*/
|
||||||
class Cubemap : public Drawable, public NFObject {
|
class Cubemap : public Drawable, public NFObject {
|
||||||
public:
|
public:
|
||||||
|
@ -52,20 +52,20 @@ bool Debug::m_timerStarted = false
|
|||||||
#define NFError(x) {nf::Debug::ErrorImp(x,__FILENAME__, __LINE__);\
|
#define NFError(x) {nf::Debug::ErrorImp(x,__FILENAME__, __LINE__);\
|
||||||
__debugbreak();}
|
__debugbreak();}
|
||||||
/**
|
/**
|
||||||
* A timer useful for timing functions
|
* Times how long functions or scopes take to execute in milliseconds
|
||||||
*
|
*
|
||||||
* To time a function, place this macro at the beginning of it:
|
* To time a function, place this macro at the beginning of it:
|
||||||
*
|
*
|
||||||
* ~~~
|
* ~~~
|
||||||
* void myFunc() {
|
* void myFunc() {
|
||||||
* NFTimer;
|
* NFTimeFunc;
|
||||||
* //Rest of function to be timed...
|
* //Rest of function to be timed...
|
||||||
* } //Prints here
|
* } //Prints here
|
||||||
* ~~~
|
* ~~~
|
||||||
*
|
*
|
||||||
* The result will be logged when the scope it's declared in ends.
|
* The result will be logged when the scope it's declared in ends.
|
||||||
*/
|
*/
|
||||||
#define NFTimer nf::Timer _nfTimer(__func__)
|
#define NFTimeFunc nf::Timer _nfTimer(__func__)
|
||||||
/**
|
/**
|
||||||
* @}
|
* @}
|
||||||
*/
|
*/
|
||||||
@ -109,7 +109,7 @@ __debugbreak();}
|
|||||||
#define NFLog(x)
|
#define NFLog(x)
|
||||||
#define NFError(x) {MessageBox(FindWindow(L"NFClass", NULL), toWide(x).data(), L"NF Engine Error", MB_OK | MB_ICONERROR);\
|
#define NFError(x) {MessageBox(FindWindow(L"NFClass", NULL), toWide(x).data(), L"NF Engine Error", MB_OK | MB_ICONERROR);\
|
||||||
std::exit(-1);}
|
std::exit(-1);}
|
||||||
#define NFTimer
|
#define NFTimeFunc
|
||||||
#define NFTimerLoad
|
#define NFTimerLoad
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio Version 16
|
# Visual Studio Version 17
|
||||||
VisualStudioVersion = 16.0.31605.320
|
VisualStudioVersion = 17.0.31919.166
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "NothinFancy", "NothinFancy\NothinFancy.vcxproj", "{1B9C5361-E301-41BF-97E7-56D65F11E2BB}"
|
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "NothinFancy", "NothinFancy\NothinFancy.vcxproj", "{1B9C5361-E301-41BF-97E7-56D65F11E2BB}"
|
||||||
EndProject
|
EndProject
|
||||||
|
BIN
docs/images/basiclighting.png
Normal file
BIN
docs/images/basiclighting.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 28 KiB |
BIN
docs/images/cameramovement.png
Normal file
BIN
docs/images/cameramovement.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 58 KiB |
BIN
docs/images/cubemap.png
Normal file
BIN
docs/images/cubemap.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 855 KiB |
BIN
docs/images/mouselook.png
Normal file
BIN
docs/images/mouselook.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 68 KiB |
BIN
docs/images/objects.png
Normal file
BIN
docs/images/objects.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.4 KiB |
@ -24,7 +24,7 @@ Inside the zip file, you will find:
|
|||||||
- **example** - The example app complete with the source and a build
|
- **example** - The example app complete with the source and a build
|
||||||
- **manual** - An offline version of this manual
|
- **manual** - An offline version of this manual
|
||||||
- **index.html** - The homepage
|
- **index.html** - The homepage
|
||||||
- **redist** - MSVC Redistributable (See @ref packaging)
|
- **redist** - MSVC Redistributable (See @ref packagingTut)
|
||||||
- **template** - A template MSVC project already setup to work with the engine
|
- **template** - A template MSVC project already setup to work with the engine
|
||||||
|
|
||||||
---
|
---
|
||||||
|
@ -6,12 +6,12 @@ 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,
|
First, follow the steps on the @ref install page. Once the template MSVC project is setup,
|
||||||
you can begin here.
|
you can begin here.
|
||||||
|
|
||||||
@section nfArch Engine Architecture
|
@section nfArchTut Engine Architecture
|
||||||
|
|
||||||
An NF app is made up of a set of states represented by the nf::Gamestate class. When
|
An NF app is made up of a set of states represented by the nf::Gamestate class. A gamestate
|
||||||
an nf::Application is running, it is either running a state or loading one. Below is an
|
has a set of objects and programmed behavior. When an nf::Application is running,
|
||||||
image that describes what the main thread of an NF app would be typically doing in
|
it is either running a state or loading one. Below is an image that describes what the
|
||||||
the program's lifetime.
|
main thread of an NF app might be typically doing in the program's lifetime.
|
||||||
|
|
||||||
@image html applifetime.png "The lifetime of a typical NF app" width=20%
|
@image html applifetime.png "The lifetime of a typical NF app" width=20%
|
||||||
|
|
||||||
@ -22,7 +22,7 @@ your state's [update function](@ref nf::Gamestate::update).
|
|||||||
To allow a translate unit to use the engine, you must include `NothinFancy.h`. This
|
To allow a translate unit to use the engine, you must include `NothinFancy.h`. This
|
||||||
header contains every class and function.
|
header contains every class and function.
|
||||||
|
|
||||||
@section createConfig Creating a Config
|
@section createConfigTut Creating a Config
|
||||||
|
|
||||||
The first step to creating an app is creating an nf::Config. A config describes how the
|
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:
|
engine should display on the screen. nf::Config has these fields:
|
||||||
@ -49,7 +49,7 @@ nf::Config conf2{ 1280, 720, false, "NF Example" };
|
|||||||
|
|
||||||
We then pass this config to an nf::Application
|
We then pass this config to an nf::Application
|
||||||
|
|
||||||
@section createApp Creating and Configuring an Application
|
@section createAppTut Creating and Configuring an Application
|
||||||
|
|
||||||
The nf::Application class represents an instance of the engine. This is the point where
|
The nf::Application class represents an instance of the engine. This is the point where
|
||||||
you will attach your states and run the engine.
|
you will attach your states and run the engine.
|
||||||
@ -72,6 +72,9 @@ Here are some functions you might want to call at this point:
|
|||||||
- [setWindowCursor](@ref nf::Application::setWindowCursor) - Sets the cursor's image
|
- [setWindowCursor](@ref nf::Application::setWindowCursor) - Sets the cursor's image
|
||||||
when it is visible and inside the window
|
when it is visible and inside the window
|
||||||
|
|
||||||
|
@note If you never call setWindowIcon before running, the default window icon will be
|
||||||
|
set for you.
|
||||||
|
|
||||||
And here are the functions you **must** call before an app can run:
|
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
|
- [addState](@ref nf::Application::addState) - Adds a state to an app so that it can
|
||||||
@ -88,7 +91,7 @@ app.setDefaultState("State 1"); //Will error without this
|
|||||||
app.run(); //Blocks until exit
|
app.run(); //Blocks until exit
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
@section createGamestate Creating a Gamestate
|
@section createGamestateTut Creating a Gamestate
|
||||||
|
|
||||||
To create a gamestate, you must create a class that publicly inherits nf::Gamestate
|
To create a gamestate, you must create a class that publicly inherits nf::Gamestate
|
||||||
and overrides its four virtual functions:
|
and overrides its four virtual functions:
|
||||||
@ -123,15 +126,215 @@ after the logo state:
|
|||||||
@image html blankapp.png "A blank gamestate" width=50%
|
@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.
|
Congratulations! You now have a basic NF app running. Now we can add objects to our world.
|
||||||
|
The window can be closed with Alt + F4 or by the close button.
|
||||||
|
|
||||||
@section createEntities Adding 3D Objects
|
For more about gamestates, see @ref gamestates.
|
||||||
|
|
||||||
|
@section createEntitiesTut Adding 3D Objects
|
||||||
|
|
||||||
|
In NF, all 3D objects are represented by the nf::Entity class. All entities have a 3D
|
||||||
|
model, a position, rotation, scale, and type among others. The [type](@ref nf::Entity::Type)
|
||||||
|
of the entity dictates how the object behaves in the physics simulation.
|
||||||
|
|
||||||
@section createUI Creating a UI
|
@note At this point, it's probably a good idea to read the @ref obLifetime page. It discusses
|
||||||
|
the creation and destruction of different objects including entities.
|
||||||
|
|
||||||
@section createCubemap Adding a Cubemap (Skybox)
|
To construct an entity, make it a member in your gamestate, or for dynamically created entites,
|
||||||
|
allocate it on the heap and add the pointer to a `std::vector` of `nf::Entity*` to keep
|
||||||
|
track of them.
|
||||||
|
|
||||||
|
~~~cpp
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
class CustomGamestate : public nf::Gamestate {
|
||||||
|
private:
|
||||||
|
nf::Entity entity1;
|
||||||
|
nf::Entity entity2;
|
||||||
|
|
||||||
|
std::vector<nf::Entity*> entities;
|
||||||
|
|
||||||
|
//Rest of class definition...
|
||||||
|
};
|
||||||
|
~~~
|
||||||
|
|
||||||
|
In your gamestate's `onEnter` function, create the entity with
|
||||||
|
[create](@ref nf::Entity::create):
|
||||||
|
|
||||||
|
~~~cpp
|
||||||
|
void onEnter(float deltaTime) {
|
||||||
|
//Places an immovable (default) cube at (0.0, 0.0, -5.0)
|
||||||
|
entity1.create(nf::BaseAssets::cube, nf::Vec3(0.0, 0.0, -5.0));
|
||||||
|
|
||||||
|
//Places a movable sphere at (3.0, 1.0, -4.0)
|
||||||
|
entity2.create(nf::BaseAssets::sphere, nf::Vec3(3.0, 1.0, -4.0), nf::Entity::Type::DYNAMIC);
|
||||||
|
}
|
||||||
|
~~~
|
||||||
|
|
||||||
|
The last step is to render the objects by rendering them in your gamestate's `render`
|
||||||
|
function:
|
||||||
|
|
||||||
|
~~~cpp
|
||||||
|
void render(nf::Renderer& renderer) {
|
||||||
|
renderer.render(entity1);
|
||||||
|
renderer.render(entity2);
|
||||||
|
}
|
||||||
|
~~~
|
||||||
|
|
||||||
|
Once the app is run, you should see two grey objects when the state loads: a static cube
|
||||||
|
and a falling sphere.
|
||||||
|
|
||||||
|
@image html objects.png "Our scene so far" width=50%
|
||||||
|
|
||||||
|
Let's add another entity that will be our ground plane so that our sphere has a place to
|
||||||
|
land.
|
||||||
|
|
||||||
|
~~~cpp
|
||||||
|
floor.create(nf::BaseAssets::plane, nf::Vec3(0.0, -3.0, 0.0));
|
||||||
|
|
||||||
|
//This plane will be the same size of the cube by default, so let's scale it on every axis...
|
||||||
|
floor.setScale(10.0f);
|
||||||
|
~~~
|
||||||
|
|
||||||
|
Currently, none of the keys on our keyboard (other than Alt + F4) does anything, so let's
|
||||||
|
make escape close the app.
|
||||||
|
|
||||||
|
@section inputTut Keyboard Input
|
||||||
|
|
||||||
|
Every gamestate has a pointer member to the parent nf::Application called `app`. Use this
|
||||||
|
member to access the input functions.
|
||||||
|
|
||||||
|
In NF, there are two ways to check for key events:
|
||||||
|
|
||||||
|
- [isKeyHeld](@ref nf::Application::isKeyHeld) - Returns true for every frame that the
|
||||||
|
tested key is held for
|
||||||
|
- [isKeyPressed](@ref nf::Application::isKeyPressed) - Returns true for only the first
|
||||||
|
frame the key is down regardless of how long it stays down
|
||||||
|
|
||||||
|
Both functions take in a key code. The key codes provided by NF all start with `NFI_` and
|
||||||
|
an uppercase letter, a number, or word denoting a special key eg `NFI_W`, `NFI_5`,
|
||||||
|
and `NFI_SPACE`.
|
||||||
|
|
||||||
|
To quit the app when escape is pressed, add this to your `update` funciton:
|
||||||
|
|
||||||
|
~~~cpp
|
||||||
|
if (app->isKeyPressed(NFI_ESCAPE))
|
||||||
|
app->quit();
|
||||||
|
~~~
|
||||||
|
|
||||||
|
`app->quit()` will cause the engine to shut down on the next frame and return from
|
||||||
|
`nf::Application::run`.
|
||||||
|
|
||||||
|
Everything is dark and grey, so let's add some basic lighting.
|
||||||
|
|
||||||
|
@section lightingTut Adding Lights
|
||||||
|
|
||||||
|
Naturally, the nf::Light class represents a light. It is constructed, created, and rendered
|
||||||
|
in the same way that an entity is.
|
||||||
|
|
||||||
|
~~~cpp
|
||||||
|
light.create(nf::Vec3(0.0, 5.0, 0.0), nf::Vec3(1.0)); //Creates a completely white light
|
||||||
|
~~~
|
||||||
|
|
||||||
|
Just as with entities, you must also render the light in your `render` function.
|
||||||
|
|
||||||
|
@image html basiclighting.png "Our scene with a light" width=50%
|
||||||
|
|
||||||
|
Let's go on to controlling the view.
|
||||||
|
|
||||||
|
@section controlCameraTut Controlling the Camera
|
||||||
|
|
||||||
|
Every gamestate has a pointer member to a nf::Camera called `camera`. Use this to control
|
||||||
|
the current camera.
|
||||||
|
|
||||||
|
Just like entites, the camera has a [type](@ref nf::Camera::Type) too. The type dictates
|
||||||
|
how the mouse interacts with moving the camea. By default, every gamestate starts with
|
||||||
|
the camera in `UI` mode which means that the mouse is free to move across the window without
|
||||||
|
affecting the camera in any way.
|
||||||
|
|
||||||
|
To change to the first person mode, write this in your `onEnter` function somewhere:
|
||||||
|
|
||||||
|
~~~cpp
|
||||||
|
camera->setType(nf::Camera::Type::FIRST_PERSON);
|
||||||
|
~~~
|
||||||
|
|
||||||
|
@note The current mouse sensitivity will be able to be changed in a future update.
|
||||||
|
|
||||||
|
Now onto actually moving the camera with the classic WASD keys.
|
||||||
|
|
||||||
|
Using our previous knowledge of keyboard input, we can write four consecutive `if`
|
||||||
|
statements for each of the movement keys:
|
||||||
|
|
||||||
|
~~~cpp
|
||||||
|
if (app->isKeyHeld(NFI_W))
|
||||||
|
//Move forward
|
||||||
|
if (app->isKeyHeld(NFI_A))
|
||||||
|
//Move left
|
||||||
|
if (app->isKeyHeld(NFI_S))
|
||||||
|
//Move backward
|
||||||
|
if (app->isKeyHeld(NFI_D))
|
||||||
|
//Move right
|
||||||
|
~~~
|
||||||
|
|
||||||
|
The functions for moving the camera is as follows:
|
||||||
|
|
||||||
|
- [move](@ref nf::Camera::move) - Moves the camera based off of an nf::Vec2 in (x, z)
|
||||||
|
- [moveZ](@ref nf::Camera::moveZ) - Moves the camera forward and backward with an offset
|
||||||
|
- [moveX](@ref nf::Camera::moveX) - Moves the camera left and right with an offset
|
||||||
|
|
||||||
|
Since the `offset` here will be called every frame, it's effectively a velocity, and when
|
||||||
|
we talk about velocities, it's important to discuss `update`'s only parameter, `deltaTime`.
|
||||||
|
|
||||||
|
Delta time in this context is the amount of time that the previous fame took to produce.
|
||||||
|
This time includes how long it takes to run your `update` and `render` functions as well
|
||||||
|
as everything else in the engine that gets a frame on screen. Why is this important?
|
||||||
|
Because it can cancel out framerate differences between different machines.
|
||||||
|
|
||||||
|
Let's say that Computer A is a modern-day gaming rig with 8 cores and an RTX 2060.
|
||||||
|
Computer A can run our game at a solid 60 FPS. Let's also say that Computer B is an
|
||||||
|
older laptop that struggles to run the engine smoothly. It runs our game at around 30 FPS.
|
||||||
|
If in our `update` function, we move the camera (or anything at all) by a set amount,
|
||||||
|
Computer A will move our view twice the distance than Computer B in any amount of real time
|
||||||
|
since there were twice the amount of frames drawn in that time.
|
||||||
|
|
||||||
|
The solution is to multiply any velocity you use with this delta time. You will have to change
|
||||||
|
your values around a little, but this will make speeds consistant across computers.
|
||||||
|
|
||||||
|
With that, we can now complete our movement logic:
|
||||||
|
|
||||||
|
~~~cpp
|
||||||
|
if (app->isKeyHeld(NFI_W))
|
||||||
|
camera->moveZ(10.0f * deltaTime);
|
||||||
|
if (app->isKeyHeld(NFI_A))
|
||||||
|
camera->moveX(-10.0f * deltaTime);
|
||||||
|
if (app->isKeyHeld(NFI_S))
|
||||||
|
camera->moveZ(-10.0f * deltaTime);
|
||||||
|
if (app->isKeyHeld(NFI_D))
|
||||||
|
camera->moveX(10.0f * deltaTime);
|
||||||
|
~~~
|
||||||
|
|
||||||
|
We are now able to move around the world.
|
||||||
|
|
||||||
|
@image html cameramovement.png "Our scene from a different angle" width=50%
|
||||||
|
|
||||||
|
Now let's take care of that black background.
|
||||||
|
|
||||||
|
@section createCubemapTut Adding a Cubemap (Skybox)
|
||||||
|
|
||||||
|
A world texture is represented by the nf::Cubemap class. Rendering this object will
|
||||||
|
display a texture in the world. We use the class the same way we use the others:
|
||||||
|
|
||||||
|
~~~cpp
|
||||||
|
cubemap.create(nf::BaseAssets::cubemap); //No position or type though
|
||||||
|
~~~
|
||||||
|
|
||||||
|
After rendering, our world will have a background.
|
||||||
|
|
||||||
|
@image html cubemap.png "Our scene with a background" width=50%
|
||||||
|
|
||||||
|
@section createUITut Creating a UI
|
||||||
|
|
||||||
@todo Lighting page?
|
@todo Lighting page?
|
||||||
|
|
||||||
@section packaging Packaging Your App
|
@section debuggingTut Debugging Your App
|
||||||
|
|
||||||
|
@section packagingTut Packaging and Distributing Your App
|
Reference in New Issue
Block a user