Videogames Laboratory

O συναρπαστικός κόσμος της ανάπτυξης βιντεοπαιχνιδιών

Δημιουργώντας το Arkanoid μέρος 1ο

Posted by Kostas Anagnostou στο 14 Σεπτεμβρίου, 2009

Ήρθε η ώρα να ξεσκονίσουμε τα πληκτρολόγια μας και να αρχίσουμε τη δεύτερη σειρά tutorial ανάπτυξης βιντεοπαιχνιδιού. Αυτή τη φορά θα ασχοληθούμε με το πολύ γνωστό Arkanoid (Taito, 1986). Στο παιχνίδι αυτό ο παίκτης χειρίζεται μια ρακέτα και προσπαθεί να οδηγήσει μια μπάλα έτσι ώστε να καταστρέψει σειρές από «τουβλάκια» στο πάνω μέρος της οθόνης.

Tο γεγονός ότι το Arkanoid έχει πολλές ομοιότητες με το Pong, οποίο αναπτύξαμε σε προηγούμενη σειρά tutotials (μέρος 1, μέρος 2, μέρος 3, μέρος 4, μέρος 5), αποτελεί απόδειξη του ότι ο σχεδιασμός παιχνιδιών είναι εξελικτική διαδικασία δηλαδή ότι οι ιδέες για νέα παιχνίδια δεν εμφανίζονται ξαφνικά αλλά αποτελούν εξέλιξη παλαιότερων ιδεών (όπως ισχύει και για τις ταινίες και τα βιβλία άλλωστε).

Ήδη γνωρίζουμε πώς να χειριστούμε τη ρακέτα, να αποκρούσουμε τη μπάλα, να περιορίσουμε τη κίνηση της μπάλας στο κλειστό πλαίσιο του παιχνιδιού και να κρατήσουμε το σκορ του παίκτη. Η διαφορά με το προηγούμενο παιχνίδι που αναπτύξαμε είναι ότι τώρα πρέπει να προσθέσουμε τα τουβλάκια και τη λογική «εξαφάνισης» τους όταν αυτά συγκρούονται με τη μπάλα.

Στο Pong είχαμε ουσιαστικά τρία αντικείμενα στο παιχνίδι, τη μπάλα και τις 2 ρακέτες. Για το λόγο αυτό είχαμε την άνεση να αποθηκεύσουμε τα δεδομένα που τα περιγράφουν (θέση, κατεύθυνση, χρώμα, μέγεθος κλπ) σε απλές μεταβλητές, και αυτό κάναμε. Στο Arkanoid όμως ο αριθμός των αντικειμένων του παιχνιδιού είναι μεγάλος, εκτός της ρακέτας και της μπάλας έχουμε και τις σειρές με τα τουβλάκια. Συνεπώς για το παιχνίδι αυτό θα προσθέσουμε μια κλάση (class) η οποία θα περιγράφει το αντικείμενο «τουβλάκι». Σημείωση: θα μπορούσαμε να κάνουμε το ίδιο και για τη μπάλα και τη ρακέτα, αλλά δεν θα προσέφερε τίποτα ιδιαίτερο μιας και έχουμε μόνο ένα αντικείμενο από κάθε κατηγορία.

Τρέχουμε τη Visual C# και δημιουργούμε ένα νέο project με το όνομα Arkanoid (δείτε εδώ αν δεν θυμάστε πως). Αφότου το project έχει δημιουργηθεί, στο Solution Explorer, κάνουμε δεξί κλικ πάνω στο όνομα του και από το μενού επιλέγουμε Add/New Item…


Από το παράθυρο που θα εμφανιστεί επιλέγουμε το template Class και το ονομάζουμε Brick.


To project τώρα θα περιέχει ένα νέο αρχείο με το όνομα Brick.cs το οποίο περιέχει μια κλάση με το όνομα Brick, η οποία είναι κενή.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Arcanoid
{
    class Brick
    {
    }
}

Ας σκεφτούμε λίγο τα χαρακτηριστικά ενός brick (τουβλάκι) στο παιχνίδι. Έχει θέση και μέγεθος, χρώμα και υφή. Επιπλέον ανάλογα με το αν έχει χτυπηθεί ή όχι, μπορεί να είναι ορατό ή αόρατο. Η θέση και το μέγεθος μπορούν να περιγραφούν με μια μεταβλητή τύπου Rectangle, όπως κάναμε και στο Pong. To χρωμα είναι μια μεταβλητή τύπου Color και η υφή μια μεταβλητή τύπου Texture. Τέλος μια μεταβλητή bool αρκεί για να περιγράψει αν το brick είναι ορατό ή αόρατο. Προσθέτουμε λοιπόν στη κλάση Βrick τις μεταβλητές αυτές:

namespace Arcanoid
{
    class Brick
    {
        Rectangle rectangle;
        Texture2D texture;
        Color color;
        bool visible;
    }
}

Για να θέσουμε αρχικές τιμές στις μεταβλητές αυτές μπορούμε να χρησιμοποιήσουμε μια constructor. O constructor είναι μια μέθοδος της κλάσης, με το ίδιο όνομα όπως η κλάση, η οποία καλείται κάθε φορά που δημιουργούμε ένα αντικείμενο της κλάσης με το τελεστή new. Χρησιμοποιείται κυρίως για αρχικοποιήσεις μεταβλητών.

    class Brick
    {
        Rectangle rectangle;
        Texture2D texture;
        Color color;
        bool visible;

        public Brick(Rectangle rect, Color col, Texture2D tex)
        {
            rectangle = rect;
            color = col;
            texture = text;
            visible = true;
        }
    }

Ο constructor μιας κλάσης είναι πάντα public και δεν επιστρέφει τιμή. Ως ορίσματα, περνάμε το Rectangle που καθορίζει τη θέση και μέγεθος του brick, το χρώμα του και την υφή του. Η μεταβλητή visible είναι true εξορισμού μιας και το τουβλάκι είναι ορατό μέχρι να χτυπηθεί από την μπάλα.

Αφότου έχουμε ορίσει τις μεταβλητές της κλάσης, πρέπει να σκεφτούμε και τις μεθόδους που χρειάζονται οι οποίες ορίζουν την λειτουργικότητα του αντικειμένου. Θέλουμε το κάθε brick να μπορεί να ζωγραφίζει τον εαυτό του στην οθόνη (Draw), και θέλουμε με κάποιο τρόπο να διαπιστώσουμε αν έχει χτυπηθεί από την μπάλα (CheckHit).

    class Brick
    {
        Rectangle rectangle;
        Texture2D texture;
        Color color;
        bool visible;

        public Brick(Rectangle rectangle, Color color, Texture2D texture)
        {
            this.rectangle = rectangle;
            this.color = color;
            this.texture = texture;
            visible = true;
        }

        public void Draw(SpriteBatch spriteBatch)
        {
            if (visible)
            {
                spriteBatch.Draw(texture, rectangle, color);
            }
        }

        public bool CheckHit(Rectangle ball)
        {
            if (visible && intersects(ball))
            {
                visible = false;
                return true;
            }
            return false;
        }
    }

H λειτουργία της μεθόδου Draw είναι προφανής, δέχεται ως όρισμα ένα spriteBatch (το οποίο είναι το αντικείμενο που χρησιμοποιούμε για να απεικονίσουμε sprites στην οθόνη όπως έχουμε αναφέρει), και αν το τρέχον brick είναι ορατό το απεικονίζει (spriteBatch.Draw )στην οθόνη με την συγκεκριμένη υφή και χρώμα.

Η μέθοδος CheckHit πραγματοποιεί περισσότερες λειτουργίες. Καταρχάς δέχεται ως όρισμα το Rectagle της μπάλας, το οποιο περιέχει τη θέση και το μέγεθος της. Στην συνέχεια, και αν το τρέχον brick είναι ορατό, ελέγχει αν το Rectagle της μπάλας τέμνει το Rectagle του brick. Αν συμβαίνει αυτό, τότε κάνει το τουβλάκι αόρατο (visible = false;) και επιστρέφει true για να ειδοποιήσει το κυρίως παιχνίδι ότι υπηρξε σύγκρουση. Σε διαφορετική περίπτωση επιστρέφει false.

H κλάση Brick είναι σχεδόν έτοιμη για χρήση, το μόνο που μας λείπει είναι η μέθοδος intersects() που υπολογίζει αν υπήρξε σύγκρουση μεταξύ του τούβλου και τη μπάλας.


Θυμίζω ότι ο τύπος Rectagle του XNA μας παρέχει τις μεταβλητές Left, Top, Right, Bottom που αποθηκεύουν στις Χ και Y συντεταγμένες κάθε γωνίας του.

Κοιτώντας το παραπάνω σχήμα είναι εύκολο να εξάγουμε τη λογική με την οποία διαπιστώνουμε αν υπάρχει επικάλυψη μεταξύ 2 Rectangles. Αν για παράδειγμα η Top συντεταγμένη του κόκκινου ορθογωνίου είναι μεγαλύτερη από τη Bottom συντεταγμένη του κίτρινου, δεν υπάρχει περίπτωση τα 2 αυτά ορθογώνια να επικαλύπτονται γιατί το κόκκινο βρίσκεται κάτω του κίτρινου. Ανάλογα ισχύει για τις περιπτώσεις πάνω, αριστερά και δεξιά.

Σε κώδικα αυτό υλοποιείται ως εξής:

    class Brick
    {
        Rectangle rectangle;
        Texture2D texture;
        Color color;
        bool visible;

        public Brick(Rectangle rectangle, Color color, Texture2D texture)
        {
            ……
        }

        public void Draw(SpriteBatch spriteBatch)
        {
            ……
        }

        public bool CheckHit(Rectangle ball)
        {
            ……
        }

        private bool intersects(Rectangle ball)
        {
            if (rectangle.Right < ball.Left ||
                rectangle.Left > ball.Right ||
                rectangle.Top > ball.Bottom ||
                rectangle.Bottom < ball.Top)
            {
                return false;
            }

            return true;

        }
    }
&#91;/sourcecode&#93;

<span style="font-family:Verdana;font-size:9pt;">H μέθοδος intersect δέχεται ως όρισμα το rectangle της μπάλας και επιστρέφει true αν υπάρχει σύγκρουση (επικάλυψη) και false διαφορετικά. Το if-statement που χρησιμοποιούμε είναι σύνθετο και ελέγχει πολλές συνθήκες με το τελεστή || (OR). Το if-statement θα είναι αληθές αρκεί μια από τις 4 συνθήκες να είναι αληθής. Στη περίπτωση αυτή δεν υπάρχει επικάλυψη μεταξύ του brick και της μπάλας και η μέθοδος επιστρέφει false.
</span>

<span style="font-family:Verdana;font-size:9pt;">Σημείωση: Για προλάβω τα σχόλια ατόμων που γνωρίζουν κάτι παραπάνω για το XNA, ο τύπος Rectangle υλοποιεί την δική του μέθοδο Intersects που κάνει τη δουλειά που κάναμε παραπάνω αυτόματα. Αν όμως χρησιμοποιείς από την αρχή το κομπιουτεράκι, δεν θα μάθεις ποτέ πρόσθεση!
</span>

<span style="font-family:Verdana;font-size:9pt;">Ολοκληρώσαμε έτσι τη κλάση Brick και είμαστε έτοιμοι να την χρησιμοποιήσουμε στο παιχνίδι. Επιστρέφουμε στη κύρια κλάση του παιχνιδιού στο αρχείο Game1.cs.
</span>

<span style="font-family:Verdana;font-size:9pt;">Τα αντικείμενα του παιχνιδιού μας είναι η ρακέτα και η μπάλα και πολλά τουβλάκια. Η δημιουργία της ρακέτας και τη μπάλας είναι εύκολη και δεν διαφέρει από αυτή του Pong. Ορίζουμε από ένα Rectangle για τη θέση και το μέγεθος (paddle, ball), 2 διανύσματα για την κατεύθυνση (paddleDirection, ballDirection) και 2 float μεταβλητές και την ταχύτητα τους (paddleSpeed, ballSpeed).
</span>


