Το προφίλ μας στο Google Plus
0

Ο κεντρικός ρόλος του game loop

Στο προηγούμενο άρθρο της σειράς αρχίσαμε να στήνουμε το παιχνίδι μας κάνοντας τη σφαίρα που θα ελέγχει ο παίκτης να κινείται αριστερά–δεξιά. Αυτή τη φορά θα μας απασχολήσει λίγη θεωρία. Ναι, και σε μας δεν αρέσει η θεωρία, αλλά ξεκινώντας από αυτή θα μπορέσουμε να κατανοήσουμε ακόμα περισσότερο πώς λειτουργεί ο κώδικας των παιχνιδιών.

Όπως αναφέραμε επιγραμματικά στο προηγούμενο άρθρο, το λεγόμενο game loop είναι ένα programming pattern και χρησιμοποιείται για να αποσυνδέουμε την εξέλιξη ενός παιχνιδιού από την είσοδο του χρήστη. Προκειμένου να δούμε πώς μας προέκυψαν αυτά τα game loops, χρειάζεται να πάμε κάποια χρόνια πίσω.

A long, long time ago…
Τον παλιό καιρό, τότε που οι οθόνες ήταν ασπρόμαυρες, κιτρινόμαυρες ή πρασινόμαυρες, ανάλογα με τη μάρκα, τα video games είχαν διαφορετική δομή σε σχέση με τα σύγχρονα. Δεν θα ήταν λάθος αν λέγαμε πως η δομή τους ήταν άμεση συνέπεια του επιπέδου της τότε τεχνολογίας. Ας μην ξεχνάμε άλλωστε –οι μικρότεροι ας μαθαίνουν– ότι η υπολογιστική ισχύς των επεξεργαστών την περίοδο εκείνη ήταν στα επίπεδα που είναι σήμερα τα πλυντήρια και τα ψυγεία. Προγραμματίζαμε τους τότε υπολογιστές, πατούσαμε ένα κουμπί και περιμέναμε να πάρουμε τ’ αποτελέσματα. Ίσως θα παρατηρήσατε ότι από τη συγκεκριμένη διαδικασία λείπει κάτι: Πέρα από το πάτημα του κουμπιού για την έναρξη των υπολογισμών, ο παράγοντας της ανθρώπινης αλληλεπίδρασης είναι ανύπαρκτος. Ουσιαστικά, μόλις αναφερθήκαμε στην επεξεργασία σύμφωνα με το μοντέλο του batch processing. Τα προγράμματα που ακολουθούν την εν λόγω λογική λέμε ότι λειτουργούν σε batch mode. Θα έχετε προσέξει τα αρχεία .bat στα Windows, τα λεγόμενα batch files — έτσι δεν είναι;

Κατά βάση, τα προγράμματα που συμμορφώνονται στη λογική του batch processing επιστρατεύονται, ακόμη και σήμερα, για εργασίες που εκτελούνται χωρίς την ανάγκη αλληλεπίδρασης με τον χειριστή (δηλαδή με τον χρήστη). Τα δεδομένα εισόδου που δέχονται είναι πιθανό να προέρχονται, π.χ., από άλλα batch files ή scripts ή/και από παραμέτρους που περνάμε αρχικά, στο command line. Είναι λοιπόν προφανές ότι τέτοια προγράμματα είναι κατάλληλα για τον αυτοματισμό μηχανικών/συνηθισμένων διαδικασιών.

Καθώς περνούσε ο καιρός άρχισε να γίνεται φανερή η ανάγκη και για προγράμματα ικανά για αλληλεπίδραση με τον χρήστη: όχι μόνο αρχικά, κατά την ενεργοποίησή τους, αλλά και κατά το χρόνο εκτέλεσης. Σιγά σιγά λοιπόν άρχισαν να γράφονται τα πρώτα διαδραστικά (interactive) προγράμματα. Μερικά από τα πρώτα προγράμματα του είδους ήταν τα παιχνίδια. Οι παλιότεροι πιθανώς θα θυμόσαστε τα πρώτα text adventure games. Σε αυτά, βλέπαμε στην οθόνη μας το κείμενο που περιέγραφε το περιβάλλον, δηλαδή το τι μπορούσε να βλέπει ο χαρακτήρας του παίκτη, κι ο υπολογιστής ανέμενε εντολές κειμένου από τον παίκτη ώστε να προχωρήσει αναλόγως.

