An experimental game engine written in C++.
Try checking the Wiki. It is currently a work in progress, but may answer some questions you have.
Edge is an experimental game engine designed with developers in mind. One of the common complaints I hear about graphics developement is that setting up an environment and wiring up boiler plate code takes too much time, and in the end, results in a spaghetti mess for anyone who hasn't seen a structured example before. This engine will aim to ease some of these concerns, attempting to provide modern design examples, but also keeping code readable for newcomers.
Short answer, why not? The idea for this engine spawned from my experience using Berkeley's NachOS as an undergrad. I thought, "Now imagine if there were an environment like this for graphics...a place where I can break things without any consequences." Thus, the idea was born.
Of course, but how am I supposed to learn how to build a good wheel if I don't start from scratch?
Good question! The answer is, you can't...yet. But here are some things you can do.
Scenes should be fairly easy to understand - they encapsulate all objects in a single view space defined by the user. You can think of them as levels. Scenes are tracked by name by the system, and while you are allowed to have multiple active scenes at the moment, support for drawing them simultaniously is still being fleshed out.
Scene * scene = new Scene(system->getActiveWindow());
SceneManager::getInstance()->scenes->insert({"main", scene});
Note: This will change in the future to something like:
Scene * mainScene = SceneManager::getInstance()->createScene("main");
Easiest peasiest (this will change to scene->addObject
)
scene->objects->insert({"myTextBox", new TextBox("Hey, you can add text now!")});
TextBox
is a bit of a special case. Most of the time, you'll want to create a new GLObject
. The constructor of GLObject
accepts a Geometry
, which is a sharable mesh definition. You can create a Cube
or RectangularPlane
like so:
scene->objects->insert({"myCube", Cube::getInstance()})
Some Geometry
objects allow access to their constructors. This allows the user to define certain properties of the mesh that mark it as unsharable. This is the case for RectangularPlane
. Let's say you wanted a grid like mesh rather than a giant 2-triangle plane:
scene->objects->insert({"myTesPlane", new RectangularPlane(2, 2)});
This will divide the plane into 2 rows and 2 columns.
Now that you have an object, let's add some color to it with a shader
ShaderProgram * shader = ShaderManager::createShaderProgram("solid_red.vertex.glsl", "solid_red.fragment.glsl");
The ShaderProgram
constructor takes in two paths. The path order is the same as the order shaders appear in the pipeline (we currently only support the vertex and fragment shader, but geometry and tesselation are coming in the future). So in this case the first path is to our vertex shader and the second is to our fragment.
Variable bindings in shaders are necessary with every draw, so we give access to that stage in the pipeline through a lambda function. This lambda will give you access to the GLObject
that the Shader
is bound to as well as the Scene
that the object sits in
shader->bindVars = [](GLObject* obj, Scene* scene) {
obj->shader->bindVariable("modelTransform", obj->getModelMatrix());
obj->shader->bindVariable<Texture>("tex", obj->textures->at(0));
};
This example shows how to bind both a Matrix
and Texture
to the shader - nothing too complicated but this is likely to become more automated in the future. Speaking of Texture
s...
All built in models come with some pre calculated UV coordinates, but you are allowed to create and attach your own - just make sure that if you do this on a shared object, you know that all instances of that object will share those UVs. This may be changes in the future, but for now, UVs are bound to a Geometry
and not a GLObject
.
scene->get<GLObject>("Cube1")->textures->push_back(TextureManager::getInstance()->loadTexture<BMPTexture>("crate", "crate.bmp"));
That's it really - you can get the texture by name and access its texture ID if necessary. This can be done off of the object itself or through the TextureManager
.
You can create your own Script
class pretty simply by extending the Script<TargetType>
class, where TargetType
is the type of the object the script is bound to. So if you want to write a Script that can only be bound to a Light
, try:
class LightMovement: public Script<Light> {
Light(Light * target): Script(target) {}
public tick(){
//do stuff to your light
}
}
Then you can bind the script to a Light
like so:
scene->get<Light>("light1")->attachScript<LightMovement>("lightMvmnt");
You can probably see a convention here - everything is named for now. Also, Scripts are not singletons, so feel free to add any public member variables you want per object's script - no need to define bindings.