Cocos2d-x resources - On-screen logging
On-screen logging (C++)
When running your app on a device, you can usually view logging output in real-time on your computer (eg. in the Xcode debug output panel for iOS, or the logcat for Android). However, this requires that the device is connected to the computer. It also means you need to watch two different screens if you need to actually play your game while expecting something to show up in the log. And if your logging outputs a useful message when you're not expecting it, or when you don't have the log open, you may never even notice it happened.
To make logging output a bit more convenient, I like to have it displayed directly on the screen of my game so I can see what's happening all in one view. This also means that the device does not need to be connected to the computer to view the log, so even users without a development environment can view the output. If you have a beta testing phase before release, you may find it handy to let your testers see warnings or errors to help you pinpoint problems.
Below you will find source code and explanation for a class (sub-class of cocos2d::Layer) which lets you add this functionality quite easily to a Cocos2d-x application. The HelloWorld sample is used as a base for demonstration. So far I have used it on only iOS and Android.
Source code
ScreenLog.h
ScreenLog.cpp
This code was confirmed to work with Cocos2d-x v3 RC1, but was originally developed in a project using v2.1.5 so it should work for either. Please let me know if I've changed anything that breaks it for v2.1.5.
Instructions for use
1. Add the two files above to your project
2. In your AppDelegate class, include ScreenLog.h and initialize the global instance of the screenlog class somewhere in the startup sequence. I like to add it in the constructor:
1 2 3 4 5 6 | AppDelegate::AppDelegate() { g_screenLog = new ScreenLog(); g_screenLog->setLevelMask( LL_DEBUG | LL_INFO | LL_WARNING | LL_ERROR | LL_FATAL ); g_screenLog->setFontFile( "UbuntuMono-R.ttf" ); g_screenLog->setTimeoutSeconds( 15 ); } |
1 2 3 4 5 6 | #define LL_FATAL 0x01 #define LL_ERROR 0x02 #define LL_WARNING 0x04 #define LL_INFO 0x08 #define LL_DEBUG 0x10 #define LL_TRACE 0x20 |
Depending on the device you are using, it may not be necessary to set a font file. If you want to log non-ascii characters you will need to use a font that supports them.
The timeout specifies how long log messages will be shown before disappearing.
3. Near the end of the AppDelegate::applicationDidFinishLaunching() function, attach the screenlog to the starting scene.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | bool AppDelegate::applicationDidFinishLaunching() { ... blah blah blah ... // create a scene. its an autorelease object auto scene = HelloWorld::scene(); // attach screenlog to the scene about to become active g_screenLog->attachToScene( scene ); // run director->runWithScene( scene ); return true; } |
4. Anywhere you want to log a message to the screen, include ScreenLog.h and call the log function like this:
1 | g_screenLog->log( LL_INFO, "Touch began: (%d,%d)", x, y ); |
Here is an example of some log messages added to the init() function of the sample HelloWorld project, using this code:
1 2 3 4 | g_screenLog->log( LL_DEBUG, "A debug message... フォント" ); g_screenLog->log( LL_INFO, "An info message... によって" ); g_screenLog->log( LL_WARNING, "A warning message... 日本語も" ); g_screenLog->log( LL_ERROR, "An error message... 表示可能" ); |

5. Important! When changing scenes, make sure you also tell the screenlog to attach itself to the new scene:
1 2 3 | auto scene = MyNextScreen::scene(); g_screenLog->attachToScene( scene ); // important CCDirector::sharedDirector()->replaceScene( scene ); |
Altering text in existing messages
The 'log' function returns a reference to the message that is created, which you can use to change the text of that message later. This can be very useful for output which would normally spew out a lot of messages and make the log hard to follow. Examples of this would be a progress indicator, or fast/frequent updates like logging the position of touches as they move.
For example let's say I want to continously update a message to show the download progress of a file. I can keep a reference to the log message from when I first create it:
1 | screenLogMessage* slm = g_screenLog->log( LL_INFO, "Downloading file: 0% complete" ); |
1 | g_screenLog->setMessageText( slm, "Downloading file: %d% complete", progress ); |
Threading issues
In ScreenLog.cpp you will see a bunch of code to do with threads and locking which might seem unnecessary at first. The reason for that is because only the main thread of the application is able to correctly create the CCLabelTTF labels used for the log display. If any other thread tries this, the labels will not be created because the OpenGL rendering context is not current for that thread. To allow any other thread to call 'log' at any time, the actual label-creation part of the process is buffered until the main thread of the application can do it. This allows logging from other threads in situations where processing is being done concurrently, such as long loading routines, or network transfers as mentioned above.
If you didn't understand that previous paragraph, just know that you can call 'log' at any time from anywhere in your code without worrying about threading issues :)
Settings to adjust
Take a look at the beginning of ScreenLog.cpp if you want to adjust the number of lines shown on screen, or the starting position of the lowest row. I figure that most people will leave the framerate counter showing while developing, so the default position for the log rows to start is set to be a little above that.
In the function ScreenLog::createLabel you can set the colors used for each log level.
Other useful tips
Included in the header file you will find a small class that can be useful for logging the progress of the program through function calls. Click here to see the class code.)
1 2 3 4 5 6 7 8 9 10 11 | class ScopeLog { public: std::string m_functionName; ScopeLog(std::string fn) { m_functionName = fn; g_screenLog->log( LL_TRACE, "Entered %s", m_functionName.c_str() ); } ~ScopeLog() { g_screenLog->log( LL_TRACE, "Exiting %s", m_functionName.c_str() ); } }; |
1 2 3 | Scene* HelloWorld::scene() { ScopeLog log(__PRETTY_FUNCTION__); |
1 2 3 | bool HelloWorld::init() { ScopeLog log(__PRETTY_FUNCTION__); |
