Development/Tutorials/Games/KGLEngine2d: Difference between revisions

    From KDE TechBase
    No edit summary
    m (some typo fixes and wording changes)
    Line 23: Line 23:


    ==Abstract==
    ==Abstract==
    KGLEngine2d is a recent try to develop a 2D games engine using openGL, and powerful enough to deliver complex and beautiful games.
    KGLEngine2d is a recent try to develop a 2D game engine using openGL, and powerful enough to deliver complex and beautiful games.
    As of now, version 1.0 is not even out, but the API is stable enough for anyone to start playing with the engine.
    As of now, version 1.0 is not even out, but the API is stable enough for anyone to start playing with the engine.
    Through this tutorial I will explain how to use a great part of the engine, included but not limited to : sprites creation, rotation and deplacement and collision detection.
    Through this tutorial I will explain how to use a great part of the engine, included but not limited to : sprites creation, rotation and deplacement and collision detection.
    Line 33: Line 33:


    ==Starting Point : the main.cpp==
    ==Starting Point : the main.cpp==
    Every application need one, so let's start with it, as it really is simple.
    Every application needs one, so let's start with it, as it is really simple.
    <code cppqt>
    <code cppqt>
    #include <KApplication>
    #include <KApplication>
    Line 66: Line 66:
    ==The KGLEngine2d pong engine==
    ==The KGLEngine2d pong engine==


    Now, as we want a bit of sophisticated features, we will have to surclass the KGLEngine2d.
    Now, as we want a bit of sophisticated features, we will have to subclass the KGLEngine2d.


    Namely, those sophisticated actions, that forces us to surclass are the keyboard interactions. As of now, those are managed in the engine's mainLoop, and there are no alternatives to use them otherwise.
    Namely, those sophisticated actions, that forces us to subclass are the keyboard interactions. As of now, those are managed in the engine's mainLoop, and there are no alternatives to use them otherwise.
    Everything else could have been done in the main, or anywhere else, using a simple slot and the engine power, but I will use the Object-Oriented conception int this tutorial.
    Everything else could have been done in the main, or anywhere else, using a simple slot and the engine power, but I will use the Object-Oriented conception in this tutorial.




    Line 96: Line 96:


    So, what happens there ?
    So, what happens there ?
    First, we set the coordinates system. We tell the engine to show us a region of 640pixels of width, and 480 of height. This only affects the rendered view, not the window size.
    First, we set the coordinates system. We tell the engine to show us a region of 640 pixels of width, and 480 of height. This only affects the rendered view, not the window size.
    Then we create and add items to the engine, no more.
    Then we create and add items to the engine, no more.
    The KGLItem constructor need no arguments, but can be given two : the item type, used for collision detection, and the dimensionts of the item.
    The KGLItem constructor can be called without any argument, but can also be given two : the item type, used for collision detection, and the dimensions of the item.
    Here, we dim the items according to what we want them to do : The walls are as large as usefull, but dim, and the goals are as tall as needed to cover the whole area, but dim.
    Here, we change the items' dimensions according to what we want them to represent : The walls have to be wide but thin, same for the goals, except they're vertical and not horizontal.
    The first two items are there so the ball will bounce when it reaches the top or bottom of the screen.
    The first two items are there so the ball will bounce when it reaches the top or the bottom of the screen.
    The third and fourth ones are the goals, so we can detect when the ball's gone past a player's racket. The goals are set to be black, this should not be done this way, they should be given an image instead, but, hey, this is a code example and i wanted to show you how to draw a simple colored rectangle.
    The third and fourth ones are the goals, so we can detect when the ball's gone past a player's racket. The goals are set to be black, this should not be done this way, they should be given an image instead, but, hey, this is a code example and i wanted to show you how to draw a simple colored rectangle.


    Line 122: Line 122:
    </code>
    </code>


    Now we create the player's rackets and the ball. These are special classes that will be explained a bit later.
    Now we create the players' rackets and the ball. These are special classes that will be explained a bit later.
    They might have been simple KGLSprites, and the collisions would have been managed in another function connected to the KGLItem::colliding(PhysicsItem*) function. But, again, let's take the Object Oriented way.
    They might have been simple KGLSprites, and the collisions would have been managed in another function connected to the KGLItem::colliding(PhysicsItem*) function. But, again, let's take the Object Oriented way.
    We also connect the ball's enterGoal signal with the score slot, so when the ball enters a goal, the engine will be told, and will update the score.
    We also connect the ball's enterGoal signal with the score slot, so when the ball enters a goal, the engine will be told, and will update the score.
    Line 157: Line 157:
    This way, we only will have to set these item's speed for them to move.
    This way, we only will have to set these item's speed for them to move.


    By default, an item can not collide with other items, so we need to define the collision rules in order to use the physics engine.
    By default, an item can't collide with other items, so we need to define the collision rules in order to use the physics engine.
    Then, we add collision detection rules. We say that an item of type BALL_TYPE can only collide with items of type RACKET_TYPE, WALL_TYPE, and GOAL_TYPE (which, in this case, is pretty much everything).
    Then, we add collision detection rules. We say that an item of type BALL_TYPE can only collide with items of type RACKET_TYPE, WALL_TYPE, and GOAL_TYPE (which, in this case, is pretty much everything).


    Line 207: Line 207:
    </code>
    </code>


    We just define KGLItems with nothing really special, but when we will change the ball and rackets from simple white squares to sprites, this will prove to be usefull.
    We just define KGLItems with nothing really special, but when we will change the ball and rackets from simple white squares to sprites, this will prove to be useful.
    Now let's look at the constructors:
    Now let's take a look at the constructors:


    <code cppqt>
    <code cppqt>

    Revision as of 11:17, 28 October 2008

    {{{KGLEngine2d tutorial}}}
    Tutorial Series   KGLEngine2d developement
    Prerequisites   None
    What's Next   Nothing at the moment
    Further Reading   KGLEngine2d's code

    Abstract

    KGLEngine2d is a recent try to develop a 2D game engine using openGL, and powerful enough to deliver complex and beautiful games. As of now, version 1.0 is not even out, but the API is stable enough for anyone to start playing with the engine. Through this tutorial I will explain how to use a great part of the engine, included but not limited to : sprites creation, rotation and deplacement and collision detection.

    We will develop a simple pong game through this example. anyone who have followed the KGLEngine2d developement a bit will have seen some kglPong screenshot, as this is the way new features are shown, using this simple game to showcase them. So this will also be the way to introduce new developpers to the technology :)

    Here is a screenshot of what this tutorial leads to :

    Starting Point : the main.cpp

    Every application needs one, so let's start with it, as it is really simple.

    1. include <KApplication>
    2. include <KAboutData>
    3. include <KCmdLineArgs>
    1. include "pongengine.h"

    int main( int argc, char **argv) {

    KAboutData aboutData( "kglpong", 0, ki18n("kglpong"), "0.1", ki18n("Classic pong game using KGLEngine2d"), KAboutData::License_GPL,

                                 ki18n("Copyright (c) 2008 Developer") );
    

    KCmdLineArgs::init( argc, argv, &aboutData ); KCmdLineOptions options; KCmdLineArgs::addCmdLineOptions(options); KApplication app;

    pongEngine pong; pong.show();

    return app.exec(); } So, what are we doing ? Initializing the KApplication, and creating an instance of our main class, the one that subclasses KGLEngine2d. Then we tell him to show himself, and that's all. I told you there was nothing exciting in here :)

    The KGLEngine2d pong engine

    Now, as we want a bit of sophisticated features, we will have to subclass the KGLEngine2d.

    Namely, those sophisticated actions, that forces us to subclass are the keyboard interactions. As of now, those are managed in the engine's mainLoop, and there are no alternatives to use them otherwise. Everything else could have been done in the main, or anywhere else, using a simple slot and the engine power, but I will use the Object-Oriented conception in this tutorial.


    setWidth(640); setHeight(480);

    const double wallThickness = 10; KGLItem *NWall = new KGLItem(WALL_TYPE, QRectF(-width()/2, height()/2-wallThickness, width(), wallThickness)); NWall->setColor(Qt::red); addGLItem(NWall);

    KGLItem *SWall = new KGLItem(WALL_TYPE, QRectF(-width()/2, -height()/2, width(), wallThickness)); SWall->setColor(Qt::red); addGLItem(SWall);

    KGLItem *player1Goal = new KGLItem(GOAL_TYPE, QRectF(-width()/2, -height()/2, wallThickness, height())); player1Goal->setColor(Qt::black); addGLItem(player1Goal);

    KGLItem *player2Goal = new KGLItem(GOAL_TYPE, QRectF(width()/2-wallThickness, -height()/2, wallThickness, height())); player2Goal->setColor(Qt::black); addGLItem(player2Goal);

    So, what happens there ? First, we set the coordinates system. We tell the engine to show us a region of 640 pixels of width, and 480 of height. This only affects the rendered view, not the window size. Then we create and add items to the engine, no more. The KGLItem constructor can be called without any argument, but can also be given two : the item type, used for collision detection, and the dimensions of the item. Here, we change the items' dimensions according to what we want them to represent : The walls have to be wide but thin, same for the goals, except they're vertical and not horizontal. The first two items are there so the ball will bounce when it reaches the top or the bottom of the screen. The third and fourth ones are the goals, so we can detect when the ball's gone past a player's racket. The goals are set to be black, this should not be done this way, they should be given an image instead, but, hey, this is a code example and i wanted to show you how to draw a simple colored rectangle.

    const double thickness = 0.05; const double wallDistance = 0.2; const double height = 0.25;

    player1Racket = new Racket(); player1Racket->setPosition(-hwidth()+wallDistance, 0); addGLItem(player1Racket);

    player2Racket = new Racket(); player2Racket->setPosition(hwidth()-wallDistance-player2Racket->width(), 0); addGLItem(player2Racket);

    ball = new Ball(); ball->setPosition(0, 0); connect(ball, SIGNAL(enterGoal(int)), this, SLOT(score(int))); addGLItem(ball);

    Now we create the players' rackets and the ball. These are special classes that will be explained a bit later. They might have been simple KGLSprites, and the collisions would have been managed in another function connected to the KGLItem::colliding(PhysicsItem*) function. But, again, let's take the Object Oriented way. We also connect the ball's enterGoal signal with the score slot, so when the ball enters a goal, the engine will be told, and will update the score.

    Then let's define which keys we will be using: KAction *up = m_collection->addAction("p1_up"); up->setShortcut(Qt::Key_Z); KAction *down = m_collection->addAction("p1_down"); down->setShortcut(Qt::Key_S); KAction *left = m_collection->addAction("p2_up"); left->setShortcut(Qt::Key_Up); KAction *right = m_collection->addAction("p2_down"); right->setShortcut(Qt::Key_Down);

    KAction *start = m_collection->addAction("start"); start->setShortcut(Qt::Key_Space); We add every action we are going to use, using their names, and then we define their keys.

    Now, the collision detection and automatic moving items system : setItemTypeMovable(RACKET_TYPE); setItemTypeMovable(BALL_TYPE);

    addItemCollisionRule(BALL_TYPE, RACKET_TYPE); addItemCollisionRule(BALL_TYPE, WALL_TYPE); addItemCollisionRule(BALL_TYPE, GOAL_TYPE);

    addItemCollisionRule(RACKET_TYPE, WALL_TYPE);

    First, we tell the engine to call the move() function of each and every item of type RACKET_TYPE and BALL_TYPE, at each call of the mainLoop. This way, we only will have to set these item's speed for them to move.

    By default, an item can't collide with other items, so we need to define the collision rules in order to use the physics engine. Then, we add collision detection rules. We say that an item of type BALL_TYPE can only collide with items of type RACKET_TYPE, WALL_TYPE, and GOAL_TYPE (which, in this case, is pretty much everything).

    Then, we add the scoreBoards : player1ScoreBoard = new KGLTextItem("0"); player1ScoreBoard->setPosition(200, hheight() - wallThickness - 60); player1ScoreBoard->font().setPointSize(60); addGLItem(player1ScoreBoard);

    player2ScoreBoard = new KGLTextItem("0"); player2ScoreBoard->setPosition(-200, hheight() - wallThickness - 60); player2ScoreBoard->font().setPointSize(60); addGLItem(player2ScoreBoard);

    These are simple text items, displaying each player's score.

    qsrand(QTime::currentTime().msec()); lastScorer = (qrand() > RAND_MAX/2);

    Lastly, we define randomly who scored last, which defines the direction the ball will take (left or right).

    The Ball and Racket Classes

    Let's take a look at the Ball and Racket headers : class Ball : public KGLItem { Q_OBJECT public: Ball(); void collidingWith(const SET_COLLIDABLE_ITEMS &); signals: void enterGoal(int); };

    class Racket : public KGLItem { Q_OBJECT public: Racket(); void collidingWith(const SET_COLLIDABLE_ITEMS &); };

    We just define KGLItems with nothing really special, but when we will change the ball and rackets from simple white squares to sprites, this will prove to be useful. Now let's take a look at the constructors:

    Ball::Ball()

    KGLItem(BALL_TYPE, RECT(0, 0, 10, 10))

    { }

    Racket::Racket()

    KGLItem(RACKET_TYPE, RECT(0, 0, 10, 30))

    { }

    So we just define the width and height of the item,as we don't need anything more, yet.

    The interesting thing right now is in the collidingWith function :

    void Ball::collidingWith(const SET_COLLIDABLE_ITEMS &items) {

    SET_COLLIDABLE_ITEMS::iterator it = items.begin(); while(it != items.end()) { if((*it)->typeName() == WALL_TYPE) setSpeed(POINT(speed().x(), -speed().y())); if((*it)->typeName() == RACKET_TYPE) setSpeed(POINT(-speed().x(), speed().y())); if((*it)->typeName() == GOAL_TYPE) emit(enterGoal(((*it)->position().x() > 0) ? 1 : 2)); ++it; } }

    We iterate on the colliding items, and depending on their type, we act. If we are colliding with a wall, we invert our vertical speed. If we are colliding with a racket, we invert our horizontal speed. If we are colliding a goal, then let the engine know it.

    SET_COLLIDABLE_ITEMS::iterator it = items.begin(); while(it != items.end()) { if((*it)->typeName() == WALL_TYPE) { moveBy(0, -speed().y()); setSpeed(POINT(0, 0)); } }

    Here, we iterate again, but there is only one possible reaction : if we hit a wall, then stop moving (and move back to somewhere away from the wall).