Update 1.0.4: Sprite instanced Rendering and colliding


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.

BufferedSprite

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.

BufferedSprite Collider

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.

CharacterController2D

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#
using Genesis.Core;
using Genesis.Graphics.RenderDevice;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Genesis.Math;
using Genesis.Core.GameElements;
using Genesis.Physics;
using Genesis.Core.Behaviors.Physics2D;
using Genesis.Graphics.Physics;
using System.Management.Instrumentation;
using Genesis.Core.Behaviors;
using Genesis.Graphics;
using System.Security.AccessControl;
using BulletSharp;

namespace Physics2DTest
{
    public partial class Form1 : Form
    {
        // Game instance for managing the game loop and scenes
        private Game m_game;

        // Constructor for the main form
        public Form1()
        {
            InitializeComponent();

            // Initialize the game and set up rendering and viewport
            m_game = new Game(new GLRenderer(this.Handle), new Genesis.Graphics.Viewport(this.ClientSize.Width, this.ClientSize.Height));
            m_game.TargetFPS = 60;
            m_game.AssetManager.LoadTextures();

            // Create a test scene
            var testScene = new Scene("TestScene");

            // Set up the camera for the test scene
            testScene.Camera = new Genesis.Graphics.Camera(new Genesis.Math.Vec3(0f, 0f), new Genesis.Math.Vec3(this.ClientSize.Width, this.ClientSize.Height), -10, 10);
            testScene.AddLayer("BaseLayer");

            // Set up the physics handler for the scene
            var physicsHandler = new PhysicsHandler2D(0f, -10f);
            testScene.PhysicHandler = physicsHandler;

            // Create a player sprite with an character controller.
            var player = new Sprite("Player", new Vec3(-300, 0), new Vec3(48, 48), m_game.AssetManager.GetTexture("player.png"));
            var controller = player.AddBehavior<CharacterController2D>(new CharacterController2D());
            controller.CreatePhysics(physicsHandler, ControllerPreset.SideScrollerController);

            // Add an collide event to the player
            controller.Rigidbody.OnCollide += (scene, game, collisionObject) =>
            {
                var collisionRby = (RigidBody)collisionObject;
                var gameElement = (GameElement)collisionRby.UserObject;
                Console.WriteLine("Colliding with " + gameElement.Name);
            };

            // Create a animation behavior
            var animationBehavior = player.AddBehavior<AnimationBehavior>(new AnimationBehavior(6, 2, 100, m_game.AssetManager.GetTexture("SpriteSheet.png")));
            var walkRight = new Animation("MoveRight", 0, 0, 5);
            animationBehavior.AddAnimation(walkRight);
            var walkLeft = new Animation("MoveLeft", 0, 1, 5);
            animationBehavior.AddAnimation(walkLeft);
            var idle = new Animation("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 scene
            var spacing = 120f;
            for (int i = 0; i < 5; i++)
            {
                var x = -300 + ((64.0f * i) + i * spacing);
                var colObject = new Sprite("ColObject_" + i, new Vec3(x, -150), new Vec3(64, 64), m_game.AssetManager.GetTexture("block.png"));
                var colPhysicsBehavior = colObject.AddBehavior(new Rigidbody2D());
                colPhysicsBehavior.CreateRigidbody(testScene.PhysicHandler, 0f);
                testScene.AddGameElement("BaseLayer", colObject);
            }

            // Event handler for game initialization
            m_game.OnInit += (game, renderer) =>
            {
                animationBehavior.Play();
                PhysicsHandler2D physicsHandler2D = (PhysicsHandler2D)testScene.PhysicHandler;
                physicsHandler2D.PhysicsWorld.DebugDrawer = new BulletDebugRenderer(m_game.RenderDevice);
            };

            // Event handler for rendering the debug information
            m_game.OnRenderEnd += (game, renderer) =>
            {
                PhysicsHandler2D physicsHandler2D = (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();
                }
                else if (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 event
        private void Form1_Load(object sender, EventArgs e)
        {

        }
    }
}

Leave a Reply

Your email address will not be published. Required fields are marked *