Welcome, Guest. Please login or register.

Login with username, password and session length

 
Advanced search

1411273 Posts in 69323 Topics- by 58380 Members - Latest Member: bob1029

March 28, 2024, 01:56:44 AM

Need hosting? Check out Digital Ocean
(more details in this thread)
TIGSource ForumsDeveloperTechnical (Moderator: ThemsAllTook)Let's make game engines - the thread
Pages: 1 [2]
Print
Author Topic: Let's make game engines - the thread  (Read 5344 times)
Sixmorphugus
Level 0
***


._.


View Profile
« Reply #20 on: May 17, 2015, 10:29:54 AM »

Continuation because I reached max post length;

Networking
Oh yes. It's the part you've been waiting for!

VOLT had multiplayer. It worked, without weird bugs or game breaking glitches. It just wasn't very good.

The system had some cool ideas. The client size didn't give too much power to the client, but gameplay looked pretty seamless from their point of view. The world could be downloaded, and appropriate loading screens would display while this happened. There was even a little GUI for entering a server IP, right next to the warning text that made absolutely sure the player knew just what they were getting themselves in for.

For other players, there was no prediction. The other characters would just jump forward whenever they received a packet from the server with a new position. Packets, by the way, were, while ID'd, always based on strings with delimiters rather than bitstreams and definitions. Positions were sent as strings, as were rotations and animations. Here's the full packet impementation for VOLT:
Code:
// net classes and definitions
enum netStatus { READY, NOTREADY, END, NETERROR };
enum packetType { ConnectHandshake, Disconnect,
ClientData, Info, DataRequest, SceneData, ObjectData, TileData,
ObjectUpdate, ObjectDestroy, ControlObjectUpdate };

// These are used by the gameManager as well as the net code but must be defined here! Add/Remove as needed by your game
// Note that ConnectHandshake, Keyboard and Disconnect are ALWAYS needed as they have hardcoded use in the server

struct voltUnPacket {
packetType type;
std::string data;
};

class voltPacket : public sf::Packet
{
public:
voltPacket();

void packData(packetType type, std::string data, sf::Packet &packetInstance);
void packData(voltUnPacket pack, sf::Packet &packetInstance);

voltUnPacket unpackData(sf::Packet &packetInstance);
};
You can see that there are two packet classes on VOLT - one represents an "unpacked" packet, which just holds the data - the other is one packaged in the SFML networking format, ready to be hopefully flung in the general direction of the server machine.

There was basic security, too:
Code:
// EXTREMELY IMPORTANT: IF THE SENDER IP ISN'T THE SERVER, DROP THAT PACKET LIKE HOT SHIT
if (senderIP != TCPsock.getRemoteAddress()) {
// !!!
continue;
}

The system was TCP/UDP based, but not for any clever reasons. Just because I basically had no idea what I was doing. I read through some SFML networking docs, wrote some small projects to test concepts, then went at it. TCP was only used because I saw it as "a good way to not need any sort of keep alive packets". VOLT's networking didn't have packet rates, or any actual good design features, because it only sent packets when something happened. All it had were the netCore classes and the extra functionality defined in the gameManager. And this system was the result of my second attempt - my first was so unspeakably bad that I to this day suppress the memory. Just goes to show; the more times you do it over, the more likely you are to "succeed".



That's almost it for my past game engine designs - soon, we'll be moving on to my current project - which is actually looking rather good, at this point.

Thanks for all your replies and support. I'll try to make more posts in the future, as well as answer any questions you might have. Keep in mind that all code shown is pulled directly from the source files, so the indentation might be a little odd for all of them.
Logged

There's no place like 127.0.0.1.
Sixmorphugus
Level 0
***


._.


View Profile
« Reply #21 on: May 23, 2015, 03:57:02 AM »

Bump.

No replies for a week?  Who, Me?
Logged

There's no place like 127.0.0.1.
oahda
Level 10
*****



View Profile
« Reply #22 on: May 23, 2015, 05:34:08 AM »

If you think that's bad, try having an actual devlog in the devlogs section. It's going to be on page 5 after a week if nobody replies. ;p

Just keep writing and people will keep up eventually.
Logged

