Advertisement

What are some good tips for programming a game that is "port"-able?

Started by August 03, 2018 01:30 PM
6 comments, last by Shaarigan 6 years, 1 month ago

Hi there,

I'm currently working on a game engine and mechanics for a game I'm designing. This internal code is relatively generic in the sense its doesn't rely on any platform specific APIs (that I'm aware of). While I've started looking around at graphic rendering APIs and what not, I've not made a decision to settle onto anything hard yet. 

As I continue developing this game, I'm curious if anyone has any tips and tricks to avoid locking myself into certain APIs in the event I would someday like to port the game to another platform in which I would need to interact with different APIs. For example, I'm still not sure on the art style and whether or not I want to do a 2D pixel game or a 3D-esque top down style. I might have to change my render API eventually if I don't like the art direction.

I'm currently wrapping all APIs into C++ classes for modularity and am using C standard typedefs to ensure correct data types across platforms. However, is there anything else I should watch out for?

Hello!

Things to be aware of in my experience are:

- Write standard conformant code. You might need to use a different compiler on different platforms. C++ compilers may have their own extensions and features (e.g. pragmas) which might not work on others, or may be able to do clever things that other can't (e.g. the Microsoft compiler in my experience is sometimes less clever than GCC when it comes to template argument deduction, resulting in some  code not compiling on msvc).

- Be aware of performance and availability of the c++ features you are using. Some more recent c++ features might not be supported on all compilers. Things like exception handling might be badly implemented and cause performance impacts on some compilers.

- Avoid calling platform specific code directly (e.g. windows api functions), since of course it will not work on other platforms.

- Graphics API are also platform specific. DirectX will only work under windows. OpenGL is more portable, but for mobile devices you will use GL ES, which is more constrained.

- If you use third party libraries, make sure that they are cross-platform and that they support all platforms that you are targeting.

- Pay attention to platform specific limitations. For example, on Android you will not write your own main, but you will write a library which will then be linked to the actuak executable. Another example, is that windows encodes unicode file names in the utf16 format, while linux in the utf8 format. 

- If you just manage your project builds in an IDE, you might need to redefine the builds for platforms on which you can't use that specific IDE.

 

In general the following will help you writing platform independent code.

- Wrap third party, platform specific, and compiler specific code behind your own interfaces. In this way, you can swap things out or support new platforms without changing the code built on top. You just need to write a new implementation for your interfaces.

- Continuously test builds in all target platforms. This will let you notice problems immediately, when they can be fixed without too much effort.

- If possible, use a library that abstracts the underlying platforms for you. This will save you a lot of time. SDL is very good and widely used. GLFW is ok as a windowing only library, but you will need something else for mobile platforms. SFML is a c++ style library, but I feel it is a bit more limited than SDL. I am also working on my own c++ middleware library (https://github.com/DavideCorradiDev/houzi-game-engine), which is partly built on top of SDL, but it is not mature yet. :)

- Use a cross-platform build system, e.g. cmake. In this way you can manage builds for all target platforms in one place.

- Figure out early what platforms you want to support, or you might have to make large redesigns later.

- Figure out early what your game needs. Swapping from 2d to 3d graphics for example might not be a light undertaking. :)

Advertisement
Quote

OpenGL is more portable, but for mobile devices you will use GL ES, which is more constrained.

Just to add to this, Apple has recently deprecated OpenGL and OpenGL ES on its platforms. They can still be used for now, but they've more or less been superseded by Metal on macOS/iOS. So that's something else to factor in when thinking about graphics APIs.

4 hours ago, Zakwayda said:

Just to add to this, Apple has recently deprecated OpenGL and OpenGL ES on its platforms. They can still be used for now, but they've more or less been superseded by Metal on macOS/iOS. So that's something else to factor in when thinking about graphics APIs.

Now that Apple platforms have semi-official support for Vulkan it makes Vulkan the graphics API to target if cross-platform support is important for you. It is still not supported on all platforms (eg. Xboxes still support only DX and Sony has their own APIs too) but porting from Vulkan to another low-level API is actually not that difficult as opposed to porting from, say, OpenGL to DX11.

Another option would be to look at a library like LLGL which abstracts away most (but not all) of the API differences and gives you a somewhat unified API to work with.

Just to add, handling text differs on unix based systems and windows   - im talking about end of the line character, the same applies for socket programming

Best advice I can give you is to start out by developing your code on multiple platforms simultaneously.  When you do that, you will discover tons of tiny surprises along the way that you need to adjust to keep the code clean on all platforms.  Fixing issues as they appear is quick and easy.  But, putting off discovery until after lots of code has been written, you are signing yourself up for lots of unpleasant surprises (and rewrites).

Advertisement

Keep it simple and build a core library that contains all these interface API code and/or split your code into modules so that each module is responsable for just one single kind of system. This makes it easier to port classes to other platforms as the "public" interface never changes to code setup on that core system while you could work on the core and it dosen't effect the rest of the game.

Also don't overcomplicate features and code while writing your system ever think about if you really need just that OS API function or could it be replaced by something self written. Keeping this question in mind gives ya a small set of OS dependent features that are

  • Filesystem Access
  • Threads/Locking
  • Window Management & System Messages
  • Audio
  • Graphics
  • Network
  • Input

Accessing the filesystem as same as threads and locking could be done via STL but I decided to go for the hard way and was suprised that those implementations are very small and fast and you could do some tricks in C++ for example that make using your API more convinient.


Drough::Thread<byte* (Drough::string)> FileFetcherThread(FileFetcherFunction);
FileFetcherThread.Start("Test.txt");

for example is my version of a templated thread.

Window Management was really simple too because you need just a few OS function calls to setup a window correctly and have it's visual style, size and position changed took me 4 hours of work until anything worked fine.

Audio and Graphics is a little bit more tricky as this depends on the underlaying API, so I decided to skip DirectX support and focus on Vulkan/OpenGL (no need for me to go for Apple platform here), while I wrote my wrappers for GL and Vulkan by myself, there was a Tool I made that grabs the specs from Khronos GitHub and creates interface classes for. There is then just a little OS API code needed to load/bind function pointer to those interface while anything else is then managed by GL/Vulkan itself.

The most annying part is Audio because you either need a plugin system or set to just one Audio API on each platform while building your code through code compiler. This maybe the only topic where I would accept to use plugins while in my opinion plugins elsewhere (like in OGRE) are a sign of bad or too general code architecture.

Networking took me a while too because there is a difference between Windows and UNIX platforms you need to be aware of while general socket access is similar on Windows, UNIX and Playstation/Nintendo SDK, Windows is using a "wait until something happened" model for asynchronous sockets, while UNIX follows the "wait until nothing happens anymore" model.

Last but not least input (if not handled through OS messages that I highly recommend to not use) is a special kind too. Without DirectX I even hadn't access to use DirectInput/XInput so the path to go here was using the HID driver interface (used for accessing USB devices). HID is mostly standard but access is different from platform to platform while you need Windows Driver SDK on Windows, there are some Libraries and also a Kernel Level Driver on UNIX while Playstation/Nintendo handles that through there API directly.

Anything else about tooling and build pipeline was already discussed above. Something worth to mention, I wrote my own tooling using C# (because .NET/Mono runs on every platform too) to handle build files

This topic is closed to new replies.

Advertisement