Jump to content

  • Log In with Google      Sign In   
  • Create Account

We're offering banner ads on our site from just $5!

1. Details HERE. 2. GDNet+ Subscriptions HERE. 3. Ad upload HERE.


Don't forget to read Tuesday's email newsletter for your chance to win a free copy of Construct 2!


Game Engine Code Design


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • You cannot reply to this topic
6 replies to this topic

#1 Danicco   Members   -  Reputation: 449

Like
1Likes
Like

Posted 18 February 2014 - 11:33 PM

Hmm I'm not sure if that's a suitable title for this topic, but here goes...

 

I'm coding a Game Engine and there's various parts of it that I come up with doubts and questions if I'm doing the right thing, and I'd like to ask for opinions and suggestions for these.

I'll post the problems I have, the solutions I've thought of, and I'd like to discuss them if there's a better way or something like that. So I'd like to ask how you would do it and why.

 

1. The Scene Draw and Object Hierarchy

Explanation: I have a Scene class that has various vectors for all the types of objects in my game, I separated them to make it faster for iterating and to avoid using interfaces in my objects.

class Scene
{
    public:
        void Draw();
    private:
        vector<Image*> _images;
        vector<Model*> _models;
        vector<Text*> _texts;
};

All objects have two Orientation classes that holds it's current position/rotation/etc and it's previous. At each Draw() call, I interpolate with these.

To support hierarchy, I have this in my objects:

class Model
{
    public:
        SceneObject* parentObject;
    private:
        Orientation currentOrientation;
        Orientation previousOrientation;
};

Here, SceneObject is a struct that holds pointers to an object's Orientation (both of them, current and previous), just so I could do this:

void Scene::Draw(float& interpolation)
{
    //Calculate each object's matrix using a recursive function to calculate it's parent's first.
    //Set a flag to define it's been calculated already so it doesn't calculate again in case of multiple children
    for(unsigned int i = 0; i < _images.size(); i++)
    {
        if(_images[i]->isOrientationCalculated == false)
        {
            CalculateOrientation(_images[i], interpolation);
        }
    }

    //Draw each one of them and reset the flag for the next frame
    for(unsigned int i = 0; i < _images.size(); i++)
    {
        _images[i]->Draw();
        _images[i]->isOrientationCalculated = false;
    }
}

Matrix4x4 CalculateOrientation(Image* image, float& interpolation)
{
    if(image->parentObject != 0)
    {
        return CalculateOrientation(image->parentObject, interpolation) * Interpolate(image->currentOrientation, image->previousOrientation, interpolation);
    }

    if(image->isOrientationCalculated == true)
    {
        return image->objectMatrix;
    }
    else
    {
        image->isOrientationCalculated = true;
        return image->objectMatrix = Interpolate(image->currentOrientation, image->previousOrientation, interpolation);
    }
}

Problem: Only children keeps tracks of their parents so parents don't know if they have children or not. I've thought of making it the other way around, of having parents keeping track of their children, but after coding it I noticed the code was nearly the same, since they're all in a single vector I'd have to keep a flag for each object to set if they've been calculated already or not so I don't calculate children's matrices twice.

 

Because of this, or rather, any way I'd choose to do (parent->children or children->parent) I would need to add the code above and it's making my Draw() function much slower, and I was trying to leave this as clean as possible.

Do you think this could be done in a better way? How?

 

2. Text Rendering and Effects

Explanation: I have a Text class that I use Bitmap Fonts with, and I have to use a function SetText(string) to set it's text value.

At first I would save buffer all the characters, and draw them with different positions depending on the text (moving a certain width/height every character/line break).

I thought it was too slow, so I made the SetText(string) function and what it does is, I count the characters, create a new buffer and a single object with all the vertex/uv data for that string, and draw it. It was much (very much!) faster than before.

 

Problem: Now with this new way of rendering the text, I can't use a basic effect I had before on texts, the one that displays text as if it's been typing (like a character per second). With all the characters in different buffers, I just had to count the string and display until a currentCharacter variable, which increased by 1 per second/timing.

 

Now since I have the entire string in a single object, I'm having to recreate the object, calculate all the positions/vertex/uv data again, and display, and then this isn't really all that fast as before. Still faster than having multiple buffers though.

 

Is there a better way to do this?

 

