Saturday, March 12, 2011

JavaFX game: Wolfs attack

I this article I develop a JavaFX game in Netbeans. You can see result of me game:

I wont deploy game for mobile phone. I try on the game in mobile emulator in Netbeans on Windows. It run fine. In Linux we have not mobile emulator yet (28.11.2009).
The source code of this game as Netbeans project are here.
Thanks for your comment under article.

Graphics

For create graphics I use Inkscape (0.47).You can look find more about JavaFX and Inkscape for example in Silveira Neto. But Inkscape 0.47 was release yet and You can not compile source code of Inkscape.

At the end I export images wolfs and sheep from svg format to png. As splash image I let svg format export in Inkscape to JavaFX class, but I was supprised that not more difficult svg image have a lots rows in JavaFX class. About 20 tausend. That big class is not work well in Netbeans editor.
Source graphics.svg file.

JavaFX class

I mean that in me game is this good ideas:

  • Collision detection from Silveira Neto.

  • Switch between splash and game screen use in Brick JavaFX game.
Let me introduce the code.

Config.fx

All game setting is in Config.fx file. You can see setting as scene width and height, some images path, animation time, game speed and etc.
public def SCENE_WIDTH = 240;
public def SCENE_HEIGHT = 320;
public def SCENE_INFOPANEL_HEIGHT = 25;
public def MARGIN = 5;

public def WOLF_IMAGE_WIDTH = 100;
public def WOLF_IMAGE_HEIGHT = 50;
public def SHEEP_IMAGE_WIDTH = 50;
public def SHEEP_IMAGE_HEIGHT = 50;

public def ANIMATION_TIME = 0.04s;

public def COUNT_OF_LIVE = 3;

public def SHEEP_IMAGES = [ Image {url: "{__DIR__}sheep_waiting.png"},
Image {url: "{__DIR__}sheep_lu.png"  },   // left up
Image {url: "{__DIR__}sheep_ld.png"  },   // left down
Image {url: "{__DIR__}sheep_ru.png"  },   // right up
Image {url: "{__DIR__}sheep_rd.png"  }    // right down
];

public def WOLF_IMAGES = [
Image {url: "{__DIR__}wolf_jump1_left.png"  },
Image {url: "{__DIR__}wolf_jump2_left.png"  },
Image {url: "{__DIR__}wolf_jump3_left.png"  },
Image {url: "{__DIR__}wolf_jump1_right.png"  },
Image {url: "{__DIR__}wolf_jump2_right.png"  },
Image {url: "{__DIR__}wolf_jump3_right.png"  }
];

//--------------------------------WOLFS
public def WAITING_DIRECTION = 0;
public def LEFT_UP_DIRECTION = 7;
public def LEFT_DOWN_DIRECTION = 1;
public def RIGHT_UP_DIRECTION = 9;
public def RIGHT_DOWN_DIRECTION = 3;

public def WOLFS_AGGRESSIVENESS = 100;
public def WOLF_SPEED = 1000;

Wolf.fx

Class Wolf has two timelines. First for jump wolf to sheep in timeline attack.
In this timeline I define path where will bee wolf jump. You can set the direction of path when the instance of class Wolf create. The second timeline move I set image cyklus for sprite.
Wolf has function start and stop. This function starting and stopping mentioned timelines.
Wolf has important function wantAttack():Boolean in which it decide when wolf start attack. This function use Random.
public class Wolf extends CustomNode{ 

    public var image:Image;
    public-init var attackPath:Path;
    public var direction:Integer;

    var aggressiveness = Config.WOLFS_AGGRESSIVENESS;
    var random : Random = new Random();
    var frame = 0;
   
    public function start() {       
        move.play();
        attack.playFromStart();
    }

    public function stop() {
        move.stop();
        attack.stop();
    }
   
    protected override function create(): Node {
        return Group {
          content: [
                ImageView{
                          image: bind image;
                          }
          ]
        } // Group
    }

    public function wantAttack():Boolean{
        if ( (not attack.running) and random.nextInt( aggressiveness ) == 0){        
            return true; //==========>
        }
        return false;
    }