Το Colossal Cave Adventure ή ADVENT τρέχει σε έναν υπολογιστή Osborne 1, γύρω στο 1982. Το παιχνίδι γράφτηκε το '76 από τον Will Crowther. Ο παίκτης καλείται να εξερευνήσει μια σπηλιά, στην οποία φήμες λέγαν ότι υπήρχε ένας αμύθητος θησαυρός. Η αλληλεπίδραση με τον παίκτη γινόταν με απλές εντολές κειμένου.

Εικόνα 1. Το Colossal Cave Adventure ή ADVENT τρέχει σε έναν υπολογιστή Osborne 1, γύρω στο 1982. Το παιχνίδι γράφτηκε το ’76 από τον Will Crowther. Ο παίκτης καλείται να εξερευνήσει μια σπηλιά, στην οποία φήμες λέγαν ότι υπήρχε ένας αμύθητος θησαυρός. Η αλληλεπίδραση με τον παίκτη γινόταν με απλές εντολές κειμένου.

Θα μπορούσαμε να πούμε ότι γινόταν ένας διάλογος μεταξύ του παίκτη και του υπολογιστή. Ο υπολογιστής περίμενε την είσοδο του παίκτη και αντιδρούσε αναλόγως.

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

while (!done)
{
	Event event = waitForEvent();
	dispatchEvent(event);
}

Όμως τα σημερινά παιχνίδια βλέπουμε ότι συνεχίζουν να εκτελούνται (οι NPCs κινούνται, τα particles συνεχίζουν να χορεύουν αριστερά–δεξιά) ακόμη κι όταν ο παίκτης κάθεται κι απλά κοιτάζει την οθόνη. Το παιχνίδι δεν παγώνει, περιμένοντας τον παίκτη να ανταποκριθεί. Ακριβώς αυτό, φίλες και φίλοι, είναι το πρώτο, κύριο σημείο αναφοράς ενός game loop: Επεξεργάσου την είσοδο του παίκτη, αλλά μην την περιμένεις. Σύμφωνα μ’ αυτή τη λογική, το loop συνεχίζει κι εκτελείται:

while (!done)
{
	processInput();
	updateGameLogic();
	renderToScreen();
}

Αφού πλέον το loop εκτελείται διαρκώς, φτάνουμε στα εξής ερωτήματα: Πόσο γρήγορα εκτελείται το game loop; Σε κάθε εκτέλεσή του, το παιχνίδι προχωράει λίγο την εξέλιξή του. Πόσο όμως πρέπει να εξελίσσουμε το παιχνίδι; Πρέπει, π.χ., να καταστραφεί το κτήριο που είχε πάρει φωτιά μετά την επίθεση του εχθρού ή όχι ακόμα;

Αν μετρήσουμε το πόσες φορές εκτελείται το loop ανά δευτερόλεπτο, παίρνουμε τον αριθμό των FPS (frames per second) του παιχνιδιού. Παλιότερα, μιας και ο αριθμός των διαθέσιμων μηχανημάτων ήταν πεπερασμένος, οι προγραμματιστές ήξεραν τα χαρακτηριστικά κάθε μηχανήματος κι έτσι μπορούσαν να υπολογίζουν τον πραγματικό χρόνο (σε ms) μεταξύ δύο διαδοχικών επαναλήψεων του loop. Το αποτέλεσμα ήταν ένα ομαλό gameplay.

Κατά τα αναμενόμενα, τα πράγματα σήμερα είναι αρκετά πιο δύσκολα. Σε γενικές γραμμές, καλύτερο μηχάνημα σημαίνει και μεγαλύτερος αριθμός FPS. Καλύτερο μηχάνημα όμως σημαίνει και ακριβότερο μηχάνημα, κι αυτή είναι μια πολυτέλεια που δεν μπορεί να έχει ο καθένας. Τις περισσότερες φορές άλλωστε, δεν γίνεται να γνωρίζουμε σε τι μηχάνημα θα τρέξει το παιχνίδι που θα φτιάξουμε. Υπάρχει τόσο μεγάλη ποικιλία εξαρτημάτων/υποσυστημάτων στο εμπόριο, που οι δυνατοί συνδυασμοί –αν όχι άπειροι– είναι απαγορευτικά πολλοί ώστε να τους λάβουμε όλους υπόψη.

Προκειμένου να λύσουμε και αυτό το πρόβλημα, οφείλουμε να προγραμματίσουμε το παιχνίδι προκειμένου να προσαρμόζεται στο εκάστοτε hardware. Κι αυτό ακριβώς είναι το δεύτερο κύριο σημείο αναφοράς ενός game loop: τρέξε το παιχνίδι με την ίδια ταχύτητα ανεξάρτητα από το μηχάνημα που εκτελείσαι.

The Game Loop
Φτάνουμε τώρα στην έννοια του pattern. Κατά τη διάρκεια του gameplay, το game loop πρέπει να τρέχει χωρίς να σταματά. Οφείλει να επεξεργάζεται την είσοδο του χρήστη χωρίς να μπλοκάρει, να εξελίσσει την κατάσταση του παιχνιδιού και να προβάλλει την τρέχουσα κατάσταση στον παίκτη. Τέλος, πρέπει να ανιχνεύει την πάροδο του χρόνου και να διαχειρίζεται το ρυθμό της εξέλιξης του gameplay. Δεν ξέρουμε πώς σας ακούγονται αυτές οι υποχρεώσεις, δεν είναι όμως παίξε γέλασε. (Κάπως ειρωνικό αυτό, αν σκεφτούμε ότι σκοπός μας εδώ είναι να παίζουμε — γιατί όχι και να γελάμε.)

Στη δική μας περίπτωση, η αλήθεια είναι ότι το game loop είναι βαθιά ριζωμένο στα ενδότερα της Unity. Δεν θα χρειαστεί, λοιπόν, να το προγραμματίσουμε. Τις γνώσεις περί game loop όμως, έχουμε την άριστη ευκαιρία να τις χρησιμοποιήσουμε ώστε να καταλάβουμε καλύτερα πώς δουλεύει η μηχανή.

Πίσω στο παιχνίδι μας!
Ας επιστρέψουμε στον κώδικα του προηγούμενου άρθρου κι ας τον ξαναδούμε υπό το φως όλων όσων μάθαμε.

using UnityEngine;
namespace Deltahacker
{
	public class Player : MonoBehaviour
	{
		public float Speed = 3f;
		
		public void Update()
		{
			float horizontal = Input.GetAxis("Horizontal");
			Vector3 movement = new Vector3(horizontal * Speed * Time.deltaTime, 0, 0);
			transform.position += movement;
		}
	}
}

Υλοποιήσαμε τη μέθοδο Update, της κλάσης Player. Για τον υπολογισμό του διανύσματος της κίνησης της σφαίρας χρησιμοποιήσαμε την κλάση Time, της Unity. Η ενσωματωμένη αυτή κλάση είναι άμεσα συνδεδεμένη με το game loop της μηχανής κι ανά πάσα στιγμή μας δίνει πληροφορίες για το χρόνο του παιχνιδιού. Συγκεκριμένα, η static μεταβλητή deltaTime επιστρέφει το χρόνο που έχει περάσει από την προηγούμενη εκτέλεση του loop. Δηλαδή μας λέει πόσος χρόνος πέρασε από την προηγούμενη φορά που εκτελέστηκε η μέθοδος Update. Η τιμή της είναι συνήθως της τάξης των milliseconds, αλλά επιστρέφεται πάντα ως seconds. Παραδείγματος χάριν, στα 60 fps η τιμή της θα είναι 1s / 60 fps = 0,01667 seconds. Ο πολλαπλασιασμός της ταχύτητας της σφαίρας με την τιμή αυτή μας βοηθάει να κινούμε με σταθερή ταχύτητα τη σφαίρα, χωρίς να μας ενδιαφέρει η ταχύτητα στην οποία τρέχει το παιχνίδι.

