Skip to content

oitzujoey/engine-1

Repository files navigation

engine-1

Honestly, I have no idea what this is going to be yet. At this point, it's just a tech demo. Here is what I do know:

  • Written in C
  • Cross-platform (Linux + Windows 10) (Console does not currently work on Windows.)
  • Client-server
  • Moddable through Lua

Installation

Dependencies

Make sure you have these dependencies before building. GL and GLEW are required for the client only. Qt is only required for the runner.

    SDL2
    Lua
    ENet
    PhysicsFS
    GL
    GLEW
    Qt6

On linux, you will need these installed to run.
On Windows, you will either need these installed, or you will need the DLLs in the working directory. I wish you luck in your library compilation.

Building

mkdir build && cd build
cmake ..
cmake --build . # or "make"

I recommend using MSYS2 and mingw to build for Windows. It was pretty painless compared to building on Ubuntu (WSL) and Arch Linux.
If you want to run the client or server in the background, be sure to define the preprocessor constant NOTERMINAL before compiling.

Configuration

cd ../base
# Clone the Oolite game resources. These are needed for the example.
git clone https://github.com/OoliteProject/oolite-binary-resources.git
cd ..
# Create the writable game directory.
mkdir workspace
# Start the server.
build/sengine-1

From the top directory on a second terminal...

# Start the client.
build/cengine-1

Project status

Currently implemented

Partially implemented

Lua sandboxes
Model loader
Model list
Entity tree
Networking
Rendering
Mod loader Scripting (for configuration) README.md

Engine documentation

Terminology

model   A standard 3D model. May include other types of models in the future.
entity  A node in the entity tree. It stores information used for manipulating models during rendering.
object  A general term for a model or an entity.

3D graphics model

The rendering system is a bit complicated, but hopefully not too difficult to understand. To render a model, it must first be loaded by the engine. This is done manually to save memory and loading time. The model is then bound to an entity. I expect that in most cases, only one model will be bound to a given entity for reasons that should become obvious shortly. Before binding can occur, the entity must first be created. Since we are going to bind a model to it, the entity must be created as a model entity. The model can now be bound to the newly created entity. After this is done, the model entity must be linked to the world entity. The world entity is created by the engine and anything linked to it will be rendered.

More concisely, with results shown after each step:

  1. Engine creates world entity before game start.
        worldEntity
  2. Load model.
        worldEntity
        model
  3. Create entity. (type is model)
        worldEntity
        model
        entity
  4. Bind model to entity.
        worldEntity
        entity
            model
  5. Bind entity to world entity.
        worldEntity
            entity
                model
  6. Render worldEntity.

The reason for this structure is to make manipulating models easier, as is explained below.

Models

Models are standard 3D models. They contain vertices and other information required for rendering. Models can be reused as many times as needed in a scene after they are loaded. They contain no position or orientation information, so they must be bound to an entity to be placed in the scene.

Model functions

Function Description
int modelIndex, int error = l_loadOoliteModel(string filePath) Loads an Oolite model of the most recent format and returns its index in the model list.

Entities

An entity is a node in the entity tree. Each entity has a type, and only objects of that type can be bound to an entity. This was touched on a bit already. To bind a model to an entity, that model must be created with the model type. To bind an entity to another entity, the parent entity must be created with the entity type. A list of types is presented below.

Entity types

Type Description
none Nothing can be bound to this entity.
entity Entities can be bound to this entity.
model Models can be bound to this entity.
... More coming soon! 1

The structure of entities are very general due to the variety of objects that can be bound to them, so not all members will be used in all situations.

Entity structure

children        Array of child objects.
children_length Length of the "children" array. Can be ignored in Lua.
childType       Type of the entity.
position        3D position. x is to the right, y is up, and z is into the screen. Default is {0, 0, 0}.
orientation     Orientation quaternion. Default is {1, 0, 0, 0}.
inUse           Internal variable used to check if an entity is deleted. Can be ignored in Lua.

