fuzzix.org

Jaws was never my scene and I don't like Star Wars
Subscribe

Repliconz - dev log 2, Collisions Revisited, Sound

In part 1 we got some basic collision detection by checking for overlapping rectangles in each move call.

Using positions of Things at a given frame for collision detection presents a problem. If an object is small and fast moving, like a bullet, it will have fairly long vectors to travel and can appear to pass through objects if the delay between frames is long enough.

Collision (or miss) illustration
The arrows coming from our things in Frame 1 (roughly) represent the vector they'll be travelling along. Between frames the bullet passes through the baddie but since collision detection occurs as "snapshots" of each frame, the baddie escapes, then mocks us.

What we need is something that will calculate if two objects of given dimensions travelling along a given vector over a given time-frame will collide. As usual, CPAN provides. Collision::2D is a module which offers us this kind of continuous collision detection.

So what has changed? Well, we now keep track of the previous move to pass to Collision::2D::dynamic_collision as we also want to perform and show the move (to avoid dodgy looking collision detection where things look like they hit when they merely get close). Game::Repliconz::Thing now has a move method which stores the parameters of the move for later calculation, then applies it to x and y (performs the move).

sub move {
    my ($self) = @_;
    $self->last_x = $self->x;
    $self->last_y = $self->y;
    $self->last_dt = $self->dt;
    $self->last_v_x = $self->v_x;
    $self->last_v_y = $self->v_y;
    $self->x += $self->v_x * $self->dt;
    $self->y += $self->v_y * $self->dt;
}

The collision detection itself is now fairly simple:

sub collision {
    my ( $self, $thing ) = @_;
    return 0 if (!$self->alive || !$thing->alive);
    my @rect = map { hash2rect( {
        x => $_->last_x,
        y => $_->last_y,
        h => $_->h,
        w => $_->w,
        xv => $_->last_v_x,
        yv => $_->last_v_y,
    } ) } ( $self, $thing );
    return dynamic_collision ($rect[0], $rect[1], interval => $self->last_dt);
}

sub check_collides_with {
    my ( $self, $things ) = @_;
    my $check_distance = 50;

    for my $thing (@{$things}) {
        next if (abs($self->x - $thing->x > $check_distance) ||
                 abs($self->y - $thing->y > $check_distance));
        if ($self->collision( $thing )) {
            $thing->hit;
            $self->hit;
            return 1;
        }
    }
}

The collision function generates a pair of rectangles (rects) for use by Collision::2D. We then check if a collision occurred at any time between the last frame and the current one with dynamic_collision.

We can then use check_collides_with on our Things, pass in another set of Things and find out if there was a hit. It only performs the expensive detection if the objects in question are within a certain distance

So in our move handler callback in Game::Repliconz we simply:

$self->{hero}->check_collides_with($self->{baddies});

for my $bullet (@{$self->{hero}->{bullets}}) {
    $bullet->check_collides_with($self->{baddies});
}

Then we can filter the lists of Things (bullets, baddies, whatever) by their alive method which simply checks remaining lives - bullets have "lives" to end their path when they hit an enemy. You could make stronger bullets as a bonus by modifying this value, of course. So how does this all look?

Youtube link

Note: there was a small bug present in the code this video was captured from, so the collision might not be 100% represented here, but it's close enough.

If you watch this video, you might notice another addition - sound.

This is done pretty simply. There has been code present since the beginning to initialise the SDL audio subsystem and load up a few samples:

sub _init_audio {
    my ( $opts ) = @_;

    SDL::init(SDL_INIT_AUDIO);

    if ( SDL::Mixer::open_audio( 44100, SDL::Constants::AUDIO_S16, 2, 4096) == 0 ) {
        $opts->{audio} = 1;
    }
    else {
        $opts->{audio} = 0;
        carp "Audio disabled : " . SDL::get_error();
        return 0;
    }

    SDL::Mixer::Channels::allocate_channels(4);
    @{$opts->{samples}}{ qw/
        bonus_sweeps
        laser
        explosion
    / } = (
        SDL::Mixer::Samples::load_WAV("$opts->{working_dir}/sound/bonus_sweeps.wav"),
        SDL::Mixer::Samples::load_WAV("$opts->{working_dir}/sound/laser.wav"),
        SDL::Mixer::Samples::load_WAV("$opts->{working_dir}/sound/explosion.wav"),
    )
}

