αντικειμενοστρεφείς μπάρες

αντικειμενοστρεφείς μπάρες

«πιάσε με αν μπορείς»

Στο προηγούμενο άρθρο για το παιχνίδι “Κινούμενες μπάρες” των μαθητών της Ελληνογαλλικής σχολής, με κύριο θέμα τη μεταφορά του παιχνιδιού από το Scratch στην Processing, χρησιμοποιήθηκε το μοντέλο του διαδικαστικού προγραμματισμού. Στο άρθρο αυτό δίνουμε τα βασικά σημεία της υλοποίησης με αντικειμενοστρεφή σχεδίαση. Με βάση τα χαρακτηριστικά του παιχνιδιού, διακρίνουμε τουλάχιστον δύο κλάσεις: τη Μπάλα και την κινούμενη Μπάρα. Από την κλάση της κινούμενης Μπάρας δημιουργούνται τρια στιγμιότυπα-αντικείμενα στο παιχνίδι, οι τρεις κινούμενες μπάρες. Επιπλέον μία τρίτη κλάση, Παιχνίδι, χρησιμεύει για το συντονισμό των άλλων αντικειμένων και για το σχεδιασμό του σκηνικού.

Μπάρα

Η κλάση Μπάρα, όπως άλλωστε και η κλάση Μπάλα, περιέχει ως βασικά ιδιωτικά δεδομένα (πεδία υπόστασης) το πλάτος και το ύψος της, τη θέση της, το χρώμα της, τη διεύθυνση της κίνησής της και την ταχύτητά της. Ως μεθόδους, προσφέρει κυρίως τις:
  • Bar(), μέθοδος κατασκευής της,
  • update(), μέθοδος ενημέρωσής της (πεδία: συντεταγμένη ψ, διεύθυνση και σημεία 0 και 1),
  • display(), μέθοδος εμφάνισής της, και
  • collision(), μέθοδος σύγκρουσης.
Η τελευταία δέχεται σαν παράμετρο-όρισμα το αντικείμενο Μπάλα, τις συντεταγμένες του οποίου χρησιμοποιεί μαζί με τις δικές της (Μπάρα) συντεταγμένες για την κλήση της διαδικασίας ελέγχου επικάλυψης, check_overlap(). Η τελευταία είναι αυτούσια η ομώνυμη διαδικασία του προηγούμενου άρθρου. Να επισημάνουμε ότι η μέθοδος collision() καλείται από το αντικείμενο Παιχνίδι από μία φορά για κάθε αντικείμενο Μπάρα.

Μπάλα

Σημαντικές διαφοροποιήσεις για την κλάση Μπάλα είναι οι μέθοδοι:
  • atStart(), μέθοδος επαναφοράς στην αρχική θέση,
  • goRight(), μέθοδος διεύθυνσης δεξιά,
  • goLeft(), μέθοδος διεύθυνσης αριστερά, και
  • point0x,() point0y(), point1x(), point1y(), μέθοδοι επιστροφής συντεταγμένων.
Οι τελευταίες περνούν στη μέθοδο check_overlap() και χρησιμοποιούνται εκεί για τον υπολογισμό των συνθηκών επικάλυψης. Οι μέθοδοι διεύθυνσης αποκρίνονται στο πληκτρολόγιο, όταν πατηθούν τα αντίστοιχα βελάκια. Η ανίχνευση του πληκτρολογίου γίνεται όπως και στο προηγούμενο άρθρο με τη χρήση της διαδικασίας της Processing, keyPressed(). Εκεί ανιχνεύεται και η άφιξη της Μπάλας στο δεξί άκρο της σκηνής και κατ’επέκταση η λήξη του παιχνιδιού.

Παιχνίδι

Η κλάση Παιχνίδι ορίζει κυρίως βοηθητικά στοιχεία της σκηνής, όπως το φόντο, το “σπίτι” και τα όρια για την οριζόντια επιλογή θέσης για τις Μπάρες, όπως και το κλείσιμο του παιχνιδιού. Όμως λαμβάνει μέρος και στην απόφαση ύπαρξης σύγκρουσης με τη μέθοδο collisions(), η οποία επιστρέφει λογική τιμή Αληθές (True) σε περίπτωση σύγκρουσης ή αλλιώς Ψευδές (False).

draw()

Με την παραπάνω ανικειμενοστραφή σχεδίαση/δομή το κυρίως πρόγραμμα που εκτελεί η draw() αποτελείται από τις παρακάτω γραμμές κώδικα:
draw() {
 game.display();

 bar1.update();
 bar2.update();
 bar3.update();
 bar1.display();
 bar2.display();
 bar3.display();

 if ( game.collisions() ) {
   ball.atStart();
   ball.display();
 } else {
   ball.display();
 }

}
Για να δείτε τον κώδικα πιέστε το επόμενο κουμπί. Μπορείτε επίσης να δείτε το παιχνίδι με μικρές βελτιώσεις αμέσως πιο κάτω.  

show code

// ///////////////////////////
// The falling bars ii:
// From Scratch to Processing
// Object Oriented approach
// by agi, 12/06/'14
// eschoolplus.gr

// Class Variables
Game game;
Bar bar1, bar2, bar3;
Ball ball;

// Non Class Variables
PFont myFont;

void setup() {

size(480, 320);
noStroke();
frameRate(30);
rectMode( CENTER );
ellipseMode( CENTER );

myFont = createFont( "Georgia", 100 );
textFont( myFont );
textAlign( CENTER, CENTER );

game = new Game();

bar1 = new Bar();
bar2 = new Bar();
bar3 = new Bar();

ball = new Ball();

}

void draw() {

// Redraw background/home
// with every iteration
//////////////////////////
game.display();

// Draw the 3 bars
//////////////////
bar1.update();
bar2.update();
bar3.update();
bar1.display();
bar2.display();
bar3.display();

// Check for collisions
///////////////////////
if ( game.collisions()) {

// Collisions:
// Draw the ellipse at the starting position
ball.atStart();
ball.display();

} else {

// No collision:
// Draw the ellipse at the current position
ball.display();

}

}

void keyPressed() {

if( key == CODED ) {

if ( keyCode == RIGHT ) {

boolean end = ball.goRight();

if ( end ) {

// Redraw home for the ball to disappear
game.home();
// End the game
game.end();

}

} else if ( keyCode == LEFT ) {

ball.goLeft();

}

}

}

class Game {

private final int XBOUND = 80; // X boundary for bars
private int _gc1, _gc2, _gc3; // color variables

Game() {

_gc1 = (int) random( 255 );
_gc2 = (int) random( 255 );
_gc3 = (int) random( 255 );

}

void home() {

// Home: winning position
/////////////////////////
fill( this._gc1, this._gc2, this._gc3 );
rect( width-30, height-40, 60, 80 );

}

void display() {

background(102);
this.home();

}

int boundary() {

return XBOUND;

}

boolean collisions() {

if ( bar1.collision( ball )) {
return true;
}
if ( bar2.collision( ball )) {
return true;
}
if ( bar3.collision( ball )) {
return true;
}
return false;

}

void end() {

// The ellipse is beyond the right boundary
fill( 255, 191, 127 );
text( "You win!", width/2, height/2 );
noLoop();

}

}

class Ball {

// ELLIPSE: CONSTANTS
private final int EWI = 60; // Ellipse dimensions
private final int ESTARTX = 30; // Start center positions
private final int ESTARTY = 30;
private final float EXSPEED = 11.4; // Speed of the ellipse - uniform

// ELLIPSE: VARIABLES
private int _expos, _eypos;
private int _exdir; // -1: LEFT or 1: RIGHT

Ball() {

// Set the starting position of the ball:
_expos = ESTARTX;
_eypos = height - ESTARTY;

}

private void update() {

_expos = _expos + _exdir*round( EXSPEED );

}

void display() {

fill( 255, 0, 0 );
ellipse( _expos, _eypos, EWI, EWI );

}

void atStart() {

_expos = ESTARTX;
_eypos = height - ESTARTY;

}

boolean goRight() {

_exdir = 1;

if ( _expos < ( width-ESTARTX ) ) { this.update(); this.display(); return false; } else { return true; } } void goLeft() { _exdir = -1; if ( _expos > ESTARTX ) {

this.update();
this.display();

}

// No else case:
// when the ball reaches far left
// then do nothing

}

float point0x() {

return ( _expos - EWI/2 );

}

float point0y() {

return ( _eypos - EWI/2);

}

float point1x() {

return ( _expos + EWI/2 );

}

float point1y() {

return ( _eypos + EWI/2 );

}

}

class Bar {

// BARS: CONSTANTS
private final int WI = 5; // Bar dimensions: Width
private final int HI = 50; // Bar dimensions: Height
private final float YSPEED = 7.6; // Speed of the bars - uniform

// BARS: VARIABLES
private int _xpos; // Starting position of bar x
private int _ypos; // Starting position of bar y
private int _ydirection; // ydirectionX, values: -1, 1
private float _sign;
private int _c1, _c2, _c3; // color variables
private float _point0x, _point0y, _point1x, _point1y;

Bar() {

// Set the starting position of the bar inside the boundaries
_xpos = (int) random( game.boundary(), width-game.boundary() );
_ypos = (int) random( 25, height - HI/2 );

// Select a random initial direction, upwards or downwards
_ydirection = ( ( _sign = random(-1, 1) ) == abs(_sign) ) ? 1 : -1;

_c1 = (int) random( 255 );
_c2 = (int) random( 255 );
_c3 = (int) random( 255 );

this.setPoints();

}

void update() {

// Update the position of the bar based on speed and direction
_ypos = constrain((int) ( _ypos + YSPEED * _ydirection ), HI/2, height - ( HI/2 ));

// Test to see if the bar exceeds the boundaries of the screen:
if (( _ypos >= ( height - ( HI/2 ))) || ( _ypos <= ( HI/2 ))) {

// If it does, reverse its direction by multiplying by -1
_ydirection *= -1;

}

this.setPoints();

}

private void setPoints() {

_point0x = _xpos - WI/2;
_point0y = _ypos - WI/2;
_point1x = _xpos + WI/2;
_point1y = _ypos + WI/2;

}

void display() {

// Draw bar
fill( _c1, _c2, _c3 );
rect(_xpos, _ypos, WI, HI);

}

// ///////////////////////////
// check_collision:
// arguments:
// none
// returns true if overlap between ball and bars ||
// false if no overlap

boolean collision( Ball b ) {

float e1 = b.point0x();
float e2 = b.point0y();
float e3 = b.point1x();
float e4 = b.point1y();

if ( check_overlap(e1,e2,e3,e4,_point0x,_point0y,_point1x,_point1y) ) {
return true;
}

return false;

}

// ///////////////////////////
// check_overlap:
// arguments:
// shape A:
// (x0,y0) (x1,y1)
// shape B:
// (x0,y0) (x1,y1)
// returns true if overlap || false if no overlap

boolean check_overlap(float ax0, float ay0, float ax1, float ay1, float bx0, float by0, float bx1, float by1) {

float topA = min(ay0, ay1);
float botA = max(ay0, ay1);
float leftA = min(ax0, ax1);
float rightA = max(ax0, ax1);
float topB = min(by0, by1);
float botB = max(by0, by1);
float leftB = min(bx0, bx1);
float rightB = max(bx0, bx1);

if ( botA < topB || botB < topA || rightA < leftB || rightB < leftA ) {

return false;

} else {

return true;

}

}

}
Αυτό το περιεχόμενο αναρτήθηκε στο γενικά και ετικέτα , , , . Bookmark the permalink. Both comments and trackbacks are currently closed.