The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

Games::Irrlicht - use the Irrlicht 3D Engine in Perl

SYNOPSIS

        package MyGame;
        use strict;

        use base Games::Irrlicht;

        use Games::Irrlicht::Constants;          get EDT_SOFTWARE etc

        # override methods:

EXPORTS

Exports nothing on default.

DESCRIPTION

The Why

When building a game or screensaver displaying some continously running animation, a couple of basics need to be done to get a smooth animation and to care of copying with varying speeds of the system. Ideally, the animation displayed should be always the same, no matter how fast the system is.

This not only includes different systems (a PS/2 for instance would be slower than a 3 Ghz PC system), but also changes in the speed of the system over time, for instance when a background process uses some CPU time or the complexity of the scene changes.

In many old (especial DOS) games, like the famous Wing Commander series, the animation would be drawn simple as fast as the system could, meaning that if you would try to play such a game on a modern machine it we end before you had the chance to click a button, simple because it wizzes a couple 10,000 frames per second past your screen.

While it is quite simple to restrict the maximum framerate possible, care must be taken to not just "burn" surplus CPU cycles. Instead the application should free the CPU whenever possible and give other applications/thread a chance to run. This is especially important for low-priority applications like screensavers.

Games::Irrlicht makes this possible for you without you needing to worry about how this is done. It will restrict the frame rate to a possible maximum and tries to achive the average framerate as close as possible to this maximum.

Games::Irrlicht also monitors the average framerate and gives you access to this value, so that you can, for instance, adjust the scene complexity based on the current framerate. You can access the current framerate, averaged over the last second (1000 ms) by calling current_fps.

Frame-rate Independend Clock

Now that our application is drawing frames (via the method draw_frame, which you should override in a subclass), we need a method to decouple the animation speed from the framerate.

If we would simple put put an animation step every frame, we would get some sort of Death of the Fast Machine" effect ala Wing Commander. E.g. if the system manages only 10 FPS, the animation would be slower than when we do 60 FPS.

To achive this, SDL::App::FPS features a clock, which runs independed of the current frame rate (and actually, independend of the system's clock, but more on this in the next section).

You can access it via a call to current_time, and it will return the ticks e.g. the number of milliseconds elapsed since the start of the application.

To effectively decouple animation speed from FPS, get at each frame the current time, then move all objects (or animation sequences) according to their speed and display them at the location that matches the time at the start of the frame. See examples/ for an example on how to do this.

Note that it is better to draw all objects according to the time at the start of the frame, and not according to the time when you draw a particular object. Or in other words, treat the time like it is standing still when drawing a complete frame. Thus each frame becomes a snapshot in time, and you don't get nasty sideeffects like one object beeing always "behind" the others just because it get's drawn earlier.

Time Warp

Now that we have a constant animation speed independend from framerate or system speed, let's have some fun.

Since all our animation steps are coupled to the current time, we can play tricks with the current time.

The function time_warp let's you access a time warp factor. The default is 1.0, but you can set it to any value you like. If you set it, for instance to 0.5, the time will pass only half as fast as it used to be. This means instant slow motion! And when you really based all your animation on the current time, as you should, then it will really slow down your entire game to a crawl.

Likewise a time warp of 2 let's the time pass twice as fast. There are virtually no restrictions to the time warp.

For instance, a time warp greater than one let's the player pass boring moments in a game, for instance when you need to wait for certain events in a strategy game, like your factory beeing completed.

Try to press the left (fast forward), right (slow motion) and middle (normal) mousebuttons in the example application and watch the effect.

If you are very bored, press the 'b' key and see that even negative time warps are possible...

Ramping Time Warp

Now, setting the time war to factor of N is nice, but sometimes you want to make dramatic effects, like slowly freezing the time into ultra slow motion or speeding it up again.

For this, ramp_time_warp can be used. You give it a time warp factor you want to reach, and a time (based on real time, not the warped, but you can of course change this). Over the course of the time you specified, the time warp factor will be adapted until it reaches the new value. This means it is possible to slowly speeding up or down.

You can also check whether the time warp is constant or currently ramping by using time_is_ramping. When a ramp is in effect, call ramp_time_warp without arguments to get the current parameters. See below for details.

The example application uses the ramping effect instead instant time warp.

Event handlers

This section describes events as external events that typically happen due to user intervention.

Such events are keypresses, mouse movement, mouse button presses, or just the flipping of the power switch. Of course the last event cannot be handled in a sane way by our framework :)

