Description:

Vingine is my custom C++ game engine that I have been working on during my time at the Guildhall.

The goal of the Vingine project is to create a multiplatform game engine capable of handling both 2D and 3D gameplay.

Quick Statistics:

Role: Sole Programmer

Language: C++

Platforms Supported:

  • Windows
  • HTML5
  • Android (NDK)
  • PS3
  • Vita

Development Time: 10 months (ongoing)

 

Sole Programmer:

  • Wrote codebase for custom C++ game engine.
  •  Added multiplatform support for Android and HTML5.
  • Built entity-component system framework for gameplay code.
 

  • Motivation:

    In order to allow for simpler cross-platform development, an abstraction layer is required between the game code and engine code. In Vingine, I chose to do this through a series of singleton classes called Interfaces.


    Design:

    The abstraction layer is created through a series of singleton classes called Interfaces. Each Interface represents a certain set of platform-specific functionality that the game engine needs to function. Calls to this abstracted functionality are made by calling a set of static functions, so that there is no need to get a pointer or reference to the interface.


    Internally, the structure of each interface varies. For the library-specific interfaces like the AudioInterface, I chose to use the "service locator" pattern. These interfaces contain a static pointer to their own class. On the initialization call, a specific internal library interface is chosen based upon a set of build preferences provided by the game. From there, all static function calls are routed through the static pointer to the internal implementation.


    Code:

    Abstracted Singleton Interface:

    
    /*********************** HEADER FILE ***********************/
    ABSTRACT SINGLETON class AudioInterface
    {
    public:
    	//Abstracted Audio Identifiers
    	typedef size_t SoundID;
    
    	typedef size_t EmitterID;
    	static const EmitterID ANY_EMITTER = 0;
    
    	typedef size_t ListenerID;
    	static const ListenerID DEFAULT_LISTENER = 0;
    
    
    	//Lifecycle
    	static void Startup();
    	static void Update( float deltaSeconds ) { s_activeAudioInterface->DoUpdate( deltaSeconds ); }
    	static void Shutdown();
    
    
    	//Public Interface
    	static SoundID GetOrLoadSound( const std::string& soundFileLocation ) { return s_activeAudioInterface->DoGetOrLoadSound( soundFileLocation ); }
    	//...More static public declarations here...
    
    private:
    	//Internal Lifecycle
    	virtual void PostStartup() = 0;
    	virtual void DoUpdate( float deltaSeconds ) = 0;
    	virtual void PreShutdown() = 0;
    
    	//Internal Interface
    	virtual SoundID DoGetOrLoadSound( const std::string& soundFileLocation ) = 0;
    	//...More internal declarations here...
    
    	//Data Members
    	static AudioInterface* s_activeAudioInterface;
    };
    
    
    
    /*********************** SOURCE FILE ***********************/
    //-----------------------------------------------------------------------------------------------
    STATIC AudioInterface* AudioInterface::s_activeAudioInterface = nullptr;
    
    #pragma region Lifecycle
    //-----------------------------------------------------------------------------------------------
    STATIC void AudioInterface::Startup()
    {
    	FATAL_ASSERTION( s_activeAudioInterface == nullptr, "Audio Interface Error", "Cannot start up multiple Audio Interfaces!" );
    
    #if defined( AUDIO_INTERFACE_USE_FMOD )
    	s_activeAudioInterface = new FMODAudioInterface();
    #elif defined( AUDIO_INTERFACE_USE_OPENAL )
    	s_activeAudioInterface = new OpenALAudioInterface();
    #elif defined( AUDIO_INTERFACE_USE_OPENSL_ES10 )
    	s_activeAudioInterface = new OpenSL10AudioInterface();
    #else
    	s_activeAudioInterface = new NullAudioInterface();
    #endif
    	s_activeAudioInterface->PostStartup();
    }
    
    //-----------------------------------------------------------------------------------------------
    STATIC void AudioInterface::Shutdown()
    {
    	FATAL_ASSERTION( s_activeAudioInterface != nullptr, "Audio Interface Error", "Cannot shut down an Audio Interface that hasn't yet been started!" );
    
    	s_activeAudioInterface->PreShutdown();
    	delete s_activeAudioInterface;
    }
    #pragma endregion //Lifecycle
    

    Internal Audio "Service":

    
    /*********************** HEADER FILE ***********************/
    SINGLETON class FMODAudioInterface : public AudioInterface
    {
    	friend class AudioInterface;
    	typedef FMOD::Channel EmitterChannel;
    	typedef FMOD::Channel ListenerChannel;
    
    public:
    	//Lifecycle
    	void PostStartup();
    	void DoUpdate( float deltaSeconds );
    	void PreShutdown();
    
    	//Public Interface
    	SoundID DoGetOrLoadSound( const std::string& soundFileLocation );
    	//...More internal audio interface declarations here...
        
    private:
    	// Should only be made by our Audio Interface
    	FMODAudioInterface();
    	~FMODAudioInterface();
    
    	//Error Checker
    	static void VerifyFMODResultOrDie( FMOD_RESULT result );
    
    	FMOD::System* m_fmodSystem;
    
    	std::vector< EmitterChannel* > m_emitterRegistry;
    	std::vector< ListenerChannel* > m_listenerRegistry;
    
    	std::map< std::string, SoundID > m_soundIDMapping;
    	std::vector< FMOD::Sound* > m_soundRegistry;
    };
    
    
    
    /*********************** SOURCE FILE ***********************/
    #pragma region Lifecycle
    //-----------------------------------------------------------------------------------------------
    void FMODAudioInterface::PostStartup()
    {
    	// Create the main system object.
    	FMOD_RESULT result = FMOD::System_Create( &m_fmodSystem );
    	VerifyFMODResultOrDie( result );
    
    	// Initialize FMOD.
    	result = m_fmodSystem->init( 512, FMOD_INIT_NORMAL, 0 );
    	VerifyFMODResultOrDie( result );
    }
    
    //-----------------------------------------------------------------------------------------------
    void FMODAudioInterface::DoUpdate( float /*deltaSeconds*/ )
    {
    	m_fmodSystem->update();
    }
    
    //-----------------------------------------------------------------------------------------------
    void FMODAudioInterface::PreShutdown()
    {
    	m_fmodSystem->release();
    	m_fmodSystem = nullptr; 
    }
    #pragma endregion //Lifecycle
    
    
    
    
    #pragma region Public Interface
    //-----------------------------------------------------------------------------------------------
    AudioInterface::SoundID FMODAudioInterface::DoGetOrLoadSound( const std::string& soundFileLocation )
    {
    	std::map< std::string, SoundID >::const_iterator soundIDIterator = m_soundIDMapping.find( soundFileLocation );
    
    	if( soundIDIterator != m_soundIDMapping.end() )
    		return soundIDIterator->second;
    
    	SoundID soundIDForNewSound = m_soundRegistry.size();
    
    	FILE* soundFile = AssetInterface::OpenAssetAsFile( soundFileLocation.c_str(), "rb" );
    	AssetInterface::SeekInAssetFile( soundFile, 0, SEEK_END );
    	size_t soundFileSize = AssetInterface::GetCurrentPositionInAssetFile( soundFile );
    	AssetInterface::SeekInAssetFile( soundFile, 0, SEEK_SET );
    
    	char* soundData = new char[ soundFileSize ];
    	AssetInterface::ReadFromAssetFile( soundData, sizeof( char ), soundFileSize, soundFile );
    	AssetInterface::CloseAssetFile( soundFile );
    	RECOVERABLE_ASSERTION( soundData != nullptr, "Sound Loading Error", "Unable to load a sound from file." );
    
    	FMOD::Sound* newSound = nullptr;
    	FMOD_CREATESOUNDEXINFO* soundBufferInfo = new FMOD_CREATESOUNDEXINFO();
    	soundBufferInfo->cbsize = sizeof( FMOD_CREATESOUNDEXINFO );
    	soundBufferInfo->length = soundFileSize;
    	FMOD_RESULT soundLoadingResult = m_fmodSystem->createSound( soundData, FMOD_2D | FMOD_OPENMEMORY_POINT, soundBufferInfo, &newSound );
    	VerifyFMODResultOrDie( soundLoadingResult );
    	delete soundBufferInfo;
    
    	m_soundRegistry.push_back( newSound );
    	m_soundIDMapping[ soundFileLocation ] = soundIDForNewSound;
    	return soundIDForNewSound;
    }
    
    //...More internal interface definitions follow...
    
    #pragma endregion //Public Interface
    

    Back to top of code tabs

  • Motivation:

    Ever since my first interaction with the Unreal engine, I have been interested in the idea of constructing my own engine out of entities and components. Since then, I have done two different iterations on the entity-component design, and I feel like the current iteration is worth using in my engine for all games going forward.


    Design:

    The entity class is a basic container of components like it is in most entity-component systems. In addition to its components, an entity also contains an ID (used by the manager), a typeID (which can be used by games to identify specific entities), and everything needed to do integration. (position, velocity, etc.)


    The entity manager is a mandatory member of all game interfaces. The class contains an object pool of entities that is created when the game interface is started up. (If a game does not wish to use entities, the size of the pool can be set to zero.) From there, any game class can call through the interface to hire and fire entities as needed.


    Components, outside of storing data, only store a pointer to their owning entity and a boolean about whether they are active for processing by their system. They are managed by component systems that store a component pool that is instantiated on startup. During each frame, every component system is given time during the update, rendering, and end of frame cleanup phases in order to perform its duties.


    Code Snippets:

    Entity Structure:

    
    //-----------------------------------------------------------------------------------------------
    struct Entity
    {
    private:
    	static const unsigned int ID_Null = 0;
    	friend class EntityManager;
    
    
    public:
    	typedef unsigned int TypeID;
    	static const TypeID TYPEID_None = 0;
    
    
    	//Construction/Destruction
    	Entity();
    	virtual ~Entity();
    	
    
    	//Component Helpers
    	template< typename ComponentType >
    	void AttachComponent( ComponentType* componentToAttach );
    
    	template< typename ComponentType >
    	void DetachComponent( ComponentType* componentToDetach );
    
    	template< typename ComponentType >
    	ComponentType* FindAttachedComponentOfType();
    
    	template< typename ComponentType >
    	bool HasAttachedComponentOfType();
    
    
    	//Entity Management Helpers
    	bool IsHired() const { return ( id != ID_Null ); }
    
    
    	//Data Members
    	unsigned int id;
    	TypeID typeID;
    	Entity* creator;
    	EntityBlueprint* blueprint;
    	std::vector< Component* > attachedComponents;
    
    	FloatVector3 position;
    	FloatVector3 velocity;
    	FloatVector3 acceleration;
    	EulerAngles orientation;
    	EulerAngles angularVelocity;
    
    private:
    	Entity* nextFiredEntity;
    };
    

    Entity Manager:

    
    /*********************** HEADER FILE ***********************/
    //-----------------------------------------------------------------------------------------------
    typedef const Entity* ConstEntityIterator;
    
    //-----------------------------------------------------------------------------------------------
    class EntityManager
    {
    public:
    	//Entity Management
    	void FireEntity( Entity* entity );
    	Entity* HireEntity();
    	void QueueEntityForFiring( Entity* entity );
    
    	//Iteration
    	ConstEntityIterator GetPoolStart() { return &m_entityPool[0]; }
    	ConstEntityIterator GetPoolEnd() { return &m_entityPool[ m_numEntitiesInPool ]; }
    
    
    private:
    	//Only the game interface is allowed to construct an entity manager
    	friend class GameInterface;
    
    
    	//Lifecycle
    	EntityManager( size_t maxEntitiesInPool );
    	~EntityManager();
    	void DoAtEndOfFrame();
    
    	//Having multiple talent managers would probably cause the entities to unionize. Bad idea.
    	EntityManager( const EntityManager& other );
    	EntityManager& operator=( const EntityManager& other );
    
    	//Helpers
    	void ShowEmptyPoolErrorMessage();
    
    	//Data Members
    	static unsigned int s_nextEntityID;
    
    	size_t m_numEntitiesInPool;
    	Entity* m_entityPool;
    	Entity* m_joblessEntityLeader;
    	std::vector< Entity* > m_entitiesWaitingForFiring;
    };
    
    #pragma region Lifecycle
    //-----------------------------------------------------------------------------------------------
    inline EntityManager::EntityManager( size_t maxEntitiesInPool )
    	: m_numEntitiesInPool( maxEntitiesInPool )
    	, m_entityPool( new Entity[m_numEntitiesInPool] )
    	, m_joblessEntityLeader( nullptr )
    { }
    
    //-----------------------------------------------------------------------------------------------
    inline EntityManager::~EntityManager()
    {
    	delete[] m_entityPool;
    }
    #pragma endregion //Lifecycle
    
    //-----------------------------------------------------------------------------------------------
    inline void EntityManager::QueueEntityForFiring( Entity* entity )
    {
    	m_entitiesWaitingForFiring.push_back( entity );
    }
    
    
    
    /*********************** SOURCE FILE ***********************/
    //-----------------------------------------------------------------------------------------------
    STATIC unsigned int EntityManager::s_nextEntityID = 1;
    
    //-----------------------------------------------------------------------------------------------
    void EntityManager::DoAtEndOfFrame()
    {
    	for( unsigned int i = 0; i < m_entitiesWaitingForFiring.size(); ++i )
    	{
    		FireEntity( m_entitiesWaitingForFiring[ i ] );
    	}
    	m_entitiesWaitingForFiring.clear();
    }
    
    #pragma region Entity Management
    //-----------------------------------------------------------------------------------------------
    void EntityManager::FireEntity( Entity* entity )
    {
    	entity->position.x = entity->position.y = entity->position.z = 0.f;
    	entity->velocity.x = entity->velocity.y = entity->velocity.z = 0.f;
    	entity->acceleration.x = entity->acceleration.y = entity->acceleration.z = 0.f;
    	entity->orientation.rollDegreesAboutX	= entity->angularVelocity.rollDegreesAboutX = 0.f;
    	entity->orientation.pitchDegreesAboutY	= entity->angularVelocity.pitchDegreesAboutY = 0.f;
    	entity->orientation.yawDegreesAboutZ	= entity->angularVelocity.yawDegreesAboutZ = 0.f;
    
    	for( unsigned int i = 0; i < entity->attachedComponents.size(); ++i )
    	{
    		entity->attachedComponents[ i ]->readyForDeletion = true;
    	}
    	entity->attachedComponents.clear();
    
    	entity->id = Entity::ID_Null;
    	entity->typeID = Entity::TYPEID_None;
    	entity->blueprint = nullptr;
    }
    
    //-----------------------------------------------------------------------------------------------
    Entity* EntityManager::HireEntity()
    {
    	Entity* hiredEntity = nullptr;
    	for( unsigned int i = 0; i < m_numEntitiesInPool; ++i )
    	{
    		Entity& entity = m_entityPool[ i ];
    
    		if( !entity.IsHired() )
    		{
    			hiredEntity = &entity;
    			break;
    		}
    	}
    
    	if( hiredEntity == nullptr )
    		ShowEmptyPoolErrorMessage();
    
    	hiredEntity->id = s_nextEntityID;
    	++s_nextEntityID;
    
    	return hiredEntity;
    }
    #pragma endregion //Entity Management
    
    //-----------------------------------------------------------------------------------------------
    /* This function has been separated so that includers of EntityManager don't need to know about
    		assertions or string conversion. */
    void EntityManager::ShowEmptyPoolErrorMessage()
    {
    	std::string errorMessage = "Ran out of entities to lend to the game!\n";
    	errorMessage.append( "Current Entity Pool Size: " );
    	errorMessage.append( ConvertIntegerToString( m_numEntitiesInPool ) );
    	FATAL_ERROR( "Entity Manager Error", errorMessage.c_str() );
    }
    

    Example Component (Warping):

    
    //-----------------------------------------------------------------------------------------------
    struct WarpComponent : public Component
    {
    	WarpComponent( float minSecondsBetweenWarps = 0.f );
    
    
    	//Data Members
    	float secondsNeededBetweenWarps;
    	float secondsSinceLastWarp;
    };
    
    //-----------------------------------------------------------------------------------------------
    inline WarpComponent::WarpComponent( float minSecondsBetweenWarps )
    	: secondsNeededBetweenWarps( minSecondsBetweenWarps )
    	, secondsSinceLastWarp( secondsNeededBetweenWarps )
    { }
    
    

    Example Component System (Warping):

    
    /*********************** HEADER FILE ***********************/
    //-----------------------------------------------------------------------------------------------
    class WarpSystem : public ComponentSystem< WarpComponent >, EventSubscriber
    {
    public:
    	WarpSystem( size_t numComponentsInPool, const FloatVector2& worldDimensions );
    
    	//Lifecycle
    	void OnAttachment( SystemManager* /*manager*/ );
    	void OnEndFrame();
    	void OnRender() const { }
    	void OnUpdate( float /*deltaSeconds*/ ) { }
    	void OnDestruction();
    
    	//Events
    	void OnWarpEngaged( EventDataBundle& eventData );
    
    private:
    	//Data Members
    	FloatVector2 m_warpBounds;
    };
    
    inline WarpSystem::WarpSystem( size_t numComponentsInPool, const FloatVector2& worldDimensions )
    	: ComponentSystem( numComponentsInPool )
    	, m_warpBounds( worldDimensions )
    { }
    
    
    
    /*********************** SOURCE FILE ***********************/
    //-----------------------------------------------------------------------------------------------
    void WarpSystem::OnAttachment( SystemManager* /*manager*/ )
    {
    	EventCourier::SubscribeForEvent( EVENT_EngageWarp, EventObserver::GenerateFromOneArgFunction< WarpSystem, &WarpSystem::OnWarpEngaged >( this ) );
    }
    
    //-----------------------------------------------------------------------------------------------
    void WarpSystem::OnEndFrame()
    {
    	for( unsigned int i = 0; i < m_numComponentsInPool; ++i )
    	{
    		if( !m_componentPool[i].readyForDeletion )
    			continue;
    
    		this->RelinquishComponent( &m_componentPool[i] );
    	}
    }
    
    //-----------------------------------------------------------------------------------------------
    void WarpSystem::OnDestruction()
    {
    	ComponentSystem< WarpComponent >::OnDestruction();
    }
    
    //-----------------------------------------------------------------------------------------------
    void WarpSystem::OnWarpEngaged( EventDataBundle& eventData )
    {
    	Entity* entityAttemptingToWarp = nullptr;
    	eventData.GetParameterOrDie( STRING_1stEntity, entityAttemptingToWarp );
    
    	WarpComponent* entityWarpComponent = entityAttemptingToWarp->FindAttachedComponentOfType< WarpComponent >();
    	if( entityWarpComponent != nullptr )
    	{
    		entityAttemptingToWarp->position.x = GetRandomFloatBetweenZeroandOne() * m_warpBounds.x;
    		entityAttemptingToWarp->position.y = GetRandomFloatBetweenZeroandOne() * m_warpBounds.y;
    	}
    }
    

    Back to top of code tabs
 

Source:

GitHub

Due to the fact that Vingine contains native development code for consoles, I cannot publicly link to the live codebase.

However, I maintain a snapshot repository without the platform-specific code on Github.