Just now, the update to version 1.0.3 has been released, bringing many interesting innovations. Additionally, there are minor changes in the game loop, so that physics is now updated as the last element before rendering.
Let’s now move on to the innovations. The BufferedSprite already existed at the release of the engine but had a rather subordinate meaning at that time. With the creation of a scene importer for Tiled, this element will gain increasing importance in the future. The reason is quite simple. Tiled works with so-called tiles, while the engine is object-based, which would result in the creation of a separate GameElement for each part drawn in Tiled. With a scene of 800 tiles in width and 600 tiles in height, you would end up with a total of 480,000 possible sprites per layer.
Even though GFX can handle a large number of draw calls, one eventually reaches its limits. Especially considering a lot of performance is wasted since not every element has logic but would still be iterated. This is where the BufferedSprite comes into play, as it allows adding multiple sprites or shapes that are simply rendered without incurring performance costs, as these sprites do not need to be iterated. Additionally, this reduces the draw calls from 480,000 to one. The only limitation is that individual sprites do not have game logic and all shapes must have the same texture. However, each sprite can have its own UV coordinates, meaning you can create a texture atlas and represent different sprites with it.
But if these sprites don’t have logic, how is it possible to collide with, for example, a tree? That’s quite simple because in the Bullet Physics Engine, which GFX uses, there is a similar function, the Compound Shape. Similar to the BufferedSprite, it can accommodate many children, thus saving performance.
Therefore, we have created a collider for the BufferedSprite that works with this Compound Shape. In a test map, we achieved a peak of 1,000 fps with about 70,000 child shapes, which is a very good result. This also means that the BufferedSprite can have the same number of shapes.
Another new feature is a behavior designed to help move a character through the world with collision detection. Currently, there are two presets, one for platformers and side-scrollers, and one for top-down control. But take a look at the example code yourself.
Example Code
C#
usingGenesis.Core;usingGenesis.Graphics.RenderDevice;usingSystem;usingSystem.Collections.Generic;usingSystem.ComponentModel;usingSystem.Data;usingSystem.Drawing;usingSystem.Linq;usingSystem.Text;usingSystem.Threading.Tasks;usingSystem.Windows.Forms;usingGenesis.Math;usingGenesis.Core.GameElements;usingGenesis.Physics;usingGenesis.Core.Behaviors.Physics2D;usingGenesis.Graphics.Physics;usingSystem.Management.Instrumentation;usingGenesis.Core.Behaviors;usingGenesis.Graphics;usingSystem.Security.AccessControl;usingBulletSharp;namespacePhysics2DTest{publicpartialclassForm1 : Form { // Game instance for managing the game loop and scenesprivateGamem_game; // Constructor for the main formpublicForm1() {InitializeComponent(); // Initialize the game and set up rendering and viewport m_game =newGame(newGLRenderer(this.Handle), newGenesis.Graphics.Viewport(this.ClientSize.Width, this.ClientSize.Height)); m_game.TargetFPS =60; m_game.AssetManager.LoadTextures(); // Create a test scenevartestScene=newScene("TestScene"); // Set up the camera for the test scene testScene.Camera =newGenesis.Graphics.Camera(newGenesis.Math.Vec3(0f, 0f), newGenesis.Math.Vec3(this.ClientSize.Width, this.ClientSize.Height), -10, 10); testScene.AddLayer("BaseLayer"); // Set up the physics handler for the scenevarphysicsHandler=newPhysicsHandler2D(0f, -10f); testScene.PhysicHandler = physicsHandler; // Create a player sprite with an character controller.varplayer=newSprite("Player", newVec3(-300, 0), newVec3(48, 48), m_game.AssetManager.GetTexture("player.png"));varcontroller= player.AddBehavior<CharacterController2D>(newCharacterController2D()); controller.CreatePhysics(physicsHandler, ControllerPreset.SideScrollerController); // Add an collide event to the player controller.Rigidbody.OnCollide += (scene, game, collisionObject) => {varcollisionRby= (RigidBody)collisionObject;vargameElement= (GameElement)collisionRby.UserObject; Console.WriteLine("Colliding with "+ gameElement.Name); }; // Create a animation behaviorvaranimationBehavior= player.AddBehavior<AnimationBehavior>(newAnimationBehavior(6, 2, 100, m_game.AssetManager.GetTexture("SpriteSheet.png")));varwalkRight=newAnimation("MoveRight", 0, 0, 5); animationBehavior.AddAnimation(walkRight);varwalkLeft=newAnimation("MoveLeft", 0, 1, 5); animationBehavior.AddAnimation(walkLeft);varidle=newAnimation("Idle", 0, 2, 5); animationBehavior.AddAnimation(idle); animationBehavior.SelectedAnimation = idle; // Add the player to the scene testScene.AddGameElement("BaseLayer", player); // Create and add several block sprites to the scenevarspacing=120f;for (inti=0; i <5; i++) {varx=-300+ ((64.0f* i) + i * spacing);varcolObject=newSprite("ColObject_"+ i, newVec3(x, -150), newVec3(64, 64), m_game.AssetManager.GetTexture("block.png"));varcolPhysicsBehavior= colObject.AddBehavior(newRigidbody2D()); colPhysicsBehavior.CreateRigidbody(testScene.PhysicHandler, 0f); testScene.AddGameElement("BaseLayer", colObject); } // Event handler for game initialization m_game.OnInit += (game, renderer) => { animationBehavior.Play();PhysicsHandler2DphysicsHandler2D= (PhysicsHandler2D)testScene.PhysicHandler; physicsHandler2D.PhysicsWorld.DebugDrawer =newBulletDebugRenderer(m_game.RenderDevice); }; // Event handler for rendering the debug information m_game.OnRenderEnd += (game, renderer) => {PhysicsHandler2DphysicsHandler2D= (PhysicsHandler2D)testScene.PhysicHandler; physicsHandler2D.PhysicsWorld.DebugDrawWorld(); Console.WriteLine(m_game.FPS); }; // Event handler for player movement based on keyboard input m_game.OnUpdate += (game, renderer) => {if (Input.IsKeyDown(Keys.A)) {if (!animationBehavior.SelectedAnimation.Name.Equals("MoveLeft")) { animationBehavior.LoadAnimation("MoveLeft"); } animationBehavior.Play(); }elseif (Input.IsKeyDown(Keys.D)) {if (!animationBehavior.SelectedAnimation.Name.Equals("MoveRight")) { animationBehavior.LoadAnimation("MoveRight"); } animationBehavior.Play(); }else { animationBehavior.Stop(); } }; // Add the test scene to the game, load the scene, and start the game loop m_game.AddScene(testScene); m_game.LoadScene("TestScene"); m_game.Start(); } // Placeholder method for form load eventprivatevoidForm1_Load(objectsender, EventArgse) { } }}