Alright guys, I just did a quick 2d test with Monkey and Unity and would like to share the numbers: I've used 2000 sprites which move randomly over the screen. Results are: Unity 4.5 APK size: 8.404.358 bytes FPS: ~20 Monkey v79c APK size: 67,331 bytes FPS: ~33 (previous value of 200 was wrong) Unity C# code: Code: using UnityEngine; using System.Collections; public class Sprites : MonoBehaviour { public GameObject _sprite; private ArrayList _spriteList; // Use this for initialization void Start() { _spriteList = new ArrayList(); for( int i = 0; i < 2000; i++ ) { GameObject sprite = (GameObject)Instantiate( _sprite, new Vector3( Random.Range( -6.0f, 6.0f ), Random.Range( -4.0f, 4.0f ) ), Quaternion.identity ); _spriteList.Add( sprite ); } } // Update is called once per frame void Update() { for( int i = 0; i < _spriteList.Count; i++ ) { GameObject s = (GameObject)_spriteList[i]; Vector3 position = s.transform.position; float deltaTime = Time.deltaTime; position += new Vector3( Random.Range( -5.0f, 5.0f ), Random.Range( -5.0f, 5.0f ) ) * deltaTime; position.x = Mathf.Clamp( position.x, -6.0f, 6.0f ); position.y = Mathf.Clamp( position.y, -4.0f, 4.0f ); s.transform.position = position; } } } Monkey code: Code: Strict Import mojo Function Main:Int() New Game() Return 0 End Function Class Sprite Field _img:Image Field _x:Float Field _y:Float Method New( img:Image, x:Float, y:Float ) _img = img _x = x _y = y End Method End Class Class Game Extends App Field _sprite:Image Field _spriteList:List<Sprite> Field _resX:Float Field _resY:Float 'FPS stuff Field _previousTime:Int Field _currentTime:Int Field _passedTime:Int Field _loopTimeAll:Int Field _fpsCounter:Int Field _fpsLastUpdate:Int Field _fps:Float Method OnCreate:Int() _sprite = LoadImage( "sprite.png" ) _spriteList = New List<Sprite> _resX = Float( DeviceWidth() ) _resY = Float( DeviceHeight() ) For Local i:Int = 0 Until 2000 Local s:Sprite = New Sprite( _sprite, Rnd( 0, _resX ), Rnd( 0, _resY ) ) _spriteList.AddLast( s ) Next _currentTime = Millisecs() SetUpdateRate(60) Return 0 End Method Method OnUpdate:Int() Return 0 End Method Method OnRender:Int() _previousTime = _currentTime _currentTime = Millisecs() _passedTime = _currentTime - _previousTime Cls() For Local s:Sprite = EachIn _spriteList Local dx:Float = Rnd( -0.1, 0.1 ) * _passedTime Local dy:Float = Rnd( -0.1, 0.1 ) * _passedTime s._x += dx s._y += dy s._x = Clamp( s._x, 0.0, _resX ) s._y = Clamp( s._y, 0.0, _resY ) DrawImage( s._img, s._x, s._y ) Next DrawText( _fps + " FPS", 10, 10 ) updateFPS() Return 0 End Method Method OnSuspend:Int() Return 0 End Method Method OnResume:Int() Return 0 End Method Method updateFPS:Void() _loopTimeAll += _passedTime _fpsCounter += 1 If( ( _currentTime - _fpsLastUpdate ) > 500 ) Local renderTime:Float = Float( _loopTimeAll ) / Float( _fpsCounter ) If( renderTime < 1.0 ) Then renderTime = 1.0 _loopTimeAll = 0 _fpsLastUpdate = _currentTime _fps = 1000.0 / renderTime _fpsCounter = 0 End If End Method End Class Sample APKs: Unity: http://www.leidel.net/dl/monkey/2dtest/unitytest.apk Monkey: http://www.leidel.net/dl/monkey/2dtest/monkeytest.apk Project files: http://www.leidel.net/dl/monkey/2dtest/unityproject.zip http://www.leidel.net/dl/monkey/2dtest/monkeyproject.zip Actually I'm pretty shocked how much faster Monkey is! Test device was a Sony Xperia S. I'd be interested in more comparison values and stuff how (especially) the Unity code could be done better?
That was not the question but if you like you can create such a project in Unity and send it to me please? I'd be interested!
I have run projects in unity with over 200K poly's fine. I find the results of your test interesting, however that test isn't really using any of the features of either engine. If you were just going to do that you might as well do it in native code. I would expect in this monkey to be faster, but I am surprised how much faster. The benefits of using unity come from using the API. Free html5 is going to be a big benefit over unity because it is unlikely the unity html5 implementation will be as flexible as monkey (because it will require webgl)
The difference is going to be in how each engine treats objects rather than polygons. And how they are dealing with them to push them to OpenGL. Basically the most expensive thing that game engines do is matrix maths on object positions. My guess is that Unity is doing some kind of matrix stack for each object based on its transform, the camera position and parent objects. Then pushing each sprite to the GPU one at a time. So my guess is that unity will build a matrix for each object by building a transform, scale and rotation matrix and multiplying them together. This will then get multiplied by the camera matrix, to generate the view matrix for the object. This is then multiplied by the projection matrix, and pushed to the GPU. As it is a 3d engine it is using 4x4 matrix, rather than the 3x3 that a 2d engine can use. So it needs to multiply 5 matrix together before it has the final matrix for the GPU, or 80 multiply operations per sprite per frame, or 16000 for all the sprites. This can be optimised if the matrix object is designed to skip identity matrix multiplications, but most I have seen are not. This matrix maths start to get very expensive as the number of objects gets larger. Plus using a view matrix for each object means that you have to render each object in a separate call to the GPU. Which is fine for a 3d engine, where you are pushing a complex vertex and index buffer, with maybe a few textures. In 2d however you can greatly optimize this. So with Monkey it is probably not using a camera, and is probably not using a matrix for each object. Instead it probably just creates 4 2d points whenever you call the draw command, using the x,y and rotation of the object. These points just get added to the end of a vertex, index and texture buffer that is being built each frame. Every time a texture changes a new vertex buffer gets started. But seeing as you are using only one texture, they probably all get added to the same buffer. When the scene is drawn this single vertex buffer gets pushed to the GPU and it means everything is drawn in a single call. Much like how a particle emitter renders all its particles. This will give it a massive speed boost over how a 3d engine works, but puts more limitations on what can be done with the sprites. One way to test this better would be to use a range of textures for the sprites, and make sure no two sprites with the same texture are drawn in sequence. This will prevent the engine from optimizing its draw calls so much. But you will probably find that it is still faster because it doesn't need to do so much matrix maths.
Ping from the Unity forum. First, you can't have 200 FPS on Android, max 60. Second, the author is comparing FPS in Unity to OnRender execution per second in Monkey. This is wrong, these metrics are different and can't be compared. Third, no draw call optimization can help with that much fillrate, the test repaints 100% of the screen more than 10 times, mobile devices can't handle that much with a good FPS rate.
Mike, I got your points even though they don't convince me yet. For the Unity FPS computation I use the HUDFPS script from here: http://wiki.unity3d.com/index.php?title=FramesPerSecond And of course Android is fixed to 60 fps but that doesn't change the fact than OnRender in Monkey only takes 5ms per loop. Actually it's almost the same stuff I use. frames per second are usually 1/rendertime. I guess you mean that I don't measure the rendertime but just this loop and rendering happens after that, did I get you right? If so, how about this test: I will render as much as many sprites till I have one Update call (in case of Monkey OnRender) per second. Would that be the same? In that case I would get the numbers of sprite per second.
some interesting tests would be comparing the game engine side. Like try may 100 2D rigidbody sprites with physics and see how they compare.
@Xaron, you can't just measure the time that the OnRender function takes. This may only be buffering commands on the OpenGL pipeline, and it won't be until the screen needs to render that these get executed. You need to measure the frame rate for each cycle, or do a glFlush() call at the end of the OnRender function to make sure you are getting the real draw time. But even then you can't just measure the OnRender part, as the engine might be doing all kinds of other processing as well in different functions. But you are still going to find that Monkey is probably faster, because it is designed for 2d rather than 3d. As you mentioned before, a different way to compare this would be to see how many objects need to be created to bring the frame rate down to a set level. Say 30 frames per second. When measuring the time difference at the same point in your code, not just the time it takes to run the one function.
Will do that next, OnlyJoe! edit: I have to admit that I was indeed wrong with my fps counter. I've adapted the fps code to measure the time between two render calls now. Using this code, I get 33 fps for Monkey which is more realistic and still 65% faster than Unity. Thanks again and sorry for that! I'm still very satisfied with these results.
Yeah, me too I'm suspicious about your numbers. Something is wrong. BTW, matrix computation isn't that expensive on modern devices. So I doubt that 2k sprite matrix computation are responsible for the low unity FPS. JC
You might want to compare memory use and data collector/recyling. Considering that RAM is very important on smartphones, we might want to see what it looks like when pushed hard. Also another test with some colliders and somewhat physics elements. I'm less concerned about the average FPS than the sudden spike in CPU usage btw.