This structure only exists in the engine, but the engine provides functions to manipulate it. Note that position and orientation are cumulative. This allows multiple models to be moved and rotated as one unit. When an entity is moved in space, all its children are moved as well since a child's reference frame is always its parent entity. The same happens when an entity is rotated. The children are rotated around the entity's origin, and the orientation of the children is rotated as well. Something to watch out for is dangling indices and index reuse. The entity list is not an associative array. It is not a linked list. It is a normal array. The index that Lua is given is not a key, but an index to an array. The list can grow but never shrinks. When an entity is deleted, it is marked deleted, but it is not actually freed. References to a deleted entity will trigger a Lua error. When a new entity is created, it will look on the deleted entity list to see if any can be reused. If this is the case, the old deleted entity is revived with a different purpose. In this case, if the index of the deleted entity is used, the operation may be valid but will likely return an unexpected result.

Entity functions

Function Description
int entityIndex, int error = l_createEntity(int entityType) Create an entity of type "entityType". Returns the index in the entity list.
int error = l_entity_linkChild(int parentEntity, int childObject) Binds a child object to an entity if it has the appropriate type.
l_entity_setPosition(int entityIndex, {x, y, z} position) Sets the position of the given entity.
l_entity_setOrientation(int entityIndex, {w, x, y, z} orientation) Sets the orientation of the given entity.

Lua Scripting

Made for Lua 5.4, but other versions may work as well.

Games are written in Lua. Since one of the goals of this engine was to be somewhat secure, the script is sandboxed. Lua libraries cannot be used. Precompiled Lua can be run at the moment, but that will be change when I can be bothered to fix it.

Boilerplate

These functions are required to run a game.

function startup()
    -- Code that runs once on startup
end

function main()
    -- Main game code
    -- This function is called once per game tick. Do not put infinite loops in here.
end

function shutdown()
    -- Code that runs on exit
end

These functions do not have to be in smain.lua or cmain.lua, but if they are not present in those files they must be in an included file. Each one has a timeout, so don't let your code take too long to execute. If the function does time out, the game will be shut down.

API

This will take some time to document. Currently the API can be found in smain.c, cmain.c, and lua_common.c. Examples of how to use these functions can be found in the example code.

In the meantime, one important note: Since the standard libraries cannot be loaded, the require function cannot be called to include other scripts. In its place is the function include, which is called in about the same way.

Key binding

Key binding is more complicated than I would have liked. Key bind functions can be found in input.c.
How to bind a key:

  1. Create a variable.
  2. Set a callback for that variable (the action to be taken when the key is pressed).
  3. Bind the variable to the key using the bind command.

This actually isn't that complicated from a user's perspective. The engine, autoexec, or gamecode should create all the bind variables and set the callbacks. The only thing the user should have to do is bind the prepared variable to the key. Here is an example.

# This is done by the developer.
create none key_quit    # Create a command variable.
command key_quit quit   # Bind the command `quit` to the variable.

# This is done by the user.
bind k_27 key_quit      # Yes, this is horrible. I need to add nice names for all the keys.

Keys can be given both up and down actions.

# Print all vars when ESC is pressed, and then quit when ESC is released.
create none +key_esc
create none -key_esc
command +key_esc vars
command -key_esc quit
bind k_27 +key_esc -key_esc

+ and - mean nothing special. They are normal characters that are used here to easily differentiate between up and down actions.

This can also be done from Lua since it can execute config commands. The real power of this is that Lua can set a Lua function as the callback to a config variable. When the key is pressed, the Lua function is called, and then Lua can act on the key press.

A key is pressed. The key bind is found. The callback is run. If the callback is a Lua function, then that function is run. This likely sets a variable. Maybe it's just me, but this seems like way too many layers.

1 Jesus is also coming soon. Expect the time frame to be about the same.

About

Incomplete networked 3D game engine

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages