Videogames Laboratory

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

Arkanoid: Game over!

Posted by Kostas Anagnostou στο 29 Δεκεμβρίου, 2009

Στη δεύτερη σειρά tutorial σχετικά με την ανάπτυξη βιντεοπαιχνιδιών γνωρίσαμε το Arkanoid, και μέσω αυτού ένα πλήθος λειτουργιών που λαμβάνουν χώρα σε ένα παιχνίδι όπως ανίχνευση συγκρούσεων, κίνηση μπάλας και ρακέτας, καταγραφή σκορ και απώλειας ζωής του παίκτη, game entities, game state management. Στο προηγούμενο tutorial είδαμε πως μπορούμε να δημιουργήσουμε μια πίστα του παιχνιδιού διαβάζοντας τις θέσεις των τούβλων από ένα αρχείο κειμένου, προκειμένου να αποσυνδέσουμε τον σχεδιασμό του παιχνιδιού από το προγραμματισμό του. Σήμερα θα ολοκληρώσουμε τη σειρά άρθρων πάνω στην ανάπτυξη του Arkanoid υλοποιώντας λειτουργίες για την υποστήριξη πολλών, διαφορετικών, επιπέδων (levels) στο παιχνίδι. Επιπλέον θα προσθέσουμε μουσική υπόκρουση στο παιχνίδι. Τα θέματα που θα αγγίξουμε στο tutorial αυτό είναι πολλά οπότε αναγκαστικά η αναφορά σε αυτά θα είναι σύντομη. Ο αναγνώστης μπορεί να ανατρέξει στο κώδικα του παιχνιδιού για περισσότερες λεπτομέρειες πάνω στην υλοποίηση.

H κλάση Level

Κάθε επίπεδο του παιχνιδιού διαφέρει από τα υπόλοιπα καταρχάς όσον αφορά το σχέδιο της πίστας (τοποθεσία και χρώμα για κάθε τούβλο). Επιπλέον κάθε πίστα μπορεί να έχει διαφορετικό φόντο ή και μουσική υπόκρουση. Για να αναπαραστήσουμε την κάθε πίστα στο παιχνίδι θα δημιουργήσουμε μια νέα κλάση, τη Level, η οποία θα περιέχει όλα τα απαραίτητα δεδομένα για κάθε επίπεδο και θα ορίζει τις απαιτούμενες λειτουργίες πάνω σε αυτά. Ο σκελετός της κλάσης Level είναι ο παρακάτω:

    class Level
    {
        int brickWidth;
        int brickHeight;
        int bricksPerRow;
        int numOfRows;
        int brickSpacing;
        int numOfVisibleBricks;
        int viewWidth;
        int viewHeight;
        ArkanoidGame game;

        Texture2D whiteTile;
        Texture2D backgroundTexture;

        string backgroundFilename;
        string audioFilename;
        List<string> lines;
        List<Brick> bricks;
        Song bgMusic;

        public bool Completed
        {
            get {}
        }

        public Level(string fileName, ArkanoidGame game)
        {
        }

        public void PlayMusic()
        {
        }

        public bool CheckHits(Rectangle ball)
        {
        }

        public void Draw(SpriteBatch spriteBatch)
        {
        }

        public void Reset()
        {
        }

        private void loadLevel(string fileName)
        {
        }

        private void createLevel()
        {
        }
    }

