Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: KiloSprite OpenGL 3.3 2D Graphics Renderer  (Read 3095 times)

0 Members and 1 Guest are viewing this topic.

JATothrim

  • Newbie
  • *
  • Posts: 2
    • View Profile
    • Email
KiloSprite OpenGL 3.3 2D Graphics Renderer
« on: June 20, 2013, 07:15:40 am »
Hello!

I'm JATothrim, kinda self-taught amateur C++ programmer. My mother language is finnish, and I write moderately in english, so please forgive me for weirdess and errors in my texts.  :) I program for fun, but now I'm making an exception.
My project originates from finnish CoolBasic community, where I'm part of the programming language dev-team. Our team idea was to develop fast multiplatform 2D game engine for the still (insert CB-community joke here  ::) ) up-coming language. This task eventualy settled for me only, which made things harder than I first thought.
In my free time, I have written thousands lines of C++ from scratch for just the graphics engine. It can render over  10k dynamic sprites  (aka. two triangles constructed from four verticles and 5 indices), that's it, moving objects with variable set of shaders. Renderer is multithreaded where it makes sense like constructing VBOs and updating parent-children-tree.

But then time passed.

The renderer moved in its own path compared to the CB dev-team. I was basicaly the only active developer writing this engine. So I finaly made an decision and I got full permission to diverge the engine from our team. I renamed the engine as 'KiloSprite' by the fact 1000 dynamic sprites does not even lower the FPS under 700.  (50 sprites and its over 2000) ;D But as I'm amateur programmer and self-taught the code is bit hacky and messy. But hey it works! And I'm learning constantly. Writing shader effects is still quite experimental and requires bit sweating to get new shader running. Setting uniforms is though very easy.

The thing is that we used first SFML, but I had then more experience from GLFW and SFML graphics module seemed then bit intrusive.. Also, we wanted to write the renderer from scratch with raw OpenGL, so I switched. But now after main body of the renderer is quite complete (but not even near redy) I feel I should go back with SFML as it has audio, very good OpenGL context handling and better threading support.

Then I thinked big: what if I would develop alternative rendering engine for SFML or other way around integrate SFML to my engine, add a physics engine like Chipmunk and the packet would be complete. an pure C++03 SDK for games.
This engine's core idea is to be easy to use and be fast at same time. Resources are handed by the ObjectSubSystem where everything is wrapped into a 'gameobject' or entity. You can build hierarchical parent-children-tree out of these objects. In future you could attach objects to physics simulated rigidbodys. This idiom will apply to quite many things.

The project is getting very big to handle on my own, so I'm looking now for people to help me with this project.  So.. would here on forums be any interested guys on this?

I'm currently in process of switching back to SFML.  Few problems arises from this and it is the GLEW + gl headers colliding with my gl3w GL core loader. Also, I have had the forward compatible bit turned on from the begining which sfml does not offically support due to the current graphics module.

Contact me with PM or email: jarmo.tiitto@gmail.com.

I'm pleased to join on SFML forums,
JATothrim.

eXpl0it3r

  • SFML Team
  • Hero Member
  • *****
  • Posts: 11030
    • View Profile
    • development blog
    • Email
AW: KiloSprite OpenGL 3.3 2D Graphics Renderer
« Reply #1 on: June 20, 2013, 07:11:40 pm »
Sounds interesting and congrats for your results! :)

My opinion is mostly rather: Write Games, not Engines!
Most of the well known engines got created after a game has been made with it. Because although the engine might give nice results for test cases, it might not be ready for a real game. ;)

Do you have anything to show off?

Writting that it might not be nice code but it works, will most likely turn already down many of the experienced users, since it just means, that there's a ton to fix.

Unfortunately though, I don't have time form anything
Official FAQ: https://www.sfml-dev.org/faq.php
Official Discord Server: https://discord.gg/nr4X7Fh
——————————————————————
Dev Blog: https://duerrenberger.dev/blog/

JATothrim

  • Newbie
  • *
  • Posts: 2
    • View Profile
    • Email
Re: KiloSprite OpenGL 3.3 2D Graphics Renderer
« Reply #2 on: June 29, 2013, 04:43:45 pm »
Hmm.. perhaps I should write a very simple game with the current engine and post the source code for the game. That would convince people a bit.
The code that I write in general is quite robust as I debug every piece of code that I have written.

Small old school arcade game coming soon: 'Bounce!'
Edit:
Bounce! is very simple old school archade game written just in three days.
Screenshot
Download

Source code for the game. I packed the game into just one .cpp file
/**
 *      (C) Jarmo A Tiitto. All rights reserved.
 *      This is the small game named Bounce!
 *      It mimics pong and breakout a litle.
 */