public class Game1 : Microsoft.Xna.Framework.Game
{
	GraphicsDeviceManager graphics;
	SpriteBatch spriteBatch;
	Texture2D whiteTile;
	Rectangle paddle;
	Vector2 paddleDirection;
	float paddleSpeed;
	Rectangle ball;
	Vector2 ballDirection;
	float ballSpeed;

Τις μεταβλητές spriteBatch και whiteTile, τις γνωρίζουμε και από το προηγούμενο tutorial, το spriteBatch είναι το αντικείμενο που θα χρησιμοποιήσουμε για να απεικονίσουμε τα αντικείμενα στην οθόνη και το whiteTile αντιπροσωπεύει μια υφή.

Για να τοποθετήσουμε τα τουβλάκια (bricks) στην πίστα χρειαζόμαστε μερικές επιπλέον μεταβλητές. Για να γίνει πιο κατανοητή η ύπαρξη τους κρατήστε στο μυαλό σας την παρακάτω εικόνα.


Χρειαζόμαστε δυο μεταβλητές brickWidth, brickHeight, με το πλάτος και το ύψος κάθε brick, μια μεταβλητή numOfRows με τον αριθμό των γραμμών από τουβλάκια, τον αριθμό από τουβλάκια σε κάθε γραμμή bricksPerRow, το κενό διάστημα μεταξύ 2 τουβλακίων brickSpacing και τη συντεταγμένη Y στη οποία θα αρχίσουμε να τοποθετούμε τα τουβλάκια rowStart. Τέλος για να αποθηκεύσουμε τα τουβλάκια θα χρησιμοποιήσουμε ένα πίνακα:

    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        Texture2D whiteTile;
        Rectangle paddle;
        Vector2 paddleDirection;
        float paddleSpeed;
        Rectangle ball;
        Vector2 ballDirection;
        float ballSpeed;

        int brickWidth;
        int brickHeight;
        int bricksPerRow;
        int numOfRows;
        int brickSpacing;
        int rowStart;
        Brick[] bricks;

        int viewWidth;
        int viewHeight;

Οι 2 τελευταίες μεταβλητές (viewWidth, viewHeight) αποθηκεύουν το πλάτος και το ύψος του παραθύρου (viewport) του παιχνιδιού.

Ας αρχίσουμε να τοποθετούμε τα αντικείμενα του παιχνιδιού, το καθένα στη θέση του, τώρα. Αυτό θα το κάνουμε στην μέθοδο Initialize():

