14 juin 2014

Some bubbles with Famo.us's physic engine in CoffeeScript and Meteor.js

Introduction

A week ago, Raix, the author of Famono package used for integrating Require.js and Famo.us with Meteor.js, has published a new release that simplifies greatly the integration.

I was willing to demonstrate just that when a nice demo of Famo.us's physic engine has shown up on my radar: http://hbsand.com/HappyBoxes/. So why not demonstrating both?


You can access the live demo here: http://famousbubble.meteor.com/.
You can also clone the code repository from Github: https://github.com/PEM--/famousbubble

Note for Chrome users on OSX : The current release of Chrome, the 36, has a bug that stops the demo running after ~1-2s. This bug is fixed in Chrome Canary version 37. I didn't see it on the current Chrome for Android or iOS. Firefox and Safari work well on OSX.

Preparing your development environment

I suppose that you have already install Node.jsMeteor.js and Meteorite on your system. 

So, lets get down to business. The first step is to create the app:
mrt create FamousBubble
cd FamousBubble
rm -rf FamousBubble.*
mkdir -p client/stylesheets client/startup client/views
mrt add coffeescript
mrt add stylus
mrt add jade
mrt add famono

For the imported Require.js package, I keep it at the bare minimum. Famono places the imported packages from a file called client/lib/smart.require:
{
  "famous": {
    "git": "https://github.com/Famous/famous.git"
  },
  "famousPolyfills": {
    "git": "https://github.com/Famous/polyfills.git"
  }
}
This has not changed from the early version.

Linking Famo.us with Meteor.js

In Famono, what has changed is the way you require your dependencies. The client/startup/famous.coffee is greatly reduced:
# This is equivalent to: require 'famousPolyfills'
famousPolyfills
# Identically, this is equivalent to: require 'famous.core.famous'
famous.core.famous
# Declaring the main context
window.mainCtx = famous.core.Engine.createContext()

Everything is now imported into the main context of Meteor.js. There is no more boilerplate code.

Our main Jade file, client/index.jade stays unchanged:
head
  title Famo.us - Physic demo
  meta(name='viewport', content='width=device-width, maximum-scale=1, user-scalable=no')
  meta(name='mobile-web-app-capable', content='yes')
  meta(name='apple-mobile-web-app-capable', content='yes')
  meta(name='apple-mobile-web-app-status-bar-style', content='black')

body
  +index

template(name='index')

The Styus file, client/stylesheets/app.styl, is also unchanged:
@import nib

html
  font-family: Helvetica, Arial, sans-serif

*
  -webkit-user-drag: none

body
  -webkit-user-callout: none
  user-select: none
  background-color: #FF851B

Like the former file, the associated CoffeeScript file client/index.coffee to the index template is almost unchanged either:
Template.index.rendered = ->
  $(document).ready ->
    window.appView = new AppView()
    mainCtx.add appView
    appView.addDragger()
    appView.addBubbles()

Easier subclassing

As the Famo.us's University told us, you create your views by inheriting from Famo.us's ones. With Famono, it is now simplified. Here the code for the AppView, client/views/AppView.coffee that we just call in the previous file, the index template:
class @AppView extends famous.core.View
  DEFAULT_OPTIONS:
    numBodies: 10
    gravity: [0, 0.0015, 0]
  constructor: (@options)->
    @constructor.DEFAULT_OPTIONS = @DEFAULT_OPTIONS
    super @options
    surf = new famous.core.Surface
      size: [400, 400]
      properties:
        backgroundColor: '#FFDC00'
        borderRadius: '8px'
    mod = new famous.core.Modifier
      origin: [.5, .5]
    @add(mod).add surf
    @gravity = new famous.physics.forces.Force @options.gravity
    @ceiling = new famous.physics.constraints.Wall
      normal: [0, 1, 0]
      distance: 200
      restitution: 0
    @floor = new famous.physics.constraints.Wall
      normal: [0, -1, 0]
      distance: 200
      restitution: 0
    @left = new famous.physics.constraints.Wall
      normal: [1, 0, 0]
      distance: 200
      restitution: 0
    @right = new famous.physics.constraints.Wall
      normal: [-1, 0, 0]
      distance: 200
      restitution: 0
    @pe = new famous.physics.PhysicsEngine()
    @collision = new famous.physics.constraints.Collision restitution: 0
    @bubbleBodies = []
    famous.inputs.GenericSync.register
      'mouse': famous.inputs.MouseSync
      'touch': famous.inputs.TouchSync
  addDragger: ->
    @dragger = new Dragger()
    @pe.addBody @dragger.body
    (@_add @dragger.state).add @dragger.shape
    sync = new famous.inputs.GenericSync ['mouse', 'touch']
    @dragger.shape.pipe sync
    sync.on 'update', (data) =>
      @dragger.position[0] += data.delta[0]
      @dragger.position[1] += data.delta[1]
      @dragger.body.setPosition @dragger.position

  addBubble: (i) =>
    bubble = new Bubble()
    @pe.addBody bubble.body
    bubble.state.transformFrom =>
      @gravity.applyForce bubble.body
      bubble.body.getTransform()
    (@add bubble.state).add bubble.shape
    @pe.attach [@right, @left, @floor, @ceiling], bubble.body
    (@pe.attach @collision, @bubbleBodies, bubble.body) if i > 0
    @pe.attach @collision, [bubble.body], @dragger.body
    @bubbleBodies.push bubble.body

  addBubbles: ->
    [0...@options.numBodies].map (i) =>
      famous.utilities.Timer.setTimeout (@addBubble.bind @, i), 1000
As you can see it, the inheritance from a Famo.us's View does not require you to wait for Famo.us to be loaded in your document. Additionally, the require step has been greatly simplified. Calling any class provided by Famo.us is done with a simple namespaced call.

I've create 2 more classes. The first one, client/views/Dragger.coffee concerns the little dragging bubble used to play with the blue bubbles:
class @Dragger
  RADIUS: 30
  constructor: ->
    @shape = new famous.core.Surface
      size: [2 * @RADIUS, 2 * @RADIUS]
      properties:
        border: '2px solid #FF4136'
        borderRadius: "#{2 * @RADIUS}px"
        backgroundColor: 'rgba(255, 255, 255, 0.5)'
    @body = new famous.physics.bodies.Circle
      radius: @RADIUS
      mass: 5
    @position = [0, 0]
    @state = new famous.core.Modifier
      origin: [.5, .5]
      transform: =>
        famous.core.Transform.translate @position[0], @position[1], 0

The second one, client/view/Bubble.coffee concerns the bubble themselves:
class @Bubble
  constructor: ->
    radius = famous.math.Random.integer 20, 60
    @shape = new famous.core.Surface
      size: [radius * 2, radius * 2]
      properties:
        backgroundColor: '#7FDBFF'
        border: '3px solid #0074D9'
        borderRadius: "#{radius}px"
    @body = new famous.physics.bodies.Circle radius: radius, mass: 2
    @state = new famous.core.Modifier origin: [.5, .5]

Conclusion

With the new Famono release, integrating Famo.us and Meteor.js is simpler than ever. My little sample is missing few things. A better separation of the controller from the views would be great. But, there is some upcoming Famo.us lessons that should arrive in the Famo.us's University. Therefore, they will surely cover their best practices. No rush.

Aucun commentaire:

Enregistrer un commentaire