Θα θέλαμε να σχολιάσουμε επίσης την κλάση MonoBehaviour, από την οποία κληρονομεί η κλάση που γράψαμε. Για να προσθέσουμε ένα script σε ένα GameObject που βρίσκεται στην σκηνή μας (είτε όχι, αν είναι Prefab), το script πρέπει να είναι απόγονος της κλάσης MonoBehaviour. Η κλάση είναι εσωτερική της Unity και περιλαμβάνει τις μεθόδους και τα events που χρειάζονται για την αλληλεπίδραση ή το χειρισμό των αντικειμένων στο παιχνίδι μας. Αργότερα θα υλοποιούμε περισσότερες από αυτές τις μεθόδους σε κάθε script μας, οπότε καλό θα ήταν να γνωρίζουμε τη σειρά με την οποία αυτές εκτελούνται.

To flowchart της Unity, όπως δίνεται στο επίσημο manual. Η μέθοδος Update που είδαμε στο script του προηγούμενου άρθρου, αποτελεί μέρος του Game Logic.

Εικόνα 2. To flowchart της Unity, όπως δίνεται στο επίσημο manual. Η μέθοδος Update που είδαμε στο script του προηγούμενου άρθρου, αποτελεί μέρος του Game Logic.

Δεν είναι απαραίτητο όλες οι κλάσεις που γράφουμε να είναι απόγονοι της κλάσης MonoBehaviour. Ίσα ίσα που καλό θα ήταν να χρησιμοποιούμε την κλάση μόνο σε όσα scripts χρειάζεται. Το πού χρειάζεται και πού όχι, μπορούμε να το καταλάβουμε αναλογιζόμενοι αν το αντικείμενο χρειάζεται να κάνει υπολογισμούς σε κάθε frame από μόνο του. Άλλες φορές, η εμπειρία μας θα μας λέει ότι η εκάστοτε κλάση δεν χρειάζεται να είναι απόγονος της MonoBehaviour.

When I say jump…
Μετά τον ορισμό του game loop και τη δεύτερη ματιά στον κώδικα της κίνησης, ήρθε η ώρα να κάνουμε τη σφαίρα που κινεί ο παίκτης να πηδάει από γραμμή σε γραμμή. Ανοίγουμε το project στη Unity και πάμε να τροποποιήσουμε το υπάρχον script μας.

Υπάρχουν αρκετοί τρόποι να κάνουμε την σφαίρα να κινείται. Άλλοι περιλαμβάνουν γραφίστες (ενσωματωμένο animation στο μοντέλο της σφαίρας), ενώ άλλοι περιλαμβάνουν τον ορισμό ενός animation με τη βοήθεια της ενσωματωμένης μηχανής που προσφέρει η Unity. Θα καταφύγουμε σε έναν πιο απλό τρόπο, παίζοντας κατευθείαν με τη θέση της σφαίρας. Ας προσθέσουμε λοιπόν τον κώδικα που χρειάζεται για το άλμα στην κλάση Player, κι ας τον μελετήσουμε λεπτομερώς.

using UnityEngine;
namespace Deltahacker
{
public class Player : MonoBehaviour
{
   		 public float Speed = 3f;

   		 [Header("Jump Setup")]
   		 public float JumpHeight = 1f;
   		 public float JumpDistance = 2f;
   		 public float JumpDuration = 0.5f;
   		 public AnimationCurve JumpCurve;
    
   		 private bool jumping;    
   		 private float jumpDirection;
   		 private float currentJumpTime;

   		 private float normalY;
   		 private float relativeCurveTime;