Sixmorphugus
Level 0
***


._.


View Profile
« Reply #23 on: May 23, 2015, 07:02:30 AM »

If you think that's bad, try having an actual devlog in the devlogs section. It's going to be on page 5 after a week if nobody replies. ;p

Just keep writing and people will keep up eventually.
Okay (:

Thanks for the encouragement, I've got another write up coming up soon.
Logged

There's no place like 127.0.0.1.
Mixer
Level 1
*


I've never really known what to put here.


View Profile WWW
« Reply #24 on: July 11, 2015, 05:42:19 AM »

Great! I personally love games, but the design behind good engines makes me go crazy Tongue we need more threads like this! Keep posting. Hand Any Key Gentleman
Logged

My twitter is @5Mixer.
I'm currently making a dungeon game, check it out :D
https://forums.tigsource.com/index.php?topic=59139.0
Cheezmeister
Level 3
***



View Profile
« Reply #25 on: July 15, 2015, 12:55:36 AM »

Quote
I like to think of it in terms of classifying side effects and their "blast radius".  No prefix (local)?  No problems there.  m?  Probably not that bad.  s?  I don't like this.  g?  Avoid at all costs.

Very true, and an important sixth sense to develop as a coder. You don't *need* Hungarian notation to have it, though.

Also, dang, you implemented a functioning networked multiplay, and it just didn't play very well? That's heartbreaking!
Logged

෴Me෴ @chzmstr | www.luchenlabs.com ቒMadeቓ RA | Nextris | Chromathud   ᙍMakingᙌCheezus II (Devlog)
Sixmorphugus
Level 0
***


._.


View Profile
« Reply #26 on: July 15, 2015, 03:54:45 AM »

Also, dang, you implemented a functioning networked multiplay, and it just didn't play very well? That's heartbreaking!
I should probably elaborate more on why it was bad.

First of all, the underlying send/receive system was a mess. To add new packet types, you'd always have to write some buggy conversion conversion code to send the values. You'll see the difference with the game engine I post next - that one can send JSON objects.
Logged

There's no place like 127.0.0.1.
baconman
Level 10
*****


Design Guru


View Profile WWW
« Reply #27 on: July 16, 2015, 02:59:53 PM »

I'm also in concurrence that threads like this DO need to exist.

Been trying my "game about games project" in GMx, Fusion, RPGMaker, but I keep hitting hoopla-walls while getting them to the point of desired subgame generation; and keep finding myself back at the "write an engine for the game in C++/VB/.NET" point.

I can design games like crazy, but I'm not so "three-steps-ahead" as it takes for engine design. :/
Logged

Sixmorphugus
Level 0
***


._.


View Profile
« Reply #28 on: August 16, 2015, 10:04:26 AM »

I'm back!

MAGE
[no github link for this one]
Dev topic for this game coming soon!***********

MAGE has always been a tricky one to talk about, the reason for that being mainly because it's still in development. It's a game engine I work on very often and have become very fond of. It's functional, it's strong, and it's damn, damn big.

MAGE is the sort of game engine mad people make. It's a project where you'll be in the black screen stage for literal weeks and still be proud of and into what you're doing. But it does fit all the previous rules I've felt out - it's made for a SPECIFIC game, it has Dynamic Asset Loading (with package system), a Scripting engine for easy extensibility, Fixed object/rendering pipeline with optimization, Extensible component based design, No “bloaties”, working, stable multiplayer, a Dynamic INPUT manager with REBINDABLE keys, and, finally, proper seperation of GUI and objects.

Also, I think I finally figured out this inheritance biz.

Design
MAGE is designed to be lightweight when running, yet responsive and usable for both the person making a (top down fast paced rpg) game with it. The package system means you get to choose what's in the memory at a given time, what functions and mechanics form the "base" of the game, and what can be stapled on - in a word, mods.

This is a little hard to explain, so let me show you the layout of the game directory to sum it up:

Quote
/Gamebase
/Packs
/platforms
mage.exe
some.dlls

The most important folder here is the Gamebase folder. When the engine starts up, that's the first folder it goes for. It contains something referred to by the engine as a "package" - which, incidentally, is also the purpose of every folder in the Packs directory. The idea is that all your base code and asset data goes in Gamebase (which contains script files for both server and client) and, while the game is AWARE of all the other, custom packages that exist in Packs, it's up to Gamebase how they're loaded; be it through some gui or just all at once no matter what. A pack cannot ever operate on files outside of it, and all a pack's config files go inside that pack. Pretty simple.

Except it wasn't. I had to redesign 3 times. It's ok now.

Conventions
  • Camel case. Everywhere.
  • I used default arguments only for things that were unimportant.
  • Destructors were usually used.

Assets
The system for assets in MAGE is simple.
All assets in MELT derive from the abstract object Asset. The 3 defaults are Sprite, Sfx and Font. Want to be able to load more? No problem! Derive Asset via the scripting engine. Just remember, your object has to provide a similar interface to Sprite, Sfx and Font for loading and use of the asset, including the routine that loads and prepares it from your file (through the engine's file objects) and the one that loads/unloads it during a loading screen.

Most of the time you don't have to mess with an asset object at all because entity/gui components do that tricky stuff for you. It's only if you're writing your own component that you have to use engine drawing functions. And usually, that's pretty easy!

Assets are separated into packs, and pack assets are separated into public and private. On the server side, Public assets are the most important - they're the ones that are sent to (and subsequently duplicated by) every player that joins your game. Private ones exist only on the server side, because... well, because it's there. You don't need to send, for example, a bunch of generator rules to the client, or a text file showing who has admin privileges. The client implements the same system, but to a different end - "private" assets on the client are for the client only, and "public", while normally kept TO the client, can be requested from them by the server at any time.

Oh, and spritesheets don't have their own animations any more. Instead, you use an animationComponent on an object. Which brings me to....

Game Objects
There are two types of Game Object in MAGE:
  • Window
  • Entity

Both derive from something called config. And config is pretty unique. Config's primary purpose is as an engine utility class that allows for an object with custom attributes defined at runtime rather than a bunch of fixed members that can't change.

You have no idea how liberating something like that is when working with C++.

Config can be used alone, for simple reading of JSON files - it can load them in and export them, populating its variable tree accordingly, without people having to use the engine's default file object for opening files, write their own JSON reader, etc. But it serves a second purpose. Config is what both Window and Entity, vastly different objects, derive from. Both Windows and Entities can, as a result, be saved to and loaded from files or data held in assets.

But both classes expand on this more, by being based on Components. Alone, an Entity (or window) does nothing but exist and take up space. With components, you can have it act in certain ways, move in certain ways, draw in certain ways - the world is your oyster.

Input
Same system as VOLT, with some improvements to the console side of things.

Networking
Oh god.

Every feature in MAGE has had to bend around this. Some are still bending.
When I started working on VOLT, I wanted to automate most of this. I wanted to create a networking system where the server/client side of things will keep out of a mod's way, and everything will be automatically handled without VOLT's large packets or dangerously unstable connections.

The first step of this is client/server commands. These are, put simply, a way for mods and the game itself to send their most simple text based communications over the network. A command has a name and a function to be called by it. When whichever side of the client/server model that is on the other end sends a command and an accompanying operand string (which can be processed however you want it to be) the function will be called to handle it. It's a beautifully simple system, without which simple net communication not handled by the engine would be a LOT harder for everyone.

Then there's the synchronization. Since everything is custom, prediction is kind of hard. Since the client doesn't know how something moves, animates, or changes, the server has to do a bit more work. Packets are time stamped, and for entities using engine components, limited prediction is possible. To further improve things, I'm debating whether to allow those using the scripting engine to "predict" object update packets before they arrive.

As I said at the beginning, if this one goes to plan, my devlog will be going up soon. Stay tuned, I guess.

And, of course, I'll probably come up with more stuff to post. But probably not soon. So I'm changing the thread title. Since I'm all out of tales to tell for the time being, this is now a thread for everyone to share their expertise on this subject. Enjoy!
Logged

There's no place like 127.0.0.1.
zleub
Level 0
**


View Profile WWW
« Reply #29 on: August 17, 2015, 05:26:40 PM »

That's been a very interesting read, don't stop here. I'm looking forward to see what your next engine can render.
Logged
TheLastBanana
Level 9
****



View Profile WWW
« Reply #30 on: August 31, 2015, 02:12:25 PM »

Figured I should contribute something here since I've been working on my engine a lot recently. ProgramGamer didn't go into much detail about his input system, so I'll rattle on a little about my own! I'm also using C++ with SFML.

I wanted my input system to be:
  • Rebindable
  • Input type agnostic -- I don't want to muck about with a bunch of if statements in player control code to determine how to handle a keyboard versus a joystick
  • Replayable -- I'm a sucker for attract mode demos

So with all that in mind, I decided that all input should pass through a single class called InputManager. Here's how I tackled each of those issues.


Rebindability
It seems to me the best way to handle this is to use lambda functions. My basic binding function looks like this:

Code:
template <typename T>
void bindInput(uint8_t bindingId, uint8_t mode, const std::function<T()>& fn, float sensitivity = 0.f);

where bindingId is an identifier for the binding (usually a user-defined constant like PLAYER_FIRE), mode is the input mode (again, user-defined constants like KEYBOARD or JOYSTICK), fn is the polling function for the input, and sensitivity is the threshold above which the input is considered nonzero (useful for ignoring analog input that tends to hover in the ~0.05 range).

The polling function can be one of three types currently: float, int8_t, or bool. In all cases, the return value should be between -1 and 1. So basically, float is used for analog input such as joysticks, bool for digital input such as buttons, and int8_t for bidirectional digital inputs.

Since these are lambda functions, you can really get the input from any source you like. Of course, there are also plenty of convenience functions to let you use SFML's native input without having to set up lambdas (I'll cover these in a bit).


Input type agnosticism
So how do you get this data back out of the input manager?

Code:
float getFloatValue(uint8_t bindingId, uint8_t mode) const;

Just pass in the binding id and input mode and you get back its value converted to a float. If the binding returns an int8_t or bool, then you just get that value cast to a float. Alternatively, for digital input:

Code:
int8_t getIntValue(uint8_t bindingId, uint8_t mode) const;

If the binding returns a float, then this will return 0 if the input is below the sensitivity threshold, and otherwise return the nearest maximum (-1 or 1).

There are also versions of these functions omitting the mode parameter, in which case the last value passed to InputManager::setInputMode(mode) is used.

So let's say we have a Space Invaders clone where the player moves on a horizontal axis. We can set up bindings like this:

Code:
enum Bindings
{
    Horizontal
}

enum InputModes
{
    Keyboard,
    Joystick
};

// This creates a bidirectional input from two keys (A pressed => -1, D pressed => 1, both/neither => 0).
inputManager.bindInput(Bindings::Horizontal, InputModes::Keyboard, sf::Keyboard::A, sf::Keyboard::D);

// This binds joystick #0's X axis with a sensitivity of 0.2f.
inputManager.bindInput(Bindings::Horizontal, InputModes::Joystick, 0, sf::Joystick::X, 0.2f);

In the game's event loop, we can switch between the Keyboard and Joystick modes when we detect events of the appropriate type.

In the player class, we can just use inputManager.getFloatValue(MOVE_HORIZONTAL) to determine the movement. If the player is using a keyboard, we get -1.f, 0.f, or 1.f. If they're using a joystick, then we get more precise values between -1.f and 1.f. Or, if we want to keep joystick players from having that much precise control, we can just use inputManager.getIntValue(MOVE_HORIZONTAL) which will always return either -1, 0, or 1. Nice and clean!

The bindings also remember their values from last frame, so you can use functions like this:

Code:
int8_t getIntDelta(uint8_t bindingId) const;

This is really useful for detecting if the button has just been tapped or if it's being held.


Replayability
You might have noticed that everything in in the input manager is using 8-bit integers. Under the hood, even floats are converted to 8-bit integers before they're returned by the getFloatValue() functions. Internally, the input values are all integers ranging from -127 to 127 (including int8_t inputs, which are either -127, 0, or 127).

The reason for this is that it's very easy to spit this data into a binary file. Later, I can grab data back out of that binary file and have it converted nicely to a float or an integer without caring about the type of the original input function. Another bonus is that since most important data is in 8-bit chunks, they don't take up much space.

If people are interested, I can go into some more detail about the replay system, but I feel like I've already gone on long enough with this post. Cheesy

If you want to check out (or steal) some of the source code, this engine is totally open-source, so take a look here:
https://github.com/VGAD/ECSE
Logged
icecold
Level 0
*


BANNED


View Profile
« Reply #31 on: September 05, 2015, 03:46:11 AM »

I've been working on my own game development framework and engine in C++ for the past 6 years. It's been for the most part functional for several years, but I enjoy working on it so much that I haven't even been bothered to make anything with it or release it to the public.
Logged
pmprog
Level 1
*


View Profile WWW
« Reply #32 on: September 09, 2015, 06:45:31 AM »

I'm starting a new engine framework here. It's now inadequately named. My original plan was for an engine that basically developed C64-style games, though I'll probably just inflate it to replace my current engine

My games tend to start with a framework, but I tweak it as I make the game. Unfortunately, I never feed it back to the original engine project, so each game has it's own splinters.

I really should try and work out git submodules properly; or find a way of making my engine as a "linkable" library instead. Actually, the latter sounds like quite a nice idea.
Logged
happymonster
Level 10
*****



View Profile WWW
« Reply #33 on: September 26, 2015, 05:25:48 AM »

More and more I'm coming to the conclusion that for small one man-projects, it's easier to do one off engines that are simple, quick to make, and maintain rather than trying to do a massive multi-purpose engine to cover all eventualities.
Logged
pelle
Level 2
**



View Profile WWW
« Reply #34 on: September 28, 2015, 03:55:12 AM »

More and more I'm coming to the conclusion that for small one man-projects, it's easier to do one off engines that are simple, quick to make, and maintain rather than trying to do a massive multi-purpose engine to cover all eventualities.

Me too. Copy-paste a few files from an old project can be useful to get started quickly, but then it is nice to start fresh and just add what is needed and in the way it is needed rather than trying to work with something that just almost is the right things you need.

BTW, for OP: "like the blob from that 80s horror flick. Was it the 80s? I think it was the 80s"

There was a remake made in the 1980's (or even early 1990's?), but the original is much older, probably 1950's, or at least looks like a classic 50's horror movie. Both of them are really good as I remember them.

