Ο όμορφος κώδικας είναι μια χαρά που γράφετε, αλλά είναι δύσκολο να μοιραστεί αυτή τη χαρά με άλλους προγραμματιστές, για να μην αναφέρουμε με τους μη προγραμματιστές. Στο ελεύθερο μου χρονικό διάστημα μεταξύ της καθημερινής μου δουλειάς και της οικογενειακής μου εποχής, έχω παίξει γύρω μου με την ιδέα ενός ποίηματος προγραμματισμού που χρησιμοποιεί το στοιχείο του καμβά για να σχεδιάσει το πρόγραμμα περιήγησης. Υπάρχουν πολλοί όροι εκεί έξω για να περιγράψουμε τα οπτικά πειράματα στον υπολογιστή, όπως το dev art, το sketch κώδικα, το demo και τη διαδραστική τέχνη, αλλά τελικά έχω εγκαταστήσει το ποίημα προγραμματισμού για να περιγράψω αυτή τη διαδικασία. Η ιδέα πίσω από ένα ποίημα είναι ένα στιλβωμένο κομμάτι της πεζογραφίας που είναι εύκολα κατανοητό, συνοπτικό και αισθητικό. Δεν είναι μια ημιτελής ιδέα σε ένα σκίτσο, αλλά ένα συνεκτικό κομμάτι που παρουσιάζεται στον θεατή για την απόλαυσή τους. Ένα ποίημα δεν είναι ένα εργαλείο, αλλά υπάρχει για να προκαλέσει ένα συναίσθημα.
Για τη δική μου απόλαυση διαβάζω βιβλία για τα μαθηματικά, τον υπολογισμό, τη φυσική και τη βιολογία. Έμαθα πολύ γρήγορα ότι όταν μετακομίζω σε μια ιδέα, οι άνθρωποι φεύγουν πολύ γρήγορα. Οπτικά μπορώ να πάρω μερικές από αυτές τις ιδέες που βρίσκω συναρπαστικές και να δώσω γρήγορα σε κάποιον μια αίσθηση απόλαυσης, ακόμη και αν δεν κατανοούν τη θεωρία πίσω από τον κώδικα και τις έννοιες που τον οδηγούν. Δεν χρειάζεστε μια λαβή σε οποιαδήποτε σκληρή φιλοσοφία ή μαθηματικά για να γράψετε ένα ποίημα προγραμματισμού, απλώς μια επιθυμία να δείτε κάτι ζωντανό και να αναπνεύσετε στην οθόνη.
Ο κώδικας και τα παραδείγματα που έχω συγκεντρώσει παρακάτω θα ξεκινήσουν μια κατανόηση για το πώς να βγάλουμε πραγματικά αυτή τη γρήγορη και εξαιρετικά ικανοποιητική διαδικασία. Εάν θέλετε να ακολουθήσετε μαζί με τον κώδικα που μπορείτε κατεβάστε εδώ τα αρχεία προέλευσης.
Το κύριο κόλπο όταν δημιουργείτε πραγματικά ένα ποίημα είναι να το κρατήσετε ελαφρύ και απλό. Μην περάσετε τρεις μήνες δημιουργώντας ένα πραγματικά δροσερό demo. Αντ 'αυτού, δημιουργήστε 10 ποιήματα που εξελίσσουν μια ιδέα. Γράψτε πειραματικό κώδικα που είναι συναρπαστικό και μην φοβάστε να αποτύχετε.
Για μια γρήγορη επισκόπηση, ο καμβάς είναι ουσιαστικά ένα στοιχείο εικόνας 2d bitmap που ζει στο DOM που μπορεί να σχεδιαστεί. Το σχεδιάγραμμα μπορεί να γίνει χρησιμοποιώντας ένα περιβάλλον 2d ή ένα περιβάλλον WebGL. Το πλαίσιο είναι το αντικείμενο JavaScript που χρησιμοποιείτε για να αποκτήσετε πρόσβαση στα εργαλεία σχεδίασης. Τα συμβάντα JavaScript που είναι διαθέσιμα για καμβά είναι πολύ barebones, σε αντίθεση με αυτά που είναι διαθέσιμα για το SVG. Οποιοδήποτε γεγονός που ενεργοποιείται είναι για το στοιχείο στο σύνολό του και όχι για κάτι που έχει σχεδιαστεί στον καμβά, ακριβώς όπως ένα κανονικό στοιχείο εικόνας. Ακολουθεί ένα βασικό παράδειγμα καμβά:
var canvas = document.getElementById('example-canvas');var context = canvas.getContext('2d');//Draw a blue rectanglecontext.fillStyle = '#91C0FF';context.fillRect(100, // x100, // y400, // width200 // height);//Draw some textcontext.fillStyle = '#333';context.font = "18px Helvetica, Arial";context.textAlign = 'center';context.fillText("The wonderful world of canvas", // text300, // x200 // y);
Είναι πολύ εύκολο να ξεκινήσετε. Το μόνο πράγμα που θα μπορούσε να είναι λίγο συγκεχυμένο είναι ότι το πλαίσιο πρέπει να ρυθμιστεί με τις ρυθμίσεις όπως fillStyle, lineWidth, γραμματοσειρά και strokeStyle πριν χρησιμοποιηθεί η πραγματική κλήση κλήσης. Είναι εύκολο να ξεχάσετε να ενημερώσετε ή να επαναφέρετε αυτές τις ρυθμίσεις και να πάρετε κάποια ακούσια αποτελέσματα.
Το πρώτο παράδειγμα έτρεξε μόνο μία φορά και εφάρμοσε μια στατική εικόνα στον καμβά. Αυτό είναι εντάξει, αλλά όταν πραγματικά διασκέδαση είναι όταν ενημερώνεται σε 60 καρέ ανά δευτερόλεπτο. Τα σύγχρονα προγράμματα περιήγησης έχουν την ενσωματωμένη λειτουργία requestAnimationFrame που συγχρονίζει τον προσαρμοσμένο κώδικα σχεδίασης με τους κύκλους κλήσης του προγράμματος περιήγησης. Αυτό βοηθά από την άποψη της αποδοτικότητας και της ομαλότητας. Ο στόχος μιας απεικόνισης θα πρέπει να είναι κώδικας που βουίζει μαζί στα 60 καρέ ανά δευτερόλεπτο.
(Σημείωση σχετικά με την υποστήριξη: υπάρχουν διαθέσιμες κάποιες απλές πολυχρηματοδοτήσεις, αν χρειαστεί να υποστηρίξετε παλαιότερα προγράμματα περιήγησης.)
var canvas = document.getElementById('example-canvas');var context = canvas.getContext('2d');var counter = 0;var rectWidth = 40;var rectHeight = 40;var xMovement;//Place rectangle in the middle of the screenvar y = ( canvas.height / 2 ) - ( rectHeight / 2 );context.fillStyle = '#91C0FF';function draw() {//There are smarter ways to increment time, but this is for demonstration purposescounter++;//Cool math below. More explanation in the text following the code.xMovement = Math.sin(counter / 25) * canvas.width * 0.4 + canvas.width / 2 - rectWidth / 2;//Clear the previous drawing resultscontext.clearRect(0, 0, canvas.width, canvas.height);//Actually draw on the canvascontext.fillRect(xMovement,y,rectWidth,rectHeight);//Request once a new animation frame is available to call this function againrequestAnimationFrame( draw );}draw();
Τώρα θα ξαναγράψω τον τύπο μου από το προηγούμενο παράδειγμα κώδικα ως μια πιο αναλυτική έκδοση που είναι πιο εύκολη στην ανάγνωση.
var a = 1 / 25, //Make the oscillation happen a lot slowerx = counter, //Move along the graph a little bit each time draw() is calledb = 0, //No need to adjust the graph up or downc = width * 0.4, //Make the oscillation as wide as a little less than half the canvasd = canvas.width / 2 - rectWidth / 2; //Tweak the position of the rectangle to be centeredxMovement = Math.sin( a * x + b ) * c + d;
Εάν θέλετε να παίξετε με τον κώδικα μέχρι τώρα, θα πρότεινα να προσθέσετε κάποια κίνηση στην κατεύθυνση y. Δοκιμάστε να αλλάξετε τις τιμές στη συνάρτηση αμαρτίας ή να μεταβείτε σε κάποια άλλη λειτουργία για να παίξετε γύρω και να δείτε τι συμβαίνει.
Πέρα από την κίνηση με τα μαθηματικά, αφιερώστε μια στιγμή για να φανταστείτε τι μπορείτε να κάνετε με διαφορετικές συσκευές εισόδου χρηστών για να μετακινήσετε ένα τετράγωνο γύρω από μια σελίδα. Υπάρχουν όλα τα είδη των διαθέσιμων επιλογών στο πρόγραμμα περιήγησης, όπως το μικρόφωνο, η κάμερα web, το ποντίκι, το πληκτρολόγιο και το gamepad. Επιπρόσθετες επιλογές που βασίζονται σε προσθήκες είναι διαθέσιμες με κάτι σαν το Leap Motion ή το Kinect. Χρησιμοποιώντας το WebSockets και ένα διακομιστή θα μπορούσατε να συνδέσετε μια απεικόνιση με το σπίτι-κατασκευασμένο υλικό. Συνδέστε ένα μικρόφωνο στο API Web Audio και οδηγείτε τα εικονοστοιχεία σας με ήχο. Μπορείτε ακόμη να χτίσετε έναν αισθητήρα κίνησης από μια κάμερα Web και να τρομάξετε μια σχολή εικονικών ψαριών (εντάξει έκανα το τελευταίο στο Flash πέντε περίπου χρόνια πριν).
Έτσι τώρα που έχετε τη μεγάλη σας ιδέα, ας γυρίσουμε πίσω σε μερικά ακόμα παραδείγματα. Ένα τετράγωνο είναι βαρετό, ας πάμε μέχρι το ante. Αρχικά, ας δημιουργήσουμε μια τετραγωνική λειτουργία που μπορεί να κάνει πολλά. Θα το ονομάσουμε Dot. Ένα πράγμα που βοηθάει όταν εργάζεστε με κινούμενα αντικείμενα είναι να χρησιμοποιήσετε διανύσματα παρά να διαχωρίσετε τις μεταβλητές x και y. Σε αυτά τα δείγματα κώδικα έχω τραβήξει στην κλάση three.js Vector2. Είναι εύκολο να το χρησιμοποιήσετε αμέσως με vector.x και vector.y, αλλά έχει επίσης μια δέσμη εύχρηστων μεθόδων για να συνεργαστείτε μαζί τους. Ρίξε μια ματιά στο τα έγγραφα για βαθύτερη κατάδυση.
Ο κώδικας αυτού του παραδείγματος γίνεται λίγο πιο πολύπλοκος επειδή αλληλεπιδρά με αντικείμενα, αλλά αξίζει τον κόπο. Ελέγξτε τον κώδικα παραδειγμάτων για να δείτε ένα νέο αντικείμενο Σκηνής που διαχειρίζεται τα βασικά του σχεδίου στον καμβά. Η νέα μας κλάση Dot θα πάρει μια λαβή σε αυτή τη σκηνή για να αποκτήσει πρόσβαση σε οποιεσδήποτε μεταβλητές όπως το πλαίσιο του καμβά που θα χρειαστεί.
function Dot( x, y, scene ) {var speed = 0.5;this.color = '#000000';this.size = 10;this.position = new THREE.Vector2(x,y);this.direction = new THREE.Vector2(speed * Math.random() - speed / 2,speed * Math.random() - speed / 2);this.scene = scene;}
Αρχικά, ο κατασκευαστής για το Dot ρυθμίζει τη διαμόρφωση της συμπεριφοράς του και ορίζει κάποιες μεταβλητές για χρήση. Και πάλι, αυτό χρησιμοποιεί την τάξη φορέα three.js. Όταν κάνετε απόδοση σε 60fps, είναι σημαντικό να προ-αρχικοποιήσετε τα αντικείμενά σας και να μην δημιουργήσετε νέα αντικείμενα ενώ κινείστε. Αυτό τρώει στη διαθέσιμη μνήμη σας και μπορεί να κάνει την εμφάνισή σας ασταθής. Επίσης, παρατηρήστε πώς πέρασε το Dot ένα αντίγραφο της σκηνής με αναφορά. Αυτό διατηρεί τα πράγματα καθαρά.
Dot.prototype = {update : function() {...},draw : function() {...}}
Όλος ο υπόλοιπος κώδικας θα οριστεί στο πρωτότυπο αντικείμενο του Dot έτσι ώστε κάθε νέα Dot που δημιουργείται να έχει πρόσβαση σε αυτές τις μεθόδους. Θα πάω να λειτουργήσω με τη συνάρτηση στην εξήγηση.
update : function( dt ) {this.updatePosition( dt );this.draw( dt );},
Διαχωρίζω τον κωδικό κλήσης από τον κωδικό ενημέρωσης. Αυτό το καθιστά πολύ πιο εύκολο να διατηρήσετε και να τροποποιήσετε το αντικείμενο, όπως και το μοτίβο MVC διαχωρίζει τη λογική ελέγχου και προβολής σας. Η μεταβλητή dt είναι η μεταβολή του χρόνου σε χιλιοστά του δευτερολέπτου από την τελευταία κλήση ενημέρωσης. Το όνομα είναι ωραίο και σύντομο και προέρχεται από (δεν φοβόμαστε) παράγωγα λογισμού. Αυτό που κάνει αυτό διαχωρίζει την κίνηση σας από την ταχύτητα του ρυθμού καρέ. Με αυτόν τον τρόπο δεν έχετε επιβραδύνσεις στυλ NES όταν τα πράγματα είναι πολύ περίπλοκα. Η κίνηση σας θα μειώσει τα πλαίσια αν εργάζεται σκληρά, αλλά θα παραμείνει με την ίδια ταχύτητα.
updatePosition : function() {//This is a little trick to create a variable outside of the render loop//It's expensive to allocate memory inside of the loop.//The variable is only accessible to the function below.var moveDistance = new THREE.Vector2();//This is the actual functionreturn function( dt ) {moveDistance.copy( this.direction );moveDistance.multiplyScalar( dt );this.position.add( moveDistance );//Keep the dot on the screenthis.position.x = (this.position.x + this.scene.canvas.width) % this.scene.canvas.width;this.position.y = (this.position.y + this.scene.canvas.height) % this.scene.canvas.height;}}(), //Note that this function is immediately executed and returns a different function
Αυτή η λειτουργία είναι λίγο περίεργη στη δομή της, αλλά είναι πρακτική για απεικονίσεις. Είναι πραγματικά ακριβό να διαθέσετε μνήμη σε μια λειτουργία. Η μεταβλητή moveDistance ορίζεται μία φορά και επαναχρησιμοποιείται οποτεδήποτε καλείται η λειτουργία.
Αυτό το διάνυσμα χρησιμοποιείται μόνο για τον υπολογισμό της νέας θέσης, αλλά δεν χρησιμοποιείται εκτός της λειτουργίας. Αυτό είναι το πρώτο μαθηματικό διάνυσμα που χρησιμοποιείται. Αυτή τη στιγμή ο διάνυσμα κατεύθυνσης πολλαπλασιάζεται με την αλλαγή στο χρόνο, και στη συνέχεια προστίθεται στη θέση. Στο τέλος υπάρχει μια μικρή δράση modulo για να διατηρηθεί η κουκκίδα στην οθόνη.
draw : function(dt) {//Get a short variable name for conveniencevar ctx = this.scene.context;ctx.beginPath();ctx.fillStyle = this.color;ctx.fillRect(this.position.x, this.position.y, this.size, this.size);}
Τελικά το εύκολο πράγμα. Πάρτε ένα αντίγραφο του πλαισίου από το αντικείμενο της σκηνής και, στη συνέχεια, σχεδιάστε ένα ορθογώνιο (ή ό, τι θέλετε). Τα ορθογώνια είναι ίσως το πιο γρήγορο πράγμα που μπορείτε να σχεδιάσετε στην οθόνη.
Σε αυτό το σημείο προσθέτω ένα νέο Dot καλώντας this.dot = new Dot (x, y, this) στον κύριο κατασκευαστή σκηνής και έπειτα στη μέθοδο ενημέρωσης σκηνής προσθέτω αυτό το.dot.update (dt) και υπάρχει μια κουκίδα με ζουμ γύρω από την οθόνη. (Ανατρέξτε στον πηγαίο κώδικα για τον πλήρη κωδικό στο πλαίσιο.)
Τώρα στη σκηνή, αντί να δημιουργούμε και να ενημερώνουμε ένα Dot , δημιουργούμε και ενημερώνουμε το DotManager . Θα δημιουργήσουμε 5000 κουκίδες για να ξεκινήσετε.
function Scene() {...this.dotManager = new DotManager(5000, this);...};Scene.prototype = {...update : function( dt ) {this.dotManager.update( dt );}...};
Είναι λίγο συγκεχυμένη σε μια γραμμή, έτσι εδώ είναι κατακερματισμένη όπως η λειτουργία αμαρτίας από νωρίτερα.
var a = 1 / 500, //Make the oscillation happen a lot slowerx = this.scene.currTime, //Move along the graph a little bit each time draw() is calledb = this.position.x / this.scene.canvas.width * 4, //No need to adjust the graph up or downc = 20, //Make the oscillation as wide as a little less than half the canvasd = 0; //Tweak the position of the rectangle to be centeredxMovement = Math.sin( a * x + b ) * c + d;
Να πάρει groovy ...
Ένα ακόμα μικρό τσίμπημα. Το μονόχρωμο είναι λίγο αδύνατο, οπότε ας προσθέσουμε λίγο χρώμα.
var hue = this.position.x / this.scene.canvas.width * 360;this.color = Utils.hslToFillStyle(hue, 50, 50, 0.5);
Αυτό το απλό αντικείμενο ενσωματώνει τη λογική των ενημερώσεων του ποντικιού από την υπόλοιπη σκηνή. Ενημερώνει μόνο το διάνυσμα θέσης σε μια κίνηση του ποντικιού. Τα υπόλοιπα αντικείμενα μπορούν στη συνέχεια να κάνουν δειγματοληψία από το διάνυσμα θέσης του ποντικιού αν έχουν περάσει μια αναφορά στο αντικείμενο. Μια προειδοποίηση που αγνοώ εδώ είναι αν το πλάτος του καμβά δεν είναι ένα προς ένα με τις διαστάσεις εικονοστοιχείων του DOM, δηλαδή έναν καμβά με αντοχή σε μεγέθυνση ή έναν καμβά υψηλότερης πυκνότητας εικονοστοιχείου (αμφιβληστροειδή) ή αν ο καμβάς δεν βρίσκεται στο πάνω αριστερά. Οι συντεταγμένες του ποντικιού θα πρέπει να προσαρμοστούν αναλόγως.
var Scene = function() {...this.mouse = new Mouse( this );...};
Το μόνο που έμεινε για το ποντίκι ήταν να δημιουργήσει το αντικείμενο του ποντικιού μέσα στη σκηνή. Τώρα που έχουμε ένα ποντίκι, ας προσελκύσουμε τις κουκίδες σε αυτό.
function Dot( x, y, scene ) {...this.attractSpeed = 1000 * Math.random() + 500;this.attractDistance = (150 * Math.random()) + 180;...}
Έχω προσθέσει κάποιες κλιμακωτές τιμές στην κουκίδα έτσι ώστε ο καθένας να συμπεριφέρεται λίγο διαφορετικά στη προσομοίωση για να δώσει λίγο ρεαλισμό. Παίξτε γύρω με αυτές τις αξίες για να πάρετε μια διαφορετική αίσθηση. Τώρα στη μέθοδο προσέλκυσης ποντικιού. Είναι λίγο μακρύς με τα σχόλια.
attractMouse : function() {//Again, create some private variables for this methodvar vectorToMouse = new THREE.Vector2(),vectorToMove = new THREE.Vector2();//This is the actual public methodreturn function(dt) {var distanceToMouse, distanceToMove;//Get a vector that represents the x and y distance from the dot to the mouse//Check out the three.js documentation for more information on how these vectors workvectorToMouse.copy( this.scene.mouse.position ).sub( this.position );//Get the distance to the mouse from the vectordistanceToMouse = vectorToMouse.length();//Use the individual scalar values for the dot to adjust the distance movedmoveLength = dt * (this.attractDistance - distanceToMouse) / this.attractSpeed;//Only move the dot if it's being attractedif( moveLength > 0 ) {//Resize the vector to the mouse to the desired move lengthvectorToMove.copy( vectorToMouse ).divideScalar( distanceToMouse ).multiplyScalar( moveLength );//Go ahead and add it to the current position now, rather than in the draw callthis.position.add(vectorToMove);}};}()
Αυτή η μέθοδος θα μπορούσε να είναι λίγο συγκεχυμένη αν δεν είστε ενημερωμένοι στο διάνυσμα μαθηματικών σας. Οι διανύσματα μπορούν να είναι πολύ οπτικοί και μπορούν να σας βοηθήσουν αν τραβήξετε μερικά μουντζούρες έξω σε ένα χαρτί που χρωματίζεται με καφέ. Σε απλή αγγλική γλώσσα, αυτή η λειτουργία παίρνει την απόσταση μεταξύ του ποντικιού και της κουκκίδας. Στη συνέχεια, μετακινεί την κουκίδα λίγο πιο κοντά στην κουκίδα με βάση το πόσο κοντά είναι ήδη στην κουκίδα και το χρονικό διάστημα που έχει περάσει. Αυτό επιτυγχάνεται με την εξεύρεση της απόστασης που πρέπει να μετακινηθεί (κανονικός κλιμακωτός αριθμός) και, στη συνέχεια, πολλαπλασιάζοντας αυτήν με τον κανονικοποιημένο φορέα (ένα διάνυσμα με μήκος 1) της κουκκίδας που δείχνει προς το ποντίκι. Εντάξει, αυτή η τελευταία πρόταση δεν ήταν απαραιτήτως καθαρή αγγλική, αλλά είναι μια αρχή.