#include <RenderSubSystem/RenderSubSystem.h>
#include <RenderSubSystem/RendererWindow.h>
#include <RenderSubSystem/Renderer.h>
#include <RenderSubSystem/RenderingLayer.h>
#include <RenderSubSystem/RenderingManager.h>
#include <RenderSubSystem/Font.h>
#include <RenderSubSystem/TextGameObject.h>
#include <ObjectSubSystem/BaseCameraObject.h>
#include <ObjectSubSystem/EmptyObject.h>
#include <ObjectSubSystem/GraphicsObject.h>
#include <ObjectSubSystem/ObjectSubSystem.h>

#include <Math/Random.h>
#include <Math/Vector.hpp>
#include <General/cvString.h>
#include <General/SharedObjectPtr.h>
#include <algorithm>
#include <boost/chrono/chrono.hpp>
#include <ctime>

/**
 *      Game code.
 */

#define SCREEN_WIDTH 800
#define SCREEN_HEIGHT 600
const float BALL_RADIUS = 20.0f;
const float PADDLE_Y_MAX = 150.0f;

class ScoreCounter;
class BoxTarget;
class BounceBall;
class Paddle;

// Score counter. Displays the score on screen.
class ScoreCounter
{
        public:
                int score;
                ksObjects::GraphicsObject * gfxtext;
                SharedObjectPtr<ksRenderer::Font> gfxfont;
                std::string highscoretxt;
                int highscore;

                ScoreCounter(ksRenderer::Font * fnt)
                        : score(0), highscore(0), gfxtext(NULL), gfxfont(fnt, ksRenderer::Font::Disposer())
                {
                        // Create general purpore graphics object.
                        gfxtext = ObjectSubSystem::instance().create<ksObjects::GraphicsObject>();

                        // Position it in our camera's coordinate space to top-left corner
                        gfxtext->position(ksMath::Vec2f(20.0f, 20.0f));

                        // Specialize the graphics to rendering text.
                        gfxtext->enableTextObject();
                        ksRenderer::TextGameObject & text = gfxtext->text();

                        highscoretxt = "\nI bet you can't go over 50!";

                        // Set font face for the text.
                        text.setFont(gfxfont.get());
                        // Define text style that is 64 pixels high.
                        int gfxstyle = text.createStyle(64);

                        // Precache style (optional)
                        text.loadStyles();

                        // Set default style and specify the text to be rendered.
                        text.setDefaultStyle(gfxstyle);
                        text.setText("Score: 0" + highscoretxt);

                        // Specify control point of the text
                        // and specify transformation.
                        text.centerText(-1, -1);
                        text.offset(ksMath::Vec2f(0.0f, 96.0f));
                        text.scale(ksMath::Vec2f(1.0f, -1.0));
                       
                }

                ~ScoreCounter()
                {
                        // we need to delete the object.
                        ObjectSubSystem::instance().deleteObject(gfxtext);
                }

                void pointsUp()
                {
                        ++score;

                        gfxtext->text().setText("Score: " + stringify(score) + highscoretxt);
                }

                void pointsDown()
                {
                        score = std::max(score - 2, 0);
                        gfxtext->text().setText("Score: " + stringify(score) + highscoretxt);
                }

                void reset()
                {
                        score = 0;
                        highscore = 0;
                        gfxtext->text().setText("Score: " + stringify(score) + "\nLooooser! ;)");
                }

                void update()
                {
                        highscore = std::max(highscore, score);

                        if(highscore >= 1)
                        {
                                highscoretxt = "\nYour best: " + stringify(highscore);
                                gfxtext->text().setText("Score: " + stringify(score) + highscoretxt);
                        }
                }

                bool gameover()
                {
                        return highscore > 0 && score == 0;
                }

};

// The box you have to hit with the ball
class BoxTarget
{
        public:
                bool hit;
                ksMath::Vec2f position;
                ksMath::Vec2f size;

                BoxTarget()
                        : hit(false)
                {
                        generate();
                }

                void generate()
                {
                        size.x = 10.0f + ksMath::RandFloat() * 100.0f;
                        size.y =  10.0f + ksMath::RandFloat() * 80.0f;
                        position.x = 50.0f + ksMath::RandFloat() * (700.0f - size.x);
                        position.y = BALL_RADIUS * 2 + 10.0f + ksMath::RandFloat() * (SCREEN_HEIGHT - PADDLE_Y_MAX - BALL_RADIUS * 2 - 10.0f - size.y);
                }

                void interact(const BounceBall & ball, ScoreCounter & score);
};

class BounceBall
{
        public:
                ksMath::Vec2f position;
                ksMath::Vec2f speed;

                BounceBall()
                {
                        reset();
                }