All the events are checked and handled by Games::Irrlicht automatically. The event QUIT (which denotes that the application should shut down) is also carried out automatically. If you want to do some tidying up when this happens, override the method quit_handler.

The event checking and handling is done at the start of each frame. This means no event will happen while you draw the current frame. Well, it will happen, but the action caused by that event will delayed until the next frame starts. This simplifies the frame drawing routine tremendously, since you know that your world will be static until the next frame.

To associate an event with an action, you use the add_event_handler method. This method get's an event kind (like KEYDOWN or MOUSEBUTTONDOWN) and an event type (like SPACE). When this specific event is encountered, the also given callback routine is called. In the simplest form, this would be an anonymous subroutine. Here is an example:

        my $handler = $app->add_event_handler ( KEYDOWN, SPACE, sub {
          my ($self) = shift;
          $self->pause(KEYDOWN);
        } );

This would pause the game until any key is pressed.

You can easily reconfigure the event to trigger for a different key like this:

        $handler->rebind( KEYDOWN, KEY_p );

If you want the same event to be triggered by different external events, then simple add another event:

        my $handler2 = $app->add_event_handler ( KEYDOWN, KEY_P, sub {
          my ($self) = shift;
          $self->pause();
        } );

This would also alow the user to pause with 'P'.

Event bindings can also be removed with del_event_handler(), if so desired.

See add_event_handler() for more details.

Timers

Of course not always should all things happen instantly. Sometimes you need to delay some events or have them happening at regular or irregular intervalls again.

For these cases, SDL::App::FPS features timers. These timers are different from the normal SDL::Timers in that they run in the application clock space, e.g. the time warp effects them. So if you application is in slow motion, the events triggers by the timers will still happen at the correct time.

Asyncronous Timers

There are still some things that need a (near) real-time response and can not wait for the next frame drawn. For instance when one music piece ends and the next needs to be faded in, it would be unfortunate to wait until the next frame start, which might come a bit late.

In these cases you can still use the normal system or Irrlicht timers, of course.

SUBCLASSING

Games::Irrlicht encapsulates any of it's private data under the key _app. So you can use any hash key other than _app to store you data, no need to encapsulate it further unless you plan on making your class sub-classable, too.

When adding subroutines to your subclass, prefix them with something unique, like __ or _myapp_ so that they do not interfere with changes in this base class.

Do not access the data in the baseclass directly, always use the accessor methods!

METHODS

The following methods should be overridden to make a usefull application:

draw_frame()

Responsible for drawing the current frame. It's first two parameters are the time (in ticks) at the start of the current frame, and the time at the start of the last frame. These times are warped according to time_warp(), see there for an explanation on how this works.

The third parameter is the current framerate, averaged. You can use this to reduce dynamically the complexity of the scene to achieve a faster FPS if it falls below a certain threshold.

The following methods can be overriden if so desired:

pre_init_handler()

Called by new() just before the creation of the application and window.

post_init_handler()

Called by new() just after creating the window.

quit_handler()

Called by main_loop() just before the application is exiting.

resize_handler()

Called automatically whenever the application window size changed.

Irrlicht Special Methods

The following methods are special to Irrlicht and can be used to get the single elements from the Irrlicht engine. With these you can then call all Irrlicht functions like:

        my $driver = $app->VideoDriver();
        $driver->getPrimitiveCountDrawn();

These generally corrospondent to the Irrlicht API. See Irrlicht itself for more information about available methods.

You can get the Irrlicht enums and constants by:

        use Games::Irrlicht::Constants;

This will export EDT_OPENGL etc into your namespace, where you can just use them like in C++.

getIrrlichtDevice

Returns the main irrlicht device class, IrrlichtDevice.

getVideoDriver

Returns the video driver class, IVideoDriver.

getSzeneManager

Returns the szene manager class.

getFileSystem

Returns the Irrlicht file system class.

getGUIEnvironment

Returns the Irrlicht GUI Environment class.

getOSOperator

Returns the Irrlicht OSOperator class.

The following methods can be used, but need not be overriden except in very special cases:

new()
        $app = Games::Irrlicht->new($options);

Create a new application, init the Irrlicht subsystem, create a window, starts the frame rate monitoring and the application time-warped clock.

