Güzel kod yazmanın bir neşesidir, ancak programcı olmayanlarla bahsetmemek için diğer programcılarla bu neşeyi paylaşmak zordur. Gündelik işimle aile zamanım arasındaki boş zamanlarımda, tarayıcıda çizim yapmak için tuval öğesini kullanarak bir programlama şiiri düşüncesiyle uğraştım. Bilgisayarda dev sanat, kod çizimi, demo ve etkileşimli sanat gibi görsel deneyleri tanımlamak için çok sayıda terim var , ama sonuçta bu süreci tanımlamak için programlama şiirine yerleştim. Bir şiirin ardındaki fikir, kolayca paylaşılabilir, özlü ve estetik olan, cilalanmış bir düzyazı parçasıdır. Bir eskiz kitabında yarı bitmiş bir fikir değil, izleyicilere keyif için sunulan bir parça. Bir şiir bir araç değildir, ama bir duygu uyandırmak için vardır.
Kendi zevkim için matematik, hesaplama, fizik ve biyoloji üzerine kitaplar okuyorum. Gerçekten çok çabuk öğrendim ki bir fikre ramble ettiğimde insanlar çok çabuk sıkıyor. Görsel olarak, büyüleyici bulduğum bu fikirlerden bazılarını alıp, kodun ve onu yönlendiren kavramların ardındaki teoriyi anlamadıkları halde, herkese bir şaşkınlık hissi verebilirim. Bir programlama şiiri yazmak için herhangi bir sabit felsefe ya da matematikte bir ele ihtiyaç duymazsınız, sadece bir şeyleri canlı görmek ve ekranda nefes almak arzusu.
Aşağıda bir araya getirdiğim kod ve örnekler, bu hızlı ve son derece tatmin edici sürecin nasıl çekileceğini anlamaya başlayacak. Kod ile birlikte takip etmek isterseniz Kaynak dosyaları buradan indirebilirsiniz.
Aslında bir şiir oluştururken ana hilesi onu hafif ve basit tutmaktır. Gerçekten havalı bir demoda üç ay geçirme. Bunun yerine, bir fikir geliştiren 10 şiir yaratın. Heyecan verici deneysel kod yazın ve başarısız olmaktan korkmayın.
Hızlı bir genel bakış için, tuval esasen çekilebilecek DOM'de yaşayan bir 2d bitmap görüntü öğesidir. Çizim, 2 boyutlu bir bağlam veya bir WebGL bağlamı kullanılarak yapılabilir. Bağlam, çizim araçlarına erişmek için kullandığınız JavaScript nesnesidir. Tuval için geçerli olan JavaScript olayları, SVG için mevcut olanlardan farklı olarak çok barebone. Tetiklenen herhangi bir olay, normal bir görüntü elemanı gibi, bir bütün olarak, tuval üzerine çizilen bir şey değildir. İşte temel bir tuval örneği:
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);
Başlamak için oldukça basit. Biraz kafa karıştırıcı olabilecek tek şey, bağlamın, gerçek çizim çağrısı kullanılmadan önce fillStyle, lineWidth, font ve strokeStyle gibi ayarlarla yapılandırılması gerektiğidir. Bu ayarları güncellemeyi veya sıfırlamayı ve bazı istenmeyen sonuçları almayı unutmak çok kolay.
İlk örnek sadece bir kez koştu ve tuval üzerine statik bir görüntü çizdi. Tamam, ama gerçekten eğlenceli olduğunda, saniyede 60 kare güncellendiğinde. Modern tarayıcılar, özel çizim kodunu tarayıcının çizim döngüleriyle eşzamanlayan yerleşik fonksiyon requestAnimationFrame'e sahiptir. Bu, verimlilik ve yumuşaklık açısından yardımcı olur. Bir görselleştirmenin hedefi, saniyede 60 kare boyunca uzanan kod olmalıdır.
(Destekle ilgili bir not: daha eski tarayıcıları desteklemeniz gerektiğinde bazı basit çoklu dolgular mevcuttur.)
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();
Şimdi formülü önceki kod örneğinden daha kolay okunan daha bozuk bir sürüm olarak yeniden yazacağım.
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;
Şimdiye kadar kod ile oynamak istiyorsanız, y yönünde biraz hareket eklemenizi tavsiye ederim. Günah işlevindeki değerleri değiştirmeyi deneyin veya etrafta oynamak ve ne olduğunu görmek için başka bir işleve geçin.
Matematikle harekete geçmenin ötesinde, bir sayfanın etrafındaki bir kareyi hareket ettirmek için farklı kullanıcı giriş cihazlarıyla neler yapabileceğinizi hayal etmek için bir dakikanızı ayırın. Mikrofon, web kamerası, fare, klavye ve gamepad de dahil olmak üzere tarayıcıda her türlü seçenek bulunmaktadır. Eklenti odaklı seçenekler Leap Motion veya Kinect gibi bir şeyle kullanılabilir. WebSockets'ı ve bir sunucuyu kullanarak, ev yapımı donanımlara bir görselleştirme bağlayabilirsiniz. Web Audio API'sına bir mikrofon bağlayın ve piksellerinizi sesle sürün. Hatta bir Webcam'den bir hareket sensörü oluşturabilir ve bir sanal balık okulu korkutursunuz (tamam ben son beşte beşte birini yaptım).
Şimdi senin büyük fikrin var şimdi daha fazla örnek geri atlayalım. Bir kare sıkıcı, hadi anteri kaldıralım. İlk önce, çok şey yapabilen kare bir fonksiyon yaratalım. Ona bir nokta diyelim. Hareketli nesnelerle çalışırken, x ve y değişkenlerini ayırmak yerine vektörleri kullanmak yararlı olur. Bu kod örneklerinde, üç.js Vector2 sınıfında çektim. Hemen vector.x ve vector.y ile kullanımı kolaydır, ancak aynı zamanda onlarla çalışmak için kullanışlı yöntemler de vardır. Şuna baksana dokümanlar daha derin bir dalış için.
Bu örneğin kodu biraz daha karmaşık hale geliyor çünkü nesnelerle etkileşiyor, ama buna değecek. Çizimin temellerini tuvale yöneten yeni bir Sahne nesnesini görmek için örnek koduna bakın. Yeni Dot sınıfımız, ihtiyaç duyacağı tuval bağlamı gibi değişkenlere erişmek için bu sahneyi kullanacaktır.
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 için kurucu ile başlayabilmek, davranışlarının yapılandırmasını ayarlar ve kullanılacak bazı değişkenleri ayarlar. Yine, bu üç.js vektör sınıfını kullanıyor. 60 fps'de görüntülerken, nesnelerinizi önceden başlatmanız ve animasyon yaparken yenilerini oluşturmamanız önemlidir. Bu, mevcut belleğinizi tüketir ve görselleştirmenizi bozabilir. Ayrıca, Dot'un manzaranın bir kopyasını referans olarak nasıl geçtiğine dikkat edin. Bu işleri temiz tutar.
Dot.prototype = {update : function() {...},draw : function() {...}}
Kodun geri kalanının tamamı, Dot'un prototip nesnesine ayarlanır, böylece oluşturulan her yeni Nokta'nın bu yöntemlere erişimi vardır. Açıklamada fonksiyona göre işlev göreceğim.
update : function( dt ) {this.updatePosition( dt );this.draw( dt );},
Çizim kodumu güncelleme kodumdan ayırıyorum. Bu, nesnenizi korumak ve çimdiklemeyi çok daha kolay hale getirir, çünkü MVC deseni kontrolünüzü ayırır ve mantığı görüntüler. Dt değişkeni, son güncelleme çağrısından bu yana geçen milisaniye cinsinden zamandır. Adı güzel ve kısa ve (korkma) kalkülüs türevleri geliyor. Bu, hareketinizin hızını kare hızından ayırır. Bu sayede işler çok karmaşık hale geldiğinde NES tarzı yavaşlama olmaz. Çok çalışıyorsa hareketiniz kareleri düşürecek, ancak aynı hızda kalacaktır.
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
Bu işlev yapısında biraz garip, ancak görselleştirmeler için kullanışlıdır. Hafızayı bir fonksiyona ayırmak gerçekten pahalı. MoveDistance değişkeni bir kez ayarlanır ve işlev çağrıldığında herhangi bir zamanda yeniden kullanılır.
Bu vektör sadece yeni pozisyonu hesaplamaya yardımcı olmak için kullanılır, ancak işlevin dışında kullanılmaz. Bu, kullanılan ilk vektör matematiktir. Şu anda yön vektörü zamandaki değişime göre çarpılmakta, sonra pozisyona eklenmektedir. Sonunda ekranda nokta tutmak için biraz modulo eylem devam ediyor.
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);}
Sonunda kolay şeyler. İçerik nesnesinin bağlamının bir kopyasını alın ve sonra bir dikdörtgen çizin (veya istediğiniz her şeyi). Dikdörtgenler muhtemelen ekranda çizebileceğiniz en hızlı şeydir.
Bu noktada, ana sahne yapıcısında this.dot = new Dot (x, y, this) öğesini çağırarak yeni bir Nokta ekledim ve sahne güncelleme yönteminde this.dot.update (dt) ekledim. Ekran etrafında yakınlaştırma bir nokta. (Bağlamda tam kodun kaynak kodunu kontrol edin.)
Şimdi sahnede, bir Dot yaratıp güncellemek yerine DotManager'ı yaratıp güncelleriz. Başlamak için 5000 nokta oluşturacağız.
function Scene() {...this.dotManager = new DotManager(5000, this);...};Scene.prototype = {...update : function( dt ) {this.dotManager.update( dt );}...};
Bir satırda biraz kafa karıştırıcı, bu yüzden burada daha önce gelen günah işlevi gibi bozuldu.
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;
İyileşmek…
Bir daha küçük çimdik. Monokrom biraz sıkıcı, bu yüzden biraz renk katalım.
var hue = this.position.x / this.scene.canvas.width * 360;this.color = Utils.hslToFillStyle(hue, 50, 50, 0.5);
Bu basit nesne, fare güncellemelerinin mantığını sahnenin geri kalanından kapsüller. Sadece bir fare hareketi ile pozisyon vektörünü günceller. Nesnelerin geri kalanı, nesneye bir referans geçtikten sonra farenin pozisyon vektöründen numune alabilirler. Burada görmezden geldiğim bir uyarı, kanvasın genişliğinin DOM'ın piksel boyutlarıyla, yani yeniden boyutlandırılan bir tuval veya daha yüksek bir piksel yoğunluğu (retina) tuvaliyle bire bir olup olmaması veya tuvalin Sol üst. Farenin koordinatlarının buna göre ayarlanması gerekir.
var Scene = function() {...this.mouse = new Mouse( this );...};
Fare için kalan tek şey, sahnenin içindeki fare nesnesini yaratmaktı. Şimdi bir fareye sahip olduğumuza göre, noktaları ona çekelim.
function Dot( x, y, scene ) {...this.attractSpeed = 1000 * Math.random() + 500;this.attractDistance = (150 * Math.random()) + 180;...}
Ben noktaya bazı skaler değerler ekledim, böylece her biri simülasyonda biraz farklı gerçekçilik vermesi için biraz farklı davrandı. Farklı bir fikir edinmek için bu değerlerle oynayın. Şimdi çekmek fare yöntemi. Yorumlar biraz uzun.
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);}};}()
Vektör matematiğinize uymuyorsanız, bu yöntem biraz kafa karıştırıcı olabilir. Vektörler çok görsel olabilir ve eğer kahve lekeli bir kâğıt parçasının üzerine bir karalama çizerseniz yardımcı olabilirler. Düz İngilizce'de, bu işlev fare ile nokta arasındaki mesafeyi elde etmektir. Daha sonra noktaya ne kadar yakın olduğuna ve geçen zaman miktarına bağlı olarak noktayı biraz daha yakın noktaya hareket ettirir. Bu, hareket mesafesini (normal bir skaler sayı) belirleyerek ve ardından, fareye işaret eden noktanın normalleştirilmiş vektörü (1 uzunluğundaki bir vektör) ile çarparak yapar. Tamam, bu son cümle mutlaka İngilizce değildi, ama bu bir başlangıç.