Ουσιαστικά η Level περιέχει δεδομένα και μεθόδους (δημιουργία, ανίχνευση κρούσης, απεικόνιση) που πριν υπήρχαν στη κύρια κλάση του παιχνιδιού. Επιπλέον έχουμε προσθέσει ένα property Completed το οποίο είναι true αν ο παίκτης έχει χτυπήσει όλα τα τουβλάκια της πίστας και false αν όχι. Δεν θα περιγράψω την υλοποίηση των μεθόδων και δεδομένων αυτών καθώς δεν διαφέρουν ουσιαστικά από αυτά του προηγούμενου tutorial. Έχουμε προσθέσει επιπλέον μερικές μεταβλητές για την υφή φόντου (backgroundTexture) καθώς και την μουσική υπόκρουση της πίστας (bgMusic). Την πληροφορία για το φόντο και το αρχείο μουσικής το διαβάζουμε από το αρχείο κειμένου που περιγράφει την πίστα. Κάνουμε την παραδοχή ότι η πρώτη γραμμή κειμένου είναι το όνομα εικόνας του φόντου, η δεύτερη το όνομα του αρχείου μουσικής και οι υπόλοιπες οι γραμμές από τουβλάκια όπως και προηγουμένως. Αλλάζουμε επίσης την loadLevel έτσι να συμπεριλάβουμε αυτή τη πληροφορία:

        private void loadLevel(string fileName)
        {
            string path = Path.Combine(StorageContainer.TitleLocation, fileName);

            StreamReader reader = new StreamReader(path);

            backgroundFilename = reader.ReadLine();
            audioFilename = reader.ReadLine();

            string line = reader.ReadLine();
            while (line != null)
            {
                lines.Add(line);
                line = reader.ReadLine();
            }
            reader.Close();
        }

Για να χρησιμοποιήσουμε τη Level, ορίζουμε ένα list από αντικείμενα του τύπου αυτού στη κύρια κλάση του παιχνιδιού

    public class ArkanoidGame : Microsoft.Xna.Framework.Game
    {
// υπόλοιπες μεταβλητές

        List<Level> levels;
        int currentLevel;

Επιπλέον ορίζουμε και μια integer μεταβλητή currentLevel οι οποία θα δείχνει στη τρέχουσα πίστα στη λίστα αυτή. Αφού δημιουργήσουμε τη λίστα στην μέθοδο Initialize():

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

            levels = new List<Level>();
            currentLevel = 0;

            gameState = GameState.Intro;

            MediaPlayer.IsRepeating = true;

            base.Initialize();
        }

είμαστε έτοιμοι να δημιουργήσουμε τις πίστες του παιχνιδιού:

        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);
            font = Content.Load<SpriteFont>("arial");

            whiteTile = Content.Load<Texture2D>("white_tile");
            logoTexture = Content.Load<Texture2D>("logo");
            logoBackgroundTexture  = Content.Load<Texture2D>("backgrounds/intro");

            levels.Add(new Level("Content/levels/Level01.txt", this));
            levels.Add(new Level("Content/levels/Level02.txt", this));
            levels.Add(new Level("Content/levels/Level03.txt", this));
            levels.Add(new Level("Content/levels/Level04.txt", this));
            levels.Add(new Level("Content/levels/Level05.txt", this));

            bgMusic = Content.Load<Song>("audio/Bill Psarras - Feel the same pt.2");
        }

Απλά κάνω Add στη λίστα levels 5 πίστες που έχω δημιουργήσει και έχω προσθέσει ως αρχεία κειμένου το Content project του παιχνιδιού. Με κάθε νέο αντικείμενο τύπου Level που δημιουργώ περνώ ως όρισμα το όνομα του αρχείου κειμένου της κάθε πίστας και μια αναφορά (this) στη κύρια κλάση του παιχνιδιού. Ο λόγος που το κάνω αυτό είναι για να έχω στη κλάση Level πρόσβαση στο Content Manager (για να μπορώ να φορτώσω περιεχόμενο) και στο GraphicsDevice του XNA.

        public Level(string fileName, ArkanoidGame game)
        {
            lines = new List<string>();
            bricks = new List<Brick>();

            this.game = game;
            viewWidth = game.GraphicsDevice.Viewport.Width;
            viewHeight = game.GraphicsDevice.Viewport.Height;

            brickSpacing = 10;
            brickHeight = 20;

            loadLevel(fileName);

            whiteTile = game.Content.Load<Texture2D>("white_tile");
            backgroundTexture = game.Content.Load<Texture2D>(backgroundFilename);
            bgMusic = game.Content.Load<Song>(audioFilename);

            createLevel();
        }

