Introduction and Use Case:
In the first part of this series, we have implemented a basic function in C++ that can be called from Unity3D or from Unreal Engine 4. Through this simple function, we have seen how to send arguments to the plugin and also to get the return value back into unity.
This was a simple example of very basic procedural methods for performing tasks. However, there would be several instances where the DLL would need to communicate with the game engine. This could be when a particular event occurs which is handled in the plugin. For example, we could have a C++ plugin which implements a TCP connection with another system and might have to notify the game logic when a certain communication is received. For this, we would need to have an event delegate mechanism where the game logic registers a delegate with the plugin and it is invoked by the plugin so that an action can be performed in the game engine.
Project Set-up in Visual Studio:
Since the way the delegates are handled in Unity3D and UE4 is different, we will first need to have conditional compilation for each of the engines separately. From here on we will have to have DLLs that are specifically compiled for Unity3D and UE4 separately. However, we will make sure that the code base is largely the same. This way we will only need to maintain a single code for both the engines.
Going back to the first post for this series, we can see that the visual studio project has already set up with 2 separate configurations: One for Unity3D and the other of Unreal. We have also declared the pre-processor directives for both these engines. If you have not already seen this post, please check it out first as I will not go into those details here.
Definitions for Delegates
We will first need to define the delegates that will be used by Unity3D and UE4. The basic idea is the same. We will need to pass a string message that would be printed in the console windows of the game engine editors. Along with the message we would also need to pass the type of message to the engine. This would tell the game engine whether the message that is sent is a log, warning or an error.
We will be using __stdcall for creating the definition that will be used by Unity 3d and std::function for creating function pointers used with UE4.
We start by creating the function definitions in the stdafx.h header file. Here is the code that we will be using:
#include<functional>
#if UNITY
//Unity callback: __stdcall maps the unmanaged function pointer to a managed delegate, managed delegate handles the capture list
typedef void(__stdcall *LogCallback)(int, const char*);
#elif UNREAL
//Unreal callback: std::function handles capture list on the unmanaged side, which __stdcall doesnt provide
typedef std::function<void(int, const char*)> LogCallback;
#endif
enum eLogType
{
iLOG = 0,
iWARNING = 1,
iERROR = 2
};
As you can see from the code above, both the function definitions do the same thing. Their signatures contain an int which defines the message type and a const char* which is the message itself.
We can now have the game engines implement the logic for displaying the message when this function is invoked from the plugin.
Logger Singleton
Next let’s implement a simple Logger singleton class. The instance of this class can be accessed from any other class within the plugin. This way we can simply send a log, warning or error from anywhere in the code. Please note that is a very simple implementation that should serve as an example only. Here are the header and source files that need to be added to the plugin project. Let’s add a new class to the project and call it “Logger“.
Logger.h
#pragma once
#include "stdafx.h"
/*
Singleton class that is used to raise events which will show logs, warnings and errors in the game engine
*/
class Logger
{
private:
static Logger *s_refInstance;
Logger();
//Log Callback delegate
LogCallback m_delLogCallback;
public:
static Logger* GetInstance();
void Initialize();
//need to have another argument for verbosity level
void RegisterForLogs(LogCallback a_delLogs);
void InvokeExceptionDelegate(const char *a_strException);
void InvokeWarningDelegate(const char * a_strWarning);
void InvokeLogDelegate(const char * a_strLog);
~Logger();
};
Logger.cpp
#include "Logger.h"
Logger *Logger::s_refInstance = NULL;
Logger* Logger::GetInstance()
{
if (s_refInstance == NULL)
{
s_refInstance = new Logger();
}
return s_refInstance;
}
Logger::Logger()
:m_delLogCallback(NULL)
{
}
Logger::~Logger()
{
}
void Logger::Initialize()
{
//TODO: Do any initialization related stuff here
return;
}
void Logger::RegisterForLogs(LogCallback a_delLogs)
{
m_delLogCallback = a_delLogs;
}
void Logger::InvokeExceptionDelegate(const char *a_strException)
{
if (m_delLogCallback == NULL)
return;
m_delLogCallback(iERROR, (char *)a_strException);
}
void Logger::InvokeWarningDelegate(const char * a_strWarning)
{
if (m_delLogCallback == NULL)
return;
m_delLogCallback(iWARNING, (char *)a_strWarning);
}
void Logger::InvokeLogDelegate(const char * a_strLog)
{
if (m_delLogCallback == NULL)
return;
m_delLogCallback(iLOG, (char *)a_strLog);
}
Logging from Plugin Code
Now that we have the logger class, all that is left to do is to use the class to print logs in the game engine.
Let’s start by creating an Initialize function in the “DataStorageInterface” class. We will use this function to do any initialization for the plugin. For now, this function will create the instance to the Logger singleton. This function will be called in the game engine first before accessing other functionality of the plugin. Here is the code for the Initialize function:
DataStorageInterface.h
DataStorage_API void Initialize();
DataStorageInterface.cpp
void Initialize()
{
//Do any initialization here
Logger::GetInstance()->Initialize();
}
We will now add a line in the SumOf that will send a simple message to the game engine which it can print in the console message. The next part of the series will focus on the engine side functionality for this. Here is the line we will add to the SumOf just before the return statement:
Logger::GetInstance()->InvokeLogDelegate("Hello from the Plugin World");
Next we expose a function that can will register the delegate to be invoke when a log, warning or error has to be raised from the DLL.
DataStorageInterface.h
DataStorage_API void RegisterForLogs(LogCallback a_delLogReceived);
DataStorageInterface.cpp
void RegisterForLogs(LogCallback a_delLogReceived)
{
Logger::GetInstance()->RegisterForLogs(a_delLogReceived);
}
Conclusion:
That is it! We now have a mechanism to invoke delegates from the plugin code which will execute the functionality in the game engine. We have leveraged this to get logs sent from the plugin. Having logs will greatly help us develop and debug the functionality of the plugin.
In the next couple of post, we will see how we can register to these delegates in Unity3D and Unreal Engine.
You can download the entire source code untill now from the repository here.
Forgot to add the link to the repository. You can download the source code for this post from: https://bitbucket.org/manjithkrishnappa/nativecppplugin/commits/tag/Logger_Class