Kaunis koodi on ilo kirjoittaa, mutta on vaikeaa jakaa sitä iloa muiden ohjelmoijien kanssa, puhumattakaan myös ei-ohjelmoijista. Oman työpäivän ja perheen välisenä aikana olen ollut leikkimässä ajatus ohjelmointi runosta käyttäen kangasta elementtiä piirrä selaimeen. Tässä on paljon termejä kuvaamaan visuaalisia kokeiluja tietokoneessa, kuten dev-taidetta, koodin luonnosta, demosta ja interaktiivisesta taidosta, mutta lopulta asettin ohjelmointimaailman kuvaamaan tätä prosessia. Runon idea on kiillotettu proosainen kappale, joka on helposti jaettavissa, ytimekäs ja esteettinen. Se ei ole puolivalmiita käsityksiä luonnoksessa, vaan yhtenäinen kappale näytetään katsojalle heidän nautinnostaan. Runo ei ole työkalu, vaan se on tunne herättää.
Omasta nautinnosta olen lukenut kirjoja matematiikasta, laskennasta, fysiikasta ja biologiasta. Olen oppinut todella nopeasti, että kun pudotan ajatukseen, se kyllästyy ihmisiin melko nopeasti. Nähtävästi voin ottaa joitain näistä ideoista, jotka ovat kiehtovia ja antavat nopeasti ihmisille ihmettekön, vaikka eivät ymmärrä koodin taustalla olevaa teoriaa ja käsitteitä, jotka ohjaavat sitä. Sinun ei tarvitse käsitellä mitään kovaa filosofiaa tai matematiikkaa kirjoittaa ohjelmoiva runo, vain halu nähdä jotain elää ja hengittää näytöllä.
Koodi ja esimerkit, jotka olen koonnut alla, auttavat ymmärtämään, miten todella poistetaan tämä nopea ja erittäin tyydyttävä prosessi. Jos haluat seurata koodia, voit lataa lähdetiedostot täältä.
Tärkein temppu, kun todella luoda runo on pitää se kevyt ja yksinkertainen. Älä vietä kolme kuukautta rakentaa todella hienoa demoa. Sen sijaan luo 10 runoa, jotka kehittävät ajatuksen. Kirjoita kokeellinen koodi, joka on jännittävä ja älä pelkää epäonnistua.
Lyhyt katsaus, kangas on olennaisesti 2d bittikarttakuvan elementti, joka asuu DOM: ssa, joka voidaan piirtää. Piirustus voidaan tehdä joko 2d-kontekstilla tai WebGL-kontekstilla. Konteksti on JavaScript-objekti, jota käytät päästäksesi piirtotyökaluihin. JavaScript-tapahtumia, jotka ovat saatavilla kankaalle, ovat hyvin paljaita, toisin kuin SVG: lle käytettävissä olevat. Mikä tahansa tapahtuma, joka laukaistaan, on elementti kokonaisuudessaan, eikä mitään piirretty kankaalle, aivan kuin tavallinen kuvaelementti. Tässä on peruskangas esimerkki:
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);
On melko yksinkertaista päästä alkuun. Ainoa asia, joka voi olla hieman sekava, on, että konteksti on määritettävä asetuksilla kuten fillStyle, lineWidth, font ja strokeStyle ennen varsinaista vetokutsua. On helppo unohtaa päivittää tai nollata nämä asetukset ja saada joitain tahattomia tuloksia.
Ensimmäinen esimerkki kesti vain kerran ja piirsi staattisen kuvan kankaalle. Se on OK, mutta kun se todella hauskaa on, kun se päivitetään 60 kuvaa sekunnissa. Nykyaikaisilla selaimilla on sisäänrakennettu toiminto requestAnimationFrame, joka synkronoi mukautetun piirroskoodin selaimen vetosykleihin . Tämä auttaa tehokkuutta ja sileyttä. Visualisoinnin kohteena tulisi olla koodi, joka häiritsee 60 kuvaa sekunnissa.
(Huomautus tuesta: käytettävissä on joitain yksinkertaisia polyfilejä, jos tarvitset vanhempia selaimia.)
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();
Nyt kirjoitan uudestaan kaavan edellisestä koodin esimerkistä erottelevana versiona, joka on helpompi lukea.
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;
Jos haluat pelata koodia toistaiseksi, suosittelen lisäämään liikkeen y-suuntaan. Yritä muuttaa arvot sin-toiminnossa tai vaihtaa jonkin muunlaista toimintoa, jolla voit katsella, mitä tapahtuu.
Siirrä liikettä matematiikan ulkopuolella ottamalla hetki kuvitellaksesi, mitä voit tehdä eri käyttäjälaitteiden avulla liikuttaa neliön sivun ympärillä. Selaimessa on kaikenlaisia vaihtoehtoja, kuten mikrofoni, web-kamera, hiiri, näppäimistö ja peliohjain. Lisäpistokäyttöisiä vaihtoehtoja on saatavana esimerkiksi Leap Motion- tai Kinect-ohjelmalla. WebSocketsin ja palvelimen avulla voit yhdistää visualisoinnin kodinrakennukseen. Liitä mikrofoni Web Audio -ohjelmaan ja aseta pikselit äänellä. Voit jopa rakentaa liiketunnistimen verkkokamerasta ja pelotella virtuaalikalan koulua (ok olen tehnyt viimeisen Flashissa viisi tai niin vuotta sitten.)
Joten nyt sinulla on suuri ideasi, hyppäämme takaisin muutamiin esimerkkeihin. Yksi neliö on tylsä, anna ante. Ensinnäkin luomme neliötoiminnon, joka voi tehdä paljon. Me kutsumme sen Dotiksi. Yksi asia, joka auttaa liikuttavien kohteiden kanssa, on käyttää vektoreita eikä erillisiä x- ja y-muuttujia. Näissä koodinäyteissä olen vetänyt kolme.js Vector2 -luokkaa. Se on helppokäyttöinen heti vector.x: n ja vector.y: n kanssa, mutta siinä on myös joukko käteviä menetelmiä työskennellä niiden kanssa. Katso asiakirjat syvemmälle sukellukselle.
Tämä esimerkkikoodi saa hieman monimutkaisemman, koska se on vuorovaikutuksessa esineiden kanssa, mutta se on sen arvoinen. Tutustu esimerkkikoodiin nähdäksesi uuden Scene- objektin, joka hallitsee piirustuksen perusteet kankaalle. Uusi Dot- luokka saa kädensijan tähän kohtaukseen päästäkseen kaikkiin muuttujiin, kuten kankaaseen, jota se tarvitsee.
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;}
Aluksi Dotin konstruktori määrittää käyttäytymisen kokoonpanon ja asettaa joitain muuttujia käytettäväksi. Jälleen tämä käyttää kolmen.js vektoriluokan. Kun asetat 60 kuvaa sekunnissa, on tärkeätä alustaa esineet uudelleen eikä luoda uusia, kun animoidaan. Tämä syö uuteen käytettävissä olevaan muistiisi ja voi tehdä visualisointisi epäselväksi. Huomaa myös, miten Dot siirretään kopion kohtauksesta viitteenä. Tämä pitää asiat puhtaina.
Dot.prototype = {update : function() {...},draw : function() {...}}
Kaikki muu koodi asetetaan Dotin prototyyppiobjektiin, niin että jokaisella uudella luodulla Dotilla on pääsy näihin menetelmiin. Menen funktiona selityksessä.
update : function( dt ) {this.updatePosition( dt );this.draw( dt );},
Poistamme piirretyn koodin päivityskoodissani. Tämä tekee helpommaksi ylläpitää ja hienosäätää esineesi, aivan kuten MVC-kuvio erottaa ohjaus- ja näkymälogiikan. Dt- muuttuja on ajan muutos millisekunnissa viimeisen päivityspyynnön jälkeen. Nimi on kiva ja lyhyt ja tulee (älä pelkää) laskentajohdannaisia. Mitä tämä tekee erottaa liikkeesi kehysnopeuden nopeudesta. Näin et saa NES-tyyliä hidastuksia, kun asiat ovat liian monimutkaisia. Liikenne pudottaa kehyksiä, jos se toimii kovasti, mutta se pysyy samalla nopeudella.
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
Tämä toiminto on hieman outoa sen rakenteessa, mutta kätevä visualisoinnille. Se on todella kallista jakaa muisti toiminnassa. MoveDistance- muuttuja asetetaan kerran, ja sitä käytetään uudelleen aina, kun toimintoa kutsutaan.
Tätä vektoria käytetään vain uuden sijainnin laskemiseen, mutta sitä ei käytetä toiminnon ulkopuolella. Tämä on ensimmäinen vektorimataatti, jota käytetään. Juuri nyt suuntavektori kerrotaan ajan muutoksella, sitten lisätään paikkaan. Lopussa on pieni modulo-toiminto, joka pitää pisteen näytössä.
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);}
Lopuksi helppo tavara. Hanki kopio asiayhteydestä kohtausobjektista ja piirrä sitten suorakulmio (tai mitä haluat). Suorakulmat ovat todennäköisesti nopein asia, jota voit piirtää näytölle.
Tällöin lisään uusi Dot kutsumalla this.dot = uusi Dot (x, y, tämä) päävalokonstruktorissa ja sitten kohtauksen päivitysmenetelmässä lisään tämä.dot.update (dt), ja siellä on piste, joka suurentaa näytön ympäri. (Katso koko koodin lähdekoodi asiayhteydestä.)
Nyt kohtauksessa Dotin luomisen ja päivittämisen sijaan luomme ja päivitämme DotManagerin . Luomme 5000 pistettä aloittaaksemme.
function Scene() {...this.dotManager = new DotManager(5000, this);...};Scene.prototype = {...update : function( dt ) {this.dotManager.update( dt );}...};
Se on hieman hämmentävää yhdestä rivistä, joten tässä se on hajonnut kuten syntifunktion aiemmin.
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;
Getting groovy ...
Vielä yksi pikku nipistää. Mustavalkoinen on hieman dramaattinen, joten lisäämme väriä.
var hue = this.position.x / this.scene.canvas.width * 360;this.color = Utils.hslToFillStyle(hue, 50, 50, 0.5);
Tämä yksinkertainen kohde ympäröi hiiren päivitykset logiikasta muusta osasta. Se päivittää sijaintivektorin vain hiiren siirrolla. Loput esineet voivat sitten näyte hiiren sijaintivektorista, jos ne viedään viittauksen kohteeseen. Yksi huomautus, jonka jätän huomiotta täällä, on, jos kankaan leveys ei ole yksi, DOM: n pikselimittareilla eli resonoituneen kuvion tai suuremman pikselitiheyden (verkkokalvon) kankaalle tai jos kangas ei sijaitse ylävasen. Hiiren koordinaatteja on mukautettava vastaavasti.
var Scene = function() {...this.mouse = new Mouse( this );...};
Ainoa hiirelle jäänyt asia oli luoda hiiren kohde kohteen sisällä. Nyt kun meillä on hiiri, houkutellaan pisteitä siihen.
function Dot( x, y, scene ) {...this.attractSpeed = 1000 * Math.random() + 500;this.attractDistance = (150 * Math.random()) + 180;...}
Lisäsin skalaarisia arvoja pisteeseen niin, että jokainen käyttäytyy hieman eri tavalla simuloinnissa ja antaa sille hieman realismia. Pelaa näitä arvoja, jotta saat erilaisen tunnelman. Nyt houkutella hiiren menetelmällä. Kommentteja on hieman kauan.
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);}};}()
Tämä menetelmä voisi olla hieman sekava, jos et ole ajan tasalla vektori matematiikassa. Vektorit voivat olla hyvin visuaalisia, ja ne voivat auttaa, jos piirrät joitain scribbles ulos kahvia värjätään romu paperilla. Selkeänä englanniksi tämä toiminto saa hiiren ja pisteen välisen etäisyyden. Se siirtää pisteen hieman lähemmäksi pistettä sen mukaan, kuinka lähellä on jo piste ja kulunut aika. Se tekee tämän selvittämällä etäisyyden liikkua (normaali skalaariluku) ja kertomalla sitten hiirellä osoitetun pisteen normalisoituun vektoriin (vektori, jonka pituus on 1). Ok, tämä viimeinen lause ei välttämättä ole pelkkää englantia, mutta se on alku.