This allocates four channels, which we can use for each event type we want to make noise for, shooting, explosions, bonuses and so on. Using a single channel means sounds won't "overlap", so for lasers and such it gives a classic "pew-pew-pewww" effect rather than an echoey/overlaid one. Hope I got my onomatopoeia right there.

The sample, channel and audio subsystem status are passed into the given Thing on instantiation.

$self->{hero} = Game::Repliconz::Guy->new( {
    field_width  => $self->{w},
    field_height => $self->{h},
    shoot_noise  => $self->{samples}->{laser},
    shoot_channel => 1,
    audio => $self->{audio},
} );

...which then plays the sound when the given event occurs:

sub shoot {
    ...
    SDL::Mixer::Channels::play_channel( $self->{shoot_channel}, $self->{shoot_noise}, 0 ) if $self->{audio};
    ...
}

The samples themselves were generated by sfxr, an excellent tool to generate old school FX for games. Each sample was created by hitting the "Randomize" button until a sound I liked played, though there is plenty of scope for controlling and fine tuning the sounds yourself.

That's it for now. As always, comments, criticism and contributions welcome.

Code for Repliconz is on Github.

by fuzzix on Tue, 28 Jan 2014 18:30. Comment.

Repliconz - dev log 1, Enemies, Collision Detection

So what's changed since part 0?

Youtube linky, Repliconz gameplay so far

The green things chasing the Guy are instances of the Baddie class. Since Baddies are Things, many methods have been moved from Guy to Game::Repliconz::Thing which are common to Baddies, including move, constrain_velocity_xy and shoot. Bullets are also Things, so now technically bullets can shoot bullets, but we won't be doing that... yet. Anyone like the idea of cluster bullets?

The movement of baddies is somewhere between a bullet and the guy. A vector is calculated from current baddie position and guy position, so the baddies pursue the guy. Converting this to a unit vector using the bullet's Pythagoras square method would allow the baddies to travel in any arbitrary direction. This results in them converging rather quickly, giving the appearance of a single, super-strong enemy.

It turns out that constrain_velocity_xy, which was created to limit the guy's speed, can also be used to limit the baddies' movement to 45 degree angles. They eventually converge using this method too, but hopefully you'll have shot a good few of them by the time that happens. Anyway, now Game::Repliconz::Baddie::move is pretty simple:

sub move {
    my ( $self, $target_x, $target_y, $dt, $app ) = @_;

    my $v_y = $target_y - $self->{y};
    my $v_x = $target_x - $self->{x};

    ($v_x, $v_y) = $self->constrain_velocity_xy($v_x, $v_y);

    $self->{x} += $v_x * $self->{velocity} * $dt;
    $self->{y} += $v_y * $self->{velocity} * $dt;

    ($self->{x} > 0 || $self->{x} < ($app->w - $self->{w})) &&
    ($self->{y} > 0 || $self->{y} < ($app->h - $self->{h})) &&
    ($self->{on_screen} = 1);
}

The on_screen property will be used later in decisions about collision detection, since bullets continue off screen and enemies spawn there.

Baddies are pushed onto a queue in the main move callback, with more being spawned when the queue goes below a certain size.

Our collision detection is currently very simple, using Collision::Util to check overlapping rectangles. I need to check out how accurate this is, as the framerate / app delay will have an effect... a bullet might be rendered on either side of a target without ever "passing through" it. The video above appears to show some baddies being "hit", yet surviving.

The last change you might have noticed is the cursor is now a sprite, which is achieved simply by hiding the cursor and rendering a png file sprite at the mouse X/Y position.

As always, comments, criticism and contributions welcome.

Code for Repliconz is on Github.

by fuzzix on Sun, 26 Jan 2014 23:27. Comment.

Repliconz - dev log 0

So I decided to play around with Perl's SDL bindings. A game appears to be happening, so let's play around with this dev log idea too, documenting the process and pitfalls of making some stuff move around on screen.

It turns out you don't need to know a whole lot to make this happen, in the simplest cases at least. Let's see what we have so far:

Youtube linky

OK, so we're not going to set the world alight just yet. Anyway, we have a guy and some bullets. We are missing enemies, scoring, action, pew pew noises and any incentive to play. These come later, I hope.

Controls are somewhere between Robotron 2084 or Smash TV (both made by the same legendary game developer) and Abuse. WASD / Arrows move the guy, aim and shoot with the mouse.

Perl's SDL distribution comes with a convenient extension called SDLx::App which is built on SDLx::Controller. This makes creating game loops simple. You set a delay between iterations, handler callbacks for the things a game loop usually contains (input events, drawing and so on) and then run it. Each callback gets a delta-T value (the time that has passed since the previous iteration) so you can tie this value to your physics model and get consistent movement even if you are occasionally starved of resources or change your frame rate (delay).

The three classes of handler we need to define are event, move and show. For those of you familiar with MVC style frameworks, these handlers are (very) roughly analogous: Model ~= move, View ~= show, Controller ~= event.

It should be noted that any number of callbacks for each handler type can be added, though this code currently has just one for each.

Event processing is triggered on input, mouse movements, container changes and so on. Our callback function receives an SDL::Event instance containing the queued events which need handling. Let's take a look at the event handler in Repliconz:

sub events {
    my ( $self, $event, $app ) = @_;
    return $app->stop if $event->type == SDL_QUIT;

    if ($event->type == SDL_KEYDOWN) {
        $self->{keys}->{$event->key_sym} = 1;
    }
    if ($event->type == SDL_KEYUP) {
        $self->{keys}->{$event->key_sym} = 0;
    }

    if ($event->type == SDL_MOUSEMOTION) {
        $self->{mouse}->{x} = $event->motion_x;
        $self->{mouse}->{y} = $event->motion_y;
    }

    if ($event->type == SDL_MOUSEBUTTONDOWN && $event->button_button == SDL_BUTTON_LEFT) {
            $self->{mouse}->{firing} = 1;
    }
    if ($event->type == SDL_MOUSEBUTTONUP && $event->button_button == SDL_BUTTON_LEFT) {
            $self->{mouse}->{firing} = 0;
    }
}

Simple enough, we store the key up/down states in $self->{keys} and mouse position / button state in $self->{mouse}. So what do we now that we know what our player is doing? We move...

How does our move handler look?

sub move {
    my ( $self, $dt, $app, $t ) = @_;
    my $v_x = 0;
    my $v_y = 0;
    my $bomb = 0;

    for (grep { $self->{keys}->{$_} } keys %{$self->{keys}}) {
        when ($self->{controls}->{keyboard}->{u}) { $v_y += -1 }
        when ($self->{controls}->{keyboard}->{d}) { $v_y += 1 }
        when ($self->{controls}->{keyboard}->{l}) { $v_x += -1 }
        when ($self->{controls}->{keyboard}->{r}) { $v_x += 1 }
        when ($self->{controls}->{keyboard}->{b}) { $bomb = 1 }
    }

    $self->{hero}->move( $v_x, $v_y, $dt, $app );

    $self->{hero}->self_destruct if ($bomb);

    $self->{hero}->shoot( $dt, $self->{mouse}->{x}, $self->{mouse}->{y} ) if ($self->{mouse}->{firing});

    for my $bullet (@{$self->{hero}->{bullets}}) { $bullet->move( $dt, $app ) }
}

There's a little more going on here. First we go through the keys which were set in the event handler and add 1 (since we end up manipulating this unit vector with an actual velocity later) to X/Y velocity for each control. Simply setting them to 1/-1 means that the hero will still move when opposite controls are being pressed, so we add them to prevent this: 1 and -1 being added will cancel each other out. Ignore the $bomb stuff, that's in the "notion" stage of development.

$self->{hero} is an instance of Game::Repliconz::Guy. Let's take a look at its move method:

sub move {
    my ( $self, $v_x, $v_y, $dt, $app ) = @_;

    ($v_x, $v_y) = constrain_velocity_xy($v_x, $v_y);

    $self->{x} += $v_x * $self->{velocity} * $dt;
    $self->{y} += $v_y * $self->{velocity} * $dt;

    ($self->{x} < 0) && ($self->{x} = 0);
    ($self->{x} > ($app->w - $self->{w})) && ($self->{x} = $app->w - $self->{w});
    ($self->{y} < 0) && ($self->{y} = 0);
    ($self->{y} > ($app->h - $self->{h})) && ($self->{y} = $app->h - $self->{h});
}

The moving itself is taken care of by setting our new coords to our unit vector coords multiplied by our velocity and delta-T, the time that has passed. This is the key to getting consistent movement even if timing / framerate changes. You can check this out by reducing the delay in app setup to effectively increase framerate. You can also manipulate $dt in your callbacks to speed up or slow down the action. For example, to add a slo-mo mode you could divide $dt by 2.

The last four lines reset our position if the guy tries to move outside the bounds of the play field. What is constrain_velocity_xy up to?

sub constrain_velocity_xy {
    my ( $v_x, $v_y ) = @_;

    ($v_y > 0) && ($v_y = 1);
    ($v_y < 0) && ($v_y = -1);
    ($v_x > 0) && ($v_x = 1);
    ($v_x < 0) && ($v_x = -1);

    # Moving diagonally, moderate eventual velocity
    if ( $v_y != 0 && $v_x != 0 ) {
        $v_y *= 0.707; # sin(45 degrees)
        $v_x *= 0.707;
    }

    return ($v_x, $v_y);
}

There are a couple of things going on here. The first is we reset the bounds of our unit vector set in the move handler callback. Since there are two sets of controls and we used an 'additive' approach to setting the movement, if you press two buttons for the same direction, you end up with twice the required vector length and, eventually, twice the velocity. If any direction is set, we reset it to 1 or -1.

The other thing is, if we are moving diagonally, our vector is no longer length 1, it's the length required to go from corner to corner of a square with sides of length 1. Pythagoras tells us this is ~1.41. To correct this and set our vector length back to 1, we reduce the coords of our vector end point to ~0.7 (sine of 45°) of their original value. This diagram on Mathematics For Blondes illustrates quite effectively why this works, I think.

If you watched the video above, you saw that the guy wasn't the only thing moving. We also have bullets. Bullets are instances of Game::Repliconz::Bullet, which are pushed in and out of a queue (currently arbitrarily limited to 20 items) in our instance of Game::Repliconz::Guy when shoot is triggered by the fire button:

sub shoot {
    my ( $self, $dt, $mouse_x, $mouse_y ) = @_;
    state $total_dt = 0;
    $total_dt += $dt;
    return unless ($total_dt >= $self->{cooling_time});
    $total_dt -= $self->{cooling_time};

    push @{$self->{bullets}}, Game::Repliconz::Bullet->new( {
        guy => $self,
        target_x => $mouse_x,
        target_y => $mouse_y
    });

    shift @{$self->{bullets}} if (scalar @{$self->{bullets}} > $self->{max_bullets});
}

We track time elapsed by adding delta-T values until our gun cooling time is reached. This allows us to easily control rate-of-fire at any stage, to implement a high ROF bonus by just reducing the cooling time, for example.

We tell the bullet about the guy and mouse position, but how do bullets aim in the mouse cursor direction? Another unit vector is created on instantiation, based on the guy's position and the mouse cursor position:

sub new {
    my ( $class, $opts ) = @_;

    $opts->{x} = $opts->{guy}->{x} + ($opts->{guy}->{w} / 2);
    $opts->{y} = $opts->{guy}->{y} + ($opts->{guy}->{h} / 2);
    $opts->{w} = 4;
    $opts->{h} = 4;

    # normalise vector : guy position -> target position
    $opts->{v_y} = $opts->{target_y} - $opts->{y};
    $opts->{v_x} = $opts->{target_x} - $opts->{x};
    my $v_len = sqrt($opts->{v_y} ** 2 + $opts->{v_x} ** 2);
    $opts->{v_y} /= $v_len;
    $opts->{v_x} /= $v_len;

    $opts->{colour} = 0xFFFFFFFF;
    $opts->{velocity} = 70;

    bless $opts, $class;
}

So we set the initial x/y of our bullet to be the centre of the guy, along with width and height for drawing later. We then make use of Pythagoras' theorem again to convert the difference between the start point and mouse position into a unit vector. If you consider the X and Y axes of our coords to be the sides of a right triangle, adding their squares will give us the length of the vector guy -> cursor. We just need to divide our coords by this value to wind up with a vector of length 1.

Once you have all this information, the move method becomes very simple:

sub move {
    my ( $self, $dt, $app ) = @_;

    $self->{x} += $self->{v_x} * $self->{velocity} * $dt;
    $self->{y} += $self->{v_y} * $self->{velocity} * $dt;
}

Now that we know where everything is (or is going to be), the next step is drawing. Our show handler is pretty naive, simply blanking the screen and calling draw for each of our objects:

sub show {
    my ( $self, $dt, $app ) = @_;
    SDL::Video::fill_rect( $app, SDL::Rect->new(0, 0, $app->w, $app->h), 0 );

    $self->{hero}->draw($app);
    for my $bullet (@{$self->{hero}->{bullets}}) { $bullet->draw($app) }

    $app->update();
}

The draw method is inherited from Game::Repliconz::Thing:

sub draw {
    my ( $self, $app ) = @_;
    $app->draw_rect( [ $self->{x}, $self->{y}, $self->{w}, $self->{h} ], $self->{colour} );
}

We simply draw a rect at x, y, of size w, h of the given colour for each instance directly onto a passed SDLx::App instance.

Doesn't get simpler than that, I think. I hope you found this interesting and/or useful. Comments, criticism and contributions welcome. You can check out the code for Repliconz on Github.

The Wolfire Games Blog features a couple of cool posts on linear algebra with a focus specifically on game development:

Linear algebra for game developers ~ part 1

Linear algebra for game developers ~ part 2

by fuzzix on Fri, 24 Jan 2014 18:21. Comment.

FreeBSD, fail2ban and newsyslog

Just a short note on FreeBSD's newsyslog and fail2ban, since I couldn't find this information on the interwebtubes.

Fail2ban does not respond to HUP/USR1 (or any other) signal to notify config or log changes. This sort of thing is achieved using fail2ban-client.

Newsyslog is geared towards the sending of signals, since that's what a *nix daemon would traditionally expect. Recently patched and working in newsyslog is the R flag, which allows you to provide a path to some executable instead of a PID file. Config for fail2ban in /etc/newsyslog.conf now looks like this:

# logfilename         mode count size when  flags [/pid_file] [sig_num]
/var/log/fail2ban.log 600  5     500  $W0D5 JR    /usr/local/bin/fail2ban-logrotate.sh

Contents of /usr/local/bin/fail2ban-logrotate.sh are simply:

#!/bin/sh

/usr/local/bin/fail2ban-client set logtarget /var/log/fail2ban.log >/dev/null

Something doesn't sit right about this approach but it appears to do the business. Better ideas welcome in the comments.

OpenBSD's newsyslog appears to allow any free form command as long as it's wrapped in double quotes. Handy.

by fuzzix on Mon, 13 Jan 2014 12:45. Comment.

Dancer2::Plugin::UnicodeNormalize

After reading about chromatic's Mojolicious::Plugin::UnicodeNormalize I thought "I could have a crack at that" and created a similar thing for my own framework of choice, Dancer2. Having never written any code that might possibly be of any utility to another human being, I have never uploaded anything to CPAN. Anyway, now there's Dancer2::Plugin::UnicodeNormalize.

Though that's not as interesting as the cool things about uploading to CPAN. The first cool thing is how simple it is with Dist::Zilla which pretty much takes care of a whole load of stuff you used to have to do by hand. It has a plugin system to do neat things like Git integration (version tagging, changelog generation and such). I wish a similar thing existed for autotools (maybe it does) but the last thing that project needs is more scripts.

The second cool thing is CPAN Testers. Anyone familiar with Perl will probably know about this already, but I think it warrants mentioning at every possible opportunity... the family finds it a drag at Christmas dinner, but I will not stop. CPAN Testers is an integration framework in which a team of excellent volunteers will build and test your code on a dizzying variety of platforms. I don't have any MacOS or Windows installs here, but I know my code builds, runs and passes all tests on these platforms.

If only you could find this out before releasing the distribution... Oh, you can. -TRIAL releases are not indexed for general release on CPAN (they show up as developer releases), though they are tested by the CPAN Testers. Releasing a trial with Dist::Zilla is as simple as dzil release --trial. Give it a couple of days and you'll have access to dozens (or even hundreds) of verbose test logs. Failing test results are mailed to you. It's a bit like Travis CI and the like, but for releases. And it requires no action on your part, you just get it for free, whether you like it or not. But you'll like it.

Criticise or contribute to Dancer2::Plugin::UnicodeNormalize on Github. This post is longer than the code.

by fuzzix on Fri, 29 Nov 2013 12:44. Comment.

A Note for Spambots

Just a quick note to by-far my largest readership, the spambot community.

It seems that some (~1%) of you have been identifying yourselves as human in the comment form, which is not really convenient for me. I have no problem with you reading, but I don't believe providing links to counterfeit handbags and pills is in the best interests of my few human readers (who, for reasons I will not go into here, get priority).

In an effort to serve everybody fairly, I have added an element to the comment form, the Mystery Box! Under normal circumstances, this should be hidden to human readers (though there are warnings to those who dare to peer beyond the silken css veil (I'm talking to you, lynx users)). Spambots are free to roam and play in the Mystery Box, POSTing URLs or encouraging comment on "this issue", while humans should have the sense to leave well enough alone. Some mysteries are best left unsolved, as Nigel Tufnel says.

If any spambots have trouble grasping this (I know bots probably prefer to consume code), here is a diff:

https://github.com/jbarrett/Wjournal/commit/7d8b205

by fuzzix on Wed, 13 Nov 2013 15:38. Comment.

Connecting an old Gamepad to PC

A couple of posts ago I mentioned A Megadrive Gamepad Repair I did.

While it's a great pad for use on classic computers and consoles, it's not so usable on a PC without dedicated hardware... which we can build quite easily with an arduino and an old serial socket.

There are projects to have the arduino act as a standard USB HID device, but after much trial and more error I could not get it working reliably. The alternative I chose was to send the joypad status over plain serial (the Arduino's FTDI serial device, the serial socket mentioned elsewhere is to plug the gamepad into) and issue Xlib key down/up events based on this to the currently focused window. Simply plug the arduino in, run 'sendkeys' and away you go. Arduino and C++ code is linked at the bottom of the post.

I connected the lines of the (9 pin D-Sub) serial socket to the Arduino (Nano 3.0) like so:

+-------------+-------------+
|   Serial    |   Arduino   |
+-------------+-------------+
|      1      |     D13     |
|      2      |     A0      |
|      3      |     A1      |
|      4      |     A2      |
|      5      |     +5V     |
|      6      |     A3      |
|      7      |     A4      |
|      8      |     GND     |
|      9      |     A5      |
+-------------+-------------+

The Gamepad can now plug straight in. I used the analogue pins just to keep them out of the way while experimenting with USB.

Video of the Gamepad in action using the Gens/GS Megadrive Emulator. Yes, I know the gameplay is poor, but it wasn't the most comfortable playing angle :) :

Megadrive Gamepad via Arduino

Gist for the (single pass) code - comments and contributions welcome.

by fuzzix on Fri, 06 Sep 2013 14:29. Comment.

History of the typewriter recited by Michael Winslow

What the fucking shitballs, more youtube? No! It's vimeo!

This has been knocking around a while, a film by Ignacio Uriarte (I could be a wanker, look up a bunch of shit on the internets and pretend I know who he is but I really have no idea) which appears to be Winslow listening to a recording of a typewriter in his headphones and immediately reproducing it with his vocal talent and a range of mics and geegaws.

Michael strains, chokes, winces, stares and pops a few veins (and just the occasional eyeball) for the benefit of this film. As entertaining as this is, I can't help but wonder what he's hearing. Would including the other part of the audio give it too much of a You Bet! vibe?

History of the typewriter recited by Michael Winslow

by fuzzix on Fri, 23 Aug 2013 23:59. Comment.

Run as root

Run as root.

Just remember to change your ircname in irssi to prevent you getting kicked from less enlightened freenode channels.

by fuzzix on Fri, 23 Aug 2013 22:18. Comment.

A Megadrive Gamepad Repair

Which console gamepad is best, the PS3, Wii or XBox 360? Well, none of the above. While it's not my absolute favourite, the Sega Megadrive's original 3 button pad is great. Solid D-pad, great buttons - just the right amount of touch needed to engage the controls, comfortable fit in the hands. One of the best things about it is its "Atari" joystick compatibility, meaning it can be used on the Atari 2600, ZX Spectrum, Commodore 64 & Amiga, the Vectrex and countless others.

Amongst the towering piles of crap I own which will one day collapse and bury me or land me on one of those TV shows about tragic hoarders who need regular visits from social services, is a pair of original Megadrive controllers. These have been through a few hands at this stage and aren't in the greatest nick - for example, they have teeth marks on them. They are literally chewed up. I know the games were frustrating back then, but that's a bit much. The bad and sad part is, if we put Columbo on the case, he'd probably find they were my teeth marks.

Anyhoo, the last time I tried them, I brought them to a friend's house for use on his C64 (which is now mine... what was that about towering piles of crap?) I noticed the D-pads were unresponsive and inclined to get stuck, so made a note to open them up and see what the problem is.

It turns out the D-pad is guided in travel by some tabs which are housed in slots inside the controller - without these, the thing has a tendency to turn / get stuck. It turns out, in each of my controllers one or more of these had broken off and gone missing. I had to fashion new ones (from bits snipped from a spare CD case - perfect width). They couldn't be glued straight onto the pad as the available surface would be too thin to support them. I had to make a fixture to glue the tab onto. Since the fixture itself would impede travel of the D-pad in the opposite direction, it had to be sanded down as much as possible without breaking.

I will go to any lengths to rescue obsolete crap nobody cares about

Anyway, I was surprised by how well this worked and the fix seems pretty robust - I gave it about as much force as I would estimate it would take to break or bend one of the original tabs and it stayed in place. I tried out a few games and noticed nothing that dampened responsiveness of the control (that said, I am fairly shite at games... youtube channel of me being shite at games coming soon!)

Tune in next time when I will walk you through my 6-hour repair job of a PS3 controller I got in Argos for a fiver.

by fuzzix on Fri, 23 Aug 2013 21:48. Comment.

< Older