Από εδώ και μπρός μπορούμε να χρησιμοποιήσουμε την αναφορά στη τρέχουσα πίστα (levels[currentLevel]) για να πραγματοποιήσουμε οποιαδήποτε λειτουργία σχετικά με αυτή. Για παράδειγμα καλώντας levels[currentLevel].Draw(spriteBatch) μπορούμε να την απεικονίσουμε (στη μέθοδο renderWorld()). Ή μπορούμε να διαπιστώσουμε αν η μπάλα συγκρούστηκε με κάποιο τούβλο καλώντας την levels[currentLevel].CheckHits(ball) (στη μέθοδο updateWorld()). Τέλος μπορούμε να διαπιστώσουμε αν ο παίκτης έχει χτυπήσει όλα τα τουβλάκια ελέγχοντας τη τιμή της levels[currentLevel].Completed. Αν συμβαίνει αυτό τότε αυξάνουμε το δείκτη currentLevel και προχωράμε στη νέα πίστα.

Για να αρχικοποιήσουμε μια πίστα χρησιμοποιούμε τη μέθοδο resetLevel():

        private void resetLevel()
        {
            paddle = new Rectangle(150, 550, 90, 15);
            paddleSpeed = 10;

            ball = new Rectangle(150, 400, 15, 15);
            ballDirection = new Vector2(1, -1);
            ballDirection.Normalize();
            ballSpeed = 5;

            levels[currentLevel].Reset();
        }

Η μέθοδος αυτή τοποθετεί μπάλα και ρακέτα στις αρχικές θέσεις και καλεί τη μέθοδο Reset() της κλάσης Level έτσι ώστε να κάνει όλα τα τουβλάκια ορατά και πάλι.

Σε περίπτωση που ο παίκτης ολοκληρώσει όλες τις πίστες (ή επιλέξει να εγκαταλήψει το παιχνίδι), πρέπει να καλέσουμε την μέθοδο resetGame() έτσι ώστε να επαναρχικοποιήσει το παιχνίδι μηδενίζοντας το σκορ και το δείκτη της τρέχουσας πίστας (currentLevel). Επίσης η μέθοδο αυτή σταματά την αναπαραγωγή της μουσικής.

        private void resetGame()
        {
            lives = 3;
            score = 0;

            currentLevel = 0;
            resetLevel();

            if (MediaPlayer.State == MediaState.Playing)
            {
                MediaPlayer.Stop();
            }
        }

Αναπαραγωγή μουσικής

Η αναπαραγωγή ενός μουσικού κομματιού στο XNA 3.1 είναι εύκολη και μπορούμε να αποφύγουμε το δύσχρηστο XACT. Tο μόνο που έχουμε να κάνουμε είναι να φορτώσουμε με τον παραδοσιακό τρόπο ένα αρχείο mp3 (μόνο mp3 όχι wav):

bgMusic = Content.Load<Song>(«ονομα_αρχείου.mp3»);

Η bgMusic είναι μια μεταβλητή τύπου Song. Για να αναπαράγουμε το αρχείο αυτό χρησιμοποιούμε το αντικείμενο MediaPlayer που υποστηρίζει το XNA:

MediaPlayer.Play(bgMusic);

Και αυτό ήταν. Το αντικείμενο αυτό υποστηρίζει λίστες από αρχεία μουσικής και λειτουργίες start/stop/skip/repeat κλπ.

Λοιπές βελτιώσεις/προσθήκες

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

Game Over!

Στο σημείο αυτό θα ολοκληρώσουμε την ενασχόληση μας με το Arkanoid. Ήταν ένα μακρύ ταξίδι αλλά γνωρίσαμε αρκετά πράγματα σε σχέση με την ανάπτυξη (ολοκληρωμένων) παιχνιδιών. Ο κώδικας του παιχνιδιού είναι διαθέσιμος ως συνήθως σε zip και SVN μέσω του Code Repository. Βελτιώστε τον, διορθώστε ότι σφάλματα τυχόν υπάρχουν και μοιραστείτε την δική σας έκδοση με όλους!

Θα ήθελα να ευχαριστήσω το Βασίλη Ψαρρά για τα μουσικά κομμάτια που χρησιμοποίησα στο tutorial αυτό. Δεν έκανα την καλύτερη χρήση των κομματιών αυτών, ο ρόλος τους ήταν περισσότερο για επίδειξη των δυνατοτήτων αναπαραγωγής μουσικής του XNA. Περισσότερα για τη πολύ καλή δουλειά του Βασίλη μπορείτε να βρείτε εδώ.