    public def attack = PathTransition {
                   node: this
                   path: bind AnimationPath.createFromPath(attackPath)
                   orientation: OrientationType.NONE;
                   interpolator: Interpolator.EASEIN
                   duration: Duration.valueOf( random.nextInt( Config.WOLF_SPEED) + 1000 );
                   repeatCount: 1
                   autoReverse: true              
                   action: function() {                       
                       Main.mainFrame.lifeCount--;                     
                    }
    };



    def move = Timeline {
        repeatCount: Timeline.INDEFINITE
        keyFrames: KeyFrame {
            time : 1s/8
            action: function() {

                    if(direction.equals(Config.LEFT_UP_DIRECTION) or direction.equals(Config.LEFT_DOWN_DIRECTION)){
                        image = Config.WOLF_IMAGES[ (++frame mod 3) ] ;
                    }else{
                        image = Config.WOLF_IMAGES[(++frame mod 3) + 3] ; 
                    }
            }
        }
    }
}

Sheep.fx

Class Sheep have only functions for image change. The variable direction keep info about direction where is sheep turn.
public class Sheep extends CustomNode{

public-read var image:Image;
public-read var direction:Integer;

public function waiting():Void{
    direction = Config.WAITING_DIRECTION;
    image = Config.SHEEP_IMAGES[0];
}

public function leftUp():Void{
    direction = Config.LEFT_UP_DIRECTION;
    image = Config.SHEEP_IMAGES[1];
}

public function leftDown():Void{
    direction = Config.LEFT_DOWN_DIRECTION;
    image = Config.SHEEP_IMAGES[2];
}

public function rightUp():Void{
    direction = Config.RIGHT_UP_DIRECTION;
    image = Config.SHEEP_IMAGES[3];
}

public function rightDown():Void{
    direction = Config.RIGHT_DOWN_DIRECTION;
    image = Config.SHEEP_IMAGES[4];
}


protected override function create(): Node {
          Group {
                content: [
                      ImageView {                               
                                image:bind image;
                        }
                ]
       } // Group
  }
}

Splash.fx

Class content splash image. It contents function start() and stop() where You can add some timelines as in Brick game. I use svg image exported from Inkscape as I mentioned above.
public class Splash extends CustomNode{ 

   def intro = Intro{};

    def background = Group{
            focusTraversable: true;
            content:[
                    Rectangle {
                            x:0;
                            y:0;
                            width:Config.SCENE_WIDTH;
                            height: Config.SCENE_HEIGHT;
                            fill: Color.web("#00bfff");
                     },                  
                     intro
                    ]
            onKeyPressed: function( e: KeyEvent ):Void {
                Main.mainFrame.startGame(Config.COUNT_OF_LIVE);
            }
   }


  public function start(){
    background.requestFocus();
  }

  public function stop(){

  }
 
  override public function create(): Node {
        Group {
            content: [
                background
            ]
        };
    }
}

Level.fx

This is class for game logic. There is class Sheep and Wolfs construct. Every Wolf has different attackPath and direction. There are function start() and stop() for play main timelines: gameLogics. In this timeline is function checkWolfsAttack(wolfs) who check Wolfs will attack. There is also collision detection as I mentioned above.
public class Level extends CustomNode{

   var state = 0;
     
   def textGameOver = Text{
                        translateX:Config.SCENE_WIDTH /4;
                        translateY:Config.SCENE_HEIGHT/4;
                        font: Font.font("SansSerif",FontWeight.BOLD,20)
                        content: "GAME OVER.";
                        visible:false;
                        }

   def infoPanel = InfoPanel{};

   var hill = Arc {
                   centerX: Config.SCENE_WIDTH/2;
                   centerY: Config.SCENE_HEIGHT;
                   radiusX: Config.SCENE_WIDTH/3;
                   radiusY: Config.SCENE_HEIGHT/3;
                   startAngle: 0;
                   length: 180;
                   type: ArcType.OPEN
                   fill: Color.GREEN
           }