   		 public void Start()
   		 {
   			 normalY = transform.position.y;
   			 float curveTime = JumpCurve[JumpCurve.length - 1].time;
   			 relativeCurveTime = curveTime / JumpDuration;
   		 }

   		 public void Update()
   		 {
   			 float horizontalAxis = Input.GetAxis("Horizontal");
   			 move(horizontalAxis);

   			 float verticalAxis = Input.GetAxis("Vertical");
   			 if (jumping)
   				 handleJump();
   			 else if (verticalAxis != 0)
   				 jump(verticalAxis);
   		 }

   		 private void move(float axis)
   		 {
   			 Vector3 movement = new Vector3(axis * Speed * Time.deltaTime, 0, 0);
   			 transform.position += movement;
   		 }

   		 private void jump(float axis)
   		 {
   			 if (axis == 0)
   				 return;

   			 jumpDirection = axis < 0 ? -1 : 1;
   			 if (jumpDirection != 0)
   			 {
   				 currentJumpTime = 0;

   				 jumping = true;
   				 handleJump();
   			 }
   		 }

   		 private void handleJump()
   		 {
   			 currentJumpTime += Time.deltaTime;
   			 float animationEval = JumpCurve.Evaluate(currentJumpTime * relativeCurveTime);
   			 float currentHeight = normalY + (animationEval * JumpHeight);
   			 float forwardMovement = jumpDirection * JumpDistance * Time.deltaTime;

   			 Vector3 jumpVector = new Vector3(transform.position.x, currentHeight, transform.position.z + forwardMovement);
   			 transform.position = jumpVector;

   			 if (currentJumpTime >= JumpDuration)
   				 jumping = false;
    	}
    }
}

Αρχικά κάναμε refactoring στην κλάση. Μετακινήσαμε τον κώδικα που είναι υπεύθυνος για την κίνηση της σφαίρας αριστερά–δεξιά στη δικιά του private μέθοδο, με όνομα move. Αντίστοιχα με τη move δημιουργήσαμε μια νέα μέθοδο jump, με μοναδικό όρισμα ένα float, η οποία θα μας υποδεικνύει προς ποια κατεύθυνση θέλει ο παίκτης να κάνει άλμα η σφαίρα. Η μέθοδος αυτή θα είναι υπεύθυνη μόνο για την έναρξη του άλματος, οπότε θα χρειαστούμε άλλη μία μέθοδο μέσα στην οποία θα τροποποιούμε τη θέση της σφαίρας, ώστε να υλοποιήσουμε το άλμα. Η μέθοδος αυτή ονομάζεται handleJump. Παράλληλα, ορίσαμε μια σειρά από public και private μεταβλητές για να μας βοηθήσουν στη διαδικασία του άλματος.

Αλλάζοντας τις τιμές των public πεδίων JumpHeight, JumpDistance, JumpDuration και JumpCurve, θα μπορούμε να μεταβάλλουμε τη συμπεριφορά του άλματος χωρίς να χρειάζεται να επεμβαίνουμε στον κώδικά μας. Αναλυτικότερα, το JumpHeight μεταβάλλει το ύψος του άλματος, το JumpDistance μεταβάλλει το μήκος του, ενώ το JumpDuration μεταβάλλει τον χρόνο που χρειάζεται για να ολοκληρωθεί το άλμα. Ο τύπος δεδομένων των τριών αυτών μεταβλητών είναι float. Η τέταρτη μεταβλητή θα ορίζει την καμπύλη του άλματος, για αυτό και χρησιμοποιούμε την ενσωματωμένη κλάση AnimationCurve. Περισσότερα γι’ αυτή, λίαν συντόμως. Χρησιμοποιώντας το attribute Header μπορούμε να προσθέσουμε μια κεφαλίδα στις τιμές που φαίνονται στον inspector, ομαδοποιώντας (οπτικά μόνο) τις τιμές που εμφανίζονται.