                void reset()
                {
                        position.x = 100.0f + ksMath::RandFloat() * 600.0f;
                        position.y = 200.0f + ksMath::RandFloat() * 100.0f;
                        speed.y = -10.0f;
                        speed.x = -15.0f + ksMath::RandFloat() * 30.0f;
                }

                void update()
                {
                        speed += ksMath::Vec2f(0.0f, 0.5f);
                        speed *= 0.999f;
                        position += speed;

                        if(position.x < BALL_RADIUS)
                        {
                                position.x = BALL_RADIUS;
                                speed.x = -speed.x * 0.9f;
                        }

                        if(position.x > SCREEN_WIDTH - BALL_RADIUS)
                        {
                                position.x = SCREEN_WIDTH - BALL_RADIUS;
                                speed.x = -speed.x * 0.9f;
                        }
                }

                void interact(ScoreCounter & score);
};

class Paddle
{
        public:
                ksMath::Vec2f position;
                ksMath::Vec2f prevposition;
                ksMath::Vec2f size;
                ksMath::Vec2f speed;
                float inertia;
                float slowness;

                Paddle(float width, float height, float _slowness)
                        : size(width, height), slowness(_slowness)
                {
                        position.x = SCREEN_WIDTH / 2 - width / 2;
                        position.y = SCREEN_HEIGHT - height;
                        prevposition = position;
                        speed = ksMath::Vec2f(0.0f);
                        inertia = 0.0f;
                }

                void update()
                {
                        prevposition = position;
                        int mx, my;
                        // Currently there is no InputSubSystem, so we must use GLFW directly.
                        glfwGetMousePos(&mx, &my);
                        ksMath::Vec2f currentpos, movement;
                        currentpos.x = mx - size.x / 2.0f;
                        currentpos.y = std::max((float)my, SCREEN_HEIGHT - PADDLE_Y_MAX);



                        movement = (currentpos - prevposition);

                        inertia += (movement * ksMath::Vec2f(slowness, 1.0f)).x;

                        speed = ksMath::Vec2f(inertia, movement.y);

                        position = prevposition + movement * ksMath::Vec2f(slowness, 1.0f) + ksMath::Vec2f(inertia, 0.0f);

                        inertia *= 0.99f;

                        // limit paddle area
                        position.x = std::min(std::max(-size.x, position.x), (float)SCREEN_WIDTH);
                        position.y = std::min(std::max(SCREEN_HEIGHT - PADDLE_Y_MAX, position.y), (float)SCREEN_HEIGHT);

                }

                void interact(BounceBall & ball);

};

void BoxTarget::interact(const BounceBall & ball, ScoreCounter & score)
{
        if(ball.position.x - BALL_RADIUS > position.x + size.x) return;
        if(ball.position.x + BALL_RADIUS < position.x) return;
        if(ball.position.y - BALL_RADIUS > position.y + size.y) return;
        if(ball.position.y + BALL_RADIUS < position.y) return;

        // ball and target intersect.
        score.pointsUp();
        generate();
}

void BounceBall::interact(ScoreCounter & score)
{
        if(position.y > SCREEN_HEIGHT - BALL_RADIUS)
        {
                position.y = SCREEN_HEIGHT - BALL_RADIUS;
                speed.y = -speed.y * 0.9f;
                score.pointsDown();
        }
}

void Paddle::interact(BounceBall & ball)
{
        // Check if ball hits the paddle
        if(ball.position.x + BALL_RADIUS > position.x + 5.0f
                && ball.position.x - BALL_RADIUS < position.x + size.x - 5.0f
                && ball.position.y + BALL_RADIUS > position.y
                && ball.position.y < position.y + size.y)
        {
                // hit top
                ball.position.y = position.y - BALL_RADIUS;
                ball.speed.y = -ball.speed.y + speed.y;
                ball.speed.x = ball.speed.x * 0.9f + speed.x * 0.5f;
                return;
        }                      
}

/**
 *      The Engine handles program startup and catches exceptions
 *      from our game_main() procedure.
 *      This is not the only way and is not yet standardized.
 */