	protected override void Initialize()
	{
		viewHeight = graphics.GraphicsDevice.Viewport.Height;
		viewWidth = graphics.GraphicsDevice.Viewport.Width;

		paddle = new Rectangle(150, 550, 90, 15);
		ball = new Rectangle(150, 400, 15, 15);

		bricksPerRow = 13;
		numOfRows = 5;
		brickSpacing = 10;

		bricks = new Brick[bricksPerRow * numOfRows];

		brickWidth = (viewWidth - (bricksPerRow - 1) * brickSpacing) / bricksPerRow ;

		brickHeight = 20;
		paddleSpeed = 10;
		rowStart = 3 * brickHeight;

		base.Initialize();
	}

Το αντικείμενο graphics.GraphicsDevice.Viewport μας δίνει το πλάτος και το ύψος του παραθύρου του παιχνιδιού. Στην συνέχεια δημιουργώ τα Rectangle για τη ρακέτα και τη μπάλα. H ρακέτα θα βρίσκεται στο σημείο (150, 500) και έχει πλάτος 90 pixel και ύψος 15. Αναλόγως για την μπάλα.

Ορίζω ότι θα έχουμε 13 τουβλάκια ανά γραμμή (bricksPerRow) και 5 γραμμές από τουβλάκια (numOfRows). Επιπλέον θέλω τα τουβλάκια να έχουν απόσταση 10 pixel μεταξύ τους στο Χ και Υ άξονα. Να τονίσω ότι οι επιλογές αυτές είναι λίγο-πολύ αυθαίρετες, απλώς έφτιαξα κάτι που φαίνεται εντάξει στο μάτι. Μπορούμε αργότερα να τα αλλάξουμε κατά πως θέλουμε.

Στη συνέχεια πρέπει να δημιουργήσουμε το πίνακα που θα αποθηκεύσει τα τουβλάκια. Ο αριθμός από τουβλάκια που υπάρχουν στην πίστα δίνεται από το γινόμενο numOfRows * bricksPerRow. Στην περίπτωση μας έχουμε 5 γραμμές με 13 τουβλάκια η κάθε μία (65 το σύνολο). Χρησιμοποιώντας το τελεστή new μπορούμε να δεσμεύσουμε μνήμη λοιπόν για numOfRows * bricksPerRow τουβλάκια:

bricks = new Brick[bricksPerRow * numOfRows];

C# και πίνακες

O παραπάνω τρόπος μπορεί να χρησιμοποιηθεί για τη δημιουργία πινάκων οποιαδήποτε τύπου. Αν ήθελα για παράδειγμα να δημιουργήσω ένα πίνακα από ακέραιους (int) με 10 στοιχεία θα έγραφα

int[] numbers;
numbers = new int[10];

Προσοχή πρέπει να δοθεί κατά τη δημιουργία και χρήση ενός πινάκα βασικών τύπων (int, float, char κλπ) και ενός πίνακα αντικειμένων όπως το Brick που ορίσαμε εμείς. Ένας πίνακας βασικών τύπων μπορεί να χρησιμοποιηθεί αμέσως μετά τη δημιουργία του με τη χρήση του τελεστή new:

int[] numbers;
numbers = new int[10];
numbers[2] = 125;

Αντίθετα η δημιουργία και χρήση ενός πίνακα Ν αντικειμένων είναι μια διαδικασία 2 βημάτων. Πρέπει καταρχάς με τη χρήση του τελεστή new να δεσμεύσουμε μνήμη για N «δείκτες» ή «αναφορές» σε αντικείμενα του δεδομένου τύπου (πχ Brick). Για τους γνώστες C/C++ ο δείκτης αυτός είναι ανάλογος του pointer και ο πίνακας που δημιουργώ είναι στην ουσία ένας πίνακας από pointers. Τα αντικείμενα αυτά δεν υπάρχουν ακόμα. Για να τα δημιουργήσω πρέπει να χρησιμοποιήσω ξανά το τελεστή new για κάθε ένα από αυτά και να τα αντιστοιχήσω στις θέσεις του πίνακα που δημιούργησα στο πρώτο βήμα. Ακούγεται (ή το έκανα να ακούγεται) περίπλοκο, αλλά δεν είναι:

Brick[] bricks;
bricks = new Brick[10]; // μέχρι το σημείο αυτό έχω μόνο το πίνακα με τις αναφορές στα αντικείμενα
brick[0] = new Brick(); // δημιουργώ ένα αντικείμενο τύπου Brick και το αντιστοιχίζω στη θέση 0
brick[0].Draw();  // Τώρα μπορώ να το χρησιμοποιήσω

brick[1].Draw();  // ΛΑΘΟΣ, δεν έχω αντιστοιχήσει ένα αντικείμενο στη θέση αυτή.
brick[1] = new Brick(); // δημιουργώ ένα αντικείμενο τύπου Brick και το αντιστοιχίζω στη θέση 1
brick[1].Draw();  // Τώρα μπορώ να το χρησιμοποιήσω

Στη συνέχεια υπολογίζουμε το πλάτος κάθε τούβλου (brickWidth) αυτόματα, έτσι ώστε οι σειρές να γεμίζουν το παράθυρο του παιχνιδιού. Η λογική είναι η εξής: έχω bricksPerRow τούβλα σε κάθε σειρά, τα οποία έχουν συνολικό πλάτος bricksPerRow * brickWidth. Επιπλέον έχω και bricksPerRow-1 κενά ανάμεσα στα τούβλα τα οποία έχουν συνολικό πλάτος (bricksPerRow-1) * brickSpacing. Θέλω το άθροισμα τους να ισούται με το πλάτος του παραθύρου viewWidth, δηλαδή:

bricksPerRow * brickWidth + (bricksPerRow-1) * brickSpacing = viewWidth

Ο καλός προγραμματιστής βιντεοπαιχνιδιών δεν πρέπει να φοβάται λίγα απλά μαθηματικά! Αν λύσω τη παραπάνω εξίσωση ως προς brickWidth θα βρω τι πλάτος πρέπει να έχει το κάθε τουβλάκι έτσι ώστε η σειρά να «γεμίζει» το παράθυρο.

Τέλος θέτω το ύψος του κάθε brick (brickHeight) σε 20, τη ταχύτητα της ρακέτας (paddleSpeed) σε 10 και προσδιορίζω ότι η γραμμές με τα τουβλάκια θέλω να αρχίζουν σε ύψος 3*brickHeight (πάλι, αυθαίρετα).

Το tutorial έχει γίνει ήδη πολύ μεγάλο και ίσως κούρασε τον αναγνώστη. Θα το σταματούσα στο σημείο αυτό όμως είναι ωραίο να μπορούμε να δούμε έστω κάποιο αποτέλεσμα των κόπων μας στην οθόνη, οπότε θα παρουσιάσω στα γρήγορα τη δημιουργία και απεικόνιση των αντικειμένων του παιχνιδιού, χωρίς να μπω στην λογική του.

Στην συνάρτηση LoadContent(), θα φορτώσουμε την υφή και θα δημιουργήσουμε στο πίνακα τις σειρές με τα τουβλάκια:

        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);

            whiteTile = Content.Load<Texture2D>("white_tile");

            for (int j = 0; j < numOfRows; j++)
            {
                for (int i = 0; i < bricksPerRow; i++)
                {
                    Rectangle rect = new Rectangle(i * (brickWidth + brickSpacing),
                                                   rowStart + j * (brickHeight + brickSpacing),
                                                   brickWidth, brickHeight);
                    bricks&#91;j * bricksPerRow + i&#93; = new Brick(rect, Color.Red, whiteTile);
                }
            }
        }
