UPDATE: When I say event in the following question, it is referring to a keyboard event. However, the question should still stand for any type of event.
I've been working (playing) with Perl's SDL implementation, specifically SDLx::App wrappings. Overall I'm pleased with the ease of prototyping and how simple some things are to implement. My headache comes from using the provided SDL::Controller main loops in regards to event callbacks.
Basic documentation is provided here http://search.cpan.org/~jtpalmer/SDL-2.540/lib/pods/SDLx/Controller.pod. In order to describe my problem, I'll walk through a scenario. You are an object on screen which can react to various events. Since there are no events in the SDL queue, none of your event handlers are called. Makes sense. Now the user depresses the space bar, and holds it down. A single key_down event is added to the event queue which triggers your space_bar_handler method. The issue deals with repeating events or reacting to state outside of the presense of events.
I would like the user to be able to hold down the space bar and have things "keep happening" as long as the key is held. Preferrably, I'll be able to control the time between events.
My first solution was to use the SDL key_repeat feature. This is adequate for a single situation, but has the side effect of limiting ALL repeating events to the particular delay. In my example, if delay between bullets (space_bar) was changed, so was my movement step.
My second solution was to remove the key_repeat and simply pump a blank event every frame, in essence making sure all event handlers were called each frame. This was completely viable but not very controllable. It's fine to react to a movement command every frame, but creating a new bullet object every frame doesn't make much sence in my context.
My question revolves around this callback loop event scheme. Is there a particularly "good" way to time events in this system. Should I be firing blank events every frame? Would an experienced SDLx::App developer be able to explain why this scheme was used and how to use it effectively?
My current code even uses SDL_key_state information to determine if a key is down (rather that just pressed for an event). This makes so much sense to use, but alas, the function is only called when an event occurs. My thought was to do away with the event loop for my desired behavior. I moved all my event callback subs into move callback subs, which seems to have the same effect of pumping a blank event every frame. This still leaves the problem of timing events. Is there a way to asynchronously delay this behavior if default is to fire every frame?
This has ended up being long winded and a bit vague I'm sure. It's pretty in depth SDLx::Controller stuff as well, but I'm fairly certain this is a common design pattern. I'm just looking for information on using this type of system effectively.
package main;
use strict;
use warnings;
use SDL;
use SDLx::App;
use SDL::Event;
use SDL::Events;
use Data::Dumper;
use player;
# Set up Application Options. Let SDLx::App take care of looping
my $app = SDLx::App->new (w => 450, h => 300,
t => "Bob Loblaw", hw_surface => 1,
double_buf => 1, dt => .2, delay => 20,);
#SDL::Events::enable_key_repeat(10, 10);
# Instantiate game objects
my $player = player->new();
$app->add_move_handler(\&move_handler);
$app->add_show_handler(\&show_handler);
$app->add_move_handler(\&event_handler);
#$app->add_event_handler(\&event_handler);
# Handle game object reaction to events
sub event_handler
{
my $event = shift;
#if ($event->type == SDL_QUIT) { exit; }
$player->move_react(@_);
$player->fire_react(@_);
}
# Handle game object movement
sub move_handler
{
# add empty event to queue to force processing of event handlers
#SDL::Events::push_event(SDL::Event->new);
$player->move_bullets(@_);
$player->move(@_);
}
# Handle game object drawing
sub show_handler
{
$app->draw_rect([0,0,$app->w,$app->h], [0,0,0,0]);
$player->draw_bullets(@_);
$player->draw(@_);
$app->update;
}
# Finally, run the app
$app->run;
1;
#! /opt/perl/bin/perl
package Mob;
use Moose;
use SDL::Events;
use Data::Dumper;
has 'position' => (is => 'rw', builder => 'build_pos');
has 'velocity' => (is => 'rw', builder => 'build_vel');
has 'max_speed' => (is => 'rw', default => 50);
# Delta velocity, or how much we change the speed each event
has 'dv' => (is => 'rw', default => 3);
sub build_pos
{
return {x => 200, y=> 200};
}
sub build_vel
{
return {x => 2, y=> 2};
}
sub move_react
{
my $self = shift;
my $event = shift;
SDL::Events::pump_events;
my $keys = SDL::Events::get_key_state;
if ($keys->[SDLK_LEFT])
{
$self->{velocity}->{x} -= $self->{dv} unless ($self->{velo
+city}->{x} < -$self->{max_speed});
}
if ($keys->[SDLK_RIGHT])
{
$self->{velocity}->{x} += $self->{dv} unless ($self->{velo
+city}->{x} > $self->{max_speed});
}
# Slow horizontal movement if neither modifier keys are presse
+d
if (!$keys->[SDLK_LEFT] && !$keys->[SDLK_RIGHT])
{
if ($self->{velocity}->{x} > 0)
{
$self->{velocity}->{x} -= 1;
}
elsif ($self->{velocity}->{x} < 0)
{
$self->{velocity}->{x} += 1;
}
}
if ($keys->[SDLK_UP])
{
$self->{velocity}->{y} -= $self->{dv} unless ($self->{velo
+city}->{y} < -$self->{max_speed});
}
if ($keys->[SDLK_DOWN])
{
$self->{velocity}->{y} += $self->{dv} unless ($self->{velo
+city}->{y} > $self->{max_speed});
}
# Slow vertical movement if neither modifier keys are pressed
if (!$keys->[SDLK_UP] && !$keys->[SDLK_DOWN])
{
if ($self->{velocity}->{y} > 0)
{
$self->{velocity}->{y} -= 1;
}
elsif ($self->{velocity}->{y} < 0)
{
$self->{velocity}->{y} += 1;
}
}
}
sub move
{
my ($self, $step, $app, $time) = @_;
SDL::Events::pump_events;
my $keys = SDL::Events::get_key_state;
$self->{position}->{x} += $self->{velocity}->{x} * $step;
$self->{position}->{y} += $self->{velocity}->{y} * $step;
# No keys? Slow them ALL DOWN
if (!$keys->[SDLK_UP] && !$keys->[SDLK_DOWN] && !$keys->[SDLK_LEFT
+] && !$keys->[SDLK_RIGHT])
{
if ($self->{velocity}->{x} > 0)
{
$self->{velocity}->{x} -= 1;
}
elsif ($self->{velocity}->{x} < 0)
{
$self->{velocity}->{x} += 1;
}
if ($self->{velocity}->{y} > 0)
{
$self->{velocity}->{y} -= 1;
}
elsif ($self->{velocity}->{y} < 0)
{
$self->{velocity}->{y} += 1;
}
}
}
sub draw
{
my ($self, $step, $app) = @_;
$app->draw_circle_filled([$self->{position}->{x}, $self->{position
+}->{y}], 10, [250,255,250,255]);
}
1;
#! /opt/perl/bin/perl
package player;
use Moose;
use List::Util qw[min max];
use SDL::Events;
use Time::HiRes;
use Data::Dumper;
use btype;
extends 'ship';
has 'score' => (is => 'rw', isa => 'Int', default => 0);
has 'bullets' => (is => 'rw', isa => 'ArrayRef[bullet]');
has 'ammo' => (is => 'rw', isa => 'Int', default => 1000);
has 'max_ammo' => (is => 'rw', isa => 'Int', default => 1000);
sub fire_react
{
my $self = shift;
my $event = shift;
my $keys = SDL::Events::get_key_state;
if ($keys->[SDLK_SPACE] && $self->{ammo} > 0)
{
push (@{$self->{bullets}}, btype->get_shot(1, $self->{position
+}));
$self->{ammo} -= 1;
}
elsif (!$keys->[SDLK_SPACE])
{
$self->{ammo} = min($self->{ammo} + 1, $self->{max_ammo});
}
}
sub move_bullets
{
my ($self, $step, $app, $time) = @_;
my @tbullets;
my $length = 0;
foreach my $bullet (@{$self->{bullets}})
{
if ($bullet->{position}->{y} < -5 || $bullet->{position}->{y}
+> $app->h + 5)
{
push(@tbullets, $bullet);
}
elsif ($bullet->{position}->{x} < -5 || $bullet->{position}->{
+x} > $app->w + 5)
{
push(@tbullets, $bullet);
}
else
{
unshift(@tbullets, $bullet);
$length += 1;
}
}
@{$self->{bullets}} = splice(@tbullets, 0, $length);
foreach my $bullet (@{$self->{bullets}})
{
$bullet->move($step);
}
}
sub draw_bullets
{
my $self = shift;
foreach my $bullet (@{$self->{bullets}})
{
$bullet->draw(@_);
}
}
1;