void game_main()
{
        using namespace ksRenderer;
        using namespace ksMath;

        /// KiloSprite consists of several subsystems.
        /// All subsystems are singletons and so can be accessed anywhere from the game code.
        /// This is pretty standard way to begin the application:
        RenderSubSystem & rendsys = RenderSubSystem::instance();
        RendererWindow & rendwindow = RendererWindow::instance();
        ObjectSubSystem & objsys = ObjectSubSystem::instance();


        // Open window
        rendsys.open(800, 600, "Bounce!", false);

        // Grap renderer objects.
        ksRenderer::Renderer * renderer = rendsys.getRenderer();
        ksRenderer::RenderingLayer * layer = renderer->getDefaultLayer();
        ksObjects::BaseCameraObject * camera = renderer->getDefaultCamera();

        // And thats it. We have renderer, a layer that contains all graphics and single camera to view the
        // game world.

        // Use screen coordinate system where (0,0) is top-left corner of the window.
        camera->useScreenCoordinates();
        rendwindow.vsync(false);

        srand(time(0));

        {
                // Load font face
                // Certain objects in KiloSprite are derived from SharedObject utility class which eases
                // the handling of resources.
                SharedObjectPtr<ksRenderer::Font> fnt(new ksRenderer::Font(), ksRenderer::Font::Disposer());
                fnt->load("media/Digital Sans EF Medium.ttf");


                ScoreCounter score(fnt.get());
                BoxTarget target;
                BounceBall ball;
                Paddle paddle(200.0f, 20.0f, 0.05f);

                // Create "gameover" text object.
                // First create the general purpose graphics container object or just graphicsobject.
                // This is a ObjectSubSystem resource.
                ksObjects::GraphicsObject * gameover = objsys.create<ksObjects::GraphicsObject>();
                // Position it on the screen.
                gameover->position(ksMath::Vec2f(400.0f, 300.0f));

                // Define the graphics object as text. This allocates a RenderSubSystem TextGameObject object
                // and adds it to the current active layer. The active layer can be selected by renderer->setScene(...)
                // However this is not now necessary as renderer has a default layer alredy active.
                gameover->enableTextObject();

                // Grab refence to the RenderSubSystem object
                ksRenderer::TextGameObject & text = gameover->text();

                // Configure the text proberties so we get big red "Game Over!" text on the screen
                text.setFont(fnt.get());

                RGBAf colors[] = {RGBAf(1.0f, 0.0f, 0.0f, 1.0f),
                        RGBAf(1.0f, 0.0f, 0.0f, 1.0f),
                        RGBAf(1.0f, 0.0f, 0.0f, 1.0f),
                        RGBAf(1.0f, 0.0f, 0.0f, 1.0f)};

                int gfxstyle = text.createStyle(140, 2, colors, 3.0f);
                int gfxsmall = text.createStyle(50, 1, colors);
                text.loadStyles();
                text.setDefaultStyle(gfxstyle);

                // Text object supports multiline text with diffrent styles (mainly sizes and colours).
                std::string txtgameover = "Game Over!\n";
                text.setText(txtgameover + "(Hit space to retry)");
                text.applyStyle(gfxsmall, utf8::distance(txtgameover.begin(), txtgameover.end()));
                text.centerText(0, 0);
                text.scale(ksMath::Vec2f(1.0f, -1.5));

                // hide the object so it is not rendered at first.
                text.hide();

                boost::chrono::steady_clock::time_point gamelogic_t0 = boost::chrono::steady_clock::now();

                while(rendwindow.is_open())
                {
               

                        boost::chrono::steady_clock::time_point gamelogic_t1 = boost::chrono::steady_clock::now();
                        if(gamelogic_t1 - gamelogic_t0 > boost::chrono::milliseconds(16))
                        {
                                gamelogic_t0 = gamelogic_t1;

                                // Run game logic

                                ball.update();
                                paddle.update();

                                ball.interact(score);
                                paddle.interact(ball);
                                target.interact(ball, score);
                                score.update();

                                if(score.gameover())
                                {
                                        // Make the text visible.
                                        gameover->text().visible();

                                        if(glfwGetKey(GLFW_KEY_SPACE))
                                        {
                                                score.reset();
                                                target.generate();
                                                ball.reset();
                                                gameover->text().hide();

                                        }
                                }
                        }
               

                        // Perform simple drawing.
                        layer->draw().fetchMaterial(BLEND_ALPHA);
                        layer->draw().filledcircle(ball.position, BALL_RADIUS, RGBAf(1.0f, 1.0f, 1.0f, 1.0f), RGBAf(0.2f, 0.2f, 0.2f, 0.1f));

                        layer->draw().fetchMaterial(RGBAf(0.0f, 1.0f, 0.0f, 1.0f), BLEND_REPLACE);
                        layer->draw().filledbox(target.position, target.size);

                        layer->draw().fetchMaterial(RGBAf(0.6f, 0.6f, 0.6f, 1.0f), BLEND_REPLACE);
                        layer->draw().filledbox(paddle.position, paddle.size);

                        // Render the current active layer.
                        // Pixels go to the window as no offscreen rendering target is active.
                        renderer->render();
                        // Show the new image to the user.
                        rendwindow.swapbuffers();
                }

        // clean up.

                objsys.deleteObject(gameover);
        }       // destroy game elements.

        // close the render subsystem. This releases all grapics resources
        rendsys.close();
}

« Last Edit: July 04, 2013, 02:53:38 pm by JATothrim »