Nice thread. It is easy to overlook threads here with all the traffic, so I often miss interesting ones for months or years until noticing by chance when they are bumped.
Logged
Daid
Level 3
***



View Profile
« Reply #35 on: September 28, 2015, 09:49:21 PM »

Being entirely self-taught (I hope to get into a university where they teach C++ soon, even if I have to completely relearn the language to get rid of my own bad habits)
Prepare to be disappointed. I'm self-taught as well. And I still depend more on that then anything I learned in the 3 C++ classes that I had.


As for engine programming. I do that as well, because I like it. I like having everything under control. My latest engine code is here:
https://github.com/daid/SeriousProton/tree/master/src

I'm especially happy about the multiplayer code implementation. It works that the server is the master and ruler of everything. When new objects are created they will be send trough the network. And members of those objects can be marked as "need to be send across the network from server to client". Every client has the same game state (so cheating is possible) but that does not matter for my game.
Example of an object that gets send across the network:
https://github.com/daid/EmptyEpsilon/blob/master/src/gameGlobalInfo.cpp

The "registerMemberReplication" functions define which variables need to be send from the server to the client. Client to server communication is a bit more complex, as that's done way less and the API isn't that good. But example of client to server communication:
https://github.com/daid/EmptyEpsilon/blob/master/src/playerInfo.cpp#L50
https://github.com/daid/EmptyEpsilon/blob/master/src/playerInfo.cpp#L80


There is also LUA script bindings, but that's a bit of a mess. It works, but I would not use it as an example.

Actual "released" game I'm using it for:
http://emptyepsilon.org/
(Open source multiplayer space ship bridge simulator)
Logged

Software engineer by trade. Game development by hobby.
The Tribute Of Legends Devlog Co-op zelda.
EmptyEpsilon Free Co-op multiplayer spaceship simulator
Pages: 1 [2]
Print
Jump to:  

Theme orange-lt created by panic