If you're completely new to NAP it's best to download the pre-compiled packaged framework and try out some of the demos. Start with helloworld demo and work your way up in difficulty. All demos are well documented and explain various core concepts, including their building blocks.
Now, if you like what you see and want to start application development in NAP, we recommend working against source instead of a package. The entire development team rolls like this, because we make frequent edits to the source code whilst developing the application; often on a seperate branch until ready for integration down-stream. Working against source also gives you deeper insight into the inner workings of the engine, details otherwise hidden behind an interface.
And contrary to what you might think, compiling and working with NAP from source is relatively straight forward and doesn't involve a lot of custom setup, compared to the framework release. The steps are explained in the online .readme, which boil down to: Clone NAP, download and extract pre-compiled Qt, setup the QT_DIR environment variable, run the setup script and generate the solution. It won't take more than 10 minutes on most supported platforms, why? Because all third party dependencies are pre-compiled and should work out of the box.
NAP has 3 distinct stages (or levels):
Good question! Having a pre-compiled binary release of the framework allows you to freeze and create a snapshot of the entire framework your application is developed against, which turns out to be very convenient when you most need it! You can choose to include the source code of your application, for easy distribution and runtime guarantees.
As we know, software is always in a state of flux and is likely unsupported, incompatible or broken 1, 2 or 5 years from now. Having a complete snapshot of the entire NAP stack helps you get back in when you don't want to; without having to gather all the various bits and pieces that are maybe no longer available, unsupported or broken. This becomes especially important when you are responsible for many projects, at many different locations on different operating systems.
It is also a convenient way to share the project with external developers, without the need to provide them with access to all your modules, branches and other changes; parts of which might be private.
Yes, in the beginning, until it clicks. If it doesn't click for you that's ok, don't beat yourself up over it! Some people like it, others don't. But those who do enjoy it often become really passionate about it.
With that out of the way; C++ already isn't the easiest language and concepts like RTTI, which are essential to how the editor and framework operate, can be especially challenging if you're new to these ideas. But once you grasp them, you’ll be rewarded with unmatched flexibility, speed, and modularity; transforming your intricate ideas into powerful tools or instruments.
And then there’s Vulkan: yes, it’s infamous for requiring hundreds of lines of code just to render a triangle. But here’s the good news; you don’t have to write all that code yourself. We’ve built a complete engine from scratch so you can start creating impressive visuals right away. In NAP, you won’t need to touch Vulkan unless you want to (and you really should, it’s incredible).
If you find the code challenging, we strongly suggest reading Effective Modern C++ by Scott Meyers. The book clearly explains the many new concepts of modern C++ that we use everywhere in NAP.
We generally recommend using CLion on both Linux and Windows. But my personal favorite is Visual Studio together with Visual Assist on Windows; it's in my DNA - fast, complete, great debugger, pretty. But most in our team switched completely to Linux and are not coming back - their words not mine..
Also: don't generate 2 profiles at the same time in CLion, for example Debug & Release. Choose one or generate them one after the other - something in our build scripts prevents profiles from being generated at the same time in CLion and we haven't figured out what exactly.
Yes you can, you don't have to use the editor. We often use a mix, where the resources known in advance are authored in the editor and others created at runtime, often on init() of another resource.
If you create a resource at runtime you must manage it's lifetime, instead of relying on the nap::ResourceManager. The easiest way to do this is to move the created resource into a unique_ptr that you keep around until the resource that created it is destroyed. This ensures your resource is deleted and that hot-loading keeps working.
When you create a resource at runtime you must set all it's properties and then call init(), for example:
That's it! The resource manager does something similar, although it assigns the properties using RTTI because is has no idea what it is creating; it only knows what settings to assign and how to initialize it.
Yes you can, although less common but sometimes very useful. First you need to create a scene (resource) that can spawn, hold and update your entity resource:
Next you create the entity to spawn including all of it's components, for example:
The returned entity_instance is a handle to the entity spawned by your scene, which manages the entity for you. The entity is destroyed when the scene is destructed or by calling Scene::Destroy. The entites in your scene receive update calles every frame until the scene is destroyed.
Parent entities are always updated before their children. This means that, if an entity has children, its components are processed first, followed by those of its descendants.
Components receive an update() call every frame in the order of declaration in the editor. This means that if your entity has 2 components in the following order: (1)transform, (2)renderer; the transform is updated before the renderer. The final (global) transform is calculated after update, on postUpdate().
Root entities are updated in no particular order. Child entities are updated in the order of declaration.
If you have experience with OpenGL and want to get started with Vulkan in NAP, I recommend reading our OpenGL to Vulkan transition blog. That article explains how and why we ported our engine from OpenGL to Vulkan, and the impact this had on performance and portability. If you're not comfortable with real-time 3D Graphics yet, it's better to start with the basics; a good book and after that some code. This will make it much easier to understand Vulkan-related code in NAP Framework; why it's there and what it does. I’ve also hosted a number of NAP rendering workshops over the years, a collection of the slides can be found here.
The good news is: you don’t need to work with Vulkan directly unless you choose to! We’ve developed a full engine from the ground up, so you can render almost anything without ever issuing a single Vulkan command.
Core engine: core/src
RTTI information: rtti/src
Utilities: utility/src
Applications: apps
Essential framework modules: system_modules
Demos: demos
Editor source: tools/napkin
Build tools: tools/buildsystem
User modules: modules
Source code: src
App-specific module: module/src
Data files: data
Source code: src
Third-party dependencies: thirdparty
Data files: data
Next to the standard pointer types (std::unique_ptr most notably), you'll frequently encounter and work with a nap::rtti::ObjectPtr; which points to a nap::rtti::Object, the most fundamental building block of the engine.
Why use a dedicated pointer type? It enables the resource manager to implement hot-reloading. When application data changes, the manager swaps out affected objects for updated instances and cleans up the old ones automatically.
Consider the following example: Every material links to a shader, and that link is a rtti::ObjectPtr<Shader>. Your material becomes invalid when the content (code) of the shader changes. Instead of keeping the old version around, the system tries to create and patch in the new version, replacing all objects it touches. This means that the shader, material and component that uses the material is re-created and patched in by the system at runtime.
It helps to view the object ptr as a link to an object and treat it as a regular pointer. However, if you don’t actually need a link to an object, avoid using it. Instead opt for a unique_ptr or (if required) a raw pointer; just remember that you’ll be responsible for managing the object’s lifetime in those cases.
We try to avoid using raw pointers as much as possible, unless safe and usage is completely encapsulated. We prefer using unique_ptr where possible and don't encourage the usage of shared_ptr, unless required by a library. The reason? shared_ptr diffuses ownership, leading to code that’s less organized; at least, that’s how we see it.
When should you use a raw pointer? Typically, we use raw pointers in component instances, rather than an object ptr. Is this safe? Yes! Because these raw pointers point to resources managed by the resource manager. If the underlying resource changes, the system automatically recreates the instance, ensuring the pointer remains valid until it is replaced.