Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Building Multiplayer Games with Phoenix and Phaser

Building Multiplayer Games with Phoenix and Phaser

Phoenix and Phaser might sound like a cheesy pop band but get these two frameworks playing nice together and you're guaranteed a party. Both are blistering fast, and incredibly easy to use. Building multiplayer games has never been easier.

This talk will take you on a step by step guide building yourself a game template, and then using that template to create several multiplayer games. We will introduce you to the joy of creating games using these two wonderful frameworks, and demonstrate some of the possibilities, as well as highlighting some of the challenges and common pitfalls to avoid when developing multiplayer games.

Beginners and anyone interested in having fun with Phoenix channels.

You can watch the talk here: https://youtu.be/I5L9_cXwBcU

Keith

May 10, 2015
Tweet

More Decks by Keith

Other Decks in Programming

Transcript

  1. Designed for web, desktop, mobile with and amazing tutorials great

    documentation Phoenix Framework Productive. Reliable. Fast A productive web framework that does not compromise speed and maintainability
  2. Designed for web, desktop, mobile with and amazing tutorials great

    documentation Phaser Game Framework The fun, fast and free HTML5 Game Framework
  3. Pixi - multi platform - multi touch - ultra fast

    sprite sheet asset loader - auto-detect (webgl or canvas) - rich text
  4. Phaser preloader - - groups - - camera physics sprites

    particles - input - - tile maps - scale manager sound
  5. Hello World Create Phoenix project Install Phaser Create a game

    Update the HTML page Start the server Create a game state Add text "Hello world" Make it draggable
  6. Create a new Phoenix project... $ m i x p

    h o e n i x . n e w d e m o . . . $ c d d e m o
  7. Install Phaser... $ m k d i r w e

    b / s t a t i c / v e n d o r / j s / p h a s e r $ c u r l - L - o w e b / s t a t i c / v e n d o r / j s / p h a s e r / p h a s h t t p s : / / r a w . g i t h u b u s e r c o n t e n t . c o m / p h o t o n s t o r m * Use vendor so Brunch doesn't pre-compile
  8. Create a Game class... e x p o r t

    c l a s s G a m e e x t e n d s P h a s e r . G a m e { / / I n i t i a l i z e P h a s e r c o n s t r u c t o r ( w i d t h , h e i g h t , c o n t a i n e r ) { s u p e r ( w i d t h , h e i g h t , P h a s e r . A U T O , c o n t a i n } } web/static/js/Game.js
  9. Create new Game instance... / / I m p o

    r t d e p e n d e n c i e s / / L e t s g o ! n e w G a m e ( 7 0 0 , 4 5 0 , " p h a s e r " ) i m p o r t { G a m e } f r o m " . / G a m e " web/static/js/app.js
  10. Set up the page template... < d i v i

    d = " p h a s e r " > < / d i v > web/templates/page/index.html.eex
  11. Start up the server... $ i e x - S

    m i x p h o e n i x . s e r v e r
  12. Game States Using states allows you to break your game

    up into smaller pieces that can handle di erent mechanics of the game, such as a menu.
  13. Create states directory $ m k d i r w

    e b / s t a t i c / j s / s t a t e s
  14. Create a new "State" i m p o r t

    { c r e a t e L a b e l } f r o m " . . / c o m m o n / l a b e l s " e x p o r t c l a s s L o b b y e x t e n d s P h a s e r . S t a t e { c r e a t e ( ) { c o n s t l a b e l = c r e a t e L a b e l ( t h i s , " H e l l o w o r l d " ) l a b e l . a n c h o r . s e t T o ( 0 . 5 ) } } web/static/js/states/Lobby.js
  15. Nothing up my sleeves c o n s t D

    E F A U L T _ S T Y L E = { f o n t : " 6 5 p x A r i a l " , f i l l : " # f f f f f f " } / / c r e a t e L a b e l : : S t a t e - > S t r i n g - > O b j e c t - > S p r i t e e x p o r t c o n s t c r e a t e L a b e l = ( s t a t e , m e s s a g e , s t y l e = D E F A U L T _ S T Y L c o n s t { c e n t e r X , c e n t e r Y } = s t a t e . w o r l d r e t u r n s t a t e . a d d . t e x t ( c e n t e r X , c e n t e r Y , m e s s a g e , s t y l e ) } web/static/js/common/labels.js
  16. Update the Game class i m p o r t

    { L o b b y } f r o m " . / s t a t e s / L o b b y " e x p o r t c l a s s G a m e e x t e n d s P h a s e r . G a m e { c o n s t r u c t o r ( w i d t h , h e i g h t , c o n t a i n e r ) { s u p e r ( w i d t h , h e i g h t , P h a s e r . A U T O , c o n t a i n t h i s . s t a t e . a d d ( " l o b b y " , L o b b y , f a l s e ) t h i s . s t a t e . s t a r t ( " l o b b y " ) } }
  17. Lets make the text draggable e x p o r

    t c l a s s L o b b y e x t e n d s P h a s e r . S t a t e { c r e a t e ( ) { c o n s t l a b e l = c r e a t e L a b e l ( t h i s , " H e l l o w o l a b e l . a n c h o r . s e t T o ( 0 . 5 ) l a b e l . i n p u t E n a b l e d = t r u e l a b e l . i n p u t . e n a b l e D r a g ( ) } } web/static/js/states/Lobby.js
  18. Create a new Phoenix channel $ m i x p

    h o e n i x . g e n . c h a n n e l L o b b y g a m e s * c r e a t i n g w e b / c h a n n e l s / l o b b y _ c h a n n e l . e x * c r e a t i n g t e s t / c h a n n e l s / l o b b y _ c h a n n e l _ t e s t . e x s A d d t h e c h a n n e l t o y o u r ` w e b / c h a n n e l s / u s e r _ s o c k e t . e x ` h a n d l e r , f c h a n n e l " g a m e s : l o b b y " , D e m o . L o b b y C h a n n e l
  19. Phoenix creates this... d e f m o d u

    l e D e m o . L o b b y C h a n n e l d o u s e D e m o . W e b , : c h a n n e l d e f j o i n ( " g a m e s : l o b b y " , p a y l o a d , s o c k e t ) d o i f a u t h o r i z e d ? ( p a y l o a d ) d o { : o k , s o c k e t } e l s e { : e r r o r , % { r e a s o n : " u n a u t h o r i z e d " } } e n d e n d d e f h a n d l e _ i n ( " s h o u t " , p a y l o a d , s o c k e t ) d o b r o a d c a s t s o c k e t , " s h o u t " , p a y l o a d { : n o r e p l y , s o c k e t } e n d . . . e n d
  20. Add the channel to the UserSocket d e f m

    o d u l e D e m o . U s e r S o c k e t d o u s e P h o e n i x . S o c k e t # # C h a n n e l s c h a n n e l " g a m e s : l o b b y " , D e m o . L o b b y C h a n n e l . . . web/channels/user_socket.ex
  21. Pass the socket to the game / / I m

    p o r t d e p e n d e n c i e s i m p o r t { G a m e } f r o m " . / G a m e " i m p o r t { S o c k e t } f r o m " p h o e n i x " c o n s t s o c k e t = n e w S o c k e t ( " / s o c k e t " , { } ) / / L e t s g o ! g a m e . s t a r t ( s o c k e t ) c o n s t g a m e = n e w G a m e ( 7 0 0 , 4 5 0 , " p h a s e r " ) web/static/app.js
  22. Connect the socket e x p o r t c

    l a s s G a m e e x t e n d s P h a s e r . G a m e { c o n s t r u c t o r ( w i d t h , h e i g h t , c o n t a i n e r ) { s u p e r ( w i d t h , h e i g h t , P h a s e r . A U T O , c o n t a i n e r , n u l l ) / / s e t u p g a m e s t a t e s t h i s . s t a t e . a d d ( " l o b b y " , L o b b y , f a l s e ) } s t a r t ( s o c k e t ) { s o c k e t . c o n n e c t ( ) } web/static/js/Game.js
  23. Create the channel i m p o r t {

    j o i n C h a n n e l } f r o m " . / c o m m o n / c h a n n e l s " e x p o r t c l a s s G a m e e x t e n d s P h a s e r . G a m e { . . . s t a r t ( s o c k e t ) { s o c k e t . c o n n e c t ( ) / / c r e a t e a n d j o i n t h e l o b b y c h a n n e l c o n s t c h a n n e l = s o c k e t . c h a n n e l ( " g a m e s : l o b b y " , { } ) j o i n C h a n n e l ( c h a n n e l , ( ) = > { c o n s o l e . l o g ( " J o i n e d s u c c e s s f u l l y " ) / / s t a r t t h e l o b b y [ n a m e , c l e a r W o r l d , c l e a r C a c h e , . . . s t a t e t h i s . s t a t e . s t a r t ( " l o b b y " , t r u e , f a l s e , c h a n n e l ) } ) }
  24. Log the channel reference e x p o r t

    c l a s s L o b b y e x t e n d s P h a s e r . S t a t e { i n i t ( . . . a r g s ) { c o n s t [ c h a n n e l ] = a r g s c o n s o l e . l o g ( c h a n n e l ) } web/static/js/Lobby.js
  25. Inside channels.js / / j o i n C h

    a n n e l : : C h a n n e l - > C h a n n e l e x p o r t c o n s t j o i n C h a n n e l = ( c h a n n e l , s u c c e s s , f a i l u r e , t i m e o u t ) c h a n n e l . j o i n ( ) . r e c e i v e ( " o k " , s u c c e s s | | j o i n O k ) . r e c e i v e ( " e r r o r " , f a i l u r e | | j o i n E r r o r ) . r e c e i v e ( " t i m e o u t " , t i m e o u t | | j o i n T i m e o u t ) r e t u r n c h a n n e l } / / j o i n O k : : R e s p o n s e - > C o n s o l e c o n s t j o i n O k = ( r e s p o n s e ) = > c o n s o l e . l o g ( ` J o i n e d s u c c e s s f u l l y ` / / j o i n E r r o r : : R e s p o n s e - > C o n s o l e c o n s t j o i n E r r o r = ( r e s p o n s e ) = > c o n s o l e . l o g ( ` F a i l e d t o j o i n c h a n / / j o i n E r r o r : : N u l l - > C o n s o l e c o n s t j o i n T i m e o u t = ( ) = > c o n s o l e . l o g ( " N e t w o r k i n g i s s u e . S t i l l w
  26. Store the channel reference e x p o r t

    c l a s s L o b b y e x t e n d s P h a s e r . S t a t e { i n i t ( . . . o p t i o n s ) { c o n s t [ c h a n n e l ] = o p t i o n s t h i s . c h a n n e l = c h a n n e l } web/static/js/Lobby.js
  27. Make the label sync } i m p o r

    t { c r e a t e S y n c L a b e l } f r o m " . . / c o m m o n / s y n c _ l a b e l s " e x p o r t c l a s s L o b b y e x t e n d s P h a s e r . S t a t e { . . . c r e a t e ( ) { c o n s t l a b e l = c r e a t e S y n c L a b e l ( t h i s , " H e l l o w o r l d " , t h i s . c h a n n e l ) } web/static/js/Lobby.js
  28. And here's the sync label i m p o r

    t { c r e a t e L a b e l } f r o m " . / l a b e l s " i m p o r t { s y n c P o s i t i o n } f r o m " . / s y n c " e x p o r t c o n s t c r e a t e S y n c L a b e l = ( s t a t e , m e s s a g e , c h a n n e l ) = > { c o n s t l a b e l = c r e a t e L a b e l ( s t a t e , m e s s a g e ) l a b e l . a n c h o r . s e t T o ( 0 . 5 ) l a b e l . i n p u t E n a b l e d = t r u e l a b e l . i n p u t . e n a b l e D r a g ( ) / / s e n d m e s s a g e o n d r a g s t o p [ s p r i t e , c h a n n e l , e v e n t ] s y n c P o s i t i o n ( l a b e l , c h a n n e l , l a b e l . e v e n t s . o n D r a g S t o p ) r e t u r n l a b e l } web/static/js/common/sync_labels.js
  29. And nally the sync position / / s y n

    c P o s i t i o n : : S p r i t e - > C h a n n e l - > E v e n t - > F u n c t i o n - > E v e e x p o r t c o n s t s y n c P o s i t i o n = ( s p r i t e , c h a n n e l , e v e n t ) = > { e v e n t . a d d ( s p r i t e = > s e n d P o s i t i o n ( s p r i t e , c h a n n e l ) ) } / / s e n d P o s i t i o n : : S p r i t e - > C h a n n e l - > S t r i n g e x p o r t c o n s t s e n d P o s i t i o n = ( s p r i t e , c h a n n e l ) = > { c o n s o l e . l o g ( s e r i a l i z e P o s i t i o n ( s p r i t e ) ) } / / s e r i a l i z e P o s i t i o n : : S p r i t e - > O b j e c t e x p o r t c o n s t s e r i a l i z e P o s i t i o n = ( { x , y } ) = > O b j e c t . a s s i g n ( { x , y web/static/js/common/sync.js
  30. Send the message / / s e n d P

    o s i t i o n : : S p r i t e - > C h a n n e l - > P u s h e x p o r t c o n s t s e n d P o s i t i o n = ( s p r i t e , c h a n n e l ) = > { c o n s t m e s s a g e = s e r i a l i z e P o s i t i o n ( s p r i t e ) c o n s o l e . l o g ( " S e n d i n g m e s s a g e " , m e s s a g e ) c h a n n e l . p u s h ( " s h o u t " , m e s s a g e ) } web/static/js/common/sync.js
  31. Receive the message / / s y n c P

    o s i t i o n : : S p r i t e - > C h a n n e l - > E v e n t - > F u n c t i o n - > E v e e x p o r t c o n s t s y n c P o s i t i o n = ( s p r i t e , c h a n n e l , e v e n t ) = > { e v e n t . a d d ( s p r i t e = > s e n d P o s i t i o n ( s p r i t e , c h a n n e l ) ) r e c e i v e P o s i t i o n ( s p r i t e , c h a n n e l ) } / / r e c e i v e P o s i t i o n = S p r i t e - > C h a n n e l - > P u s h e x p o r t c o n s t r e c e i v e P o s i t i o n = ( s p r i t e , c h a n n e l ) = > { c h a n n e l . o n ( " s h o u t " , ( m e s s a g e ) = > { c o n s o l e . l o g ( " R e c e i v e d m e s s a g e " , m e s s a g e ) } ) } web/static/js/common/sync.js
  32. Remember this? d e f m o d u l

    e D e m o . L o b b y C h a n n e l d o . . . d e f h a n d l e _ i n ( " s h o u t " , p a y l o a d , s o c k e t ) d o b r o a d c a s t s o c k e t , " s h o u t " , p a y l o a d { : n o r e p l y , s o c k e t } e n d . . . e n d web/channels/lobby_channel.ex
  33. Lets make a position handler d e f m o

    d u l e D e m o . L o b b y C h a n n e l d o . . . # b r o a d c a s t p o s i t i o n d a t a t o e v e r y o n e e l s e d e f h a n d l e _ i n ( " p o s i t i o n " , p a y l o a d , s o c k e t ) d o b r o a d c a s t _ f r o m s o c k e t , " p o s i t i o n " , p a y l o a d { : n o r e p l y , s o c k e t } e n d . . . e n d web/channels/lobby_channel.ex
  34. Update the sync to use position / / s e

    n d P o s i t i o n : : S p r i t e - > C h a n n e l - > P u s h e x p o r t c o n s t s e n d P o s i t i o n = ( s p r i t e , c h a n n e l ) = > { . . . c h a n n e l . p u s h ( " p o s i t i o n " , m e s s a g e ) } / / r e c e i v e P o s i t i o n = S p r i t e - > C h a n n e l - > P u s h e x p o r t c o n s t r e c e i v e P o s i t i o n = ( s p r i t e , c h a n n e l ) = > { . . . c h a n n e l . o n ( " p o s i t i o n " , c a l l b a c k ) } web/static/js/common/sync.js
  35. Remember to restart your server [ e r r o

    r ] G e n S e r v e r # P I D < 0 . 4 1 3 . 0 > t e r m i n a t i n g * * ( F u n c t i o n C l a u s e E r r o r ) n o f u n c t i o n c l a u s e m a t c h i n g i n D e m o . L o b ( d e m o ) w e b / c h a n n e l s / l o b b y _ c h a n n e l . e x : 2 0 : D e m o . G a m e C h a n n e l . h a n d l e _ i n ( " p o s i t i o n " , % { " x " = > 2 4 9 , " y " = > % P h o e n i x . S o c k e t { a s s i g n s : % { } , c h a n n e l : D e m o . L o b b y C h a n n e l , c h # P I D < 0 . 4 1 3 . 0 > , e n d p o i n t : D e m o . E n d p o i n t , h a n d l e r : D e m o . U s e r S o j o i n e d : t r u e , p u b s u b _ s e r v e r : D e m o . P u b S u b , r e f : " 1 0 " , s e r i a l i P h o e n i x . T r a n s p o r t s . W e b S o c k e t S e r i a l i z e r , t o p i c : " g a m e s : l o b b y " P h o e n i x . T r a n s p o r t s . W e b S o c k e t , t r a n s p o r t _ n a m e : : w e b s o c k e t , t r # P I D < 0 . 3 9 6 . 0 > } )
  36. Now nally we can update the sprite position! / /

    r e c e i v e P o s i t i o n = S p r i t e - > C h a n n e l - > P u s h e x p o r t c o n s t r e c e i v e P o s i t i o n = ( s p r i t e , c h a n n e l ) = > { c o n s t c a l l b a c k = ( m e s s a g e ) = > { c o n s o l e . l o g ( " R e c e i v e d m e s s a g e " , m e s s a g e ) c o n s t { x , y } = m e s s a g e s p r i t e . p o s i t i o n . s e t T o ( x , y ) } c h a n n e l . o n ( " p o s i t i o n " , c a l l b a c k ) } web/static/js/common/sync.js
  37. Use the onDragUpdate event e x p o r t

    c o n s t c r e a t e S y n c L a b e l = ( s t a t e , m e s s a g e , c h a n n e l ) = > { . . . r e t u r n l a b e l } s y n c P o s i t i o n ( l a b e l , c h a n n e l , l a b e l . e v e n t s . o n D r a g U p d a t e ) web/static/js/Lobby.js
  38. So what to do? One option is to have the

    server gather and send only one update, and set a broadcast frequency for the node d e f s t a r t _ b r o a d c a s t i n g d o { : o k , _ } = : t i m e r . s e n d _ i n t e r v a l ( 2 0 0 , : b r o a d c a s t ) e n d d e f h a n d l e _ i n f o ( : b r o a d c a s t , s t a t e ) d o # g a t h e r a l l t h e i n f o r m a t i o n t h e c l i e n t n e e d s h e r e . . . . b r o a d c a s t _ u p d a t e ( s t a t e ) { : n o r e p l y , s t a t e } e n d
  39. ElixirScript d e f m o d u l e

    G a m e d o J S . i m p o r t M e n u , " . / E l i x i r . M e n u " d e f i n i t ( ) d o g a m e = J S . n e w P h a s e r . G a m e , [ 8 0 0 , 6 0 0 , P h a s e r . C A N V A S , " p h a s e r " ] g a m e . s t a t e . a d d ( " m e n u " , % { " c r e a t e " = > & M e n u . c r e a t e / 1 , " u p d a t e " = > & M e n u . u p d a t e / 1 } ) g a m e . s t a t e . s t a r t ( " m e n u " ) e n d e n d
  40. ElixirScript d e f u p d a t e

    ( g a m e ) d o E l i x i r . E n u m . e a c h ( s p r i t e s , f n ( s p r i t e ) - > J S . u p d a t e ( s p r i t e , % { " r o t a t i o n " = > s p r i t e . r o t a t i o n + 0 . 1 } ) e n d ) e n d d e f p i n i t ( ) d o E l i x i r . A g e n t . s t a r t ( f n - > % { " s p r i t e s " = > [ ] } e n d , [ n a m e : _ _ M O e n d d e f p s p r i t e s ( ) d o E l i x i r . A g e n t . g e t ( _ _ M O D U L E _ _ , f n ( x ) - > x . s p r i t e s e n d ) e n d d e f p a d d _ s p r i t e ( s p r i t e ) d o E l i x i r . A g e n t . u p d a t e ( _ _ M O D U L E _ _ , f n ( x ) - > % { x | " s p r i t e s " = > x . s p r i t e s + + [ s p r i t e ] } e n d ) e n d
  41. Elm

  42. Explore Gami cation Turn based games Winning/Losing Levels Challenges Virtual

    Goods Leaderboards Power/Energy High Scores Badges Authentication/Sign in Social sign in - Facebook/Twitter/Oauth Gestures on mobile (http://hammerjs.github.io/)