new() gets a hash ref with options, the following options are supported:

        width           the width of the application window in pixel
        height          the width of the application window in pixel
        depth           the depth of the screen (colorspace) in bits
        max_fps         maximum number of FPS to do (save CPU cycles)
        resizeable      when true, the application window will be resizeable
                        You should install an event handler to watch for
                        events of the type VIDEORESIZE.
        renderer        type of renderer. The following are possible:
                          OpenGL
                          DirectX8
                          DirectX9
                          Software
                          Null
        config          Path and name of the config file, defaults to
                          'config/client.cfg'.
        time_warp       Defauls to 1.0 - initial time warp value.
        fullscreen      0 = windowed, 1 - fullscreen
        title           Name of the app, will be the window title
        useconsole      enable a console (which can be shown/hidden)
        showfps         print fps (0 - disable, 1 upper-left, 2 lower-left,
                         3 lower-right, 4 upper-right corner)
        font_fps        name of the .fnt file containing the config for the
                        font for the FPS
        font_console    name of the .fnt file containing the config for the
                         font for the Console
        debug           0: disable, 1 (or higher for more): print debug info

useconsole and showfps do currently not work.

new() also parses the command line options via Getopt::long, meaning that

        ./app.pl --fullscreen --width=800 --noresizeable

will work as intended. If you want to prevent command line parsing, simple clear @ARGV = () before calling new().

Please note that, due to the resulution of the timer, the maximum achivable FPS with capping is about 200-300 FPS even with an empty draw routine. Of course, my machine could do about 50000 FPS; but then it hogs 100% of the CPU. Thus the framerate capping might not be accurate and cap the rate at a much lower rate than you want. However, only max_fps > 100 is affected, anything below 100 works usually as intended.

Set max_fps to 0 to disable the frame-rate cap. This means the app will burn all the CPU time and try to achive as much fps as possible. This is not recommended except for benchmarking!

save
        $app->save($additional_data);

Saves the application state to a file. $additional data can contain a reference to additional data that will also be saved. See also load().

load
        ($data,$error) = $app->load();

Loads the application state from a file. If additional data was passed to save(), then $data will contain a references to this data afterwards. $error will contain any error that might occur, or undef.

screenshot
        $app->screenshot($path,$filename);

Save a screenshot in BMP format of the current surface to a file.

$path and $filename are optional, default is the current directory and filenames named like 'screenshot_0000.bmp'. The first non-existing filename will be used if $filename is undef, otherwise the caller is responsible for finding a free filename.

main_loop()
        $app->main_loop();

The main loop of the application, only returns when a QUIT event occured, or $self->quit() was called.

quit()
        $self->quit();

Set a flag to quit the application at the end of the current frame. Can be called in draw_frame(), for instance.

is_fullscreen()
        if ($app->is_fullscreen())
          {
          }

Returns true if the application is currently in fullscreen mode.

width()
        my $w = $self->width();

Return the current width of the application's surface.

height()
        my $w = $self->height();

Return the current height of the application's surface.

depth()
        my $w = $self->depth();

Return the current bits per pixel of the application's surface in bits, e.g. 8, 16, 24 or 32.

add_timer()
        $app->add_timer($time,$count,$delay,$callback, @args ]);

Adds a timer to the list of timers. When time is 0, the timer fires immidiately (calls $callback). When the count was 1, and time 0, then the timer will not be added to the list (it already expired) and undef will be returned. Otherwise the unique timer id will be returned.

@args can be empty, otherwise the contents of these will be passed to the callback function as additional parameters.

The timer will fire for the first time at $time ms after the time it was added, and then wait $delay ms between each shot. if $count is positive, it gives the number of shots the timer fires, if it is negative, the timer will fire endlessly until it is removed.

The timers added via add_timer() are coupled to the warped clock.

del_timer()
        $app->del_timer($timer);
        $app->del_timer($timerid);

Delete the given timer (or the one by the given id).

timers()

Return count of active timers.

hide_mouse_cursor()
        $self->hide_mouse_cursor( $vis );

Hides the mouse cursor if $vis is true.

add_group()
        $group = $app->add_group();

Convienence method to create a new SDL::App::FPS::Group and bind it to this application. Things (like timers) can be grouped together into groups and thus can be enabled, deleted etc. in batches more easily.

option()
        print $app->option('max_fps'),"\n";     # get
        $app->option('max_fps',40);             # set to 40

Get/sets an option defined by the key (name) and an optional value. The name space should be prepended with an underscore, like:

        print @{ $app->option('console_background_color') },"\n";
freeze_time_warp_ramp()
        $app->freeze_time_warp_ramp();

Disables any ramping of the time warp that might be in effect.

freeze_time()
        $app->freeze_time();