   var sheep = Sheep{
                translateX:Config.SCENE_WIDTH /2 - Config.SHEEP_IMAGE_WIDTH /2;
                translateY:(Config.SCENE_HEIGHT - Config.SCENE_HEIGHT/3)  - Config.SHEEP_IMAGE_HEIGHT;
            };
    var wolf1 = Wolf{
                    direction:Config.LEFT_DOWN_DIRECTION;
                    translateX:- Config.WOLF_IMAGE_WIDTH; //outside
                    attackPath:Path{
                        elements: [
                              MoveTo { x:-Config.WOLF_IMAGE_WIDTH  y: (Config.SCENE_HEIGHT - Config.SCENE_INFOPANEL_HEIGHT) - Config.WOLF_IMAGE_HEIGHT/2 },
                              QuadCurveTo { x: Config.SCENE_WIDTH /2;
                                            y: (Config.SCENE_HEIGHT - Config.SCENE_HEIGHT/3)- Config.WOLF_IMAGE_HEIGHT/2;
                                            controlX: Config.SCENE_WIDTH /6;
                                            controlY: (Config.SCENE_HEIGHT - Config.SCENE_HEIGHT/3)- Config.WOLF_IMAGE_HEIGHT/2;
                                           }
                                 ]
                            }
                    };
    var wolf2 = Wolf{
                    direction:Config.LEFT_UP_DIRECTION;
                    translateX:- Config.WOLF_IMAGE_WIDTH; //outside
                    attackPath:Path{
                        elements: [
                              MoveTo { x:-Config.WOLF_IMAGE_WIDTH    y: Config.WOLF_IMAGE_HEIGHT/2 },
                              QuadCurveTo { x: Config.SCENE_WIDTH /2;
                                            y: (Config.SCENE_HEIGHT - Config.SCENE_HEIGHT/3)- Config.WOLF_IMAGE_HEIGHT/2;
                                            controlX: Config.SCENE_WIDTH/2;
                                            controlY: Config.WOLF_IMAGE_HEIGHT;
                                           }
                                 ]
                            }
                    };
    var wolf3 = Wolf{
                    direction:Config.RIGHT_UP_DIRECTION;
                    translateX:- Config.WOLF_IMAGE_WIDTH; //outside
                    attackPath:Path{
                        elements: [
                              MoveTo { x: Config.SCENE_WIDTH + Config.WOLF_IMAGE_WIDTH  y: Config.WOLF_IMAGE_HEIGHT/2 },
                              QuadCurveTo { x: Config.SCENE_WIDTH /2;
                                            y: (Config.SCENE_HEIGHT - Config.SCENE_HEIGHT/3)- Config.SHEEP_IMAGE_HEIGHT/2;
                                            controlX: Config.SCENE_WIDTH/2;
                                            controlY: Config.WOLF_IMAGE_HEIGHT;
                                           }
                                 ]
                            }
                    };
    var wolf4 = Wolf{
                    direction:Config.RIGHT_DOWN_DIRECTION;
                    translateX:- Config.WOLF_IMAGE_WIDTH; //outside
                    attackPath:Path{
                        elements: [
                              MoveTo { x: Config.SCENE_WIDTH + Config.WOLF_IMAGE_WIDTH  y: (Config.SCENE_HEIGHT - Config.SCENE_INFOPANEL_HEIGHT) - Config.WOLF_IMAGE_HEIGHT/2 },
                              QuadCurveTo { x: Config.SCENE_WIDTH /2;
                                            y: (Config.SCENE_HEIGHT - Config.SCENE_HEIGHT/3)- Config.WOLF_IMAGE_HEIGHT/2;
                                            controlX: Config.SCENE_WIDTH  - Config.SCENE_WIDTH /6;
                                            controlY: (Config.SCENE_HEIGHT - Config.SCENE_HEIGHT/3) - Config.WOLF_IMAGE_HEIGHT/2;
                                           }
                                 ]
                            }
                    };
    var wolfs: Wolf[] = [wolf1,wolf2,wolf3,wolf4];
    var group: Group;

    public function start() {      
        gameLogics.play();
        group.content[0].requestFocus();
        state = 2;
    }

    public function stop() {       
        gameLogics.stop();
    }
      
