流体模拟
HTML
CSS
JavaScript

'use strict'; const canvas = document.getElementsByTagName('canvas')[0]; canvas.width = canvas.clientWidth; canvas.height = canvas.clientHeight; const params = { alpha: false, depth: false, stencil: false, antialias: false }; let gl = canvas.getContext('webgl2', params); const isWebGL2 = !!gl; if (!isWebGL2) { gl = canvas.getContext('webgl', params) || canvas.getContext('experimental-webgl', params); } gl.clearColor(0.0, 0.0, 0.0, 1.0); const halfFloat = gl.getExtension('OES_texture_half_float'); let support_linear_float = gl.getExtension('OES_texture_half_float_linear'); if (isWebGL2) { gl.getExtension('EXT_color_buffer_float'); support_linear_float = gl.getExtension('OES_texture_float_linear'); } const TEXTURE_DOWNSAMPLE = 1; const DENSITY_DISSIPATION = 0.98; const VELOCITY_DISSIPATION = 0.99; const SPLAT_RADIUS = 0.005; const CURL = 30; const PRESSURE_ITERATIONS = 25; class GLProgram { constructor (vertexShader, fragmentShader) { this.uniforms = {}; this.program = gl.createProgram(); gl.attachShader(this.program, vertexShader); gl.attachShader(this.program, fragmentShader); gl.linkProgram(this.program); if (!gl.getProgramParameter(this.program, gl.LINK_STATUS)) throw gl.getProgramInfoLog(this.program); const uniformCount = gl.getProgramParameter(this.program, gl.ACTIVE_UNIFORMS); for (let i = 0; i < uniformCount; i++) { const uniformName = gl.getActiveUniform(this.program, i).name; this.uniforms[uniformName] = gl.getUniformLocation(this.program, uniformName); } } bind () { gl.useProgram(this.program); } } function compileShader (type, source) { const shader = gl.createShader(type); gl.shaderSource(shader, source); gl.compileShader(shader); if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) throw gl.getShaderInfoLog(shader); return shader; }; const baseVertexShader = compileShader(gl.VERTEX_SHADER, ` precision highp float; precision mediump sampler2D; attribute vec2 aPosition; varying vec2 vUv; varying vec2 vL; varying vec2 vR; varying vec2 vT; varying vec2 vB; uniform vec2 texelSize; void main () { vUv = aPosition * 0.5 + 0.5; vL = vUv - vec2(texelSize.x, 0.0); vR = vUv + vec2(texelSize.x, 0.0); vT = vUv + vec2(0.0, texelSize.y); vB = vUv - vec2(0.0, texelSize.y); gl_Position = vec4(aPosition, 0.0, 1.0); } `); const displayShader = compileShader(gl.FRAGMENT_SHADER, ` precision highp float; precision mediump sampler2D; varying vec2 vUv; varying vec2 vL; varying vec2 vR; varying vec2 vT; varying vec2 vB; uniform sampler2D uTexture; void main () { gl_FragColor = texture2D(uTexture, vUv); } `); const splatShader = compileShader(gl.FRAGMENT_SHADER, ` precision highp float; precision mediump sampler2D; varying vec2 vUv; uniform sampler2D uTarget; uniform float aspectRatio; uniform vec3 color; uniform vec2 point; uniform float radius; void main () { vec2 p = vUv - point.xy; p.x *= aspectRatio; vec3 splat = exp(-dot(p, p) / radius) * color; vec3 base = texture2D(uTarget, vUv).xyz; gl_FragColor = vec4(base + splat, 1.0); } `); const advectionManualFilteringShader = compileShader(gl.FRAGMENT_SHADER, ` precision highp float; precision mediump sampler2D; varying vec2 vUv; uniform sampler2D uVelocity; uniform sampler2D uSource; uniform vec2 texelSize; uniform float dt; uniform float dissipation; vec4 bilerp (in sampler2D sam, in vec2 p) { vec4 st; st.xy = floor(p - 0.5) + 0.5; st.zw = st.xy + 1.0; vec4 uv = st * texelSize.xyxy; vec4 a = texture2D(sam, uv.xy); vec4 b = texture2D(sam, uv.zy); vec4 c = texture2D(sam, uv.xw); vec4 d = texture2D(sam, uv.zw); vec2 f = p - st.xy; return mix(mix(a, b, f.x), mix(c, d, f.x), f.y); } void main () { vec2 coord = gl_FragCoord.xy - dt * texture2D(uVelocity, vUv).xy; gl_FragColor = dissipation * bilerp(uSource, coord); gl_FragColor.a = 1.0; } `); const advectionShader = compileShader(gl.FRAGMENT_SHADER, ` precision highp float; precision mediump sampler2D; varying vec2 vUv; uniform sampler2D uVelocity; uniform sampler2D uSource; uniform vec2 texelSize; uniform float dt; uniform float dissipation; void main () { vec2 coord = vUv - dt * texture2D(uVelocity, vUv).xy * texelSize; gl_FragColor = dissipation * texture2D(uSource, coord); } `); const divergenceShader = compileShader(gl.FRAGMENT_SHADER, ` precision highp float; precision mediump sampler2D; varying vec2 vUv; varying vec2 vL; varying vec2 vR; varying vec2 vT; varying vec2 vB; uniform sampler2D uVelocity; vec2 sampleVelocity (in vec2 uv) { vec2 multiplier = vec2(1.0, 1.0); if (uv.x < 0.0) { uv.x = 0.0; multiplier.x = -1.0; } if (uv.x > 1.0) { uv.x = 1.0; multiplier.x = -1.0; } if (uv.y < 0.0) { uv.y = 0.0; multiplier.y = -1.0; } if (uv.y > 1.0) { uv.y = 1.0; multiplier.y = -1.0; } return multiplier * texture2D(uVelocity, uv).xy; } void main () { float L = sampleVelocity(vL).x; float R = sampleVelocity(vR).x; float T = sampleVelocity(vT).y; float B = sampleVelocity(vB).y; float div = 0.5 * (R - L + T - B); gl_FragColor = vec4(div, 0.0, 0.0, 1.0); } `); const curlShader = compileShader(gl.FRAGMENT_SHADER, ` precision highp float; precision mediump sampler2D; varying vec2 vUv; varying vec2 vL; varying vec2 vR; varying vec2 vT; varying vec2 vB; uniform sampler2D uVelocity; void main () { float L = texture2D(uVelocity, vL).y; float R = texture2D(uVelocity, vR).y; float T = texture2D(uVelocity, vT).x; float B = texture2D(uVelocity, vB).x; float vorticity = R - L - T + B; gl_FragColor = vec4(vorticity, 0.0, 0.0, 1.0); } `); const vorticityShader = compileShader(gl.FRAGMENT_SHADER, ` precision highp float; precision mediump sampler2D; varying vec2 vUv; varying vec2 vL; varying vec2 vR; varying vec2 vT; varying vec2 vB; uniform sampler2D uVelocity; uniform sampler2D uCurl; uniform float curl; uniform float dt; void main () { float L = texture2D(uCurl, vL).y; float R = texture2D(uCurl, vR).y; float T = texture2D(uCurl, vT).x; float B = texture2D(uCurl, vB).x; float C = texture2D(uCurl, vUv).x; vec2 force = vec2(abs(T) - abs(B), abs(R) - abs(L)); force *= 1.0 / length(force + 0.00001) * curl * C; vec2 vel = texture2D(uVelocity, vUv).xy; gl_FragColor = vec4(vel + force * dt, 0.0, 1.0); } `); const pressureShader = compileShader(gl.FRAGMENT_SHADER, ` precision highp float; precision mediump sampler2D; varying vec2 vUv; varying vec2 vL; varying vec2 vR; varying vec2 vT; varying vec2 vB; uniform sampler2D uPressure; uniform sampler2D uDivergence; vec2 boundary (in vec2 uv) { uv = min(max(uv, 0.0), 1.0); return uv; } void main () { float L = texture2D(uPressure, boundary(vL)).x; float R = texture2D(uPressure, boundary(vR)).x; float T = texture2D(uPressure, boundary(vT)).x; float B = texture2D(uPressure, boundary(vB)).x; float C = texture2D(uPressure, vUv).x; float divergence = texture2D(uDivergence, vUv).x; float pressure = (L + R + B + T - divergence) * 0.25; gl_FragColor = vec4(pressure, 0.0, 0.0, 1.0); } `); const gradientSubtractShader = compileShader(gl.FRAGMENT_SHADER, ` precision highp float; precision mediump sampler2D; varying vec2 vUv; varying vec2 vL; varying vec2 vR; varying vec2 vT; varying vec2 vB; uniform sampler2D uPressure; uniform sampler2D uVelocity; vec2 boundary (in vec2 uv) { uv = min(max(uv, 0.0), 1.0); return uv; } void main () { float L = texture2D(uPressure, boundary(vL)).x; float R = texture2D(uPressure, boundary(vR)).x; float T = texture2D(uPressure, boundary(vT)).x; float B = texture2D(uPressure, boundary(vB)).x; vec2 velocity = texture2D(uVelocity, vUv).xy; velocity.xy -= vec2(R - L, T - B); gl_FragColor = vec4(velocity, 0.0, 1.0); } `); const blit = (() => { gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer()); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, -1, 1, 1, 1, 1, -1]), gl.STATIC_DRAW); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, gl.createBuffer()); gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array([0, 1, 2, 0, 2, 3]), gl.STATIC_DRAW); gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(0); return (destination) => { gl.bindFramebuffer(gl.FRAMEBUFFER, destination); gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0); } })(); function clear (target) { gl.bindFramebuffer(gl.FRAMEBUFFER, target); gl.clear(gl.COLOR_BUFFER_BIT); } function createFBO (texId, w, h, internalFormat, format, type, param) { gl.activeTexture(gl.TEXTURE0 + texId); let texture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, texture); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, param); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, param); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, w, h, 0, format, type, null); let fbo = gl.createFramebuffer(); gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0); gl.viewport(0, 0, w, h); gl.clear(gl.COLOR_BUFFER_BIT); return [texture, fbo, texId]; } function createDoubleFBO (texId, w, h, internalFormat, format, type, param) { let fbo1 = createFBO(texId , w, h, internalFormat, format, type, param); let fbo2 = createFBO(texId + 1, w, h, internalFormat, format, type, param); return { get first () { return fbo1; }, get second () { return fbo2; }, swap: () => { let temp = fbo1; fbo1 = fbo2; fbo2 = temp; } } } let textureWidth; let textureHeight; let density; let velocity; let divergence; let curl; let pressure; function initFramebuffers () { textureWidth = gl.drawingBufferWidth >> TEXTURE_DOWNSAMPLE; textureHeight = gl.drawingBufferHeight >> TEXTURE_DOWNSAMPLE; const internalFormat = isWebGL2 ? gl.RGBA16F : gl.RGBA; const internalFormatRG = isWebGL2 ? gl.RG16F : gl.RGBA; const formatRG = isWebGL2 ? gl.RG : gl.RGBA; const texType = isWebGL2 ? gl.HALF_FLOAT : halfFloat.HALF_FLOAT_OES; density = createDoubleFBO(0, textureWidth, textureHeight, internalFormat , gl.RGBA , texType, support_linear_float ? gl.LINEAR : gl.NEAREST); velocity = createDoubleFBO(2, textureWidth, textureHeight, internalFormatRG, formatRG, texType, support_linear_float ? gl.LINEAR : gl.NEAREST); divergence = createFBO (4, textureWidth, textureHeight, internalFormatRG, formatRG, texType, gl.NEAREST); curl = createFBO (5, textureWidth, textureHeight, internalFormatRG, formatRG, texType, gl.NEAREST); pressure = createDoubleFBO(6, textureWidth, textureHeight, internalFormatRG, formatRG, texType, gl.NEAREST); } initFramebuffers(); const displayProgram = new GLProgram(baseVertexShader, displayShader); const splatProgram = new GLProgram(baseVertexShader, splatShader); const advectionProgram = new GLProgram(baseVertexShader, support_linear_float ? advectionShader : advectionManualFilteringShader); const divergenceProgram = new GLProgram(baseVertexShader, divergenceShader); const curlProgram = new GLProgram(baseVertexShader, curlShader); const vorticityProgram = new GLProgram(baseVertexShader, vorticityShader); const pressureProgram = new GLProgram(baseVertexShader, pressureShader); const gradienSubtractProgram = new GLProgram(baseVertexShader, gradientSubtractShader); function pointerPrototype () { this.id = -1; this.x = 0; this.y = 0; this.dx = 0; this.dy = 0; this.down = false; this.moved = false; this.color = [30, 0, 300]; } let pointers = []; pointers.push(new pointerPrototype()); for (let i = 0; i < 10; i++) { const color = [Math.random() * 10, Math.random() * 10, Math.random() * 10]; const x = canvas.width * Math.random(); const y = canvas.height * Math.random(); const dx = 1000 * (Math.random() - 0.5); const dy = 1000 * (Math.random() - 0.5); splat(x, y, dx, dy, color); } let lastTime = Date.now(); Update(); function Update () { resizeCanvas(); const dt = Math.min((Date.now() - lastTime) / 1000, 0.016); lastTime = Date.now(); gl.viewport(0, 0, textureWidth, textureHeight); advectionProgram.bind(); gl.uniform2f(advectionProgram.uniforms.texelSize, 1.0 / textureWidth, 1.0 / textureHeight); gl.uniform1i(advectionProgram.uniforms.uVelocity, velocity.first[2]); gl.uniform1i(advectionProgram.uniforms.uSource, velocity.first[2]); gl.uniform1f(advectionProgram.uniforms.dt, dt); gl.uniform1f(advectionProgram.uniforms.dissipation, VELOCITY_DISSIPATION); blit(velocity.second[1]); velocity.swap(); gl.uniform1i(advectionProgram.uniforms.uVelocity, velocity.first[2]); gl.uniform1i(advectionProgram.uniforms.uSource, density.first[2]); gl.uniform1f(advectionProgram.uniforms.dissipation, DENSITY_DISSIPATION); blit(density.second[1]); density.swap(); for (let i = 0; i < pointers.length; i++) { const pointer = pointers[i]; if (pointer.moved) { splat(pointer.x, pointer.y, pointer.dx, pointer.dy, pointer.color); pointer.moved = false; } } curlProgram.bind(); gl.uniform2f(curlProgram.uniforms.texelSize, 1.0 / textureWidth, 1.0 / textureHeight); gl.uniform1i(curlProgram.uniforms.uVelocity, velocity.first[2]); blit(curl[1]); vorticityProgram.bind(); gl.uniform2f(vorticityProgram.uniforms.texelSize, 1.0 / textureWidth, 1.0 / textureHeight); gl.uniform1i(vorticityProgram.uniforms.uVelocity, velocity.first[2]); gl.uniform1i(vorticityProgram.uniforms.uCurl, curl[2]); gl.uniform1f(vorticityProgram.uniforms.curl, CURL); gl.uniform1f(vorticityProgram.uniforms.dt, dt); blit(velocity.second[1]); velocity.swap(); divergenceProgram.bind(); gl.uniform2f(divergenceProgram.uniforms.texelSize, 1.0 / textureWidth, 1.0 / textureHeight); gl.uniform1i(divergenceProgram.uniforms.uVelocity, velocity.first[2]); blit(divergence[1]); clear(pressure.first[1]); pressureProgram.bind(); gl.uniform2f(pressureProgram.uniforms.texelSize, 1.0 / textureWidth, 1.0 / textureHeight); gl.uniform1i(pressureProgram.uniforms.uDivergence, divergence[2]); for (let i = 0; i < PRESSURE_ITERATIONS; i++) { gl.uniform1i(pressureProgram.uniforms.uPressure, pressure.first[2]); blit(pressure.second[1]); pressure.swap(); } gradienSubtractProgram.bind(); gl.uniform2f(gradienSubtractProgram.uniforms.texelSize, 1.0 / textureWidth, 1.0 / textureHeight); gl.uniform1i(gradienSubtractProgram.uniforms.uPressure, pressure.first[2]); gl.uniform1i(gradienSubtractProgram.uniforms.uVelocity, velocity.first[2]); blit(velocity.second[1]); velocity.swap(); gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight); displayProgram.bind(); gl.uniform1i(displayProgram.uniforms.uTexture, density.first[2]); blit(null); requestAnimationFrame(Update); } function splat (x, y, dx, dy, color) { splatProgram.bind(); gl.uniform1i(splatProgram.uniforms.uTarget, velocity.first[2]); gl.uniform1f(splatProgram.uniforms.aspectRatio, canvas.width / canvas.height); gl.uniform2f(splatProgram.uniforms.point, x / canvas.width, 1.0 - y / canvas.height); gl.uniform3f(splatProgram.uniforms.color, dx, -dy, 1.0); gl.uniform1f(splatProgram.uniforms.radius, SPLAT_RADIUS); blit(velocity.second[1]); velocity.swap(); gl.uniform1i(splatProgram.uniforms.uTarget, density.first[2]); gl.uniform3f(splatProgram.uniforms.color, color[0] * 0.3, color[1] * 0.3, color[2] * 0.3); blit(density.second[1]); density.swap(); } function resizeCanvas () { if (canvas.width != canvas.clientWidth || canvas.height != canvas.clientHeight) { canvas.width = canvas.clientWidth; canvas.height = canvas.clientHeight; initFramebuffers(); } } canvas.addEventListener('mousemove', (e) => { pointers[0].moved = pointers[0].down; pointers[0].dx = (e.offsetX - pointers[0].x) * 10.0; pointers[0].dy = (e.offsetY - pointers[0].y) * 10.0; pointers[0].x = e.offsetX; pointers[0].y = e.offsetY; }); canvas.addEventListener('touchmove', (e) => { e.preventDefault(); const touches = e.targetTouches; for (let i = 0; i < e.touches.length; i++) { let pointer = pointers[i]; pointer.moved = pointer.down; pointer.dx = (touches[i].pageX - pointer.x) * 10.0; pointer.dy = (touches[i].pageY - pointer.y) * 10.0; pointer.x = touches[i].pageX; pointer.y = touches[i].pageY; } }, false); canvas.addEventListener('mousedown', () => { pointers[0].down = true; pointers[0].color = [Math.random() + 0.2, Math.random() + 0.2, Math.random() + 0.2]; }); canvas.addEventListener('touchstart', (e) => { const touches = e.targetTouches; for (let i = 0; i < touches.length; i++) { if (i >= pointers.length) pointers.push(new pointerPrototype()); pointers[i].id = touches[i].identifier; pointers[i].down = true; pointers[i].x = touches[i].pageX; pointers[i].y = touches[i].pageY; pointers[i].color = [Math.random() + 0.2, Math.random() + 0.2, Math.random() + 0.2]; } }); window.addEventListener('mouseup', () => { pointers[0].down = false; }); window.addEventListener('touchend', (e) => { const touches = e.changedTouches; for (let i = 0; i < touches.length; i++) for (let j = 0; j 1'use strict';
2const canvas = document.getElementsByTagName('canvas')[0];
2canvas.width = canvas.clientWidth; canvas.height = canvas.clientHeight;
2const params = { alpha: false, depth: false, stencil: false, antialias: false };
2let gl = canvas.getContext('webgl2', params); const isWebGL2 = !!gl;
2if (!isWebGL2) {
2gl = canvas.getContext('webgl', params) || canvas.getContext('experimental-webgl', params);
2}
2gl.clearColor(0.0, 0.0, 0.0, 1.0);
2const halfFloat = gl.getExtension('OES_texture_half_float');
2let support_linear_float = gl.getExtension('OES_texture_half_float_linear');
2if (isWebGL2) {
2 gl.getExtension('EXT_color_buffer_float');
2support_linear_float = gl.getExtension('OES_texture_float_linear');
2}
2const TEXTURE_DOWNSAMPLE = 1;
2const DENSITY_DISSIPATION = 0.98;
2const VELOCITY_DISSIPATION =
20.99;
2const SPLAT_RADIUS = 0.005;
2const CURL = 30;
2const PRESSURE_ITERATIONS = 25;
2class GLProgram {
2constructor (vertexShader, fragmentShader) {
2this.uniforms = {};
2this.program =
2gl.createProgram();
2gl.attachShader(this.program, vertexShader);
2gl.attachShader(this.program, fragmentShader);
2gl.linkProgram(this.program);
2if (!gl.getProgramParameter(this.program, gl.LINK_STATUS))
2throw gl.getProgramInfoLog(this.program);
2const uniformCount = gl.getProgramParameter(this.program, gl.ACTIVE_UNIFORMS);
2for (let i = 0; i < uniformCount; i++) {
2const uniformName = gl.getActiveUniform(this.program, i).name;
2this.uniforms[uniformName] = gl.getUniformLocation(this.program, uniformName);
2}
2}
2bind () {
2gl.useProgram(this.program);
2}
2}
2function compileShader (type, source) {
2const shader = gl.createShader(type);
2 gl.shaderSource(shader, source);
2gl.compileShader(shader);
2if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS))
2throw
2gl.getShaderInfoLog(shader);
2return shader;
2};
2const baseVertexShader = compileShader(gl.VERTEX_SHADER, `
2precision highp float;
2precision mediump sampler2D;
2attribute vec2 aPosition;
2varying vec2 vUv;
2varying vec2 vL;
2varying vec2 vR;
2varying vec2 vT;
2varying vec2 vB;
2uniform vec2 texelSize;
2void main () {
2vUv = aPosition * 0.5 + 0.5;
2vL = vUv - vec2(texelSize.x, 0.0);
2vR = vUv + vec2(texelSize.x, 0.0);
2vT = vUv + vec2(0.0, texelSize.y);
2vB = vUv - vec2(0.0, texelSize.y);
2gl_Position = vec4(aPosition, 0.0, 1.0);
2}
2`);
2const displayShader = compileShader(gl.FRAGMENT_SHADER, `
2 precision highp float;
2 precision mediump sampler2D;
2 varying vec2 vUv;
2 varying vec2 vL;
2 varying vec2 vR;
2 varying vec2 vT;
2 varying vec2 vB;
2 uniform sampler2D uTexture;
2 void main () {
2 gl_FragColor = texture2D(uTexture, vUv);
2 }
2`);
2const splatShader = compileShader(gl.FRAGMENT_SHADER, `
2 precision highp float;
2 precision mediump sampler2D;
2 varying vec2 vUv;
2 uniform sampler2D uTarget;
2 uniform float aspectRatio;
2 uniform vec3 color;
2 uniform vec2 point;
2 uniform float radius;
2 void main () {
2 vec2 p = vUv - point.xy; p.x *= aspectRatio; vec3 splat = exp(-dot(p, p) / radius) * color; vec3 base = texture2D(uTarget, vUv).xyz; gl_FragColor = vec4(base + splat, 1.0); } `); const advectionManualFilteringShader = compileShader(gl.FRAGMENT_SHADER, ` precision highp float; precision mediump sampler2D; varying vec2 vUv; uniform sampler2D uVelocity; uniform sampler2D uSource; uniform vec2 texelSize; uniform float dt; uniform float dissipation; vec4 bilerp (in sampler2D sam, in vec2 p) { vec4 st; st.xy = floor(p - 0.5) + 0.5; st.zw = st.xy + 1.0; vec4 uv = st * texelSize.xyxy; vec4 a = texture2D(sam, uv.xy); vec4 b = texture2D(sam, uv.zy); vec4 c = texture2D(sam, uv.xw); vec4 d = texture2D(sam, uv.zw); vec2 f = p - st.xy; return mix(mix(a, b, f.x), mix(c, d, f.x), f.y); } void main () { vec2 coord = gl_FragCoord.xy - dt * texture2D(uVelocity, vUv).xy; gl_FragColor = dissipation * bilerp(uSource, coord); gl_FragColor.a = 1.0; } `); const advectionShader = compileShader(gl.FRAGMENT_SHADER, ` precision highp float; precision mediump sampler2D; varying vec2 vUv; uniform sampler2D uVelocity; uniform sampler2D uSource; uniform vec2 texelSize; uniform float dt; uniform float dissipation; void main () { vec2 coord = vUv - dt * texture2D(uVelocity, vUv).xy * texelSize; gl_FragColor = dissipation * texture2D(uSource, coord); } `); const divergenceShader = compileShader(gl.FRAGMENT_SHADER, ` precision highp float; precision mediump sampler2D; varying vec2 vUv; varying vec2 vL; varying vec2 vR; varying vec2 vT; varying vec2 vB; uniform sampler2D uVelocity; vec2 sampleVelocity (in vec2 uv) { vec2 multiplier = vec2(1.0, 1.0); if (uv.x < 0.0) { uv.x = 0.0; multiplier.x = -1.0; } if (uv.x > 1.0) { uv.x = 1.0; multiplier.x = -1.0; } if (uv.y < 0.0) { uv.y = 0.0; multiplier.y = -1.0; } if (uv.y > 1.0) { uv.y = 1.0; multiplier.y = -1.0; } return multiplier * texture2D(uVelocity, uv).xy; } void main () { float L = sampleVelocity(vL).x; float R = sampleVelocity(vR).x; float T = sampleVelocity(vT).y; float B = sampleVelocity(vB).y; float div = 0.5 * (R - L + T - B); gl_FragColor = vec4(div, 0.0, 0.0, 1.0); } `); const curlShader = compileShader(gl.FRAGMENT_SHADER, ` precision highp float; precision mediump sampler2D; varying vec2 vUv; varying vec2 vL; varying vec2 vR; varying vec2 vT; varying vec2 vB; uniform sampler2D uVelocity; void main () { float L = texture2D(uVelocity, vL).y; float R = texture2D(uVelocity, vR).y; float T = texture2D(uVelocity, vT).x; float B = texture2D(uVelocity, vB).x; float vorticity = R - L - T + B; gl_FragColor = vec4(vorticity, 0.0, 0.0, 1.0); } `); const vorticityShader = compileShader(gl.FRAGMENT_SHADER, ` precision highp float; precision mediump sampler2D; varying vec2 vUv; varying vec2 vL; varying vec2 vR; varying vec2 vT; varying vec2 vB; uniform sampler2D uVelocity; uniform sampler2D uCurl; uniform float curl; uniform float dt; void main () { float L = texture2D(uCurl, vL).y; float R = texture2D(uCurl, vR).y; float T = texture2D(uCurl, vT).x; float B = texture2D(uCurl, vB).x; float C = texture2D(uCurl, vUv).x; vec2 force = vec2(abs(T) - abs(B), abs(R) - abs(L)); force *= 1.0 / length(force + 0.00001) * curl * C; vec2 vel = texture2D(uVelocity, vUv).xy; gl_FragColor = vec4(vel + force * dt, 0.0, 1.0); } `); const pressureShader = compileShader(gl.FRAGMENT_SHADER, ` precision highp float; precision mediump sampler2D; varying vec2 vUv; varying vec2 vL; varying vec2 vR; varying vec2 vT; varying vec2 vB; uniform sampler2D uPressure; uniform sampler2D uDivergence; vec2 boundary (in vec2 uv) { uv = min(max(uv, 0.0), 1.0); return uv; } void main () { float L = texture2D(uPressure, boundary(vL)).x; float R = texture2D(uPressure, boundary(vR)).x; float T = texture2D(uPressure, boundary(vT)).x; float B = texture2D(uPressure, boundary(vB)).x; float C = texture2D(uPressure, vUv).x; float divergence = texture2D(uDivergence, vUv).x; float pressure = (L + R + B + T - divergence) * 0.25; gl_FragColor = vec4(pressure, 0.0, 0.0, 1.0); } `); const gradientSubtractShader = compileShader(gl.FRAGMENT_SHADER, ` precision highp float; precision mediump sampler2D; varying vec2 vUv; varying vec2 vL; varying vec2 vR; varying vec2 vT; varying vec2 vB; uniform sampler2D uPressure; uniform sampler2D uVelocity; vec2 boundary (in vec2 uv) { uv = min(max(uv, 0.0), 1.0); return uv; } void main () { float L = texture2D(uPressure, boundary(vL)).x; float R = texture2D(uPressure, boundary(vR)).x; float T = texture2D(uPressure, boundary(vT)).x; float B = texture2D(uPressure, boundary(vB)).x; vec2 velocity = texture2D(uVelocity, vUv).xy; velocity.xy -= vec2(R - L, T - B); gl_FragColor = vec4(velocity, 0.0, 1.0); } `); const blit = (() => { gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer()); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, -1, 1, 1, 1, 1, -1]), gl.STATIC_DRAW); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, gl.createBuffer()); gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array([0, 1, 2, 0, 2, 3]), gl.STATIC_DRAW); gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(0); return (destination) => { gl.bindFramebuffer(gl.FRAMEBUFFER, destination); gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0); } })(); function clear (target) { gl.bindFramebuffer(gl.FRAMEBUFFER, target); gl.clear(gl.COLOR_BUFFER_BIT); } function createFBO (texId, w, h, internalFormat, format, type, param) { gl.activeTexture(gl.TEXTURE0 + texId); let texture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, texture); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, param); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, param); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, w, h, 0, format, type, null); let fbo = gl.createFramebuffer(); gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0); gl.viewport(0, 0, w, h); gl.clear(gl.COLOR_BUFFER_BIT); return [texture, fbo, texId]; } function createDoubleFBO (texId, w, h, internalFormat, format, type, param) { let fbo1 = createFBO(texId , w, h, internalFormat, format, type, param); let fbo2 = createFBO(texId + 1, w, h, internalFormat, format, type, param); return { get first () { return fbo1; }, get second () { return fbo2; }, swap: () => { let temp = fbo1; fbo1 = fbo2; fbo2 = temp; } } } let textureWidth; let textureHeight; let density; let velocity; let divergence; let curl; let pressure; function initFramebuffers () { textureWidth = gl.drawingBufferWidth >> TEXTURE_DOWNSAMPLE; textureHeight = gl.drawingBufferHeight >> TEXTURE_DOWNSAMPLE; const internalFormat = isWebGL2 ? gl.RGBA16F : gl.RGBA; const internalFormatRG = isWebGL2 ? gl.RG16F : gl.RGBA; const formatRG = isWebGL2 ? gl.RG : gl.RGBA; const texType = isWebGL2 ? gl.HALF_FLOAT : halfFloat.HALF_FLOAT_OES; density = createDoubleFBO(0, textureWidth, textureHeight, internalFormat , gl.RGBA , texType, support_linear_float ? gl.LINEAR : gl.NEAREST); velocity = createDoubleFBO(2, textureWidth, textureHeight, internalFormatRG, formatRG, texType, support_linear_float ? gl.LINEAR : gl.NEAREST); divergence = createFBO (4, textureWidth, textureHeight, internalFormatRG, formatRG, texType, gl.NEAREST); curl = createFBO (5, textureWidth, textureHeight, internalFormatRG, formatRG, texType, gl.NEAREST); pressure = createDoubleFBO(6, textureWidth, textureHeight, internalFormatRG, formatRG, texType, gl.NEAREST); } initFramebuffers(); const displayProgram = new GLProgram(baseVertexShader, displayShader); const splatProgram = new GLProgram(baseVertexShader, splatShader); const advectionProgram = new GLProgram(baseVertexShader, support_linear_float ? advectionShader : advectionManualFilteringShader); const divergenceProgram = new GLProgram(baseVertexShader, divergenceShader); const curlProgram = new GLProgram(baseVertexShader, curlShader); const vorticityProgram = new GLProgram(baseVertexShader, vorticityShader); const pressureProgram = new GLProgram(baseVertexShader, pressureShader); const gradienSubtractProgram = new GLProgram(baseVertexShader, gradientSubtractShader); function pointerPrototype () { this.id = -1; this.x = 0; this.y = 0; this.dx = 0; this.dy = 0; this.down = false; this.moved = false; this.color = [30, 0, 300]; } let pointers = []; pointers.push(new pointerPrototype()); for (let i = 0; i < 10; i++) { const color = [Math.random() * 10, Math.random() * 10, Math.random() * 10]; const x = canvas.width * Math.random(); const y = canvas.height * Math.random(); const dx = 1000 * (Math.random() - 0.5); const dy = 1000 * (Math.random() - 0.5); splat(x, y, dx, dy, color); } let lastTime = Date.now(); Update(); function Update () { resizeCanvas(); const dt = Math.min((Date.now() - lastTime) / 1000, 0.016); lastTime = Date.now(); gl.viewport(0, 0, textureWidth, textureHeight); advectionProgram.bind(); gl.uniform2f(advectionProgram.uniforms.texelSize, 1.0 / textureWidth, 1.0 / textureHeight); gl.uniform1i(advectionProgram.uniforms.uVelocity, velocity.first[2]); gl.uniform1i(advectionProgram.uniforms.uSource, velocity.first[2]); gl.uniform1f(advectionProgram.uniforms.dt, dt); gl.uniform1f(advectionProgram.uniforms.dissipation, VELOCITY_DISSIPATION); blit(velocity.second[1]); velocity.swap(); gl.uniform1i(advectionProgram.uniforms.uVelocity, velocity.first[2]); gl.uniform1i(advectionProgram.uniforms.uSource, density.first[2]); gl.uniform1f(advectionProgram.uniforms.dissipation, DENSITY_DISSIPATION); blit(density.second[1]); density.swap(); for (let i = 0; i < pointers.length; i++) { const pointer = pointers[i]; if (pointer.moved) { splat(pointer.x, pointer.y, pointer.dx, pointer.dy, pointer.color); pointer.moved = false; } } curlProgram.bind(); gl.uniform2f(curlProgram.uniforms.texelSize, 1.0 / textureWidth, 1.0 / textureHeight); gl.uniform1i(curlProgram.uniforms.uVelocity, velocity.first[2]); blit(curl[1]); vorticityProgram.bind(); gl.uniform2f(vorticityProgram.uniforms.texelSize, 1.0 / textureWidth, 1.0 / textureHeight); gl.uniform1i(vorticityProgram.uniforms.uVelocity, velocity.first[2]); gl.uniform1i(vorticityProgram.uniforms.uCurl, curl[2]); gl.uniform1f(vorticityProgram.uniforms.curl, CURL); gl.uniform1f(vorticityProgram.uniforms.dt, dt); blit(velocity.second[1]); velocity.swap(); divergenceProgram.bind(); gl.uniform2f(divergenceProgram.uniforms.texelSize, 1.0 / textureWidth, 1.0 / textureHeight); gl.uniform1i(divergenceProgram.uniforms.uVelocity, velocity.first[2]); blit(divergence[1]); clear(pressure.first[1]); pressureProgram.bind(); gl.uniform2f(pressureProgram.uniforms.texelSize, 1.0 / textureWidth, 1.0 / textureHeight); gl.uniform1i(pressureProgram.uniforms.uDivergence, divergence[2]); for (let i = 0; i < PRESSURE_ITERATIONS; i++) { gl.uniform1i(pressureProgram.uniforms.uPressure, pressure.first[2]); blit(pressure.second[1]); pressure.swap(); } gradienSubtractProgram.bind(); gl.uniform2f(gradienSubtractProgram.uniforms.texelSize, 1.0 / textureWidth, 1.0 / textureHeight); gl.uniform1i(gradienSubtractProgram.uniforms.uPressure, pressure.first[2]); gl.uniform1i(gradienSubtractProgram.uniforms.uVelocity, velocity.first[2]); blit(velocity.second[1]); velocity.swap(); gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight); displayProgram.bind(); gl.uniform1i(displayProgram.uniforms.uTexture, density.first[2]); blit(null); requestAnimationFrame(Update); } function splat (x, y, dx, dy, color) { splatProgram.bind(); gl.uniform1i(splatProgram.uniforms.uTarget, velocity.first[2]); gl.uniform1f(splatProgram.uniforms.aspectRatio, canvas.width / canvas.height); gl.uniform2f(splatProgram.uniforms.point, x / canvas.width, 1.0 - y / canvas.height); gl.uniform3f(splatProgram.uniforms.color, dx, -dy, 1.0); gl.uniform1f(splatProgram.uniforms.radius, SPLAT_RADIUS); blit(velocity.second[1]); velocity.swap(); gl.uniform1i(splatProgram.uniforms.uTarget, density.first[2]); gl.uniform3f(splatProgram.uniforms.color, color[0] * 0.3, color[1] * 0.3, color[2] * 0.3); blit(density.second[1]); density.swap(); } function resizeCanvas () { if (canvas.width != canvas.clientWidth || canvas.height != canvas.clientHeight) { canvas.width = canvas.clientWidth; canvas.height = canvas.clientHeight; initFramebuffers(); } } canvas.addEventListener('mousemove', (e) => { pointers[0].moved = pointers[0].down; pointers[0].dx = (e.offsetX - pointers[0].x) * 10.0; pointers[0].dy = (e.offsetY - pointers[0].y) * 10.0; pointers[0].x = e.offsetX; pointers[0].y = e.offsetY; }); canvas.addEventListener('touchmove', (e) => { e.preventDefault(); const touches = e.targetTouches; for (let i = 0; i < e.touches.length; i++) { let pointer = pointers[i]; pointer.moved = pointer.down; pointer.dx = (touches[i].pageX - pointer.x) * 10.0; pointer.dy = (touches[i].pageY - pointer.y) * 10.0; pointer.x = touches[i].pageX; pointer.y = touches[i].pageY; } }, false); canvas.addEventListener('mousedown', () => { pointers[0].down = true; pointers[0].color = [Math.random() + 0.2, Math.random() + 0.2, Math.random() + 0.2]; }); canvas.addEventListener('touchstart', (e) => { const touches = e.targetTouches; for (let i = 0; i < touches.length; i++) { if (i >= pointers.length) pointers.push(new pointerPrototype()); pointers[i].id = touches[i].identifier; pointers[i].down = true; pointers[i].x = touches[i].pageX; pointers[i].y = touches[i].pageY; pointers[i].color = [Math.random() + 0.2, Math.random() + 0.2, Math.random() + 0.2]; } }); window.addEventListener('mouseup', () => { pointers[0].down = false; }); window.addEventListener('touchend', (e) => { const touches = e.changedTouches; for (let i = 0; i < touches.length; i++) for (let j = 0; j < pointers.length; j++) if (touches[i].identifier == pointers[j].id) pointers[j].down = false; });-->

3D太阳系
HTML
CSS
JavaScript

$(window).load(function(){ var body = $("body"), universe = $("#universe"), solarsys = $("#solar-system"); var init = function() { body.removeClass('view-2D opening').addClass("view-3D").delay(2000).queue(function() { $(this).removeClass('hide-UI').addClass("set-speed"); $(this).dequeue(); }); }; var setView = function(view) { universe.removeClass().addClass(view); }; $("#toggle-data").click(function(e) { body.toggleClass("data-open data-close"); e.preventDefault(); }); $("#toggle-controls").click(function(e) { body.toggleClass("controls-open controls-close"); e.preventDefault(); }); $("#data a").click(function(e) { var ref = $(this).attr("class"); solarsys.removeClass().addClass(ref); $(this).parent().find('a').removeClass('active'); $(this).addClass('active'); e.preventDefault(); }); $(".set-view").click(function() { body.toggleClass("view-3D view-2D"); }); $(".set-zoom").click(function() { body.toggleClass("zoom-large zoom-close"); }); $(".set-speed").click(function() { setView("scale-stretched set-speed"); }); $(".set-size").click(function() { setView("scale-s set-size"); }); $(".set-distance").click(function() { setView("scale-d set-distance"); }); init(); });

Mercury
Venus
Mars
Jupiter
Saturn
Uranus
Neptune
Sun
HTML5小游戏
HTML
CSS
JavaScript

