DEV Community

loading...
Cover image for Perl animated game. Playing with Perl, Inline C++ and SDL2 (part 2!)

Perl animated game. Playing with Perl, Inline C++ and SDL2 (part 2!)

ibrierley profile image Ian ・8 min read

(Full code and Git link at bottom, Part 1 is here)

First things first. On the shoulders of giants are small irrelevant programs made :D. I have to give full kudos to folks who write things like Inline, Inline::cpp, SDL etc. So much work put into things like these, I think they often don't get enough appreciation, so a big round of applause from me.

Our aims in Part 2:

  1. Set up some basic Perl classes for game Entities.
  2. Use Perls Inline::cpp to access more c++ classes and methods (so we can access SDL2).
  3. Get those c++ methods, to take some information from Perl and display to the screen using our system SDL2 lib (installed from Part 1).

We should end up with a window and image as below:
Alt Text

Right, recap time for Part 2!! The main code and Git link is at the end. If you've followed that, you should hopefully have Inline::cpp working with some c++ code, and SDL2 window appearing. Small acorns and all that! Any problems let us know.

So let's develop the Perl and c++ a bit further to get an image on the page, and a basic Object passing some info between the languages.

We'll create an Entity in Perl (i.e an object that represents something we can move about), and the fiddly bit, is an Object in c++ which represents the graphics portion of the object. There's a few ways to skin this cat, and I'm not sure which is the best approach, and I'm happy to hear others ideas.

My main theory is to leave all the graphical heavy lifting on c++, things like graphics, vertices, any big datasets. Then decision making, logic etc, where an object is, the stuff we want to play with easily in Perl. So x,y in Perl, how an object is represented graphically in c++.

For the moment, I'm going to call the Perl game object/entity an erm Entity, and the c++ object a Gfx object, and link the two (well Inline automatically links c++ objects to Perl).

We will end up with:

Perls $some_entity->gfx accesses the c++ Gfx class.
Perls $sdl element accesses the c++ Sdl class.
Enter fullscreen mode Exit fullscreen mode

So I'm going to create a new package for the Entity in Perl. the gfx methods we'll define later in c++ below.

This is going to go right at the top of our code after our "use" statements. It's a basic object with a few attributes. Note the "Gfx"

Perl: Creating a game Entity class, with access to c++ Gfx class.


package Entity;

sub new {
    my $class = shift;
    my $type = shift;

    my $self = bless {
        'alive'   => 1,
        'x'       => 1,
        'y'       => 2,
        'width'   => 100,
        'height'  => 100,
        'type'    => $type,
        'gfx'     => new Gfx,     # Reference a c++ class in later code
    }, $class;

    # Set the x,y coordinates of our new c++ Gfx object.
    $self->gfx->setxy( $self->{ 'x' }, $self->{ 'y' } );   

    return $self;
}

sub gfx    { my $self = shift; return $self->{ 'gfx' } };     # Simple accessor.
Enter fullscreen mode Exit fullscreen mode

Whenever we create a new player, enemy, object, it will have a few attributes, like if it's alive, where it is on the screen etc. Lower down in our main package, we'll create one, which will be the player! It may be nice to create getters/setters for x,y etc, but I'm trying to keep the code down just for the moment for simplicity.

my $player = Entity->new('player');
$player->{ 'x' } = 3;
$player->{ 'y' } = 4;

# $player->gfx->method is calling c++!
$player->gfx->setwidth($player->{ width }, $player->{ height });   # This will reference the c++ class we'll see soon!
$player->gfx->setTextureSlot(1);    # Don't worry about this just yet...it's which image we will use later for the player.
Enter fullscreen mode Exit fullscreen mode

The call..

$player->gfx->someMethod()
Enter fullscreen mode Exit fullscreen mode

is calling the c++ object gfx of class Gfx we have created in our c++ code.

Now, we need Perl to call those c++ classes, and methods. Remember, Inline::cpp makes classes available (and structs with a bit of extra work, see Part 1 tutorial). So let's have a look at an example c++ class to use!

c++ creating our Graphics class that each Perl game Entity will reference

class Gfx {
    public: 
        int x, y, w, h;     // set x,y, width, height
        int textureSlot;    // which texture/image slot this object will use for visuals
        void setxy( int nx, int ny );   
        void setwidth( int nw, int nh );
        void setTextureSlot( int slot );
};

void Gfx::setTextureSlot( int slot ) { textureSlot = slot; }
void Gfx::setxy( int nx, int ny )    { x = nx; y = ny; }
void Gfx::setwidth( int nw, int nh ) { w = nw; h = nh; }
Enter fullscreen mode Exit fullscreen mode

So there's not a lot of clues here, but the c++ Gfx class has an x,y, w(width), h(height), and a couple of methods to set its options we can use on the c++ side. Why don't we write this bit in Perl ? Well, we want to pass some information to various methods in c++, so it makes it simpler passing those types about. Again, there may be a better way to do this!