    def gameLogics = Timeline {
                            repeatCount: Timeline.INDEFINITE
                            keyFrames: KeyFrame {
                                                time : Config.ANIMATION_TIME;
                                                action: function() {                                                       

                                                            if (state != 2) {
                                                               return; //==========>
                                                            }
                                                            
                                                            if (Main.mainFrame.lifeCount == 0){
                                                                gameOver();
                                                            }

                                                            checkWolfsAttack(wolfs);
                                                        }
                                                }
                            };
  
   override public function create(): Node {
        group = Group {
            content: [
                Rectangle{
                        focusTraversable: true
                         x:0;
                         y:0;
                         width:Config.SCENE_WIDTH;
                         height:Config.SCENE_HEIGHT;
                         fill: Color.web("#00bfff");
                                     
                    onKeyPressed: function( e: KeyEvent ):Void {
                      
                        //skip to begin
                        if (state == 3 and e.code == KeyCode.VK_5){
                            Main.mainFrame.state = 0;
                        }
                      
                        if (e.code == KeyCode.VK_7) {
                                sheep.leftUp();
                                                              
                        } else if (e.code == KeyCode.VK_1) {
                                sheep.leftDown();
                                                               
                        }else if (e.code == KeyCode.VK_9) {
                                sheep.rightUp();
                                                               
                        }else if (e.code == KeyCode.VK_3) {
                                sheep.rightDown();
                        }

                        checkCollision(wolfs);
                    }

                    onKeyReleased: function( e: KeyEvent ):Void {                              
                                sheep.waiting();

                    }
                },

                 hill, sheep, wolf1, wolf2, wolf3, wolf4,  infoPanel, textGameOver

                    ]
        }
     }
     
    function checkCollision(wolfs:Wolf[]):Void{ 
             for(wolf in wolfs){
                if(wolf.attack.running and sheep.direction.equals(wolf.direction) and isCollision(wolf)){   
                        hit(wolf);
                }
             }      
     }
   
     function isCollision(wolf:Wolf): Boolean {
        return (collision(wolf.translateX, wolf.translateY, wolf.translateX + wolf.image.width, wolf.translateY + wolf.image.height, sheep.translateX, sheep.translateY, sheep.translateX + sheep.image.width, sheep.translateY + sheep.image.height));
     }

     function collision(ax, ay, bx, by, cx, cy, dx, dy): Boolean {
        return not ((ax > dx)or(bx < cx)or(ay > dy)or(by < cy));
     }

     function hit(wolf:Wolf):Void{
        wolf.stop();
        wolf.translateX = - Config.WOLF_IMAGE_WIDTH;
        Main.mainFrame.score++;
     }

     function checkWolfsAttack(wolfs:Wolf[]){
        for(wolf in wolfs){                
            if (wolf.wantAttack()){                
                wolf.start();
            }
        }
     }

     function gameOver(){             
        textGameOver.visible = true;
        sheep.visible = false;
        for (wolf: Wolf in wolfs){
            wolf.visible = false;
        }
        state = 3;
     }         
}

Main.fx

You can see switch between splash and level
public var mainFrame: MainFrame;

function run(__ARGS__ : String[]) {

    mainFrame = MainFrame {
        title: "Wolfs attaks"
        resizable: false
        scene: Scene {
            width:Config.SCENE_WIDTH;
            height:Config.SCENE_HEIGHT;
        }
    }
}

public class MainFrame extends Stage {

    // Instance of splash (if exists)
    var splash: Splash;

    // Instance of level (if exists)
    var level: Level;

    // Number of lifes
    public var lifeCount: Integer;

    // Current score
    public var score: Integer;

    // Initializes game (lifes, scores etc)
    public function startGame(lifeCount:Integer) { 
        this.lifeCount = lifeCount;
        score = 0;
        state = 1;
    }

    // Current state of the game. The next values are available
    // 0 - Splash
    // 1 - Level
    public var state: Integer = 0 on replace {

        if (state < 1 ) {
            splash.stop();
            level.stop();

            level = null;
            splash = Splash {};


            scene.content = [
                splash
            ];

            splash.start();

        } else {
            level.stop();
            splash = null;
            level = Level {}

            scene.content = [
                level
            ];

            level.start();
        }
    };
}

Conclusion

Game run fain in mobile emulator in Netbeans. On real device we must wait yet. I look forward my new mobile phone will be support JavaFX profile.

No comments:

Post a Comment