Τα private πεδία είναι περισσότερο βοηθητικά. H boolean μεταβλητή jumping θα καθίσταται true μόνον όταν η σφαίρα έχει ξεκινήσει το άλμα της. Στη jumpDirection θα αποθηκεύουμε την τιμή του κάθετου άξονα (πλήκτρα [W] και [S]) όταν ξεκινάει το άλμα. Με αυτόν τον τρόπο, αν ο χρήστης κατά τη διάρκεια του άλματος αλλάξει την τιμή του άξονα δεν θα επηρεαστεί η κίνηση της σφαίρας. Η currentJumpTime θα κρατάει την ώρα που πέρασε από την αρχή του άλματος (μια και θέλουμε το άλμα να σταματήσει με το πέρας του JumpDuration). Στη μεταβλητή normalY θα αποθηκεύσουμε την αρχική θέση της σφαίρας, στον κατακόρυφο άξονα y. Η relativeCurveTime θα μας βοηθήσει να αναγάγουμε το χρόνο που είναι δηλωμένος στην καμπύλη του άλματος, στο χρόνο που χρειάζεται για να ολοκληρωθεί το άλμα.

Ας μιλήσουμε λίγο για την καμπύλη του άλματος. Η κλάση AnimationCurve είναι μια συλλογή από καμπύλες (Βezier curves) που υποδηλώνουν τη συνάρτηση που τροποποιεί μια τιμή. Επιστρέφοντας στη Unity (αφού σώσουμε τον κώδικά μας) και βλέποντας τον Inspector του αντικειμένου Player, θα παρατηρήσουμε ότι στο component Player που ορίζεται από το script μας έχουν προστεθεί τα public πεδία που έχουμε ορίσει στην κλάση.

Αλλάζοντας στον κώδικα τα public πεδία των κλάσεων που έχουμε προσκολλήσει σε ένα game object, θα δούμε ότι οι αλλαγές αυτές αντανακλώνται και στο παράθυρο του Inspector του gameobject.

Εικόνα 3. Αλλάζοντας στον κώδικα τα public πεδία των κλάσεων που έχουμε προσκολλήσει σε ένα game object, θα δούμε ότι οι αλλαγές αυτές αντανακλώνται και στο παράθυρο του Inspector του gameobject.

Κάνοντας κλικ στο σκούρο γκρι κουτί που υποδηλώνει την τιμή του JumpCurve, θα εμφανιστεί ένας editor. Σ’ αυτόν μπορούμε να δηλώσουμε την καμπύλη του άλματος, δηλαδή τη συνάρτηση στην οποία θα βασιστούμε για να τροποποιούμε το ύψος που βρίσκεται η σφαίρα (τη θέση της στον κατακόρυφο άξονα y). Ας κατασκευάσουμε την καμπύλη χωρίς να μπούμε σε λεπτομέρειες για το πώς δουλεύουν οι καμπύλες Bezier. Θα τις εξετάσουμε σε επόμενο άρθρο.

  • Στον editor κάνουμε δεξί κλικ στην τιμή (0,0) κι επιλέγουμε Add Key.
  • Το ίδιο κάνουμε και για τις τιμές (0.7,0) και (1,0). Κρατώντας πατημένο το αριστερό κλικ του ποντικιού πάνω σ’ ένα κλειδί, μπορούμε να δούμε με μεγάλη ακρίβεια τη θέση του πάνω στους δύο άξονες. Η ροδέλα του ποντικιού ρυθμίζει τη μεγέθυνση των αξόνων. Με το πλήκτρο [Ctrl] και τη ροδέλα, ρυθμίζουμε τη μεγέθυνση μόνο στον άξονα x. Επίσης, με το [Shift] και τη ροδέλα ρυθμίζουμε τη μεγέθυνση μόνο στον άξονα y. Μπορούμε να αλλάξουμε τη θέση ενός κλειδιού σέρνοντάς το.
  • Μεταφέρουμε το μεσαίο κλειδί προς τα πάνω, από τη θέση (0.7,0) στη θέση (0.7,0.97).

Το αποτέλεσμα πρέπει να είναι σαν αυτό στην εικόνα που ακολουθεί. Κλείνοντας τον editor η καμπύλη αποθηκεύεται αυτόματα.

