[C++] Metrics Integration
Repository
Summary
In modern development of games—regardless of console, PC, or mobile development—metrics have become an important tool for understanding and designing around player behavior. Although many resources exist to help integrate a metrics system into one's own product, many of these are designed for professional and commercial use, meaning a professional license which costs several thousands of dollars, or a contractual obligation. Neither of these is ideal for a student trying to learn the fundamentals of game programming and game design, so I looked for a more accessible service.
This led me to GameAnalytics.com, a site designed with the most minimal aspects of metrics but have some fundamental features which make it more desirable over other free alternatives:
Thanks to Jason Ericson who also developed around this service, and drove a lot of my design decisions when creating this for my teams at DigiPen.
This led me to GameAnalytics.com, a site designed with the most minimal aspects of metrics but have some fundamental features which make it more desirable over other free alternatives:
- No price
- Access to all data via HTTP POST requests using their REST API
- Customization and dashboard configuration using their web tool
- [DEPRECATED] Support for heatmaps which allow mapping in-game of metrics you care about.
Thanks to Jason Ericson who also developed around this service, and drove a lot of my design decisions when creating this for my teams at DigiPen.
Purpose
The below code constructs an interface for interacting with the GameAnalytics.com servers, as they do not have support for C++ on their website. The classes construct and store the different types of events that are supported by the GameAnalytics servers, and then converts them to JSON using the rapidjson library, and dispatches the HTTP POST request using cURL. The dispatching can be done separately from the creation of events, to either send in bulk or send when a connection is guaranteed by the client.
[DEPRECATED]
Heatmaps are stored as a deque of pairs, which stores both the point (vec3) of the data given back by the server, and the value associated with the type of event. The interface makes a request to the server to get all types from a specified period for an event, and then stores them into the deque by using rapidjson’s parser. This deque can then be used to create and maintain heatmaps by using a primitive shape and an alpha layer.
[DEPRECATED]
Heatmaps are stored as a deque of pairs, which stores both the point (vec3) of the data given back by the server, and the value associated with the type of event. The interface makes a request to the server to get all types from a specified period for an event, and then stores them into the deque by using rapidjson’s parser. This deque can then be used to create and maintain heatmaps by using a primitive shape and an alpha layer.
Dependencies
In order to integrate with GameAnalytics' REST API, I needed to use the following libraries / functions:
Analytics.h
/* -------------------------------------------------------------------------------- Author : Riley Pannkuk Editors: N/A -------------------------------------------------------------------------------- */ // INTERFACE INCLUDES ---------------------------------------------------------- #include <string> #include <deque> #include <utility> #include <exception> #include <map> #include <cfloat> #include "Dependencies\rapidjson\document.h" //------------------------------------------------------------------------------ #ifndef ANALYTICS_H #define ANALYTICS_H #define LINK_STATIC 1 #if LINK_STATIC == 0 #ifdef METRICSDLL_EXPORTS #define METRICSDLL_API __declspec(dllexport) #else #define METRICSDLL_API __declspec(dllimport) #endif #else #define METRICSDLL_API #endif namespace analytics { // DEFINITIONS /////////////////////////////////////////////////////////////////-
namespace category { std::string const design = "design"; std::string const error = "error"; std::string const business = "business"; std::string const user = "user"; }; namespace severity { std::string const critical = "critical"; std::string const error = "error"; std::string const warning = "warning"; std::string const info = "info"; std::string const debug = "debug"; }; std::string const game_key = "FILL_ME_IN"; /* Key for Game */ std::string const secret_key = "FILL_ME_IN"; /* Secret Key for (used for validation) */ std::string const data_api_key = "FILL_ME_IN"; /* Unused currently */ struct vec3 { vec3(void) : x(0.f), y(0.f), z(0.f) {}; vec3(float x_, float y_, float z_) : x(x_), y(y_), z(z_) {}; bool operator!=(const vec3& rhs) const { return ((x!=rhs.x)&&(y!=rhs.y)&&(z!=rhs.z)) ? true : false; }; bool operator==(const vec3& rhs) const { return ((x==rhs.x)&&(y==rhs.y)&&(z==rhs.z)) ? true : false; }; float x; float y; float z; }; const vec3 NULL_VEC(FLT_MAX,FLT_MAX,FLT_MAX); const std::deque<std::pair<vec3, float>> NULL_HEATMAP; const std::string NULL_TIME_START = "00000000"; const std::string NULL_TIME_END = "99999999"; int _data_to_string(char *data, size_t size, size_t nmemb, std::string *result); //////////////////////////////////////////////////////////////////////////////// // EXCEPTIONS //////////////////////////////////////////////////////////////////-
class analyticsException : public std::exception { public: virtual const char* what() const throw() = 0; }; class excInvalidKeys : public analyticsException { public: virtual const char* what() const throw() { return "ANALYTICS EXCEPTION: Invalid keys. Please check your project page to see if your keys are correct and try again."; } }; class excFileNotFound : public analyticsException { public: virtual const char* what() const throw() { return "ANALYTICS EXCEPTION: File not found. Please check that keys.ini exists in your project."; } }; class excHeatmapLoadError : public analyticsException { public: METRICSDLL_API excHeatmapLoadError(std::string a, std::string e) : area(a), eventID(e) {}; virtual const char* what() const throw() { return std::string("ANALYTICS EXCEPTION: Unable to obtain heatmap data from source <" + area + "><" + eventID + ">.").c_str(); } private: std::string area; std::string eventID; }; //////////////////////////////////////////////////////////////////////////////// // CLASSES ///////////////////////////////////////////////////////////////////// struct analytics_event; /** The analytics manager will be the structure that dispatches events across * the server to the website. */ class analytics_manager {-
public: // CONSTRUCTORS METRICSDLL_API analytics_manager(std::string version); // DESTRUCTORS METRICSDLL_API ~analytics_manager(void); // METHODS void METRICSDLL_API add_event(analytics_event& event); void METRICSDLL_API dispatch_event(analytics_event& event); void METRICSDLL_API dispatch_category(std::string category); void METRICSDLL_API dispatch_all_categories(void); std::deque<std::pair<vec3, float> > METRICSDLL_API load_heatmap(std::string area, std::string event_id, std::string start_time = NULL_TIME_START, std::string end_time = NULL_TIME_END); bool METRICSDLL_API get_heatmap(std::string area, std::string eventID, std::deque<std::pair<vec3, float> > &points); private: void _set_user_id(void); void _set_session_id(void); void _dispatch_events_array(rapidjson::Value *eventsArray, std::string cat); void _write(rapidjson::Value &eventValue); void _read(std::string cat); void _read_keys(void); // MEMBERS std::string _user_id; std::string _session_id; std::string _build; rapidjson::Document *_doc; std::map<std::string, rapidjson::Value*> _json_writers; std::map<std::string, std::map< std::string, std::deque<std::pair<vec3, float> > > > _heatmaps; }; /** An event sent out to Game Analytics caching the data relating to the type * of event you want. This is the generic event class from which all other * analytics event types are derived. */ struct analytics_event {-
//For event type reading friend class analytics_manager; // CONSTRUCTORS analytics_event(std::string _event_id, std::string _area = "NULL", vec3 _location = NULL_VEC, std::string event_type = "NULL") : event_id(_event_id), area(_area), location(_location), _event_type(event_type) {}; virtual void _write(rapidjson::Value &eventArray, rapidjson::MemoryPoolAllocator<> &allocator) const =0; // MEMBERS const std::string event_id; std::string area; vec3 location; protected: std::string _event_type; }; // analytics_event /** A design event is an analytis_event that contains data related to gameplay * specifically. Store a value associated with the event_id, e.g. ammo * for a SHOTGUN_SHELL_PICKUP event. */ struct design_event : public analytics_event {-
METRICSDLL_API design_event(std::string _event_id, std::string _area = "NULL", vec3 _location = NULL_VEC, float _value = unsigned(-1)) : analytics_event(_event_id, _area, _location, category::design), value(_value) {}; virtual void _write(rapidjson::Value &eventArray, rapidjson::MemoryPoolAllocator<> &allocator) const; // MEMBERS float value; // Value associated with the event ID }; /** An error event is an analytis_event from a detected error or exception * thrown during gameplay. It supports the area and location fields from * its base analytics_event class, but NOT event_id. */ struct error_event : public analytics_event {-
METRICSDLL_API error_event(std::string _message, std::string _severity, std::string _area = "NULL", vec3 _location = NULL_VEC ) : analytics_event("ERROR", _area, _location, category::error), message(_message), severity(_severity) {}; virtual void _write(rapidjson::Value &eventArray, rapidjson::MemoryPoolAllocator<> &allocator) const; // MEMBERS std::string message; std::string severity; }; /** A business event is an analytis_event for analyzing business * transactions and monetization in the game. It utilizes all * fields from analytics_event, as well as it's own currency * and amount events. */ struct business_event : public analytics_event {-
METRICSDLL_API business_event(std::string _event_id, std::string _currency = "NULL", int _amount = -1, std::string _area = "NULL", vec3 _location = NULL_VEC ): analytics_event(_event_id, _area, _location, category::business), currency(currency), amount(amount) {}; virtual void _write(rapidjson::Value &eventArray, rapidjson::MemoryPoolAllocator<> &allocator) const; // MEMBERS std::string currency; //Type of currency being used, e.g. "USD" int amount; //Amount the purchase cost }; /** A user event is an analytis event that provides data on the individual * playing the game. It does not make use of the event_id, area, or location * values found in the above analytics_event class. Instead, it has it's own * data fields related to the specific machine the user is on. */ struct user_event : public analytics_event {-
METRICSDLL_API user_event(std::string _gender = "NULL", int _birth_year = -1, int _friend_count = -1, std::string _facebook_id = "NULL", std::string _googleplus_id = "NULL", std::string _ios_id = "NULL", std::string _android_id = "NULL", std::string _adtruth_id = "NULL", std::string _platform = "NULL", std::string _device = "NULL", std::string _os_major = "NULL", std::string _os_minor = "NULL", std::string _install_publisher = "NULL", std::string _install_site = "NULL", std::string _install_campaign = "NULL", std::string _install_adgroup = "NULL", std::string _install_ad = "NULL", std::string _install_keyword = "NULL") : analytics_event("NULL", "NULL", vec3(), category::user), gender(_gender), birth_year(_birth_year), friend_count(_friend_count), facebook_id(_facebook_id), googleplus_id(_googleplus_id), ios_id(_ios_id), android_id(_android_id), adtruth_id(_adtruth_id), platform(_platform), device(_device), os_major(_os_major), os_minor(_os_minor), install_publisher(_install_publisher), install_site(_install_site), install_campaign(_install_campaign), install_adgroup(_install_adgroup), install_ad(_install_ad), install_keyword(_install_keyword) {}; virtual void _write(rapidjson::Value &eventArray, rapidjson::MemoryPoolAllocator<> &allocator) const; // MEMBERS std::string gender; int birth_year; int friend_count; std::string facebook_id; std::string googleplus_id; std::string ios_id; std::string android_id; std::string adtruth_id; std::string platform; std::string device; std::string os_major; std::string os_minor; std::string install_publisher; std::string install_site; std::string install_campaign; std::string install_adgroup; std::string install_ad; std::string install_keyword; }; //////////////////////////////////////////////////////////////////////////////// }; #endif // ANALYTICS_H