3. Input Class and Game Commands

Explanation: I don't have an Input class yet, for now, I'm coding on Windows only and I know how to get keyboard and mouse input. I want the Input class to be oblivious of the input or system, so I can make it cross platform and depending on the system, I only point/fill the Input class variables for the game to use.

 ________                                   _______     ________
| System |---(unique input code/message)---| Input |---| Engine |
|________|                                 |_______|   |________|
                                                    \
                                                      \ (configures/add it's specific game commands)
                                                        \ ______
                                                         | Game |
                                                         |______|

So a game A could add a specific command for Directional Keys and give it the type of float or bool.

It's game code would only worry about checking these specific commands:

if(_input.IsPressed("MOVE_LEFT")) //MOVE_LEFT command created
{
    character.MoveLeft();
}

So far this is simple to do...

Problem: Can I make a code to change the state of the Game's commands with it being unaware of the actual input? Like as if it doesn't care if it's keyboard, joystick, mouse or even touchscreen?

Seeing as how I check for an input on Windows:

LRESULT CALLBACK MessageProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{    
    switch(uMsg)
    {
        case WM_KEYDOWN:
        { 
            myGame->inputClass.Pressed(wParam, true);        
            break;
        }
    }

    return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

I'm "locked" in the sense that I must have wParam defined to a command, and I don't want to have a game command locked to a specific charCode like that if possible.

I've thought of making a Keyboard, Mouse, Joystick class and look for a way to make they work in every platform I'd want to code in, but that wouldn't change much since I would still be locking the commands for the specific codes... but at least I would be sure to work in any platform.

 

Is there any way to do this? If not I'll probably stick to creating and setting codes for every platform/input type, though it's not much work, but I'd like to achieve something more... automatic.

 

Hmm typing this last one made me realize that it seems something stupid to ask... it doesn't seem possible to have an input like that, I'll leave it like this though since I'd like to discuss how input classes are made.



Sponsor:

#2 Danicco   Members   -  Reputation: 449

Like
0Likes
Like

Posted 20 February 2014 - 07:04 PM

Hmm I finished these things and I'll post here how I did:

 

1. The Scene Draw and Object Hierarchy

I couldn't find a way to make it easier, so I ended up putting a few more checks and calls in the scene during the update but tried to simplify it to the best I could.

void GameEngine::Update()
{
    //Reset positions for interpolation
    scene.Update();

    //Where mostly likely something will change
    state.Update();

    //Check what changed and set a flag to know if I have to calculate it's matrix again or not
    scene.UpdateFlags();
}

And the scene functions:

//Reseting previous = current values for interpolation
//Also setting the flag for all objects to undefined
void Scene::Update()
{
    for(unsigned int i = 0; i < _objects.size(); i++)
    {
        _objects[i]->previousOrientation = _objects[i]->currentOrientation;
        _objects[i]->objectFlag = SceneUpdateFlag::UNDEFINED;
    }
}

//Then later, after the game state has updated
//It returns a flag since it's also called recursively
SceneUpdateFlag Scene::UpdateFlags()
{
    if(objectFlag == SceneUpdateFlag::UNDEFINED) //Only if it hasn't been checked yet
    {
        if(previousOrientation != currentOrientation) //If the state updated this object...
        {
            objectFlag = SceneUpdateFlag::UPDATED; //Object has been updated, will need to recalc matrix
        }
        else
        {
            if(objectParent != 0) //If it has a parent
            {
                if(objectParent->UpdateFlag() == SceneUpdateFlag::UPDATED) //If the parent has been updated, so will this
                {
                    objectFlag = SceneUpdateFlag::UPDATED;
                }
                else
                {
                    objectFlag = SceneUpdateFlag::NOT_UPDATED;
                }
            }
            else //doesn't have a parent, so only checks if orientation changed
            {
                objectFlag = SceneUpdateFlag::NOT_UPDATED;
            }
        }
    }
    return objectFlag;
}

Then during the Draw call, I check:

Matrix4x4& SceneObject::GetMatrix(float& interpolation)
{
    if(objectFlag == SceneUpdateFlag::UPDATED) //This has been updated, will need to calc it's matrix
    {
        if(objectParent != 0) //If has parent
        {
            objectMatrix = objectParent->GetMatrix(interpolation) * Lerp(currentOrientation, previousOrientation, interpolation);
        }
        else //no parent, just it's matrix based on time
        {
            objectMatrix = Lerp(currentOrientation, previousOrientation, interpolation);
        }
    }
}

It was pretty annoying to figure why it wasn't interpolating (I was reseting the flag at the draw call, but since it's a fixed time step update, I was missing various render calls without recalculating the interpolation), but this is the best I could get to work with fixed time step and interpolating all the objects.

 

2. Text Rendering and Effects

This one... I haven't finished implementing this yet but I got the idea from an animation module I was coding. Usually I do this (memory allocation for the string):

(single VBO)
 _ _   _ _   _ _   _ _   _ _   _ _   _ _   _ _ 
| A | |   | | S | | T | | R | | I | | N | | G |
|_ _| |_ _| |_ _| |_ _| |_ _| |_ _| |_ _| |_ _|

vertex memory 0                               vertex memory N

Now I decided to do like this:

(single VBO)
 _ _   _ _   _ _   _ _   _ _   _ _   _ _   _ _ 
| G | | N | | I | | R | | T | | S | |   | | A |
|_ _| |_ _| |_ _| |_ _| |_ _| |_ _| |_ _| |_ _|

vertex memory 0                               vertex memory N

(reversed only the positions of the letters)

The values are still the same for vertex floats and UVs and such, only the memory allocation is what's different.

With this, I should be able to call:

glVertexAttribPointer(layoutPosition, layoutBufferSize, GL_FLOAT, GL_FALSE, 0, (void*)bufferOffset);

With bufferOffset being the (length - currentCharacter * typeof(float)), probably with some correction or adjustment...

This should give me back the effect I want without losing performance like I was, hopefully!

 

3. Input Class and Game Commands

For this one I didn't want to have huge switchs checking for all characters and definitely not parsing through all the key's values to find the one I want to see if it's checked or not, so I did it the other way around.

I created a Keyboard class (for now, still haven't done Mouse/Joystick) with the array of 256 keys.

class KeyboardKey
{
    public:
        KeyboardKey()
        { 
            command = 0; 
            keyState = KeyState::RELEASED;
        };

        Command* command;
        KeyState keyState;
};

class Keyboard
{
     public:
         void SetKey(int keyCode, bool isPressed)
         {
             keyboard[keyCode].keyState = isPressed ? KeyState::PRESSED : KeyState::RELEASED;
             if(keyboard[keyCode].command != 0) //if it has a game command assigned to it
             {
                 Input input = new Input();
                 input->command = keyboard[keyCode].command; //save the command
                 input->state = keyboard[keyCode].keyState; //it's state
                 input->time = timeModule.GetRealTime(); //and the time it was pressed
                 
                 inputQueue.push_back(input); //add to a vector
             }
         }    
 
         KeyboardKey keyboard[256];
};

Then I have a Input.Update() function that is called before the State.Update() and I set the state for that time step:

void InputModule::Update(uint64_t currentTime)
{
    for(unsigned int i = 0; i < inputQueue.size(); i++) //checking all inputs
    {
        if(inputQueue[i].time < currentTime - timeLimit) //if it's past the time limit it can stay in the vector
        {
            //erase it            
            delete inputQueue[i]; //memory first
            inputQueue.erase(inputQueue.begin() + i, inputQueue.begin() + i + 1); //remove 1 element at i from the vector
            i--; //go back 1
            continue; //start over
        }
        
        if(inputQueue[i].time > lastTimeUpdated && inputQueue[i]->time <= currentTime) //if it's after the last time I checked and before the current call's time step
        {
            //I have a vector of all Game Commands, and I just change their state here
            inputQueue[i]->command->commandState = inputQueue[i]->state; //setting the command it has to it's current state
        }
        lastTimeUpdated = currentTime;
    }
}

This way I left for the Game Code to create their Commands for the game, such as "MOVE_LEFT", "MOVE_RIGHT", and assign it to a key. When that key is pressed, since it have a command attached, I put the event (with time of clicking) in a vector.

Then, the next time the game updates, I'll update the input state by checking all input events that happened during that timestep, set the commands state's (PRESSED, RELEASED, etc, there's more omitted) and then the Game Code will only know about how the inputs were during that time.

 

I left the vector with a time limit so the game can check previous inputs as well.

It's pretty simple, and I think it's way too simple... although with this I covered what I have to do for now (check for input sequences, and check for previous input state and compare to the current input state), I'm still thinking it's way too simple and I'll probably notice something is missing in the future... when it'll be a pain to understand what I did here and fix it.

 

Missing functionality, wrong logic, bad performance? Any inputs (no pun intended) on these are appreciated!



#3 SunDog   Members   -  Reputation: 232

Like
0Likes
Like

Posted 21 February 2014 - 09:25 PM

Why do you need to interpolate the orientations?

 

I would also split out the drawing into a seperate Renderer class.   The scene would just hold data and manage the data.  

 

Having an entire class for 'Keyboard Key' seems like overkill


Edited by SunDog, 21 February 2014 - 09:26 PM.


#4 Buckeye   Crossbones+   -  Reputation: 5680

Like
0Likes
Like

Posted 21 February 2014 - 10:06 PM

If you're going to have a hierarchical structure, where a child's attributes at any time may depend on the parent's attributes, a parent knowing its children may be more efficient. I.e., you can call an update or render function for the root of the hierarchy, and each object will take care of it's children without having to test whether it's orientation or other attributes are up-to-date.


Please don't PM me with questions. Post them in the forums for everyone's benefit, and I can embarrass myself publicly.


#5 slayemin   Members   -  Reputation: 2796

Like
0Likes
Like

Posted 22 February 2014 - 03:24 AM

I'm also working on a game and building its engine at the same time (mostly because it's fun and I want to see if I can actually do it -- and I'm a victim of 'Not Invented Here'). Here are some of my design principles/decisions:

 

Object Principles:

1. Every drawable object needs to know how to draw itself.

2. Every object must have an Update() method which can be called to update the object.

3. Every object must be able to load itself and unload itself (allocate memory, load external assets). This will come in extra handy when I save a game or load a game.

 

Architecture Principles:

1. There will be many game states (main menu, game world, building designer, etc) which need to be managed. Each "game state" is actually being treated as a separate, self-contained game. These are going to be managed by a "GameStateManager" class, which is mostly just a wrapper on a stack datastructure. So, when you start up the game, the game state manager pushes a "Main Menu" game state onto the stack. If you use the menu system to start a new game, the game state manager will unload the current game state and initialize a "new game world" game state using a world settings class (which is created when you're creating your new game). Once you're in the game world, you can launch a building editor. This pushes a building editor system onto the top of the stack and you play around, designing a building for your village. This new building then gets pushed down into the game world, and you can now use it. The key here is that you can easily switch from one game state to another by simply pushing and popping them off of the game state stack. This comes with one important limitation though: You can't be running more than one game state at a time.

 

2. All game systems (particle systems, environment managers, terrain managers, etc) must be able to be initialized with a class settings file as a parameter to its constructor. If done right, you can create a huge variety in systems by just using a different set of initialization settings (ie, a smoke particle trail, fire explosions, etc) without having to change class code to support it.

 

3. My "scene" is managed by a "Scene Manager" class. This contains an Octree as its internal datastructure for all game objects which have anything to do with rendering a scene. All light sources, cameras, rendered game objects, and primitives are included here. The scene manager contains a list of cameras, but only one is active at a time. This lets you save a users current camera settings and switch to another camera to show an important cut scene or something else, and then switch back. Since the scene manager knows which camera is active and is using an octree as its backend datastructure for storing renderable objects, I can easily apply frustum culling to only render the objects which are in view with minimal CPU overhead costs.

4. My terrain system uses 16x16 blocks to render sections of terrain. It can switch the resolution of a block to use fewer triangles if the camera is distant and the resulting resolution change is below a set "pop" artifact threshold. Each block is stored in a terrain manager class, and the terrain manager class is also responsible for making sure that each block gets injected into the scene manager class.

 

 

Things I haven't even thought of yet:
1. How do I plan to take advantage of multi-threading? If I use multiple threads, how do I plan to keep my threads syncronized? What should happen to the game if a thread fails or gets aborted? Do I crash to desktop or try for a graceful recovery?
 

2. Do I want to support multiplayer? If so, how is my game played in multiplayer? How does the game design change? How does the game architecture change to support that? What happens if a player connection is dropped? How is the game host decided? How are game objects going to be serialized and deserialized? What game data needs to be sent to other players? How much should I trust the data sent to the client host by the clients (ie, is cheating/hacking happening?)
 

3. How am I planning on doing animation? I vaguely know that it's going to require something called a "scene graph" for each object, and something called "KeyFrames" and maybe "bones". I also don't want to manually create object animations, so I'll want to use a third party tool (Blender?) and figure out how to integrate their output assets into my content creation pipeline.

 

4. Sound and music management is an afterthought right now. I haven't even touched that.

 

 

Minor Critiques of your code:

1. Your commenting needs a lot of work. You don't need to comment every single line, especially if its very obvious as to what is going on (example: i--; //go back 1.) What you do need to comment on is what you're thinking/doing on a higher, more abstract level. When you're coding this, you know exactly what you're thinking and you have a lot of context on why you're doing it. Months later, you'll come back and stare at the code and read the comments. Comments like "delete some memory" will be an obvious description of what is happening on that line of code, but you'll have no idea why its doing it or what its trying to do. You'll essentially have to refigure out the high level design and your objectives before you can figure out what your code is doing. You're not only not saving yourself time with your comments, you're actually adding to it and creating extra clutter, which leads to faster eye strain and mental fatigue. You can use a multi line comment to generally describe what you're doing and how you're trying to do it and skimp on the line by line comments -- unless something is particularly tricky or confusing.

 

2. I don't know if you included it, but your movement commands for objects would probably benefit from an amount value. So, when you say, "Move left!" you're also telling it how far you want it to move left during that frame. Also, what happens if your game thread lags for a moment? You'll probably want to include an "elapsed game time" value in your movement to compensate for it.

 

3. For your Scene.UpdateFlags(), what is the difference between "UNDEFINED" and "NOT_UPDATED"? I am honestly a bit confused about what you're trying to do here. It seems like you're trying to use a flag to act as a "dirty" bit so that you know when to do an interpolation. This seems like it could be simplified a ton. Rather than having a separate function to determine if an objects state has changed, you could set the state to "changed" in your objects update code when it changes. You can get away with using a boolean as well. It'd look something like this:

class GameObject
{
private Vector3 Position;
private Vector3 LastPosition;
private Vector3 Velocity;
private bool updated = false;

public void Update(GameTime gameTime)
{
       Position += Velocity;
       updated = Position != LastPosition;
       LastPosition = Position;
}
}

As far as scene trees go, you can store separate transform matricies for each object. When a parent object gets transformed, you also send that transform down to all of its connected children. For example, if my arm is a segmented object, it is connected to my body at the shoulder. The shoulder can rotate my arm. That rotation gets fed down to all things connected to my shoulder socket. If I bend my elbow joint, that transform gets sent down to my wrist and finger joints. So, since matricies act as accumulators, a shoulder rotation would simply need to multiply the world matricies of all connected child objects to that shoulder. Since each object segment knows how to render itself based on its world transforms... it's too easy. Every object doesn't know what its parent is doing, but it just has to make sure that it sends its transforms down to all connected children and everything will be okay.


Eric Nevala

Indie Developer | Dev blog


#6 Danicco   Members   -  Reputation: 449

Like
0Likes
Like

Posted 22 February 2014 - 07:06 AM


Having an entire class for 'Keyboard Key' seems like overkill

 

Heh indeed, I noticed that way later and just made a single "KeyInput" class for all that.

 


If you're going to have a hierarchical structure, where a child's attributes at any time may depend on the parent's attributes, a parent knowing its children may be more efficient. I.e., you can call an update or render function for the root of the hierarchy, and each object will take care of it's children without having to test whether it's orientation or other attributes are up-to-date.

 

I tested doing it both ways and it didn't really change anything at all, the same calls, the same checks were made, just not in the same order.

I think it's because I have a single vector that contains all objects and I call it the "Scene".

The hierarchy is implemented as a "SceneGraph" of some sorts and it just links an object to another (and knows how they relate), so I don't actually have a hierarchical structure where I know (or have) the root element.

 

I tested a long time ago using a multi level linked list to work as a hierarchical structure, and I don't remember why but I scratched the idea. I think I asked for opinions on the code and got a bunch of feedback on how it was bad and all so I switched to single vector containing all the Scene and making the SceneGraph work separatedly.

Kinda like what LSpiro writes here.

Also, great site with a bunch of great ideas that helped me shape the engine so far.



#7 Danicco   Members   -  Reputation: 449

Like
0Likes
Like

Posted 22 February 2014 - 08:09 AM


I'm also working on a game and building its engine at the same time (mostly because it's fun and I want to see if I can actually do it -- and I'm a victim of 'Not Invented Here'). Here are some of my design principles/decisions:

 

A... are you me?

 

I'm also doing nearly everything what you've posted, except some parts which I haven't gotten to yet, like making the Oct/Quadtree. I'm still finishing up a Scene/SceneGraph then I'll think on how to optimize how they appear and update.

 

And I have no game system yet... I'm still at the very basics of the engine, just providing the basic classes that does stuff (Image, Models, Camera, Text, etc) and providing Input and other support classes.

At the end of it I want to be able to have a very customizable engine, and having to use the fewest commands to do it's tasks. I'm almost done on the basics and I'm starting to think on the... features. Physics, Particles, Environment/Ambients, Effects, etc

 

1. Your commenting needs a lot of work.

 

Ah the comments are mostly for here, the code isn't even copy/pasted from the original code. There's usually a lot more lines of other stuff going on that I omit and I change most variable names to really obvious names to give a better context.

 

But yeah, I wasn't commenting on anything at all until a few months ago, since the engine was really small, but then I took a break for a couple of months and when I got back I was like "what am I doing here". Then I went back and commented EVERYTHING.

 


I don't know if you included it, but your movement commands for objects would probably benefit from an amount value. So, when you say, "Move left!" you're also telling it how far you want it to move left during that frame. Also, what happens if your game thread lags for a moment? You'll probably want to include an "elapsed game time" value in your movement to compensate for it.

 

I'm using a time step implementation to deal with lags and such.

I changed all input to deal with float values (thinking of switching to int if float's imprecise enough to give me trouble later on, but it seems it isn't) mostly for axis values.

So at each Update() call, I only get the input state of a tiny time (like 0.00 to 0.01) and deal with only that input. When it lags, I do it multiple times, now with the next time step (0.01 to 0.02) until I'm "up to date". My "time" class also provides and handles the elapsed game time.

 

The only limitation I found here is that during that time I don't check for multiple of the SAME input. For example someone pressed 'A' twice in that microsecond. My Update() will only receive/deal with: "A is being pressed". Dunno if this will be an issue later...

 


For your Scene.UpdateFlags(), what is the difference between "UNDEFINED" and "NOT_UPDATED"? I am honestly a bit confused about what you're trying to do here. It seems like you're trying to use a flag to act as a "dirty" bit so that you know when to do an interpolation. This seems like it could be simplified a ton. Rather than having a separate function to determine if an objects state has changed, you could set the state to "changed" in your objects update code when it changes.

 

Undefined means it needs to be computed for this "time block".

My Update() code will be called exactly X times per second and it's something like this:

(1 time block)
 _
| | #1 (set all objects' flags to UNDEFINED)
| |
| | #2 (call game's Update() function, objects might change or not)
| |
|_| #3 (loop through the UNDEFINED ones and figure which ones changed - NOT_UPDATED or UPDATED)

What you suggested is actually what I'm doing at the #3 (loop through the UNDEFINED ones and figure which ones changed - NOT_UPDATED or UPDATED)

 

My Scene is a vector that holds all objects, so I iterate in a straight line (0 to N). But 1 might be a child or parent to 4, so 4 will probably be defined when I reach 1. If that's the case, it's no longer UNDEFINED and I don't need to check it.

 

Since an object's matrix will need to be recalculated even if their parent's orientation changed, not only theirs (or I would lose interpolation for the children), so when I reach an object I check all it's parents until the root parent - if any of them changed, I'll have to interpolate this one as well with the new parent's matrix.

By this time, I already checked all those parents... so they're no longer UNDEFINED and when I reach them again in the vector, I know even their parents have been checked, so I only check for single ones or remaining children (still UNDEFINED).

 

Thank you for the comments, that give me a bunch of things to think!

I'm almost done with the basic foundation and I was starting to get lost "what should I do next?" and you gave me lots of ideas on what to do next!






Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS