Saturday, February 5, 2011

Project Jumper Part 12: Odds and Ends

Things are almost starting to look like an actual game at this point. In fact, I'm almost not embarrassed to show people the product to date. From here on out, I'm going to try to keep the most current version of the game up on Kongregate, mostly just to try and give it a little more visibility. I'll still be posting the incremental versions as well, so nothing's changing there.

A few posts ago, evgeny asked about integrating flash movieclips into flixel, and about multiplayer. Unfortunately, I don't think I'm going to be covering either of those in much depth, if at all, for a couple different reasons.

Multiplayer is a great thing, but it's actually fairly tricky to pull of in Flash. Basically, it's all about how you get the players communicating with each other. The most direct way is have both players' clients talk directly to each other, like a direct IP connection or your classic LAN game or something. Unfortunately, Flash doesn't really provide the tools to make this feasible. Another way to do it is the client-server model, where each player's client dials in to a central server to get the data it needs. This actually does work, but it requires you to actually have a server, and that's a little beyond the scope of this tutorial, I think.

As for integrating movieclips, actually that doesn't seem like a terribly hard thing to do. The only thing is that I don't have the actual Flash development tools, nor am I likely to get them any time soon, nor do I really know how to create/animate vector art in general. So, really, I'm just not the person to be asking! If you want to go about it, maybe start with this code snippet on FlashGameDojo, and run from there.

If someone either has a good tutorial on either of these topics, or would like to write one, feel free to let me know so I can either the tutorial up here or provide a link or whatever.

OK, time to start tweaking things!
Collecting Coins
Looks more like a yellow blood cell to me
Running around and shooting Spikemonstas is all well and good, but let's give lizardhead something a little more interesting to do with his time. How about the old standby, piles and piles of mysterious floating coins to collect. Why coins? Because they're easy to draw.

There's really nothing special about adding the coins. They're just FlxSprites that I lay out like the monsters, except they can't move. The only big difference is that they get their own FlxU.overlap callback function to kill the coin instead of lizardhead.

Obviously, we want to be able to keep track of how many coins have been collected. We could create a new variable to store this, but it turns out we've already got one available in FlxG. Actually, we have a couple. FlxG.score is a single Number, and FlxG.scores is an Array that you can stuff full of any number of trackables.

This is actually a supremely useful concept right here. Because FlxG.score is a property of the class itself, not of any particular object that's an instance of the class, then we can access that property from any package that imports FlxG! No fussing around with passing references or anything, it's a global variable accessible to the entire program.

You know what else is a global variable we can access from anywhere? Whatever FlxState is currently active. This means, if we felt like it, we could call FlxG.state.add(something) from anywhere. This could be useful for tidying up code. Like maybe instead of all of the gibs being created and added from PlayState itself, each sprite could be in charge of creating its own gibs, and then just calling FlxG.state.add(gibs) to make them show up. Just something to think about for the future.

So, anyway, over in Coin.as we can take advantage of this nifty little feature. Easy stuff, we just increment the score counter every time a coin is collected.

override public function kill():void 
{
    super.kill();
    FlxG.play(sndCoin, 3, false, 50);
    FlxG.score++;
}
Simple enough.

Creating HUD elements
Just collecting coins doesn't mean a whole lot if we don't get any feedback about how much we've earned! Let's create a simple score display to demonstrate how to make HUD elements. Once again, I basically just cut and paste out of Mode, and adjust to fit my needs.

//HUD - score
var ssf:FlxPoint = new FlxPoint(0,0);
_score = new FlxText(0,0,FlxG.width);
_score.color = 0xFFFF00;
_score.size = 16;
_score.alignment = "center";
_score.scrollFactor = ssf;
_score.shadow = 0x131c1b;
add(_score);
Only real thing of note here is that scrollFactor variable. It's an FlxPoint, which really just means I could have just as easily done _score.scrollFactor.x=0; and _score.scrollFactor.y=0;. The point is that if you set something's scrollFactor.x and .y to 0, then it won't move no matter what else you do with the camera.

Of course, to actually display the correct score, we need to change the text in _score in update(). That's just one line.

_score.text = '$' + FlxG.score.toString();

The same basic principle applies to any sort of HUD element. Just make the FlxSprite or FlxText or whatever, slap it on late enough in the order that it's drawn on top of everything, update when needed.

Enemy Firing
OK, one last thing. Life just isn't hazardous enough for lizardhead. Let's make those lurker creatures shoot at him. I'm not going to reproduce all the code here, since it's mostly duplicating old stuff, but here's some of the differences.

private function shoot(P:Player):void
{
    var bulletX:int = x;
    var bulletY:int = y+4;
            
    if (_cooldown > GUN_DELAY)
   {
        var bullet:Bullet = _bullets.getFirstAvail as Bullet;
        if (bullet == null)
        {
            bullet= new Bullet();
            _bullets.add(bullet);
        }
            if (P.x
            {
                bulletX -= bullet.width-8; // nudge it a little to the side so it doesn't emerge from the middle
            }
            else
            {
                bulletX += width-8;
            }
            bullet.angleshoot(bulletX, bulletY, BULLET_SPEED, P);
            FlxG.play(sndShoot, 1, false,50);
            _cooldown = 0; // reset the shot clock
    }
}
Two things are different in Lurker.shoot(). First off, there's no limit to the number of badguy bullets that can be out there. If there's not a spare bullet in the FlxGroup _bullets, it makes a new one. This is basic memory management for anything that you're not sure how many you'll need. Use an old one if you can, make a new one if you must.
Also, instead of calling bullet.shoot, it's bullet.angleshoot. This is a new function I whipped up so that the lurkers can aim directly at lizardhead. Unfair, I know. Here's what Bullet.angleshoot looks like:

public function angleshoot(X:int, Y:int, Speed:int, Target:FlxPoint):void
{
    super.reset(X, Y);
    solid = true;
    var dangle:Number = FlxU.getAngle(Target.x-(x+(width/2)), Target.y-(y+(height/2)));  //This gives angle in degrees
    var rangle:Number = dangle * (Math.PI / 180);          //We need it in radians
    velocity.x = Math.cos(rangle) * Speed;
    velocity.y = Math.sin(rangle) * Speed;
}
There's some math in here, I'm afraid. First, we need to know which direction to shoot. FlxU.getAngle will give the angle of any point with respect to the origin, but we actually want the angle with respect to the bullet. That means we need to subtract out the target's location, offset to aim for the center of mass.
From there, we have to convert the angle from degrees to radians, and then apply basic trigonometry to set the velocity.x and velocity.y components based on direction and total speed.

One final thought: The more work I put into Jumper, the more I realize how painful it is working without organization... but on the other hand, I'm glad I'm doing it this way. Even if the code ends up being an unworkable mishmash of styles and techniques, I'll actually know for next time what does and doesn't work. For example, whenever I think of something new to add to Jumper, I just bolt it on wherever it will fit. A better planned game would have all its elements organized, cleanly grouped up.

I still need to work on commenting my code better, though.

Source code here, flash file here.

No comments:

Post a Comment