Η καμπύλη στην οποία θα βασίσουμε την αλλαγή της θέσης της σφαίρας, στον άξονα y.

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

Επιστρέφοντας στον κώδικα, παρατηρούμε ότι έχουμε υλοποιήσει και τη μέθοδο Start. Η εν λόγω μέθοδος καλείται αμέσως πριν κληθεί για πρώτη φορά η μέθοδος Update. Ουσιαστικά, η κλήση της υποδηλώνει ότι το αντικείμενο είναι έτοιμο προς rendering. Σε αυτή τη μέθοδο αρχικοποιούμε τη θέση στον άξονα y του αντικειμένου, καθώς και την τιμή που θα μας βοηθήσει στην αναγωγή του χρόνου της καμπύλης. Την τιμή της αναγωγής τη βρίσκουμε διαιρώντας την τιμή του χρόνου που δηλώσαμε στην καμπύλη, με την τιμή του χρόνου της διάρκειας του άλματος. Την τιμή του χρόνου της καμπύλης μπορούμε να την πάρουμε έμμεσα, αναζητώντας την τιμή του χρόνου του τελευταίου key που έχουμε δηλώσει σ’ αυτή.

Τέλος, ας κοιτάξουμε και τη μέθοδο handleJump. Σε αυτήν αποθηκεύουμε το χρόνο που έχει περάσει από την αρχή του άλματος, προσθέτοντας το χρόνο που πέρασε για το κάθε καρέ (frame). Με τη βοήθεια της μεθόδου Evaluate, της κλάσης AnimationCurve, παίρνουμε την τιμή της θέσης y που πρέπει να βρίσκεται η σφαίρα (αφού κάνουμε αναγωγή στο χρόνο της καμπύλης) τη δεδομένη χρονική στιγμή. Βασιζόμενοι στο χρόνο που πέρασε, βρίσκουμε την απόσταση που πρέπει να διανύσει η σφαίρα στον άξονα z (για να πάει μπροστά ή πίσω). Κρατώντας τη θέση x της σφαίρας (που έχει ήδη αλλάξει πιο πριν από τη μέθοδο move), τροποποιούμε τις τιμές για τους άξονες y και z του διανύσματος και το θέτουμε ως τη νέα θέση της σφαίρας. Όταν ο χρόνος του άλματος παρέλθει, ενημερώνουμε την κλάση ότι το άλμα τελείωσε θέτοντας τη μεταβλητή jumping σε false.

Τρέχοντας το project μας και πατώντας τα κουμπιά [W] ή [S], βλέπουμε τη σφαίρα να πηδάει εμπρός ή πίσω. Πατώντας ταυτόχρονα τα [A] ή [D] μπορούμε να δούμε ότι η πλευρική κίνηση δεν έχει επηρεαστεί από τις νέες αλλαγές, πράγμα που ήταν κι ο αρχικός σκοπός.

Δοκιμάστε να αλλάξετε τις τιμές των public μεταβλητών ή την καμπύλη του άλματος μέσω του inspector, παρατηρώντας αντίστοιχα τις αλλαγές στη συμπεριφορά της σφαίρας. Οι αλλαγές των τιμών στα components που είναι προσκολλημένα σε ένα game object, μπορούν να αλλάζουν ακόμη κι όταν το παιχνίδι τρέχει. Σ’ αυτή την περίπτωση όμως η διακοπή του παιχνιδιού θα επαναφέρει τις αλλαγές στις αρχικές τους τιμές. Σας παροτρύνουμε να υποβάλλετε τις όποιες απορίες έχετε σχετικά με τον κώδικα, αφήνοντας σχόλιο στο site του περιοδικού. Να υπενθυμίσουμε ότι μπορείτε να βρείτε την τελευταία έκδοση του κώδικα στη σελίδα του project στο GitHub και συγκεκριμένα στο https://github.com/ikromm/project-sphere.

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

Leave a Reply

You must be logged in to post a comment.

Σύνδεση

Αρχείο δημοσιεύσεων