Καλή χρονιά σε όλους, θα επανέρθουμε το 2010 με περισσότερα tutorial!

8 Σχόλια to “Arkanoid: Game over!”

  1. Όλη η σειρά του Arkanoid ήταν πολύ ενδιαφέρουσα 🙂 Περιμένω την επόμενη… Θα είναι κάποιo 3d παιχνίδι ή πάλι 2d?

  2. θα εξερευνήσουμε ακόμα λίγο το 2D πριν μεταβούμε σε 3D. Το 2D αρκεί για εκπαιδευτικούς (και όχι μόνο) σκοπούς. Είναι πολλά πράγματα που μπορούμε να δούμε ακόμα σε 2D όπως particles, scrolling, platforming, pixel shaders, multiplayer, physics.

  3. darklynx said

    Φαντάζομαι το πρώτο που πρέπει να δούμε είναι ένα tile-based σύστημα,μπορώ να πω ότι το αποφύγαμε εντέχνως τόσο καιρό.Επίσης αν μπούμε σε platform games καλό θα ήταν να δούμε και την έννοια των game components στο XNA,που διευκολύνουν το διαχωρισμό του κώδικα που θα εκτελείται στις Update και Draw.

  4. Θα αφήσουμε το tile-engine για λίγο αργότερα, υπάρχουν άλλα θέματα που μπορούμε να δούμε πριν. Μαλλον για ένα top-down space shooter προσανατολίζομαι, να δούμε game entities, managers, particles/explosions, background scrolling, bullets κλπ.

    Οσον αφορά τα game components του XNA δεν έχω πειστεί ακόμα για την χρησιμότητα τους, δεν προσφέρουν κάτι που δεν μπορείς να υλοποιήσεις με απλές κλάσεις (οι οποίες μπορούν κάλλιστα να έχουν Update και Draw μεθόδους). Ίσως φταίει η κλασσική C/C++ παιδεία μου στην ανάπτυξη βιντεοπαιχνιδιών! 😉

  5. darklynx said

    Top-down shooting χωρίς tile engine;Μάλλον θα είναι πιο δύσκολο από το να εξηγηθεί το πως τη φτιάχνουμε.
    Όσον αφορά τα components πράγματι μπορούμε να κάνουμε τα εξής μόνοι μας:
    α)να φτιάξουμε μια κλάση για κάθε αντικείμενο του παιχνιδιού
    β)να τους βάλουμε Update και Draw
    γ)να τις βάλουμε σε μια λίστα ώστε να τις παρακολουθούμε πιο εύκολα
    δ)στις Update και Draw της Game κλάσης να καλούμε τις Update και Draw των αντικειμένων μας.
    Δεν είναι δύσκολο και δεν απαιτεί υπερβολικό χρόνο.Αφού όμως όλα τα παραπάνω μας τα προσφέρει το Framework,γιατί να τα ξαναφτιάξουμε από την αρχή;

  6. Στην περίπτωση αυτή, δεν νομίζω ότι ένα απλό scrolling texture με αστέρια θα χρειαζόνταν ένα tile-engine! Επίσης σχετικά με τα components, ακόμα και με αυτά, δεν θα έπρεπε να υλοποιήσουμε τα α-γ? Μονο το δ κάνει αυτόματα το ΧΝΑ με το component framework.

  7. […] Στη δεύτερη σειρά tutorial σχετικά με την ανάπτυξη βιντεοπαιχνιδιών γνωρίσαμε το Arkanoid, και μέσω αυτού ένα πλήθος λειτουργιών που λαμβάνουν χώρα σε ένα παιχνίδι όπως ανίχνευση συγκρούσεων, κίνηση μπάλας και ρακέτας, καταγραφή σκορ και απώλειας ζωής του παίκτη, game entities, game state management. [Περισσότερα] […]

  8. […] Permanent link to Arkanoid- Game over! […]

Sorry, the comment form is closed at this time.

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