Sets the time warp factor to 0, effectively stopping the warped clock. Note that the real clock still ticks and frames are still drawn, so you can overlay some menu/animation over a static (froozen in time) background. Of course it might be more efficient to save the current drawn frame as image and stop the drawing if the not-changing background altogether.

thaw_time()
        $app->thaw_time();

Sets the time warp factor back to what it was before freeze_time() was called. Does nothing when the clock is not frozen.

ramp_time_warp
        $app->ramp_time_warp($target_factor,$time_to_ramp);

Set a tagret time warp factor and a time it will take to get to this factor. The time warp (see time_warp()) will then be gradually adjusted to the target factor. $time_to_ramp is in ms (aka 1000 == one second).

It is sometimes a good idea to read out the current time warp and ramp it to a specific factor, like so:

        $time_warp = $app->time_warp();
        $app->ramp_time_warp($time_warp * 2, 1000);

But you need to restrict this somehow, otherwise the clock might be speed up or slowed down to insanely high or low speeds. So sometimes it is just better to do this:

        sub enable_slow_motion
          {
          # no matter how fast clock now is, slow it down to a fixed value
          $app->ramp_time_warp(0.5, 1000);
          }

When ramp_time_warp() is called without arguments, and ramping is in effect, it returns a list consisting of:

        target factor           # to where we ramp
        time to ramp            # how long it takes (ticks)
        current time warp       # where are currently
        start time warp         # from where we ramp (factor)
        start time warp time    # from where we ramp (real time ticks)

When no ramping is in effect, it returns an empty list or undef.

You can disable/stop the time warping by setting a new time warp factor directly like so:

        my $t = $app->time_warp(); $app->time_warp($t);

Or easier:

        $app->freeze_time_warp();
time_warp
        $app->time_warp(2);             # fast forward

Get or set the current time warp, e.g. the factor how fast the time passes. The new time warp will be effective from the next frame onwards.

Please note that setting a time warp factor will disable time warp ramping.

time_is_ramping
        if ($app->time_is_ramping())
          {
          }

Returns true if the time warp factor is currently beeing ramped, e.g. chaning.

time_is_frozen
        if ($app->time_is_frozen())
          {
          }

Return true if the time is currently frozen, e.g. the clock is standing still.

frames()

Return number of frames drawn since start of app.

start_time()

Return the time when the application started in ticks.

current_fps()

Return current number of frames per second, averaged over the last 1000ms.

max_fps()

Return maximum number of frames per second we ever achieved.

min_fps()

Return minimum number of frames per second we ever achieved.

now()

Return current time at the start of the frame in ticks, unwarped. See current_time for a warped version. This is usefull for tracking the real time clock as opposed to the warped application clock.

current_time()

Return current time at the start of this frame (the same as it is passed to draw_frame(). This time will be warped by time_warp, e.g a time_warp of 2 makes it go twice as fast as GetTicks(). Note that the returned value will only change at the start of each frame.

lastframe_time()

Return time at the start of the last frame. See current_time(). The same value is passed to draw_frame().

get_clock
        ($day,$hour,$minute,$second,$ms) = $app->get_clock();

Returns the current time (see current_time()) in a day, hour, minute, second and millisecond format. See set_clock() on how to make the current time a certain date and time.

set_clock
        $app->set_clock($day,$hour,$minute,$second,$ms);

        $app->set_clock(1,12,30);       # set current time to day 1, 12:30

Set's the current time to a specific date and time so that get_clock() returns the proper format.

clock_to_ticks
        $app->clock_to_ticks(0,12,30);          # 12 hours, 30 minutes
        $app->clock_to_ticks(10,5,0,12);        # 10 days, 5 hours, 12 seconds
        $app->clock_to_ticks(0,0,0,123);        # 123 seconds

Return time given as days, hours, minutes, seconds and ms (undef counts as 0). This is handy for setting timers than expire in a couple of hours, instead of just a few milli seconds.

Internal Methods

_activated_thing()
        $app->_activated_thing($thing);

When a thing (timer, event handler, button etc) is (re)activated, it notifies the app by calling this routine with itself as argument. Done automatically by the thing itself.

_deactivated_thing()
        $app->_deactivated_thing($thing);

When a thing (timer, event handler, button etc) is deactivated, it notifies the app by calling this routine with itself as argument. Done automatically by the thing itself.

AUTHORS

(c) 2002, 2003, 2004 Tels <http://bloodgate.com/>

SEE ALSO

http://irrlicht.sf.net/

1 POD Error

The following errors were encountered while parsing the POD:

Around line 1921:

You forgot a '=back' before '=head1'