Now, if you have your code from Part 1, we also had the Sdl class. We're going to extend that a bit to be able to store an image.

This is in the existing Sdl class (from Part 1)

  const int NUMBER_OF_IMAGES = 2;

  SDL_Texture *textureList[ NUMBER_OF_IMAGES ];
  SDL_Rect currentSpritePos;
  SDL_Surface* tempSurface;


  void loadImageIntoSlot( char *newImage, int slot ) {
        tempSurface = IMG_Load( newImage );
        if( tempSurface == NULL ) {
                std::cout << "Unable to use surface\n";
        } else {
                SDL_Texture *myTextureAddress = SDL_CreateTextureFromSurface( renderer, tempSurface );
                textureList[ slot ] = myTextureAddress;
        }
        if( textureList[ slot ] == NULL ) { std::cout << "Unable to load image \n"; }
        SDL_FreeSurface( tempSurface );
    }
Enter fullscreen mode Exit fullscreen mode

Eeek! What on earth ? Ok, this is where the SDL docs need to be read a bit to understand what each one does. There's a wiki at https://wiki.libsdl.org/ but it needs a bit of digging through the tutorials and API.

So what I'm doing here, is creating a method which will take (from Perl in our case) the name of an image, which "slot" we'll load it into (just our own array here, so we can reuse image/textures). Convert this to a texture, which we'll store in the graphics card. So we'll have an array of pointers to texures/images.

So we do

tempSurface = IMG_Load( newImage );
Enter fullscreen mode Exit fullscreen mode

load in the image from the filesystem, and test it loaded ok.

SDL_Texture *myTextureAddress = SDL_CreateTextureFromSurface( renderer, tempSurface );
Enter fullscreen mode Exit fullscreen mode

This is creating a texture from the image. The difference here, is that in SDL2 the textures can be stored on the graphics card. So we'll manipulate "textures" now instead of images.

SDL_FreeSurface( tempSurface );
Enter fullscreen mode Exit fullscreen mode

We no longer need that temporary bit of image/surface space, so we'll get rid of it to be nice and clean.

Now, we're going to create another couple of c++ methods.

First one, clear the "renderer" or screen in this case (sort of), but we'll set a colour for it to clear to. In this case, a healthy "grey" colour ;). (couple of languages differences for our friends over the pond there ;))

    void renderClear() {
        SDL_SetRenderDrawColor(renderer, 200, 200, 200, 200);
        SDL_RenderClear(renderer);
    }
Enter fullscreen mode Exit fullscreen mode

Then renderPresent will update the screen with any rendering we've stored (we save it to a buffer which then gets splatted to the screen).

    void renderPresent() {
        SDL_RenderPresent(renderer);
    }
Enter fullscreen mode Exit fullscreen mode

Now, this following bit, we're just using the Perl entities gfx reference, copying the basic values into an Sdl rect, and then passing to an SDL Render method, which takes a texture, a position (our Rect) and displays it on the screen with SDL_RenderCopyEx. There's a couple of bits on there, like SDL_FLIP and whether we it to be a mirror image reversed or not. Why don't we just pass the Perl entity ? c++ doesn't know about Perl objects, and Perl doesn't know about SDL Rect types, so we have to convert them.

    void updatePosition( Gfx *gfx ) {
        currentSpritePos.x = gfx->x;
        currentSpritePos.y = gfx->y;
        currentSpritePos.w = gfx->w;
        currentSpritePos.h = gfx->h;
        SDL_RenderCopyEx(renderer, textureList[ gfx->textureSlot ], NULL, &currentSpritePos, 0.2, NULL, SDL_FLIP_NONE);
    }
Enter fullscreen mode Exit fullscreen mode

Now, finally back to Perl! We've created our $sdl object in Part1 which opened up a window and referenced Sdl2.

We'll now call these new c++ methods, firstly to load our image.
Then to clear the screen to grey.
Then to update our c++ reference to our Entity with our new position.
Then to splat the screen with the buffered texture.

$sdl->loadImageIntoSlot("Camel1.png", 1);
$sdl->renderClear();
$sdl->updatePosition( $player->gfx );
$sdl->renderPresent();

Enter fullscreen mode Exit fullscreen mode

You can download the Camel from Github here, save into the same folder as the script.

FULL CODE BELOW (or Git link at bottom)! (Save both sections in one file)

Perl code:

#!/usr/bin/perl

use strict;
use warnings;
use Data::Dumper;
use Time::HiRes qw( usleep gettimeofday );

use Inline CPP => config => libs => '-L/usr/local/lib -lSDL2 -lSDL2_image -lSDL-Wl,-rpath=/usr/local/lib';
use Inline CPP => 'DATA';

# Game object class
package Entity;

sub new {
    my $class = shift;
    my $type = shift;

    my $self = bless {
        'alive'   => 1,
        'x'       => 1,
        'y'       => 2,
        'width'   => 100,
        'height'  => 100,
        'type'    => $type,
        'gfx'     => new Gfx, # calling c++ class
    }, $class;

    # calling c++ Gfx class
    $self->gfx->setxy($self->{ 'x' },$self->{ 'y' });

    return $self;
}

sub gfx    { my $self = shift; return $self->{ 'gfx' } };


package main;

# Set up a new Graphics window for the game
my $sdl = Sdl->new; # Calling c++ class
$sdl->init();

# Create our player
my $player = Entity->new('player');
$player->{ 'x' } = 3;
$player->{ 'y' } = 4;

# Call c++ class/methods below, so c++ has the correct positional info

$player->gfx->setwidth($player->{ 'width' }, $player->{ 'height' });
$player->gfx->setTextureSlot(1);

# Store our image in an texture/image array slot
$sdl->loadImageIntoSlot("Camel1.png", 1);

# Clear the screen in grey
$sdl->renderClear();

# Draw our player
$sdl->updatePosition( $player->gfx );

# Update the screen from our drawn buffer
$sdl->renderPresent();

sleep 2;


__DATA__
__CPP__
Enter fullscreen mode Exit fullscreen mode

c++ code

#include <SDL2/SDL.h>
#include <SDL2/SDL_image.h>
#include <iostream>
#include <map>

const int SCREEN_WIDTH = 800;
const int SCREEN_HEIGHT = 600;
const int NUMBER_OF_IMAGES = 2;

SDL_Renderer *renderer = NULL;
SDL_Window *window;
SDL_Surface* tempSurface;

// Class that Perl references, so SDL can access the relevant Perl game Entity variables

class Gfx {
    public: 
        int x, y, w, h;
        int textureSlot;
        void setxy( int nx, int ny );
        void setwidth( int nw, int nh );
        void setTextureSlot( int slot );
};

void Gfx::setTextureSlot( int slot ) { textureSlot = slot; }
void Gfx::setxy( int nx, int ny ) { x = nx; y = ny; }
void Gfx::setwidth( int nw, int nh ) { w = nw; h = nh; }

// Set up our SDL Window and Renderer for displaying to the screen

class Sdl {
  private:

  public:

    Sdl() {
        std::cout << "C++ Constructor for Sdl\n";
    }   
    ~Sdl() {
        std::cout << "C++ Destructor for Sdl\n";
    }

    SDL_Texture *textureList[ NUMBER_OF_IMAGES ];
    SDL_Rect currentSpritePos;

    int init() {
        int x = SDL_Init(SDL_INIT_VIDEO);
        if( x == 0 ) {
            window = SDL_CreateWindow("SDL2 Window",
                                       SDL_WINDOWPOS_CENTERED,
                                       SDL_WINDOWPOS_CENTERED,
                                       SCREEN_WIDTH, SCREEN_HEIGHT,
                                       0);
            renderer = SDL_CreateRenderer( window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC );
        } else {
            std::cout << "Had a problem creating a window! " << SDL_GetError();
        }
        if( window == NULL ) {
            std::cout << "Window null";
            exit( 0 );
        }
        if( renderer == NULL ) {
            std::cout << "Renderer null";
            exit( 0 );
        }
        return 0;
  }


   // Save our image as a texture into an array of possible textures
    void loadImageIntoSlot( char *newImage, int slot ) {
        tempSurface = IMG_Load( newImage );
        if( tempSurface == NULL ) {
                std::cout << "Unable to use surface\n";
        } else {
                SDL_Texture *myTextureAddress = SDL_CreateTextureFromSurface( renderer, tempSurface );
                textureList[ slot ] = myTextureAddress;
        }
        if( textureList[ slot ] == NULL ) { std::cout << "Unable to load image \n"; }
        SDL_FreeSurface( tempSurface );
    }

    // clear the screen in grey
    void renderClear() {
        SDL_SetRenderDrawColor(renderer, 200, 200, 200, 200);
        SDL_RenderClear(renderer);
    }

    // update our Graphics card buffer
    void renderPresent() {
        SDL_RenderPresent(renderer);
    }

    // Render our texture to the screen in the correct position.    
    void updatePosition( Gfx *gfx ) {
        currentSpritePos.x = gfx->x;
        currentSpritePos.y = gfx->y;
        currentSpritePos.w = gfx->w;
        currentSpritePos.h = gfx->h;
        SDL_RenderCopyEx(renderer, textureList[ gfx->textureSlot ], NULL, &currentSpritePos, 0.2, NULL, SDL_FLIP_NONE);
    }


};
Enter fullscreen mode Exit fullscreen mode

Code and image all avaiable on Github here

Extra thoughts:
methods like renderClear(); feel too SDL specific, so maybe Perl should start to reference more graphics library agnostic names. Eg clearScreen() and updateScreen(), then if we want to use Opengl or something, the Perl part of the code would likely need to change less.

Discussion (0)

Forem Open with the Forem app