From 939313889c3d134d75feef7511d6f28962249c13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Lemaire?= Date: Sun, 15 Oct 2017 19:54:37 +0200 Subject: [PATCH] Added background shader in map view --- README.md | 4 +- TODO.md | 2 +- out/assets/shaders/map-background.glsl | 56 ++++++++++++++++++++++++++ spacetac | 22 +++++----- src/ui/AssetLoading.ts | 6 +++ src/ui/common/UIBuilder.spec.ts | 27 +++++++++++++ src/ui/common/UIBuilder.ts | 34 ++++++++++++++++ src/ui/map/UniverseMapView.ts | 50 ++++++++++++++++------- 8 files changed, 176 insertions(+), 25 deletions(-) create mode 100644 out/assets/shaders/map-background.glsl diff --git a/README.md b/README.md index ba7d5ce..7040c35 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,9 @@ ## How to develop -The only hard dependency of the toolchain is Python3. +The only hard dependency of the toolchain is [yarn](https://yarnpkg.com). + +If yarn is not installed on your system but Python3 is, yarn will be automatically installed in a local virtual environment. If you want to build on your computer, clone the repository, then run: diff --git a/TODO.md b/TODO.md index 7a8519c..ab60a4e 100644 --- a/TODO.md +++ b/TODO.md @@ -105,7 +105,7 @@ Technical * Pack all images in atlases, and split them by stage * Pack sounds -* Use shaders for backgrounds, with fallback images +* Add toggles for shaders, automatically disable them if too slow, and initially disable them on mobile * Replace jasmine with mocha+chai Network diff --git a/out/assets/shaders/map-background.glsl b/out/assets/shaders/map-background.glsl new file mode 100644 index 0000000..0bd5e5e --- /dev/null +++ b/out/assets/shaders/map-background.glsl @@ -0,0 +1,56 @@ +// Star Nest by Pablo Román Andrioli + +// This content is under the MIT License. + +precision mediump float; + +#define iterations 15 +#define formuparam 0.53 + +#define volsteps 12 +#define stepsize 0.11 + +#define zoom 0.800 +#define tile 0.850 + +#define brightness 0.0015 +#define darkmatter 0.300 +#define distfading 0.770 +#define saturation 0.900 + +uniform vec2 resolution; +uniform vec2 offset; +uniform float scale; + +void main() +{ + //get coords and direction + vec2 uv=gl_FragCoord.xy/resolution.xy-.5; + uv.y*=resolution.y/resolution.x; + vec3 dir=vec3(uv*zoom*20./pow(scale,0.5),1.); + vec3 from=vec3(4.+offset.x*0.05,-2.+offset.y*0.05,-2.5-1.0/scale); + + //volumetric rendering + float s=0.1,fade=1.; + vec3 v=vec3(0.); + for (int r=0; r6) fade*=1.-dm; // dark matter, don't render near + //v+=vec3(dm,dm*.5,0.); + v+=fade; + v+=vec3(s,s*s,s*s*s*s)*a*brightness*fade; // coloring based on distance + fade*=distfading; // distance fading + s+=stepsize; + } + v=mix(vec3(length(v)),v,saturation); //color adjust + gl_FragColor = vec4(v*.01,1.); +} diff --git a/spacetac b/spacetac index 170578c..d27948a 100755 --- a/spacetac +++ b/spacetac @@ -1,15 +1,19 @@ #!/bin/bash # Main build/run tool for SpaceTac -# Uses yarn installed in local node.js environment -# REQUIRES python3, else falls back to system-wide npm +# REQUIRES yarn +# If yarn is not found, python3 may be used to create a local node.js environment with yarn in it, and use it +yarn=$(which yarn 2>/dev/null) set -e -which python3 > /dev/null 2>&1 || ( npm "$@" && exit 0 ) +if [ "x${yarn}" != "x" ] +then + "${yarn}" "$@" +else + dir=$(dirname $0) -dir=$(dirname $0) - -test -x "${dir}/.env/bin/nodeenv" || ( virtualenv -p python3 "${dir}/.env" && "${dir}/.env/bin/pip" install --upgrade nodeenv ) -test -e "${dir}/.env/node/bin/activate" || "${dir}/.env/bin/nodeenv" --node=6.11.1 --force "${dir}/.env/node" -test -e "${dir}/.env/node/bin/yarn" || "${dir}/.env/node/bin/shim" "${dir}/.env/node/bin/npm" install -g yarn@1.1.0 -PATH="${dir}/.env/node/bin:${PATH}" yarn "$@" + test -x "${dir}/.env/bin/nodeenv" || ( virtualenv -p python3 "${dir}/.env" && "${dir}/.env/bin/pip" install --upgrade nodeenv ) + test -e "${dir}/.env/node/bin/activate" || "${dir}/.env/bin/nodeenv" --node=6.11.1 --force "${dir}/.env/node" + test -e "${dir}/.env/node/bin/yarn" || "${dir}/.env/node/bin/shim" "${dir}/.env/node/bin/npm" install -g yarn@1.1.0 + PATH="${dir}/.env/node/bin:${PATH}" yarn "$@" +fi diff --git a/src/ui/AssetLoading.ts b/src/ui/AssetLoading.ts index c6be551..f2c38db 100644 --- a/src/ui/AssetLoading.ts +++ b/src/ui/AssetLoading.ts @@ -123,6 +123,8 @@ module TK.SpaceTac.UI { this.loadSound("music/division.mp3"); this.loadSound("music/spring-thaw.mp3"); + + this.loadShader("map-background.glsl"); } this.load.start(); @@ -158,5 +160,9 @@ module TK.SpaceTac.UI { loadSound(path: string) { this.load.audio(AssetLoading.getKey(path), "assets/sounds/" + path); } + + loadShader(path: string) { + this.load.shader(AssetLoading.getKey(path), "assets/shaders/" + path); + } } } diff --git a/src/ui/common/UIBuilder.spec.ts b/src/ui/common/UIBuilder.spec.ts index beb9f81..8887514 100644 --- a/src/ui/common/UIBuilder.spec.ts +++ b/src/ui/common/UIBuilder.spec.ts @@ -169,6 +169,33 @@ module TK.SpaceTac.UI.Specs { expect(a).toBe(3); }) + it("can create shaders", function () { + let builder = new UIBuilder(testgame.view); + + let shader1 = builder.shader("test-shader-1", "test-image-1"); + expect(shader1 instanceof Phaser.Image).toBe(true); + expect(shader1.name).toEqual("test-image-1"); + expect(shader1.filters.length).toBe(1, "one filter set on shader1"); + + let shader2 = builder.shader("test-shader-2", { width: 500, height: 300 }); + expect(shader2 instanceof Phaser.Image).toBe(true); + expect(shader2.width).toEqual(500); + expect(shader2.height).toEqual(300); + expect(shader2.filters.length).toBe(1, "one filter set on shader2"); + + let i = 0; + let shader3 = builder.shader("test-shader-3", "test-image-3", 50, 30, () => { return { a: i++, b: { x: 1, y: 2 } } }); + expect(shader3.x).toEqual(50); + expect(shader3.y).toEqual(30); + expect(shader3.filters[0].uniforms["a"]).toEqual({ type: '1f', value: 0 }, "uniform a initial"); + expect(shader3.filters[0].uniforms["b"]).toEqual({ type: '2f', value: Object({ x: 1, y: 2 }) }, "uniform b initial"); + shader3.update(); + expect(shader3.filters[0].uniforms["a"]).toEqual({ type: '1f', value: 1 }, "uniform a updated"); + expect(shader3.filters[0].uniforms["b"]).toEqual({ type: '2f', value: Object({ x: 1, y: 2 }) }, "uniform b updated"); + + expect(testgame.view.getLayer("base").children.length).toBe(3, "view layer should have three children"); + }) + it("creates sub-builders, preserving text style", function () { let base_style = new UITextStyle(); base_style.width = 123; diff --git a/src/ui/common/UIBuilder.ts b/src/ui/common/UIBuilder.ts index 0aec167..163cebb 100644 --- a/src/ui/common/UIBuilder.ts +++ b/src/ui/common/UIBuilder.ts @@ -8,6 +8,8 @@ module TK.SpaceTac.UI { export type UIGroup = Phaser.Group export type UIContainer = Phaser.Group | Phaser.Image + export type ShaderValue = number | { x: number, y: number } + /** * Text style interface */ @@ -185,6 +187,38 @@ module TK.SpaceTac.UI { return result; } + /** + * Add a fragment shader area, with optional fallback image + */ + shader(name: string, base: string | { width: number, height: number }, x = 0, y = 0, updater?: () => { [name: string]: ShaderValue }): UIImage { + let source = this.game.cache.getShader(name); + source = "" + source; + let uniforms: any = {}; + if (updater) { + iteritems(updater(), (key, value) => { + uniforms[key] = { type: (typeof value == "number") ? "1f" : "2f", value: value }; + }); + } + let filter = new Phaser.Filter(this.game, uniforms, source); + let result: Phaser.Image; + if (typeof base == "string") { + result = this.image(base, x, y); + result.filters = [filter]; + filter.setResolution(result.width, result.height); + } else { + result = filter.addToWorld(x, y, base.width, base.height); + this.add(result); + } + if (updater) { + result.update = () => { + iteritems(updater(), (key, value) => filter.uniforms[key].value = value); + filter.update(); + } + } + filter.update(); + return result; + } + /** * Change the content of an component * diff --git a/src/ui/map/UniverseMapView.ts b/src/ui/map/UniverseMapView.ts index 6b0868a..9bae4ad 100644 --- a/src/ui/map/UniverseMapView.ts +++ b/src/ui/map/UniverseMapView.ts @@ -66,6 +66,8 @@ module TK.SpaceTac.UI { create() { super.create(); + let builder = new UIBuilder(this); + this.layer_universe = this.getLayer("universe"); this.layer_overlay = this.getLayer("overlay"); @@ -143,7 +145,16 @@ module TK.SpaceTac.UI { } }); - this.setZoom(2); + this.setZoom(2, 0); + + // Add a shader background + builder.shader("map-background", { width: this.getWidth(), height: this.getHeight() }, 0, 0, () => { + let scale = this.layer_universe.scale.x; + return { + offset: { x: (920 - this.layer_universe.x) / scale, y: -(540 - this.layer_universe.y) / scale }, + scale: scale + } + }); // Trigger an auto-save any time we go back to the map this.autoSave(); @@ -226,46 +237,57 @@ module TK.SpaceTac.UI { */ setCamera(x: number, y: number, span: number, duration = 500, easing = Phaser.Easing.Cubic.InOut) { let scale = 1000 / span; - this.tweens.create(this.layer_universe.position).to({ x: 920 - x * scale, y: 540 - y * scale }, duration, easing).start(); - this.tweens.create(this.layer_universe.scale).to({ x: scale, y: scale }, duration, easing).start(); + let dest_x = 920 - x * scale; + let dest_y = 540 - y * scale; + if (duration) { + this.tweens.create(this.layer_universe.position).to({ x: dest_x, y: dest_y }, duration, easing).start(); + this.tweens.create(this.layer_universe.scale).to({ x: scale, y: scale }, duration, easing).start(); + } else { + this.layer_universe.position.set(dest_x, dest_y); + this.layer_universe.scale.set(scale); + } } /** * Set the camera to include all direct-jump accessible stars */ - setCameraOnAccessible(star: Star) { + setCameraOnAccessible(star: Star, duration: number) { let accessible = star.getNeighbors().concat([star]); let xmin = min(accessible.map(star => star.x)); let xmax = max(accessible.map(star => star.x)); let ymin = min(accessible.map(star => star.y)); let ymax = max(accessible.map(star => star.y)); let dmax = Math.max(xmax - xmin, ymax - ymin); - this.setCamera(xmin + (xmax - xmin) * 0.5, ymin + (ymax - ymin) * 0.5, dmax * 1.2); + this.setCamera(xmin + (xmax - xmin) * 0.5, ymin + (ymax - ymin) * 0.5, dmax * 1.2, duration); } /** * Set the alpha value for all links */ - setLinksAlpha(alpha: number) { - this.game.add.tween(this.starlinks_group).to({ alpha: alpha }, 500 * Math.abs(this.starlinks_group.alpha - alpha)).start(); + setLinksAlpha(alpha: number, duration = 500) { + if (duration) { + this.game.add.tween(this.starlinks_group).to({ alpha: alpha }, duration * Math.abs(this.starlinks_group.alpha - alpha)).start(); + } else { + this.starlinks_group.alpha = alpha; + } } /** * Set the current zoom level (0, 1 or 2) */ - setZoom(level: number) { + setZoom(level: number, duration = 500) { let current_star = this.player.fleet.location ? this.player.fleet.location.star : null; if (!current_star || level <= 0) { - this.setCamera(0, 0, this.universe.radius * 2); - this.setLinksAlpha(1); + this.setCamera(0, 0, this.universe.radius * 2, duration); + this.setLinksAlpha(1, duration); this.zoom = 0; } else if (level == 1) { - this.setCameraOnAccessible(current_star); - this.setLinksAlpha(0.6); + this.setCameraOnAccessible(current_star, duration); + this.setLinksAlpha(0.6, duration); this.zoom = 1; } else { - this.setCamera(current_star.x, current_star.y, current_star.radius * 2); - this.setLinksAlpha(0.2); + this.setCamera(current_star.x, current_star.y, current_star.radius * 2, duration); + this.setLinksAlpha(0.2, duration); this.zoom = 2; }