Krásný kód je radost psát, ale je obtížné sdílet tuto radost s ostatními programátory, nemluvě o programátorů. Ve volném čase mezi svou denní a rodinnou dobou jsem si hrál s myšlenkou programové básně s použitím elementu plátna pro kreslení prohlížeče. Tam je spousta termínů tam popisovat vizuální experimenty na počítači takový jak dev umění, skica kódu, demo, a interaktivní umění, ale nakonec jsem se usadil na programování básni popisovat tento proces. Myšlenka básně je leštěná próza, která je snadno sdílená, stručná a estetická. Nejde o napůl dokončenou myšlenku v náčrtu skic, ale souhrnný kus, který se divákovi představí pro jejich zábavu. Báseň není nástroj, ale existuje k vyvolání emocí.
Pro svůj vlastní zážitek jsem četl knihy o matematice, výpočtech, fyzice a biologii. Naučila jsem se opravdu rychle, že když vyrazím na nějakou myšlenku, lidem je dost rychle. Vizuálně můžu vzít některé z těchto myšlenek, které považuji za fascinující, a rychle dát někomu pocit zázraku, i když nerozumí teorii, která stojí za kódem a pojmy, které ho řídí. Nepotřebujete rukojeť na žádnou tvrdou filozofii ani matematiku, abyste napsali programovou báseň, jen touhu vidět něco živého a dýchat na obrazovce.
Kód a příklady, které jsem dal dohromady níže, začne chápat, jak vlastně vytáhnout tento rychlý a vysoce uspokojující proces. Pokud byste chtěli dodržovat kód, můžete zde stahujte zdrojové soubory.
Hlavním trikem při vytváření básně je udržet ji lehké a jednoduché. Neztrácejte tři měsíce a budujte jednu opravdu skvělou ukázku. Místo toho vytvořte 10 básní, které vyvíjejí nápad. Napište experimentální kód, který je vzrušující a nebojte se, aby selhal.
Pro rychlý přehled je plátno v podstatě 2m bitmapový obrazový prvek, který žije v doméně DOM, na který lze vložit. Kreslení lze provést pomocí kontextu 2d nebo kontextu WebGL. Kontext je objekt JavaScript, který používáte k získání přístupu k nástrojům pro kreslení. Události jazyka JavaScript, které jsou k dispozici pro plátno, jsou velmi barebones, na rozdíl od těch, které jsou k dispozici pro SVG. Každá událost, která je spuštěna, je pro prvek jako celek, nikoli nic nakresleného na plátně, stejně jako normální obrazový prvek. Zde je příklad základního plátna:
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);
Je to docela jednoduché začít. Jediná věc, která by mohla být trochu matoucí, je, že kontext musí být nakonfigurován pomocí nastavení jako fillStyle, lineWidth, font a strokeStyle před použitím skutečného volání. Je snadné zapomenout na aktualizace nebo obnovení těchto nastavení a získat některé neúmyslné výsledky.
První příklad běžel pouze jednou a nakreslil statický obraz na plátno. To je v pořádku, ale když se opravdu baví, je to, když je aktualizován rychlostí 60 snímků za sekundu. Moderní prohlížeče mají vestavěnou funkci requestAnimationFrame, která synchronizuje vlastní kreslicí kód s výkresovými cykly prohlížeče. To pomáhá z hlediska efektivity a hladkosti. Cílem vizualizace by mělo být kód, který bzučí po 60 snímcích za sekundu.
(Poznámka k podpoře: k dispozici jsou některé jednoduché polyfily, pokud potřebujete podporovat starší prohlížeče.)
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();
Nyní přepsám vzorec z předchozího příkladu kódu jako více rozloženou verzi, která je snadněji čitelná.
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;
Pokud chcete hrát s kódem tak daleko, doporučuji přidat nějaký pohyb ve směru y. Zkuste změnit hodnoty ve funkci hříchu nebo přepnout na jiný druh funkce, abyste si mohli zahrát a uvidíte, co se stane.
Kromě jízdy s matematikou si chvíli představte, co můžete udělat s různými uživatelskými vstupními zařízeními, abyste mohli přesunout čtverec kolem stránky. V prohlížeči jsou k dispozici všechny možnosti včetně mikrofonu, webové kamery, myši, klávesnice a herní plochy. K dispozici jsou další doplňky založené na pluginuch, například Leap Motion nebo Kinect. Pomocí serveru WebSockets a serveru můžete připojit vizualizaci k hardwaru doma. Připojte mikrofon k rozhraním Web Audio API a pohněte pixely zvukem. Můžete dokonce postavit snímač pohybu z webové kamery a vyděsit školu virtuálních ryb (ok, že jsem udělal poslední ve Flash asi pět let předtím.)
Takže teď, když máte svůj velký nápad, jděte zpět do několika dalších příkladů. Jeden čtverec je nudný, pojďme ante. Nejdříve vytvoříme čtvercovou funkci, která dokáže hodně. Říkáme tomu Dot. Jedna věc, která pomáhá při práci s pohyblivými objekty, je použít vektory namísto oddělené proměnné x a y. V těchto ukázkách kódu jsem vytažil třídu ve třídě Vector2. Je snadno použitelné hned s vector.x a vector.y, ale má také spoustu užitečných metod, jak s nimi pracovat. Podívejte se na dokumenty pro hlubší ponor.
Kód tohoto příkladu je trochu složitější, protože interaguje s objekty, ale bude to stát za to. Podívejte se na kód příkladu a podívejte se na nový objekt scény, který spravuje základy kresby na plátno. Naše nová třída Dot získá přístup k této scéně, aby získala přístup ke všem proměnným, jako je kontext plátna, který bude potřebovat.
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;}
Začneme-li konstruktor Dot nastaví konfiguraci svého chování a nastaví některé proměnné, které budou používat. Opět platí, že používá třídě vektoru three.js. Při vykreslování rychlostí 60 snímků za sekundu je důležité předinicializovat vaše objekty a během animace vytvářet nové. To je do vaší dostupné paměti a může způsobit, že se vaše vizualizace roztříští. Také si všimněte, jak Dot předal kopii scény odkazem. To udržuje věci čisté.
Dot.prototype = {update : function() {...},draw : function() {...}}
Veškerý zbytek kódu bude nastaven na prototyp objektu Dot tak, aby každý nový Dot, který se vytvoří, má přístup k těmto metodám. Ve vysvětlení se budem věnovat funkci podle funkce.
update : function( dt ) {this.updatePosition( dt );this.draw( dt );},
Odděluji svůj kreslicí kód od svého aktualizačního kódu. Díky tomu je mnohem jednodušší udržovat a vyladit váš objekt, podobně jako model MVC odděluje vaši kontrolu a logiku zobrazení. Proměnná dt je změna v čase v milisekundách od posledního volání aktualizace. Jméno je hezké a krátké a pochází z derivátů počtu (nemějte strach). Co to dělá, odděluje váš pohyb od rychlosti rychlosti snímků. Tímto způsobem nedochází ke zpomalení stylu NES, když se věci příliš komplikují. Váš pohyb bude spadat rámy, pokud bude tvrdě pracovat, ale zůstane stejnou rychlostí.
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
Tato funkce je trochu zvláštní ve své struktuře, ale je užitečná pro vizualizaci. Je skutečně nákladné přidělit paměť ve funkci. Proměnná moveDistance je jednou nastavena a znovu použita kdykoli se funkce volá.
Tento vektor se používá pouze k výpočtu nové pozice, ale nepoužívá se mimo tuto funkci. Toto je první vektorová matematika, která se používá. V tuto chvíli je směrný vektor vynásoben změnou v čase a poté přidán do pozice. Na konci je trochu modulo akce pokračuje udržet bod na obrazovce.
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);}
Konečně snadné věci. Získat kopii kontextu z objektu scény a nakreslete obdélník (nebo co chcete). Obdélníky jsou pravděpodobně nejrychlejší věc, kterou můžete kreslit na obrazovce.
V tomto okamžiku přidám nový Dot voláním this.dot = new Dot (x, y, this) v hlavním konstruktéru scény a potom v metodě aktualizace scény přidám tento.dot.update (dt) a tam je bodka se přiblížila kolem obrazovky. (Podívejte se na zdrojový kód pro celý kód v kontextu.)
Nyní ve scéně, než vytvoření a aktualizaci Dot , vytváříme a aktualizujeme DotManager . Vytvoříme 5 000 bodů, abychom mohli začít.
function Scene() {...this.dotManager = new DotManager(5000, this);...};Scene.prototype = {...update : function( dt ) {this.dotManager.update( dt );}...};
Je to trochu zmatené v jednom řádku, takže tady je rozděleno jako funkce hříchu od dříve.
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;
Získání groovy ...
Ještě jeden malý krok. Monochromatický je trochu hrubý, takže přidáme nějakou barvu.
var hue = this.position.x / this.scene.canvas.width * 360;this.color = Utils.hslToFillStyle(hue, 50, 50, 0.5);
Tento jednoduchý objekt zapouzdřuje logiku aktualizací myší ze zbytku scény. Aktualizuje pouze vektor polohy na pohyb myši. Zbývající objekty pak mohou vzorkovat z vektoru polohy myši, pokud jsou předány odkaz na objekt. Jedna poznámka, kterou zde ignoruji, je, pokud šířka plátna není jedna k jedné s dimenzemi pixelů DOM, tj. Plátna citlivě změněného plátna nebo plátna s vyšší hustotou pixelů (retina), nebo pokud plátno není umístěno na vlevo nahoře. Souřadnice myši budou muset být odpovídajícím způsobem upraveny.
var Scene = function() {...this.mouse = new Mouse( this );...};
Jediná věc, která zůstala pro myši, bylo vytvořit objekt myši uvnitř scény. Nyní, když máme myš, přitahujeme na ni tečky.
function Dot( x, y, scene ) {...this.attractSpeed = 1000 * Math.random() + 500;this.attractDistance = (150 * Math.random()) + 180;...}
Přidala jsem do tečky nějaké skalární hodnoty, takže se každý z nich chová v simulaci trochu jinak, aby získal trochu realismu. Zahrajte si s těmito hodnotami, abyste získali jiný pocit. Nyní na metodu přitahování myší. Je to trochu dlouhá s komentáři.
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);}};}()
Tato metoda by mohla být trochu matoucí, pokud nejste aktuální ve své vektorové matematice. Vektory mohou být velmi vizuální a mohou vám pomoct, pokud na kávu zbarvený šrot papíru nakreslíte nějaké čmáranice. V obyčejné angličtině tato funkce získává vzdálenost mezi myší a tečkou. To je pak přesunout tečku trochu blíže k bodu podle toho, jak blízko je to k bodu a čas, který uplynul. Dělá to tím, že zjistí vzdálenost, kterou se má pohybovat (normální skalární číslo), a pak ho vynásobíme normalizovaným vektorem (vektor s délkou 1) bodky směřující k myši. Ok, poslední věta nebyla nutně čistá angličtina, ale je to začátek.