/* Copyright (c) 2013 dissimulate at codepen Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* Customisable map data */ var map = { tile_size: 16, /* Key vairables: id [required] - an integer that corresponds with a tile in the data array. colour [required] - any javascript compatible colour variable. solid [optional] - whether the tile is solid or not, defaults to false. bounce [optional] - how much velocity is preserved upon hitting the tile, 0.5 is half. jump [optional] - whether the player can jump while over the tile, defaults to false. friction [optional] - friction of the tile, must have X and Y values (e.g {x:0.5, y:0.5}). gravity [optional] - gravity of the tile, must have X and Y values (e.g {x:0.5, y:0.5}). fore [optional] - whether the tile is drawn in front of the player, defaults to false. script [optional] - refers to a script in the scripts section, executed if it is touched. */ keys: [ {id: 0, colour: '#333', solid: 0}, {id: 1, colour: '#888', solid: 0}, {id: 2,colour: '#555',solid: 1,bounce: 0.35}, {id: 3,colour: 'rgba(121, 220, 242, 0.4)',friction: {x: 0.9,y: 0.9},gravity: {x: 0,y: 0.1},jump: 1,fore: 1}, {id: 4,colour: '#777',jump: 1}, {id: 5,colour: '#E373FA',solid: 1,bounce: 1.1}, {id: 6,colour: '#666',solid: 1,bounce: 0}, {id: 7,colour: '#73C6FA',solid: 0,script: 'change_colour'}, {id: 8,colour: '#FADF73',solid: 0,script: 'next_level'}, {id: 9,colour: '#C93232',solid: 0,script: 'death'}, {id: 10,colour: '#555',solid: 1}, {id: 11,colour: '#0FF',solid: 0,script: 'unlock'} ], /* An array representing the map tiles. Each number corresponds to a key */ data: [ [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2], [2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2], [2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2], [2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2], [2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2], [2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2], [2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2], [2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2], [2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2], [2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 6, 6, 6, 6, 6, 2], [2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 1, 1, 1, 1, 2], [2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 1, 1, 1, 1, 2], [2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 1, 1, 1, 1, 2], [2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 1, 1, 1, 1, 1, 2, 2, 2], [2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 7, 1, 1, 1, 1, 1, 1, 1, 2], [2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 4, 2, 2, 2, 1, 2], [2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 4, 2, 1, 1, 1, 2], [2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 4, 2, 1, 2, 2, 2], [2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 4, 2, 1, 2], [2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 4, 2, 1, 2], [2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 4, 2, 1, 2], [2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 4, 2, 1, 2], [2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 4, 2, 1, 2], [2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 4, 2, 1, 2], [2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 4, 2, 1, 2], [2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 4, 2, 1, 2], [2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 4, 2, 1, 2], [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 2, 1, 2], [2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 4, 2, 1, 2], [2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 4, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2], [2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 4, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 2], [2, 1, 2, 2, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 4, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 2], [2, 1, 2, 2, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 4, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 2], [2, 1, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 8, 1, 1, 1, 2], [2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 6, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 2], [2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 9, 9, 9, 2, 10, 10, 10, 10, 10, 10, 1, 1, 1, 1, 1, 1, 1, 11, 2, 2, 2, 2, 4, 2], [2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 10, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 2], [2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 2], [2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 2], [2, 6, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 2, 1, 1, 1, 1, 1, 1, 2], [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 1, 1, 1, 1, 1, 1, 1, 2], [2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2], [2, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 6, 6, 6, 2, 2, 2, 2, 2, 2, 6, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2], [2, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2], [2, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2], [2, 1, 1, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2], [2, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2], [2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2], [2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 2, 5, 5, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2], [2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2], [2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2], [2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2], [2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 2], [2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 2], [2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 2], [2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 2], [2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 2], [2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2], [2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2], [2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 5, 5, 1, 1, 1, 1, 1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2], [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2] ], /* Default gravity of the map */ gravity: { x: 0, y: 0.3 }, /* Velocity limits */ vel_limit: { x: 2, y: 16 }, /* Movement speed when the key is pressed */ movement_speed: { jump: 6, left: 0.3, right: 0.3 }, /* The coordinates at which the player spawns and the colour of the player */ player: { x: 2, y: 2, colour: '#FF9900' }, /* scripts refered to by the "script" variable in the tile keys */ scripts: { /* you can just use "this" instead of your engine variable ("game"), but Codepen doesn't like it */ change_colour: 'game.player.colour = "#"+(Math.random()*0xFFFFFF<<0).toString(16);', /* you could load a new map variable here */ next_level: 'alert("Yay! You won! Reloading map.");game.load_map(map);', death: 'alert("You died!");game.load_map(map);', unlock: 'game.current_map.keys[10].solid = 0;game.current_map.keys[10].colour = "#888";' } }; /* Clarity engine */ var Clarity = function () { this.alert_errors = false; this.log_info = true; this.tile_size = 16; this.limit_viewport = false; this.jump_switch = 0; this.viewport = { x: 200, y: 200 }; this.camera = { x: 0, y: 0 }; this.key = { left: false, right: false, up: false }; this.player = { loc: { x: 0, y: 0 }, vel: { x: 0, y: 0 }, can_jump: true }; window.onkeydown = this.keydown.bind(this); window.onkeyup = this.keyup.bind(this); }; Clarity.prototype.error = function (message) { if (this.alert_errors) alert(message); if (this.log_info) console.log(message); }; Clarity.prototype.log = function (message) { if (this.log_info) console.log(message); }; Clarity.prototype.set_viewport = function (x, y) { this.viewport.x = x; this.viewport.y = y; }; Clarity.prototype.keydown = function (e) { var _this = this; switch (e.keyCode) { case 37: _this.key.left = true; break; case 38: _this.key.up = true; break; case 39: _this.key.right = true; break; } }; Clarity.prototype.keyup = function (e) { var _this = this; switch (e.keyCode) { case 37: _this.key.left = false; break; case 38: _this.key.up = false; break; case 39: _this.key.right = false; break; } }; Clarity.prototype.load_map = function (map) { if (typeof map === 'undefined' || typeof map.data === 'undefined' || typeof map.keys === 'undefined') { this.error('Error: Invalid map data!'); return false; } this.current_map = map; this.current_map.background = map.background || '#333'; this.current_map.gravity = map.gravity || {x: 0, y: 0.3}; this.tile_size = map.tile_size || 16; var _this = this; this.current_map.width = 0; this.current_map.height = 0; map.keys.forEach(function (key) { map.data.forEach(function (row, y) { _this.current_map.height = Math.max(_this.current_map.height, y); row.forEach(function (tile, x) { _this.current_map.width = Math.max(_this.current_map.width, x); if (tile == key.id) _this.current_map.data[y][x] = key; }); }); }); this.current_map.width_p = this.current_map.width * this.tile_size; this.current_map.height_p = this.current_map.height * this.tile_size; this.player.loc.x = map.player.x * this.tile_size || 0; this.player.loc.y = map.player.y * this.tile_size || 0; this.player.colour = map.player.colour || '#000'; this.key.left = false; this.key.up = false; this.key.right = false; this.camera = { x: 0, y: 0 }; this.player.vel = { x: 0, y: 0 }; this.log('Successfully loaded map data.'); return true; }; Clarity.prototype.get_tile = function (x, y) { return (this.current_map.data[y] && this.current_map.data[y][x]) ? this.current_map.data[y][x] : 0; }; Clarity.prototype.draw_tile = function (x, y, tile, context) { if (!tile || !tile.colour) return; context.fillStyle = tile.colour; context.fillRect( x, y, this.tile_size, this.tile_size ); }; Clarity.prototype.draw_map = function (context, fore) { for (var y = 0; y < this.current_map.data.length; y++) { for (var x = 0; x < this.current_map.data[y].length; x++) { if ((!fore && !this.current_map.data[y][x].fore) || (fore && this.current_map.data[y][x].fore)) { var t_x = (x * this.tile_size) - this.camera.x; var t_y = (y * this.tile_size) - this.camera.y; if(t_x < -this.tile_size || t_y < -this.tile_size || t_x > this.viewport.x || t_y > this.viewport.y) continue; this.draw_tile( t_x, t_y, this.current_map.data[y][x], context ); } } } if (!fore) this.draw_map(context, true); }; Clarity.prototype.move_player = function () { var tX = this.player.loc.x + this.player.vel.x; var tY = this.player.loc.y + this.player.vel.y; var offset = Math.round((this.tile_size / 2) - 1); var tile = this.get_tile( Math.round(this.player.loc.x / this.tile_size), Math.round(this.player.loc.y / this.tile_size) ); if(tile.gravity) { this.player.vel.x += tile.gravity.x; this.player.vel.y += tile.gravity.y; } else { this.player.vel.x += this.current_map.gravity.x; this.player.vel.y += this.current_map.gravity.y; } if (tile.friction) { this.player.vel.x *= tile.friction.x; this.player.vel.y *= tile.friction.y; } var t_y_up = Math.floor(tY / this.tile_size); var t_y_down = Math.ceil(tY / this.tile_size); var y_near1 = Math.round((this.player.loc.y - offset) / this.tile_size); var y_near2 = Math.round((this.player.loc.y + offset) / this.tile_size); var t_x_left = Math.floor(tX / this.tile_size); var t_x_right = Math.ceil(tX / this.tile_size); var x_near1 = Math.round((this.player.loc.x - offset) / this.tile_size); var x_near2 = Math.round((this.player.loc.x + offset) / this.tile_size); var top1 = this.get_tile(x_near1, t_y_up); var top2 = this.get_tile(x_near2, t_y_up); var bottom1 = this.get_tile(x_near1, t_y_down); var bottom2 = this.get_tile(x_near2, t_y_down); var left1 = this.get_tile(t_x_left, y_near1); var left2 = this.get_tile(t_x_left, y_near2); var right1 = this.get_tile(t_x_right, y_near1); var right2 = this.get_tile(t_x_right, y_near2); if (tile.jump && this.jump_switch > 15) { this.player.can_jump = true; this.jump_switch = 0; } else this.jump_switch++; this.player.vel.x = Math.min(Math.max(this.player.vel.x, -this.current_map.vel_limit.x), this.current_map.vel_limit.x); this.player.vel.y = Math.min(Math.max(this.player.vel.y, -this.current_map.vel_limit.y), this.current_map.vel_limit.y); this.player.loc.x += this.player.vel.x; this.player.loc.y += this.player.vel.y; this.player.vel.x *= .9; if (left1.solid || left2.solid || right1.solid || right2.solid) { /* fix overlap */ while (this.get_tile(Math.floor(this.player.loc.x / this.tile_size), y_near1).solid || this.get_tile(Math.floor(this.player.loc.x / this.tile_size), y_near2).solid) this.player.loc.x += 0.1; while (this.get_tile(Math.ceil(this.player.loc.x / this.tile_size), y_near1).solid || this.get_tile(Math.ceil(this.player.loc.x / this.tile_size), y_near2).solid) this.player.loc.x -= 0.1; /* tile bounce */ var bounce = 0; if (left1.solid && left1.bounce > bounce) bounce = left1.bounce; if (left2.solid && left2.bounce > bounce) bounce = left2.bounce; if (right1.solid && right1.bounce > bounce) bounce = right1.bounce; if (right2.solid && right2.bounce > bounce) bounce = right2.bounce; this.player.vel.x *= -bounce || 0; } if (top1.solid || top2.solid || bottom1.solid || bottom2.solid) { /* fix overlap */ while (this.get_tile(x_near1, Math.floor(this.player.loc.y / this.tile_size)).solid || this.get_tile(x_near2, Math.floor(this.player.loc.y / this.tile_size)).solid) this.player.loc.y += 0.1; while (this.get_tile(x_near1, Math.ceil(this.player.loc.y / this.tile_size)).solid || this.get_tile(x_near2, Math.ceil(this.player.loc.y / this.tile_size)).solid) this.player.loc.y -= 0.1; /* tile bounce */ var bounce = 0; if (top1.solid && top1.bounce > bounce) bounce = top1.bounce; if (top2.solid && top2.bounce > bounce) bounce = top2.bounce; if (bottom1.solid && bottom1.bounce > bounce) bounce = bottom1.bounce; if (bottom2.solid && bottom2.bounce > bounce) bounce = bottom2.bounce; this.player.vel.y *= -bounce || 0; if ((bottom1.solid || bottom2.solid) && !tile.jump) { this.player.on_floor = true; this.player.can_jump = true; } } // adjust camera var c_x = Math.round(this.player.loc.x - this.viewport.x/2); var c_y = Math.round(this.player.loc.y - this.viewport.y/2); var x_dif = Math.abs(c_x - this.camera.x); var y_dif = Math.abs(c_y - this.camera.y); if(x_dif > 5) { var mag = Math.round(Math.max(1, x_dif * 0.1)); if(c_x != this.camera.x) { this.camera.x += c_x > this.camera.x ? mag : -mag; if(this.limit_viewport) { this.camera.x = Math.min( this.current_map.width_p - this.viewport.x + this.tile_size, this.camera.x ); this.camera.x = Math.max( 0, this.camera.x ); } } } if(y_dif > 5) { var mag = Math.round(Math.max(1, y_dif * 0.1)); if(c_y != this.camera.y) { this.camera.y += c_y > this.camera.y ? mag : -mag; if(this.limit_viewport) { this.camera.y = Math.min( this.current_map.height_p - this.viewport.y + this.tile_size, this.camera.y ); this.camera.y = Math.max( 0, this.camera.y ); } } } if(this.last_tile != tile.id && tile.script) { eval(this.current_map.scripts[tile.script]); } this.last_tile = tile.id; }; Clarity.prototype.update_player = function () { if (this.key.left) { if (this.player.vel.x > -this.current_map.vel_limit.x) this.player.vel.x -= this.current_map.movement_speed.left; } if (this.key.up) { if (this.player.can_jump && this.player.vel.y > -this.current_map.vel_limit.y) { this.player.vel.y -= this.current_map.movement_speed.jump; this.player.can_jump = false; } } if (this.key.right) { if (this.player.vel.x < this.current_map.vel_limit.x) this.player.vel.x += this.current_map.movement_speed.left; } this.move_player(); }; Clarity.prototype.draw_player = function (context) { context.fillStyle = this.player.colour; context.beginPath(); context.arc( this.player.loc.x + this.tile_size / 2 - this.camera.x, this.player.loc.y + this.tile_size / 2 - this.camera.y, this.tile_size / 2 - 1, 0, Math.PI * 2 ); context.fill(); }; Clarity.prototype.update = function () { this.update_player(); }; Clarity.prototype.draw = function (context) { this.draw_map(context, false); this.draw_player(context); }; /* Setup of the engine */ window.requestAnimFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function(callback) { return window.setTimeout(callback, 1000 / 60); }; var canvas = document.getElementById('canvas'), ctx = canvas.getContext('2d'); canvas.width = 400; canvas.height = 400; var game = new Clarity(); game.set_viewport(canvas.width, canvas.height); game.load_map(map); /* Limit the viewport to the confines of the map */ game.limit_viewport = true; var Loop = function() { ctx.fillStyle = '#333'; ctx.fillRect(0, 0, canvas.width, canvas.height); game.update(); game.draw(ctx); window.requestAnimFrame(Loop); }; Loop();

Use the left, right and up arrow keys to move.

万花筒
HTML
CSS
JavaScript

//nothing to see here

目不转睛的狮子
HTML
CSS
JavaScript

物理仿真
HTML
CSS
JavaScript

IT培训网web

优质的IT教育机构

IT培训网web前端学院

3158895217

Web免费教程共享 — 从基础到实战

[ Free Tutorials ]

第一节:Cocos2d-JS概述

102人正在观看

第二节:开发环境搭建

106人正在观看

第三节:Cocos2d-JS安装配置

185人正在观看

第四节:实战:游戏消灭寿司开发-核心API(1)

125人正在观看

第五节:实战:游戏消灭寿司开发-核心API(2)

98人正在观看

第六节:实战:游戏消灭寿司开发-添加内容到场景(1)

95人正在观看

第七节:实战:游戏消灭寿司开发-添加内容到场景(2)

88人正在观看

第八节:实战:游戏消灭寿司开发-action(1)

195人正在观看

第九节:实战:游戏消灭寿司开发-action小结

163人正在观看

第十节:实战:游戏消灭寿司开发-移除屏幕SushiSprite

105人正在观看

第十一节:实战:游戏消灭寿司开发-场景转换

99人正在观看

第十二节:实战:游戏消灭寿司开发-事件管理器创建交互(1)

205人正在观看

第十三节:实战:游戏消灭寿司开发-事件管理器创建交互(2)

96人正在观看

第十四节:实战:游戏消灭寿司开发-SushiSprite消失动画

85人正在观看

第十五节:实战:游戏消灭寿司开发-项目其他工作(1)

150人正在观看

第十六节:实战:游戏消灭寿司开发-项目其他工作(2)

156人正在观看

第十七节:实战:游戏消灭寿司开发-项目其他工作(3)

95人正在观看

第十八节:实战:游戏消灭寿司开发-项目其他工作(4)

88人正在观看

第十九节:实战:游戏消灭寿司开发-项目其他工作(5)

111人正在观看

>

IT培训网Web全栈式实战公开课

  • 第一节:Cocos2d-JS概述

    播放:95040

  • 第二节:开发环境搭建

    播放:88716

  • 第三节:Cocos2d-JS安装配置

    播放:82598

  • 第四节:实战:游戏消灭寿司
    开发-核心API(1)

    播放:98486

  • 第五节:实战:游戏消灭寿司
    开发-核心API(2)

    播放:95815

  • 第六节:实战:游戏消灭寿司
    开发-添加内容到场景(1)

    播放:90287

  • 第七节:实战:游戏消灭寿司
    开发-添加内容到场景(2)

    播放:85469

  • 第八节:实战:游戏消灭寿司
    开发-action(1)

    播放:82586

  • 第九节:实战:游戏消灭寿司
    开发-action小结

    播放:89521

  • 第十节:实战:游戏消灭寿司
    开发-移除屏幕SushiSprite

    播放:83547

  • 第十一节:实战:游戏消灭寿司
    开发-场景转换

    播放:85289

  • 第十二节:实战:游戏消灭寿司
    开发-事件管理器创建交互(1)

    播放:83558

  • 第十三节:实战:游戏消灭寿司
    开发-事件管理器创建交互(2)

    播放:88824

  • 第十四节:实战:游戏消灭寿司
    开发-SushiSprite消失动画

    播放:82250

  • 第十五节:实战:游戏消灭寿司
    开发-项目其他工作(1)

    播放:81110

  • 第十六节:实战:游戏消灭寿司
    开发-项目其他工作(2)

    播放:85294

  • 第十七节:实战:游戏消灭寿司
    开发-项目其他工作(3)

    播放:87123

  • 第十八节:实战:游戏消灭寿司
    开发-项目其他工作(4)

    播放:82563

  • 第十九节:实战:游戏消灭寿司
    开发-项目其他工作(5)

    播放:85486

Web开源核心代码分享

HTML,CSS,JS核心代码全览

了解更多交互效果

Web前端基础教程

我们不共享单车,我们只共享干货

www.tedu.cn

Web前端基础教程

IT培训网与Web前端

IT培训网简介

IT培训网与Web

HTML5篇

简介

浏览器支持

内联 SVG

拖放(Drag 和 Drop)

CSS3篇

简介

边框

背景

3D转换

JavaScript篇

用法

输出

语法

语句

获取更多教程