examples/moles: add graphical effects; particles and flashes
authorAlexis Laferrière <alexis.laf@xymus.net>
Tue, 11 Aug 2015 18:58:20 +0000 (14:58 -0400)
committerAlexis Laferrière <alexis.laf@xymus.net>
Wed, 12 Aug 2015 18:42:12 +0000 (14:42 -0400)
Signed-off-by: Alexis Laferrière <alexis.laf@xymus.net>

examples/mnit_moles/src/effects.nit [new file with mode: 0644]
examples/mnit_moles/src/moles_android.nit
examples/mnit_moles/src/moles_linux.nit

diff --git a/examples/mnit_moles/src/effects.nit b/examples/mnit_moles/src/effects.nit
new file mode 100644 (file)
index 0000000..a34a864
--- /dev/null
@@ -0,0 +1,339 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Graphical effects
+module effects
+
+intrude import geometry::points_and_lines
+import mnit::opengles1
+
+import moles
+
+# Position, or vector
+class Pos
+       super Point[Float]
+
+       # Addition of `self` and `other`
+       fun +(other: Pos): Pos do return new Pos(other.x + x, other.y + y)
+end
+
+redef class PointerEvent
+       # Convert to `Pos`
+       fun to_pos: Pos do return new Pos(x, y)
+end
+
+redef class Float
+       # Pretty alias to `pow`
+       private fun ^(other: Float): Float do return self.pow(other)
+end
+
+# Graphical effect
+abstract class Effect
+       # Time of creation since (appropriate) app lauch
+       var created_at: Float = app.clock.total.to_f
+
+       # Is this effect dead?
+       var dead = false
+
+       # Time to live of this effect, in seconds
+       var ttl: Float = ttl_base + ttl_rand.rand is lazy
+
+       # Time to live base value
+       private var ttl_base: Float
+
+       # Variation range to add to `ttl_base`
+       private var ttl_rand: Float
+
+       private fun update_and_draw(display: Display, t: Float) do end
+end
+
+# Full screen flash
+class Flash
+       super Effect
+
+       # Red value
+       var r: Float
+
+       # Green value
+       var g: Float
+
+       # Blue value
+       var b: Float
+
+       # Start alpha value
+       var a: Float
+
+       # Reduction in alpha value per seconds
+       var da: Float
+
+       redef fun update_and_draw(display, t)
+       do
+               var dt = t - created_at
+               var a = a - da * dt
+               if a <= 0.0 then
+                       dead = true
+                       return
+               end
+
+               native_flash(r, g, b, a, display.width.to_f, display.height.to_f)
+       end
+
+       private fun native_flash(r, g, b, a, w, h: Float) `{
+               GLfloat colors[] =
+               {
+                   r, g, b, a,
+                   r, g, b, a,
+                   r, g, b, a,
+                   r, g, b, a,
+               };
+               GLfloat coords[] =
+               {
+                   0.0, 0.0, 0.0,
+                   0.0, h, 0.0,
+                   w, 0.0, 0.0,
+                   w, h, 0.0,
+               };
+
+               glLoadIdentity();
+
+               glEnableClientState(GL_VERTEX_ARRAY);
+               glEnableClientState(GL_COLOR_ARRAY);
+
+               glEnable(GL_BLEND);
+               glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+
+               glDisable(GL_DEPTH_TEST);
+               glDisable(GL_TEXTURE_2D);
+
+               glVertexPointer(3, GL_FLOAT, 0, coords);
+               glColorPointer(4, GL_FLOAT, 0, colors);
+               glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+
+               glDisableClientState(GL_VERTEX_ARRAY);
+               glDisableClientState(GL_COLOR_ARRAY);
+               glDisable(GL_BLEND);
+
+               if ((mnit_opengles_error_code = glGetError()) != GL_NO_ERROR) {
+                       fprintf(stderr, "Error drawing: %i\n", mnit_opengles_error_code);
+               }
+       `}
+end
+
+# Visible particle
+abstract class Particle
+       super Effect
+
+       # Effect image
+       var img: Image
+end
+
+# Particle moving along a cubic Bézier curve
+class CubicBezierParticle
+       super Particle
+
+       # Points on this curve, from the start to the end with the handles in the middle
+       #
+       # Require: `points.length == 4`
+       var points: Array[Pos]
+
+       redef fun update_and_draw(display, t)
+       do
+               assert points.length == 4
+
+               var dt = t - created_at
+               var p = dt / ttl
+
+               var i = 1.0-p
+               var bx = i*i*i * points[0].x + 3.0*i*i*p * points[1].x +
+                        3.0*i*p*p * points[2].x + p*p*p*points[3].x
+               var by = i*i*i * points[0].y + 3.0*i*i*p * points[1].y +
+                        3.0*i*p*p * points[2].y + p*p*p*points[3].y
+
+               img.scale = display_scale
+               if display isa Opengles1Display then display.color(1.0, 1.0, 1.0, p)
+               display.blit_centered(img, bx, by)
+               if display isa Opengles1Display then display.reset_color
+
+               if dt > ttl then dead = true
+       end
+end
+
+# Particle falling like a feather
+class FeatheryParticle
+       super Particle
+
+       # Origin of effect
+       var from: Pos
+
+       # Randomized variation so this particle is unique
+       var ddt: Float = pi.rand
+
+       # Direction: `-1.0` for left, `1.0` for right
+       var dir: Float = if 2.rand == 0 then -1.0 else 1.0
+
+       # Randomized variation on X
+       var ddx: Float = (4.0 - 8.0.rand)^2.0
+
+       # Randomized variation on Y
+       var ddy: Float = (12.0 - 24.0.rand)^2.0
+
+       redef fun update_and_draw(display, t)
+       do
+               var dt = t - created_at
+
+               var dx = ddx + 30.0*(dt+ddt).sin
+               dx *= dir
+               var dy = ddy + 20.0*dt + 16.0*(dt*2.0+ddt*2.0).cos
+               var pos = from + new Pos(dx, dy)
+
+               if display isa Opengles1Display then display.color(1.0, 1.0, 1.0, 5.0-5.0*dt/ttl)
+               display.blit_centered(img, pos.x, pos.y)
+               if display isa Opengles1Display then display.reset_color
+
+               if dt > ttl then dead = true
+       end
+end
+
+# Particles that start small then grow bigger and fade out
+private class BlowUpParticle
+       super Particle
+
+       # Origin/center of effect
+       var from: Pos
+
+       redef fun update_and_draw(display, t)
+       do
+               var dt = t - created_at
+               var p = dt/ttl
+
+               if display isa Opengles1Display then display.color(1.0, 1.0, 1.0, 2.0-p*2.0)
+
+               img.scale = p*4.0*display_scale
+               display.blit_centered(img, from.x, from.y)
+
+               if display isa Opengles1Display then display.reset_color
+
+               if dt > ttl then dead = true
+       end
+end
+
+redef class Screen
+       private var particles = new Array[Particle]
+       private var flashes = new Array[Flash]
+
+       redef fun draw_hud(display)
+       do
+               var t = app.clock.total.to_f
+
+               # Particles
+               for particle in particles do particle.update_and_draw(display, t)
+               for particle in particles.reverse_iterator do if particle.dead then
+                       particles.remove particle
+               end
+
+               # Flashes
+               for flash in flashes do flash.update_and_draw(display, t)
+               for flash in flashes.reverse_iterator do if flash.dead then
+                       flashes.remove flash
+               end
+
+               super
+       end
+
+       private var score_center = new Pos(48.0*display_scale, 32.0*display_scale) is lazy
+       private var score_entry = new Pos(48.0*display_scale, 256.0*display_scale) is lazy
+
+       private var score_history = new List[Int]
+       private var score_history_max_length = 60
+
+       redef fun draw_score(display, score)
+       do
+               # Use an history to smooth the score
+               score_history.add game.points
+               if score_history.length > score_history_max_length then score_history.shift
+               var sum = 0
+               for h in score_history do sum += h
+               var avg = sum.to_f/score_history.length.to_f
+               var d = game.points.to_f - avg
+
+               # Color the score according to positive or negative changes
+               var r = 1.0
+               var g = 1.0
+               var b = 1.0
+               if d > 0.0 then
+                       r = 1.0-d.to_f/2.0
+                       b = r
+                       g = 1.0-d.to_f/10.0
+               else if d < 0.0 then
+                       g = 1.0+d.to_f/5.0
+                       b = g
+               end
+
+               if display isa Opengles1Display then display.color(r, g, b, 1.0)
+
+               # Draw the score itself
+               super(display, avg.to_i)
+
+               if display isa Opengles1Display then display.reset_color
+       end
+end
+
+redef class HoleContent
+       # Add a `CubicBezierParticle` from `event` to the score box with `img`
+       private fun bezier_to_score(img: Image, event: PointerEvent)
+       do
+               app.screen.particles.add new CubicBezierParticle(2.0, 0.0, img,
+                       [event.to_pos, event.to_pos + new Pos(0.0, -128.0),
+                        app.screen.score_entry, app.screen.score_center])
+       end
+end
+
+redef class Mole
+
+       # Number of hair particles
+       private var n_hair_on_hit = 20
+
+       redef fun hit(game, hole, event)
+       do
+               super
+
+               app.assets.hair.scale = display_scale
+               for i in n_hair_on_hit.times do
+                       app.screen.particles.add new FeatheryParticle(2.0, 2.0, app.assets.hair, event.to_pos)
+               end
+
+               bezier_to_score(app.assets.point, event)
+               app.screen.particles.add new BlowUpParticle(0.5, 0.0, app.assets.point, event.to_pos)
+       end
+end
+
+redef class Trap
+       # Image for `CubicBezierParticle` effect towards the score board
+       protected fun penalty_img: Image do return app.assets.penalty_ten
+
+       # `Flash` effects on hit
+       protected fun flashes: Array[Flash] do
+               return [new Flash(0.5, 0.0, 1.0, 0.0, 0.0, 0.5, 2.0)]
+       end
+
+       redef fun hit(game, hole, event)
+       do
+               super
+
+               bezier_to_score(penalty_img, event)
+               app.screen.particles.add new BlowUpParticle(0.5, 0.0, penalty_img, event.to_pos)
+
+               app.screen.flashes.add_all flashes
+       end
+end
index d89500f..722202d 100644 (file)
@@ -20,6 +20,7 @@ import mnit_android
 import android::portrait
 
 import moles
+import effects
 
 redef class Game
        redef fun columns do return 3
index f7ddd91..2d8a27a 100644 (file)
@@ -16,7 +16,9 @@
 
 module moles_linux
 
-import moles
 import mnit_linux
 
+import moles
+import effects
+
 redef fun display_scale do return 0.25