&#91;/sourcecode&#93;

<span style="font-family:Verdana;font-size:9pt;">Το whiteTile είναι μια άσπρη εικόνα (υφή) την οποία χρησιμοποιούμε ως παράμετρο στην spriteBatch.Draw() αργότερα για να απεικονίσουμε τα αντικείμενα του παιχνιδιού. Στη συνέχεια με ένα διπλό for-loop δημιουργώ στην μνήμη τα τουβλάκια (με το τελεστή new), ορίζοντας τη θέση και το μέγεθος τους (Rectagle rect), το χρώμα τους και τη υφή τους. Ουσιαστικά διατρέχουμε το πίνακα bricks που είχαμε δημιουργήσει στην Initialize και για κάθε θέση του δεσμεύουμε μνήμη για κάθε αντικείμενο-τούβλο.
</span>

<span style="font-family:Verdana;font-size:9pt;">Τέλος απεικονίζω τα τουβλάκια, τη μπάλα και την ρακέτα στην μέθοδο Draw(), ως συνήθως, χρησιμοποιώντας το αντικείμενο spriteBatch:
</span>


        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.Black);

            spriteBatch.Begin();

            foreach (Brick brick in bricks)
            {
                brick.Draw(spriteBatch);
            }

            spriteBatch.Draw(whiteTile, paddle, Color.White);
            spriteBatch.Draw(whiteTile, ball, Color.Yellow);

            spriteBatch.End();

            base.Draw(gameTime);
        }

Έχουμε εξηγήσει πως χρησιμοποιούμε το spriteBatch για να απεικονίσουμε τη ρακέτα και τη μπάλα στην οθόνη. Το μόνο στο οποίο αξίζει να αναφερθώ είναι ο τρόπος με τον οποίο απεικονίζουμε τα τουβλάκια. Διατρέχουμε με ένα for-each loop το πίνακα με αυτά (bricks) και για κάθε ένα από αυτά καλώ τη μέθοδο απεικόνισης του Draw, δίνοντας το αντικείμενο spriteBatch ως όρισμα. Θυμίζω ότι τη μέθοδο Draw της κλάσης Brick την ορίσαμε εμείς παραπάνω. Η μέθοδος αυτή απεικονίζει το αντίστοιχο τουβλάκι στην οθόνη.

Το for-each loop που χρησιμοποίησα παραπάνω είναι ένας κάπως πιο ευανάγνωστος και εύχρηστος τρόπος να διατρέξουμε τα στοιχεία ενός πίνακα. Είναι ισοδύναμος με το κλασσικό for-loop που στην συγκεκριμένη περίπτωση θα ήταν

for (int i = 0; i < bricks.Length; i++) { bricks[i].Draw(spriteBatch); } [/sourcecode] Πατώντας τώρα το F5, το παιχνίδι θα κάνει επιτέλους compile και θα τρέξει παρουσιάζοντας τη παρακάτω εικόνα:


Σήμερα κάναμε αρκετή δουλειά ώστε να στήσουμε το παιχνίδι, στο επόμενο tutorial θα είναι πιο εύκολα τα πράγματα καθώς θα προσθέσουμε συγκρούσεις και τη λογική του παιχνιδιού. Μπορείτε να βρείτε το κώδικα σε zip μορφή αλλά και μέσω SVN στο Code Repository.

