Tuesday, December 28, 2010

Project Jumper Part 4: Introducing the opposition

It's getting a bit lonely for helmutguy to be running around in the level all by himself. Let's make him some friends to play with. In the spirit of grabbing random things and throwing them at the project, the part of helmutguy's new "friend" will be played by Skel-Monsta. Helmutguy and Skel-Monsta will have so much fun together.

Since we're adding a new game element, it makes sense to create a new class to hold it. It's actually really fast to do in FlashDevelop. Over in the project tab on the right, just right click on the folder where you want to put the new class, and choose Add -> New Class... and you'll get a little form to fill out.








This form is pretty straightforward.  Let's call the class 'Enemy' and make its parent class FlxSprite. You'll have to hit Browse and scroll down to the org.flixel section, no biggie. Go ahead and check off the option to generate a constructor matching the base class. That'll save a few seconds extra time.

The constructor it makes includes a SimpleGraphic parameter. I don't want to use it, so I just cut it out of the declaration and the super(); function. Also, it gives default values for X and Y. I cut those out, too, which makes the compiler a little happier later. Just have to remember that I always have to pass along starting values.



In the class declaration, I'm going to just copy and paste most of the stuff from Player.as, and edit it to what I need. Obviously, the graphics and animations have to be changed, and I want to change some of the physics. While helmutguy is a helpless victim to gravity, Skel-Monsta mocks the puny pull of the earth. He shall fly elegantly through the cave. His GRAVITY constant will be 0. I should probably just cut it out entirely, but I might use this guy as a template for other kinds of enemies, so I'm going to leave in useless variables like GRAVITY and JUMP_SPEED.

Come to think of it, in a real game there will be multiple kinds of enemies, so Enemy.as should only have the most generic code that applies to all of them, with child classes for each different actual enemy. Enh, whatever. I can always change it later if I go that route.

Anyway. Just like in Player.as, we need an update() function. It's going to look a lot different, though, since the enemy is controlled totally differently than the player is. We need to think of some logic to govern Skel-Monsta's behavior. Since I just want to get him into the game and working as quickly as possible, let's go with super simple logic.
1) If the player isn't within a certain distance, don't do anything.
2) If the player is to the left, move left. If the player is to the right, move right. Same deal with up and down.

That's it. A simple chase behavior.

Both of those steps require that the enemy knows where the player is. The easiest way to do this is just pass the player into the enemy when we construct it. Does that sound dirty? I can't even tell anymore. Anyway.

protected var _player:Player;
goes in with the declarations, and our constructor looks like this:

        public function Enemy(X:Number, Y:Number, ThePlayer:Player) 
        {
            super(X, Y);
                        
            loadGraphic(Skelmonsta, true, true);  //Set up the graphics
            addAnimation("walking", [0, 1], 10, true);
            addAnimation("idle", [0]);
            _player = ThePlayer;
            
            drag.x = RUN_SPEED * 7;
            drag.y = JUMP_SPEED * 7;
            acceleration.y = GRAVITY;
            maxVelocity.x = RUN_SPEED;
            maxVelocity.y = JUMP_SPEED;    
        }

Then we need to have the update() function handle all the moving around and stuff. This is just a super simple behavior algorithm; I can already think of a better way to do it, but this will do for now.

        public override function update():void
        {
            acceleration.x = acceleration.y = 0; // Coast to 0 when not chasing the player
            
            var xdistance:Number = _player.x - x; // distance on x axis to player
            var ydistance:Number = _player.y - y; // distance on y axis to player
            var distancesquared:Number = xdistance * xdistance + ydistance * ydistance; // absolute distance to player (squared, because there's no need to spend cycles calculating the square root)
            if (distancesquared < 65000) // that's somewhere around 16 tiles
            {
                if (_player.x < x)
                {
                    facing =RIGHT; // The sprite is facing the opposite direction than flixel is expecting, so hack it into the right direction
                   acceleration.x = -drag.x;
                }
                else if (_player.x > x)
                {
                    facing = LEFT;
                    acceleration.x = drag.x;
                }
                if (_player.y < y) { acceleration.y = -drag.y; }
                else if (_player.y > y) { acceleration.y = drag.y;}
            }
            //Animation

            if (!velocity.x && !velocity.y) { play("idle"); }
            else {play("walking");}
            
            super.update();
        }


Now we have to put it into our playstate. For now, we'll just do it pretty much the same way as we added the player, remembering to have it collide with the map, too. Or not, if we prefer. Maybe Skel-Monsta can move through walls. Also, remember that when you add the enemy, you have to pass along the player. It will look like this:

    add(skelmonsta = new Enemy(1260, 640,player));// I used DAME to find the coordinates I want.

This wasn't much of an update, I know. The enemy doesn't actually DO anything when it gets to the player. That sort of interaction is a little more complicated, and I think I'll save that for its own.
Meanwhile, the source code and the flash file. (Skel-Monsta is in the far bottom right of the map. Go find him!)

1 comment: