~mathuin/haxefighting-game

37bacc1d2825dafb027a38efce19670f5f4d1069 — superpat 4 years ago 1fd515f
Huge overhaul of comments

I also rearranged the position of certain functions and variables to make reading the
files easier.
5 files changed, 235 insertions(+), 98 deletions(-)

M Source/Ai.hx
M Source/Bullet.hx
M Source/Main.hx
M Source/Plane.hx
M Source/Player.hx
M Source/Ai.hx => Source/Ai.hx +18 -1
@@ 6,42 6,56 @@ import openfl.events.TimerEvent;
import openfl.geom.Point;
import Random;

// This class represents every enemy plane in the game
class Ai extends Plane {
    // Variables
    private var destination:Point;
    static inline var gameWidth:Float = 1300;
    static inline var gameHeight:Float = 900;
    
    // Constructor (sets starting speed and chooses a destination)
    public function new(designation:String) {
        super(designation);
        destination = genDestination();
        speed = 3;
    }

    /// Methods
    // The function dictates the choices the ai must make
    public function action(player:Player, bullets:Array<Bullet>) {
        // Calculates distance from player
        var planeDistance:Float = Math.sqrt(Math.pow(player.x - this.x, 2) + Math.pow(player.y - this.y, 2));

        // When the ai is too close it attempts an escape manoeuver
        if (planeDistance < this.height * 3) {
            if (this.hitTestPoint(destination.x, destination.y)) {
                this.destination = genDestination();
            }

            // Smooth rotation
            if (this.rotation < calcRotation(destination)) {
                this.rotation += 1;
            } else {
                this.rotation -= 1;
            }

        // Attack the player
        } else if (planeDistance < this.height * 4) {
            shoot(bullets);
            setSpeed(this.speed + 1);
            this.rotation = calcRotation(new Point(player.x, player.y)) ;

        // Close in on player
        } else if (planeDistance < this.height * 5) {
            setSpeed(this.speed + 1);

            this.rotation = calcRotation(new Point(player.x, player.y));

        // Slowly start engaging the player in a dogfight
        } else if (planeDistance < this.height * 6) {
            this.rotation = calcRotation(new Point(player.x, player.y));
            this.setSpeed(this.speed - 1);

        // When player is out of sight, choose an alternate destination
        } else {
            this.setSpeed(this.speed - 1);



@@ 49,6 63,7 @@ class Ai extends Plane {
                this.destination = genDestination();
            }

            // Smooth rotation
            if (this.rotation < calcRotation(destination)) {
                this.rotation += 1;
            } else {


@@ 57,6 72,7 @@ class Ai extends Plane {
        }
    }

    // This calculates the necessary degree of rotation to get from point a to b
    private function calcRotation(point:Point):Float {
        var xDistance:Float = point.x - this.x;
        var yDistance:Float = point.y - this.y;


@@ 64,6 80,7 @@ class Ai extends Plane {
        return Math.atan2(yDistance, xDistance) * 180 / Math.PI + 90;
    }

    // Generates a new destination which the ai will strive to reach
    private function genDestination():Point {
        var x:Float = Random.float(this.width * 2, gameWidth - this.width * 2);
        var y:Float = Random.float(this.height *2, gameHeight - this.height * 2);

M Source/Bullet.hx => Source/Bullet.hx +7 -0
@@ 6,15 6,21 @@ import openfl.Assets;
import openfl.display.Bitmap;
import openfl.display.BitmapData;

// The bullet class represents every bullet shot in the game
class Bullet extends Sprite {
    // Variables
    private var model:Bitmap;
    public var speed:Float = 0;

    // Constructor
    public function new() {
        super();
        loadImg();
    }

    /// Methods

    // Loads all image assets required for the bullet and resizes them accordingly
    private function loadImg() {
        var PATH:String = "assets/";
        var load:BitmapData = Assets.getBitmapData(PATH + "bullet" + ".PNG");


@@ 25,6 31,7 @@ class Bullet extends Sprite {
        this.scaleY = 0.08;
    }

    // Changes bullet position according to degree of rotation
    public function move() {
        this.x += Math.sin (this.rotation * Math.PI / 180) * this.speed;
        this.y += Math.cos (this.rotation * Math.PI / 180) * -this.speed;

M Source/Main.hx => Source/Main.hx +140 -64
@@ 12,46 12,65 @@ import openfl.display.SimpleButton;
import openfl.events.MouseEvent;
import Random; 

// A simple 2d topdown dogfighting game where the one player plane
// must defeat a horde of drones
class Main extends Sprite {
    /// Variables
    // Game dimensions
    static inline var gameWidth:Float = 1300;
    static inline var gameHeight:Float = 900;
    // Player
    var plane:Player = new Player("F-24", 10);
    var playerBullets:Array<Bullet> = new Array<Bullet>();

    // Ai
    var ai:Array<Ai> = new Array<Ai>(); 
    var aiBullets:Array<Bullet> = new Array<Bullet>();
    static inline var aiNumber:Int = 5;
    var key:Keyboard;
    static inline var gameWidth:Float = 1300;
    static inline var gameHeight:Float = 900;

    // Game state info
    var ammoBox:TextField = new TextField();
    var gameOverPopup:Sprite = new Sprite();
    var startMenu:SimpleButton;
    var tempText = new TextField();

    // Constructor
	public function new () {
		super();

        // Load background gfx
        loadBackground("bluecloud_up.jpg");
        loadButton("go.png");

        // Add player to game stage
        plane.x = Random.float(plane.width, gameWidth - plane.width);
        plane.y = Random.float(plane.height, gameHeight - plane.height);

        addChild(plane);

        // Add enemy planes to game stage
        for (i in 0 ... aiNumber) {
            addAi();
        }

        // Init start menu
        loadButton("go.png");

        startMenu.x = gameWidth / 3;
        startMenu.y = gameHeight / 3;

        addChild(startMenu);
        startMenu.addEventListener(MouseEvent.CLICK, startGame);
	}

    // Methods

    // Gfx
    private function loadBackground(string:String) {
        var PATH:String = "assets/";
        
        var load:BitmapData = Assets.getBitmapData(PATH + string);
        var background:Bitmap = new Bitmap(load);

        // Add loaded background to stage
        background.width = gameWidth;
        background.height = gameHeight;
        addChild(background);


@@ 63,6 82,7 @@ class Main extends Sprite {
        var load:BitmapData = Assets.getBitmapData(PATH + string);
        var img:Bitmap = new Bitmap(load);

        // Create start menu with bitmap data
        startMenu = new SimpleButton(img, img, img, img);
    }



@@ 72,33 92,60 @@ class Main extends Sprite {
        var load:BitmapData = Assets.getBitmapData(PATH + string);
        var img:Bitmap = new Bitmap(load);

        // Add img to game stage
        img.x = gameWidth / 4;
        img.y = gameHeight / 2;
        addChild(img);

    }

    private function startGame(e:MouseEvent) {
        stage.addChild(ammoBox);
        stage.addEventListener(Event.ENTER_FRAME, nextFrame);
        stage.addEventListener(KeyboardEvent.KEY_DOWN, action);
        startMenu.removeEventListener(MouseEvent.CLICK, startGame);
        startMenu.visible = false;
        tempText.visible = false;
    }

    // Spawn ai planes
    private function addAi() {
        var tempAi:Ai = new Ai("F-45A");

        // Set position
        tempAi.x = Random.float(tempAi.width, gameWidth - tempAi.width);
        tempAi.y = Random.float(tempAi.height, gameHeight - tempAi.height);

        // Add to stage
        ai.push(tempAi);

        stage.addChild(ai[ai.length - 1]);
    }

    // Start the game
    private function startGame(e:MouseEvent) {
        // Show score box
        stage.addChild(ammoBox);

        // Remove start menu
        startMenu.removeEventListener(MouseEvent.CLICK, startGame);
        startMenu.visible = false;

        // Initialize movement
        stage.addEventListener(Event.ENTER_FRAME, nextFrame);
        stage.addEventListener(KeyboardEvent.KEY_DOWN, action);
    }

    // Test that checks whether object hits game borders
    private function hitTestFrame(object:Sprite):Bool {
        // Return true if impact
        if (object.x - object.width / 2 < 0) {
            return true;
        } else if (object.x + object.width / 2 > gameWidth) {
            return true;
        } else if (object.y - object.height / 2 < 0) {
            return true;
        } else if (object.y + object.height / 2 > gameHeight) {
            return true;
        }

        // Return false if no impact
        return false;
    }

    // Main game loop
    private function nextFrame(e:Event):Void {
        // Game over if all enemies are dead
        if (ai.length == 0) {
            gameOver(true);
        }


@@ 107,25 154,37 @@ class Main extends Sprite {
        playerTurn();
    }

    // Governs player related game state
    private function playerTurn() {
        // Update ui
        ammoBox.text = "Ammo : " + plane.getAmmo() + "\nHitpoints : " + plane.getHitpoints();

        // Calculate new player position and also move his bullets
        plane.move();
        movePlayerBullets();

        // Govern player game over, if he has no more hitpoints, bullets or if he hits the borders of the game
        if (hitTestFrame(plane) || plane.getHitpoints() == 0 || plane.getAmmo() == 0) {
            plane.impact();
            gameOver(false);
        }
    }

    // Captures keyboard event so player can shoot/change direction
    private function action(e:KeyboardEvent):Void {
        plane.action(e, playerBullets);
    }

    // Move player bullets and manage impact on enemy planes
    private function movePlayerBullets() {
        // Iterators
        var i:Int = 0;
        var j:Int;

        // Move player
        // If there is impact
        var impact:Bool;

        // Iterate through player bullets
        var impact:Bool;
        while (playerBullets[i] != null) {
            // Move bullets
            playerBullets[i].move();


@@ 134,45 193,86 @@ class Main extends Sprite {
            impact = false;
            j = 0;

            // Check if bullets hit stage frame
            // Check if bullet hits stage frame
            if (hitTestFrame(playerBullets[i])) {
                stage.removeChild(playerBullets[i]);
                playerBullets.remove(playerBullets[i]);
                impact = true;
            // Check if bullet hit enemy and destroy enemy

            // Check if bullet hits enemy and destroy enemy
            } else {
                // Iterate through enemies
                while (ai[j] != null) {
                    // Test for impact
                    if (playerBullets[i].hitTestObject(ai[j])) {
                        ai[j].impact();

                        // Remove bullet
                        stage.removeChild(playerBullets[i]);
                        playerBullets.remove(playerBullets[i]);

                        // Remove enemy
                        stage.removeChild(ai[j]);
                        ai.remove(ai[j]);

                        // Confirm impact
                        impact = true;

                    // Manages changing object index
                    } else {
                        j++;
                    }
                }
            } 

            // Manages changing object index
            if (!impact) {
                i++;
            }
        }
    }

    private function gameOver(win:Bool) {
        stage.removeEventListener(Event.ENTER_FRAME, nextFrame);
        stage.removeEventListener(KeyboardEvent.KEY_DOWN, action);
    //  Governs ai related game state
    private function aiTurn() {
        // Iterator
        var i:Int = 0;

        if (win) {
            loadImg("gamewin.png");
        } else {
            loadImg("gameover.png");
        // Iterate through enemies
        while (ai[i] != null) {
            // Define direction and calculate new position
            ai[i].action(plane, aiBullets);
            ai[i].move();

            // Test whether enemy hits game borders
            if (hitTestFrame(ai[i])) {
                // Remove enemy
                stage.removeChild(ai[i]);
                ai.remove(ai[i]);

            // Check if enemy impacts player
            } else if (ai[i].hitTestObject(plane)) {
                ai[i].impact();

                // Remove enemy plane
                stage.removeChild(ai[i]);
                ai.remove(ai[i]);

                // Player occurs damage
                plane.setHitpoints(plane.getHitpoints() - 2);

            // Manages changing index
            } else {
                i++;
            }
        }

        // Execute ai bullet movement
        moveAiBullets();
    }

    // Calculates new bullet position and control impacts
    private function moveAiBullets() {
        // Control variables
        var i:Int = 0;
        var impact:Bool;



@@ 191,61 291,37 @@ class Main extends Sprite {

                impact = true;

            // Check if bullet hit enemy and destroy enemy
            // Check if bullet hit player
            } else if (aiBullets[i].hitTestObject(plane)) {
                // Remove bullet
                stage.removeChild(aiBullets[i]);
                aiBullets.remove(aiBullets[i]);

                // Player is dealt damage
                plane.setHitpoints(plane.getHitpoints() - 1);
                plane.impact();

                impact = true;
            } 

            // Manage changing index
            if (!impact) {
                i++;
            }
        }
    }

    private function aiTurn() {
        var i:Int = 0;

        while (ai[i] != null) {
            ai[i].action(plane, aiBullets);
            ai[i].move();

            if (hitTestFrame(ai[i])) {
                stage.removeChild(ai[i]);
                ai.remove(ai[i]);
            } else if (ai[i].hitTestObject(plane)) {
                ai[i].impact();
                stage.removeChild(ai[i]);
                ai.remove(ai[i]);
                plane.setHitpoints(plane.getHitpoints() - 2);
            } else {
                i++;
            }
        }

        moveAiBullets();
    }

    private function action(e:KeyboardEvent):Void {
        plane.action(e, playerBullets);
    }
    // Game over screen
    private function gameOver(win:Bool) {
        // Pause game
        stage.removeEventListener(Event.ENTER_FRAME, nextFrame);
        stage.removeEventListener(KeyboardEvent.KEY_DOWN, action);

    private function hitTestFrame(object:Sprite):Bool {
        if (object.x - object.width / 2 < 0) {
            return true;
        } else if (object.x + object.width / 2 > gameWidth) {
            return true;
        } else if (object.y - object.height / 2 < 0) {
            return true;
        } else if (object.y + object.height / 2 > gameHeight) {
            return true;
        // Load game end message
        if (win) {
            loadImg("gamewin.png");
        } else {
            loadImg("gameover.png");
        }

        return false;
    }
}

M Source/Plane.hx => Source/Plane.hx +41 -17
@@ 8,72 8,96 @@ import openfl.display.Bitmap;
import openfl.display.BitmapData;
import motion.Actuate;


// This superclass represents every plane in the game
class Plane extends Sprite {
    /// Variables
    // Gfx info
    private var model:Bitmap;
    private var designation:String;

    // Timer manages the time required to reload a weapon
    private var reload:Timer = new Timer(1000);
    private var canShoot:Bool;
    private var speed:Int;

    // Constructor (Set gfx model and reload mechanism)
    public function new(designation:String) {
        super();
        this.designation = designation;
        loadImg();

        // Start reload mechanism
        canShoot = true;
        reload.addEventListener(TimerEvent.TIMER, reloadWeapon);
        reload.start();
    }

    private function reloadWeapon(e:TimerEvent) {
        canShoot = true;
    /// Properties
    // Speed must be contained between 3 and 8
    public function setSpeed(v) {
        if (v < 3) {
            this.speed = 3;
        } else if (v > 8) {
            this.speed = 8;
        } else {
            this.speed = v;
        }
    }

    public function getSpeed() {
        return this.speed;
    }


    /// Methods
    // Loads all image assets required for the plane and resizes them accordingly
    private function loadImg() {
        var PATH:String = "assets/";
        
        var load:BitmapData = Assets.getBitmapData(PATH + designation + ".PNG");

        model = new Bitmap(load);
        model.x = this.x - (model.width / 2);
        addChild(model);

        this.scaleX = 0.35;
        this.scaleY = 0.35;
    }

    // Changes plane position according to current level of rotation
    public function move() {
        this.x += Math.sin(this.rotation * Math.PI / 180) * this.speed;
        this.y += Math.cos(this.rotation * Math.PI / 180) * -this.speed;
    }

    // Allows plane to shoot
    private function reloadWeapon(e:TimerEvent) {
        canShoot = true;
    }

    // Shoot bullet to enemy
    public function shoot(bullets:Array<Bullet>) {
        // Can only shoot if weapon is reloaded
        if (canShoot) {
            var bullet:Bullet = new Bullet();

            bullet.x = this.x + bullet.width / 2;        
            bullet.y = this.y;

            // Set bullet speed
            bullet.rotation = this.rotation;
            bullet.speed = this.speed + 5;

            // Let bullet loose on game stage
            bullets.push(bullet);
            stage.addChild(bullets[bullets.length - 1]);

            // Wait for reload
            canShoot = false;
        }
    }

    public function setSpeed(v) {
        if (v < 3) {
            this.speed = 3;
        } else if (v > 8) {
            this.speed = 8;
        } else {
            this.speed = v;
        }
    }

    public function getSpeed() {
        return this.speed;
    }

    // Small animation for when plane is under fire (Blinking)
    public function impact() {
        Actuate.tween(this, 0.5, { alpha: 0.5});
        Actuate.tween(this, 0.5, { alpha: 1}, false).delay(0.5);

M Source/Player.hx => Source/Player.hx +29 -16
@@ 4,10 4,13 @@ import openfl.display.Sprite;
import openfl.events.KeyboardEvent;
import openfl.ui.Keyboard;

// The player plane represents the active player
class Player extends Plane {
    // Variables
    private var hitpoints:Int;
    private var ammo:Int;

    // Constructor that defines the planes speed, ammo and number of hitpoints
    public function new(designation:String, hitpoints:Int) {
        super(designation);
        this.hitpoints = hitpoints;


@@ 15,22 18,8 @@ class Player extends Plane {
        ammo = 20;
    }

    public function action(k:KeyboardEvent, bullets:Array<Bullet>) {
        switch (k.keyCode) {
            case Keyboard.W:
                this.setSpeed(this.speed + 1);
            case Keyboard.S:
                this.setSpeed(this.speed - 1);
            case Keyboard.D:
                this.rotation += speed / 2;
            case Keyboard.A:
                this.rotation -= speed / 2;
            case Keyboard.SPACE:
                this.shoot(bullets);
                ammo--;
        }
    }

    /// Properties
    // Hitpoints can never be lower than 0
    public function getHitpoints() {
        return this.hitpoints;
    }


@@ 43,7 32,31 @@ class Player extends Plane {
        }
    }

    // Ammo can only be changed from within the plane
    public function getAmmo():Int {
        return this.ammo;
    }

    /// Methods
    // This function modify the player state, either allowing him to change direction, speed or shoot bullets
    public function action(k:KeyboardEvent, bullets:Array<Bullet>) {
        switch (k.keyCode) {
            // Accelerate
            case Keyboard.W:
                this.setSpeed(this.speed + 1);
            // Slow down
            case Keyboard.S:
                this.setSpeed(this.speed - 1);
            // Turn right
            case Keyboard.D:
                this.rotation += speed / 2;
            // Turn left
            case Keyboard.A:
                this.rotation -= speed / 2;
            // Attack!
            case Keyboard.SPACE:
                this.shoot(bullets);
                ammo--;
        }
    }
}