Thumbnail / Header for article: Player interface

Player interface

This is part 5 in my article series about how I created “Pizza Peers”.

Haven’t read the other entries? Go to the devlog overview.

Now that we can communicate with the game over the internet, we need an interface to make this communication easy and intuitive.

At the end of the previous article, I showed you the “updatePlayer()” function. We’ll be creating the interface for that in this article.

(The rest of the interface consists of buttons, buttons, and even more buttons. I’ll give one example of how such a button works, and that should be enough.)

Receiving signals

In the previous article, I showed you how the game could send and receive signals.

Now I’ll show you how the player (the one holding the smartphone) can do this.

Remember the “on(data)” listener we placed on the peer? The one I told you would do all the magic? Well, that was no understatement.

It actually handles all signals for both computer and player. It’s just that the computer needs an extra step to relay this information to the in-game objects.

The code below is all placed within the on(data) listener on the peer.

This code is for buying ingredients. The first statement actually performs the buying action within the game. The second is triggered when a player enters a buying location, the third when that player leaves the location again.

 1//
 2// This signal is handled by the computer
 3// gm = a reference to the mainGame scene within Phaser
 4//
 5
 6// player has requested to BUY an ingredient
 7if(data.type == 'buy') {
 8  gm.buyAction(peer);
 9}
10
11// 
12// These signals are both handled by the player
13//
14
15// player is at an INGREDIENT location
16if(data.type == 'ing') {
17  // create the button
18  var button = document.createElement("button");
19  button.classList.add('buyButton');
20  button.innerHTML = "Buy " + data.ing + " for " + data.price + "";
21
22  // append to dynamic interface
23  document.getElementById('dynamicInterface').appendChild(button);
24
25  // add event handler
26  // when we click this button ...
27  button.addEventListener ("click", function() {
28    // we buy the ingredient for that price!
29    // (the computer should remember the offer it made)
30    var msg = { 'type':'buy' }
31    peer.send( JSON.stringify(msg) );
32  });
33}
34
35// player has moved away from an INGREDIENT location 
36// so clear the interface
37if(data.type == 'ing-end') {
38  document.getElementById('dynamicInterface').innerHTML = '';
39}

NOTE: Even though computers and players use different parts, it’s all collected within the same listener. (A player will simply never received a “buy” event, so it will always ignore that if-statement.)

Anything you want to happen within the game, just use the structure above. Make up the signals you need and send/receive them on the peer. If it’s the computer, relay the signal to the game object (if needed).

Moving players, again

Phones do not have a joystick, or moving parts at all for that matter. I also don’t want to emulate a keyboard and print a bunch of (arrow) keys on the screen.

So, how do we allow 360 degree movement on the smartphone? Well, I chose the simplest route: simply treat the whole screen as the joystick.

Whenever you touch the screen, it calculates the vector between the center of the screen and your touch, and that’s your movement.

The code below is all you need.

(In the full code I also allow using a non-touch screen to control the game, but I want to keep it simple. I mainly allowed mouse input because it made testing much quicker on my laptop.)

 1// the current peer connected with this (smartphone) interface
 2var curPeer = null;
 3
 4function startController(peer) {
 5  // save reference to our peer
 6  curPeer = peer;
 7
 8  gameDiv.addEventListener('touchstart', onTouchEvent);
 9  gameDiv.addEventListener('touchmove', onTouchEvent);
10  gameDiv.addEventListener('touchend', onTouchEvent);
11  gameDiv.addEventListener('touchcancel', onTouchEvent);
12
13  // insert movement image at the center
14  // (it rotates to show how you're currently moving)
15  document.getElementById('movementArrow').style.display = 'block';
16}
17
18function onTouchEvent(e) {
19  var x = 0, y = 0;
20
21  if(e.type == 'touchstart' || e.type == 'touchmove' || e.type == 'touchend' || e.type == 'touchcancel'){
22    // these coordinates are not available when touch ends
23    // because, well, there's no touch anymore
24    if(e.type == 'touchstart' || e.type == 'touchmove') {
25      x = e.touches[0].pageX;
26      y = e.touches[0].pageY;
27    }
28
29    // prevent default behaviour + bubbling from touch into mouse events
30    e.preventDefault();
31    e.stopPropagation();
32  }
33
34  // if the interaction has ENDED, reset vector so player becomes static
35  // don't do anything else
36  if(e.type == 'touchend' || e.type == 'touchcancel') {
37    var msg = { 'type': 'move', 'vec': [0,0] };
38    curPeer.send( JSON.stringify(msg) );
39
40    return false;
41  }
42
43  // get center of screen
44  var w  = document.documentElement.clientWidth, 
45      h  = document.documentElement.clientHeight;
46  var cX = 0.5*w, 
47      cY = 0.5*h;
48
49  // get vector between position and center, normalize it
50  var length = Math.sqrt((x-cX)*(x-cX) + (y-cY)*(y-cY))
51  var vector = [(x - cX)/length, (y - cY)/length];
52
53  // rotate movement arrow to match
54  var angle = Math.atan2(vector[1], vector[0]) * 180 / Math.PI;
55  if(angle < 0) { angle += 360; }
56  document.getElementById('movementArrow').style.transform = 'rotate(' + angle + 'deg)';
57
58  // send this vector across the network
59  var message = { 'type': 'move', 'vec': vector }
60  curPeer.send( JSON.stringify(message) );
61
62  return false;
63}

All we need to do to send a signal, is call peer.send( message ).

The message is, again, stringified JSON. We can put in any properties we like, but I recommend keeping it as small and simple as possible. You don’t want to waste any bandwidth with online games.

What now?

You might be thinking: woah, it’s that simple? Why doesn’t everyone use this technique if it offers realtime (online) multiplayer?

(If you weren’t thinking that, well, eh, I’m gonna continue this line of thought anyway.)

Of course, there are downsides to this. There are things you simply cannot do and exceptions you need to take into account every step of the way.

The next article will talk about those!