Επεκτάσεις που μπορείτε να δοκιμάσετε στο παιχνίδι στη μορφή αυτή είναι κάθε γραμμή από τουβλάκια να έχει διαφορετικό χρώμα, ή να προσθέσετε υφές στα τουβλάκια έτσι ώστε να μοιάζουν ξύλινα, ή μεταλλικά κλπ, να κάνετε το ίδιο για την ρακέτα και τη μπάλα, ή να προσθέσετε μια υφή στο φόντο ώστε να μην είναι μαύρο.

Σχόλια, ερωτήσεις και παρατηρήσεις στο γνωστό μέρος! Επίσης μπορείτε να συζητήσετε θέματα σχετικά με την υλοποίηση, το κώδικα, το σχεδιασμό και βελτιώσεις του Arkanoid στο Videogames Laboratory forum.

11 Σχόλια to “Δημιουργώντας το Arkanoid μέρος 1ο”

  1. konsnos said

    Το παιχνίδι αποτυγχάνει στο compile. Πρόσεξα από τα αρχεία του Code Repository, πως στο αρχείο Brick.cs έχεις προσθέσει τα

    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Graphics;

    Τα πρόσθεσα και εγώ και λειτουργεί κανονικά.

    Πολύ καλός οδηγός για 1ο μέρος! Στο τέλος και λόγο του μεγέθους του πράγματι έγινε κουραστικός, αλλά καλά έκανες και πρόσθεσες τα κομμάτια για να δούμε το παιχνίδι οπτικά.

  2. Σπύρος (spahar) said

    Πολύ καλή αρχή για τη δεύτερη σειρά tutorials. Τελικά το Arkanoid είναι πολύ καλή επιλογή, ελπίζω συντομα η συνέχεια.

  3. @konsnos: σωστη παρατήρηση, σε ευχαριστώ.

  4. […] Ήρθε η ώρα να ξεσκονίσουμε τα πληκτρολόγια μας και να αρχίσουμε τη δεύτερη σειρά tutorial ανάπτυξης βιντεοπαιχνιδιού. Αυτή τη φορά θα ασχοληθούμε με το πολύ γνωστό Arkanoid (Taito, 1986).  [Περισσότερα] […]

  5. […] με το προηγούμενο άρθρο τη δεύτερη σειρά tutorial πάνω στην ανάπτυξη […]

  6. […] στιγμής στο παιχνίδι Arkanoid (μέρος 1, μέρος 2) έχουμε υλοποιήσει την κλάση που εκπροσωπεί τα […]

  7. […] Permanent link to Δημιουργώντας το Arkanoid μέρος 1ο […]

  8. Dionysis said

    Δεν χρειάζεται ένα else μετά τις if?? ή μετά το return σταματά να εκτελεί εντολές στην συνάρτηση?? αλλιώς θα επιστρέψει αρχικά π.χ. true και μετά false. Γενικά έχω δουλέψει μόνο κάποια C++ και καθόλου C# οπότε κάποιες διευκολύνσεις που προσφέρει η C# δεν τις γνωρίζω (π.χ. τώρα είδα για πρώτη φορά το foreach loop).

  9. Δεν ξέρω σε ποιο if αναφέρεσαι αλλά γενικά ναι, οταν συναντήσει την return σταματάει τα πάντα και επιστρέφει. Η C++ και η C# εχουν αρκετές ομοιότητες, αν γνωρίζεις τη μία μπορείς να κάνεις αρκετά βήματα στην άλλη.

  10. Dionysis said

    Στα σημεία

    26 if (visible && intersects(ball))
    27 {
    28 visible = false;
    29 return true;
    30 }
    31 return false;

    και

    25 if (rectangle.Right ball.Right ||
    27 rectangle.Top > ball.Bottom ||
    28 rectangle.Bottom < ball.Top)
    29 {
    30 return false;
    31 }
    32
    33 return true;

  11. GCoder said

    Όπως είπε και ο Κώστας όταν το πρόγραμμα συναντήσει return σταματάει και επιστρέφει την τιμή. Οπότε θα επιστρέψει ή true ή false.

Sorry, the comment form is closed at this time.

 
Αρέσει σε %d bloggers: