Game Board: Enabling Simple Games in TouchDevelop

Game Board: Enabling Simple Games in TouchDevelop
Manuel Fähndrich
Microsoft Research
One Microsoft Way, Redmond WA 98052, USA
[email protected]
February 23, 2012
Abstract
TouchDevelop is a novel application creation environment for anyone to script their smartphones
anywhere – you do not need a separate PC. TouchDevelop allows you to develop mobile device applications
that can access your data, your media, your sensors and allows using cloud services including storage,
computing, and social networks. TouchDevelop targets students, and hobbyists, not necessarily the
professional developer. Typical TouchDevelop applications are written for fun, or for personalizing the
phone.
Starting with version 1.2 of TouchDevelop, we include a “game board” API for writing simple spritebased games. The API includes a primitive physics engine to simplify common game loops where objects
have speed and experience gravity and friction. Game board elements can be orchestrated using events
based on touch and regular timing intervals. This report describes the game board API and the simple
physics engine behind it.
1
Introduction
TouchDevelop [1] is a novel application creation environment for anyone to script their smartphones anywhere
– you do not need a separate PC. TouchDevelop allows you to develop mobile device applications that can
access your data, your media, your sensors and allows using cloud services including storage, computing, and
social networks. TouchDevelop targets students, and hobbyists, not necessarily the professional developer.
Typical TouchDevelop applications are written for fun, or for personalizing the phone. TouchDevelop is
available for free on the Windows Phone 7 marketplace.
Enabling people to write simple games is a big draw of TouchDevelop. However, without some built-in
support for creating, moving, and animating objects on a game board, the amount of code and data a script
will have to deal with quickly becomes unmanageable.
To enable simple games with a manageable amount of code, TouchDevelop includes the board and sprite
APIs for writing simple sprite-based games. The board implementation includes a primitive physics engine
to simplify common game loops where objects have speed and experience gravity and friction. Additionally,
the API provides simple touch input functions for scripts.
2
Coordinates and Units
Positions in the game board API are based on pixels. The origin of the grid is the top left corner when
holding the phone upgright, the x-axis is horizontal, the y-access vertical.
Sprite positions refer to the center of the sprite, i.e., the half-way point of its width and height before
any rotation is applied. The reasoning behind this choice is so that sprites rotate around their center. In
the future we may provide more control for offsetting sprites from their position.
pixels
pixels
Speed and acceleration are measured in second
and second
2.
1
3
The Board API
3.1
Creating a Board
To create a board, use the media → create board property and bind the result to a variable:
board := media → create board(640)
The numeric parameter is the height of the board in pixels. 640 is the maximum height given the current layout of TouchDevelop’s user interface. Once created, the board dimensions can be retrieved via board → width
and board → height.
Often, you want the board to take up the entire screen of your device. For that purpose, there’s an
alternative way to create the board using media → create full board. When you post a full board to the wall, it
will take up the entire display.
board := media → create full board
3.2
Displaying a Board
A board remains invisible until you post it to the wall. Once posted, updates to the board state and the
sprites on the board become visible only when calling board → update on wall. In general, the code for a game
loop looks something like this:
action main()
data → board := media → create board(640)
... create sprites ...
data → board → post to wall
event gameloop()
... move objects around ...
board → update on wall
3.3
Visual Properties of a Board
A board has a background color and a background picture that can be set individually. The default background color is transparent and so is the default background picture.
board := media → create board(640)
board → set background(colors → green)
board → post to wall
The above example sets the board background to green. The example below let’s the user pick a picture
from the picture library and uses that to fill the background of the board
board := media → create board(640)
pic := media → choose picture
board → set background picture(pic)
board → post to wall
Setting both the background color and the background image will show only the background image.
3.4
Creating Sprites
There are four different kinds of sprites that can be created based on their visual contents:
• board → create ellipse creates an elliptic sprite, good for balls and ufos
• board → create rectangle creates a rectangular sprite
2
• board → create text creates a text sprite that simply displays a piece of updatable text at a particular
position of the board.
• board → create picture creates a sprite from a picture, using the picture content and dimension.
In the future, we may separate the content from the sprite itself so as to make it easier to change the sprite
content. Sprites have an initial position centered on the board and no speed or angular rotation. Such
properties can be set on the sprite using the sprite API.
board := media → create board(640)
ball := board → create ellipse(20,20)
board → post to wall
The above code creates a single circular sprite with width and height equal to 20 pixels. The sprite is
displayed in the middle of the board as this is the starting board position.
Setting the color, position, and speed of sprites is done via the sprite API (Section 4).
3.5
Enumerating over Sprites
A foreach loop can be used to enumerate all sprites on a board:
foreach sprite in board do
...
The number of sprites on a board is returned by board → count and any particular sprite can be retrieved by
an index between 0 and board → count using board → at(i).
3.6
Sprite Collections
During games it is very useful to have several collections of sprites, e.g., the missiles currently flying, the
bullets from the space ship, the enemies, etc. To make this management of sprite collections easy, use
board → create sprite set. Operations to add and remove sprites from sprite sets are exposed on sprite set API
(Section 5).
3.7
Creating Obstacles
Obstacles are currently just walls that can be added to the board. Walls cannot be moved once created.
board := media → create board(640)
board → create obstacle(100,100,50,50,1)
board → post to wall
The code above creates a wall (a single line) at position 100, 100 on the screen and extending diagonally for
50 pixels in both the x and y direction. The last parameter of a wall is its elasticity. It is used to determine
how much of the impulse a moving sprite retains when reflecting off the wall. An elasticity of 1 means the
entire impulse is maintained, whereas 0 means full abosorption of the impulse (a sprite will stick to the wall).
3.8
Board Boundary
By default, the board is open, meaning it has no boundary. Objects moving off the visual part of the
board will simply continue moving away. Since it is common to need reflecting walls around the board, the
create boundary(distance) property can be used to create a reflective set of walls around the board at the given
distance.
3
3.9
Gravity
We can set an acceleration vector on the board that will apply to all sprites. For instance, we can use the
accelerometer reading from the phone to set the board acceleration, thereby creating the illusion that the
objects are affected by earth’s gravity:
p := senses → acceleration quick → scale(1000)
board → set gravity(p → x, p → y)
If you have sprites that you don’t want to be affected by gravity, set their friction to 1.0.
3.10
Animation
Once sprites are given speeds or we have acceleration, the board engine can update a sprite’s position. To
update all sprite animtations for one time step, use the board → evolve property. A time step is simply the
time since the last call to board → evolve or the creation of the board.
We now have enough parts to create our first simple moving sprite script:
board := media → create board(640)
board → create boundary(0)
board → create obstacle(100,100,50,50,1)
ball := board → create ellipse(20,20)
board → post to wall
while true do
p := senses → acceleration quick → scale(1000)
board → set gravity(p → x, p → y)
board → evolve
board → update on board
The script above creates a board and adds a reflective wall around the entire board. It then creates a small
obstacle wall and a single circular sprite. Within the game loop, we read the accelerometer and use its
pixels
readings to set the gravity on the game board in seconds
2 . To make the ball accelerate more quicly, we scale
the acceleration by a factor of 1000. This simple script works, but an explicit loop like above is not the best
way to handle animations. The next section shows how to use the game loop event instead.
3.11
The Game Loop
Although an explicit game loop as shown in the previous section can be used for quick testing of an animation,
it is better to use the gameloop event for code that should run at regular intervals. In order to access the board
from the gameloop event, store your board into a data variable (e.g., using the promote to data refactoring
that shows up when you select a local variable).
Add the gameloop event in the event section of your script. Events contain code like normal actions, but
they are invoked when an event triggers. The gameloop event is triggered roughly every 50 milliseconds.
With the gameloop, the animation code from the previous section now looks as follows:
action main()
data → board := media → create board(640)
data → board → create boundary(0)
data → board → create obstacle(100,100,50,50,1)
ball := data → board → create ellipse(20,20)
data → board → post to wall
event gameloop()
p := senses → acceleration quick → scale(1000)
board → set gravity(p → x, p → y)
board → evolve
board → update on board
4
3.12
Friction
In the animation from the previous section, you may notice that the ball keeps on moving without slowing
down. That’s because we have given the board nor the ball any friction. Each sprite can have its own friction
setting, but if not set each sprite experiences the default friction from the board. A friction is the fraction
of the forward speed that is experienced as a backwards force. A friction of 0 corresponds to no friction at
all, and a friction of 1 means the sprite will not move at all.
We can add a bit of friction to the board in the previous animation using:
board := media → create board(640)
board → set friction(0.01)
...
3.13
Springs and Anchors
A spring can be added between two sprites in order to create an acceleration towards each other. Without
friction to dissipate energy over time, sprites linked by a spring will oscillate indefinitely. With friction, they
will eventually converge on the same point.
A common scenario is to fix one of the two spring-linked sprites on the board (make it unmovable).
Sprites with friction set to 1.0 are unmoved by acceleration. Even though you can create any sprite and
set its friction to 1.0, the board API contains a create anchor property that creates an invisible sprite with
friction set to 1.0.
A sprite linked to an anchor via a spring therefore oscillates around the anchor. If you give the sprite an
initial velocity vector perpendicular to the direction of the spring, the sprite will circle around the anchor.
With multiple anchors and springs, you can create some interesting oscilation paths.
The API for creating a spring takes a stiffness parameter. Think of this parameter as a factor of how
hard the spring pulls objects together.
action main()
data → board := media → create board
sprite := data → board → create ellipse(20,20)
anchor := data → board → create anchor(10,10)
sprite → move(20,0)
data → board → create spring(sprite, anchor, 100)
sprite → set speed y(20)
data → board → post to wall
event gameloop()
board → evolve
board → update on wall
The code above produces a sprite that will circle the center of the board.
3.14
Touch Input
Another nice aspect of the board API is that it provides touch input state to your script. You can test if the
user touches the board using board → touched and obtain the beginning coordinate of a gesture (board → touch
start), the current coordinate (board → touch current), and the coordinate where the finger was lifted off the
screen (board → touch end). In addition, the final velocity of the finger movement is obtained by board → touch
velocity. These properties can be used to add more balls to our little animation from the previous section.
The best way to make use of touch input is via touch events. See Section 6 on events for more information.
5
3.15
Debugging Sprites
To make it a bit simpler to debug sprite position and speed related problems in your scripts, you can turn
on a debug mode on the board:
board → set debug mode(true)
If debug mode is on, the board will display the position and speed of a sprite next to the sprite content.
Additionally, the width and height is displayed as a box around the sprite. Also, in debug mode, even
invisible sprites are displayed. This can be useful to find forgotten sprites or where anchors are placed.
4
The Sprite API
Sprites are movable objects that you can use to visually represent parts of a game, such as your space ship,
a bullet, a monster, etc. You create new sprites through the board API. Once you have a sprite, you can
change its position, speed, etc, through the sprite API.
4.1
Position and Speed
Position and speed can be set via a number of properties on sprites, either by setting individual x and y
coordinates, or by setting both at once.
”Set the sprite position to (newx,newy)”
sprite → set x(newx)
sprite → set y(newy)
”Another way to set sprite position to (newx,newy)”
sprite → set pos(newx, newy)
”Set sprite speed to (vx, vy)”
sprite → set speed x(vx)
sprite → set speed y(vy)
”Another way to set speed to (vx,vy)”
sprite → set speed(vx, vy)
The current position of a sprite is obtained by sprite → x and sprite → y. The speed is obtained via sprite → speed
x and sprite → speed y.
4.2
Rotation
You can set the angle of rotation of a sprite via sprite → set angle(degrees). If you want the sprite to rotate
via sprite → set angular speed(dps). The current angle and angular
continuously, set the angular speed to degrees
sec
speed is read by sprite → angle and sprite → angular speed.
4.3
Friction
By default, sprites use the friction of the underlying board. However, you can change each sprite’s friction
individually through sprite → set friction(f). A friction of 0.0 means no friction at all, and 1.0 means the object
is unmovable by acceleration. The current friction can be read via sprite → friction.
4.4
Width and Height
Sprites always obtain a width and height when created. It can be read via sprite → width and sprite → height.
You can change the width and height of an existing sprite via sprite → set width and sprite → set height. For
text sprites, the width and height only determine the positioning and bounding box. The text size itself is
solely determined by the font size used when the text sprite is created.
6
4.5
Color
The sprite’s color can be set via sprite → set color(c). For picture sprites, the color setting has no effect.
4.6
Text Content
For text sprites, you can change the content of the sprite by using sprite → set text(new text). For non-text
sprites, setting the text has no effect. The current text of a text sprite can be obtained by sprite → text.
4.7
Elasticity
When a sprite collides with a wall (obstacle), the amount of energy preserved depends on the product of the
sprite’s and the wall’s elasticity. If the product is 1.0, then the reflection is fully elastic and no energy is lost.
If the product is 0.0, then the collision absorbs all energy and the sprite will effectively stick to the wall.
Values between 0 − −1 will dampen the velocity of sprites upon each reflection. By default the elasticity of
a sprite is 1.0, i.e., fully elastic.
4.8
Visibility
After creation, a sprite is by default visible (unless it is an anchor). To change the sprite visibility, use
sprite → hide and sprite → show. The current visibility can be queried using sprite → is visible.
4.9
Helper Functions
A few properties on sprites provide functionality you could implement in a script itself, but it is so common
that we preferred to provide it on the sprite API directly.
sprite → move(deltax, deltay) is equivalent to
sprite → set pos(sprite → x + deltax, sprite → y + deltay)
sprite → move towards(other, fraction) is equivalent to
sprite → set pos(sprite → x + (other → x − sprite → x) ∗ fraction, sprite−y + (other → y − sprite → y) ∗ fraction)
Finally, sprite → speed towards(other, magnitude) puts sprite on a trajectory towards the other sprite with a
speed vector of the given magnitude.
4.10
Determining Overlap
Two functions provide overlap tests between sprites. sprite → overlaps with(other) returns a boolean that is true
if the two sprites overlap. The overlap is determined solely by position and the relative width and height.
It does not take into account the rotation of the sprites.
A more general overlap test is provided by sprite → overlap with(sprite set) which returns a subset of sprites
from sprite set that overlaps with the given sprite. This allows testing for overlap with a large number of
sprites at once.
5
The Sprite Collection API
When writing simple games with multiple objects of the same kind (e.g. multiple shots, missiles, etc), it
quickly becomes necessary to group related sprites into collections. The board API provides a way to create
sprite sets via board → create sprite set. A sprite set represents a collection of sprites. You can add and remove
sprites from the set, enumerate over the sprites in a set using foreach, test if a sprite is in a given set etc.
One common use of sprite sets is to use them to keep track of the “state” of a sprite. E.g., you might
have a number of shots that can be fired from a space ship. If the shots are not active, you could keep
7
them in a sprite set called inactive shots. As a shot is fired, you remove a shot from the inactive shots via
shot := inactive shots → remove first, set its position and speed, and add it to another set of active shots via
active shots → add(shot). This way, you can easily keep track of active and inactive shots, so that no new shots
can be fired unless you have inactive shots, comparing only active shots for overlap with targets, etc.
6
Events
Events are actions that are automatically invoked when certain events occur. The board, has six specific
kinds of events which we detail in the next sections.
6.1
Gameloop Event
A typical outline for a script that uses the board looks as follows:
action main()
data → board := media → create full board
// create sprites, sprite sets, obstacles, and springs
// ...
data → board → post to wall
event gameloop()
// test and react to overlaps (collisions) etc.
// ..
board → evolve
board → update on wall
The gameloop event is where you put code that needs to run regularly (every 50ms). You would put collision
detection code there, as well as things that should happen after a certain time, or when a certain position is
reached.
Make sure you don’t spend too much time in the game loop. Avoid code that take a long time to run,
otherwise, the gameloop will not run every 50ms and your animations will stutter.
To react to touch inputs, use the events in the following sections.
6.2
Tap board Event
This event fires if you tap anywhere on the board (unless there is a sprite where you tap). Tapping means
that your finger leaves the screen at approximately the same position where it first touched (as opposed to
swiping). The event fires once the finger is lifted.
For an example on how to use this event, the code below creates a new ball where you tap the board.
action main()
data → board := media → create full board
data → board → create boundary(0)
data → board → post to wall
event gameloop()
var p := senses → acceleration quick → scale(100)
board → set gravity(p → x, p → y)
board → evolve
board → update on board
event tap board: board(x,y)
var sprite := data → board → create ellipse(10,10)
sprite → set pos(x,y)
8
As you can see, the tap board event has two parameters x and y that tell you the position where the tap
occurred.
6.3
Swipe board Event
To give the balls some initial speed, we can write an event that triggers when you swipe your finger on the
board, i.e, you lift it off some distance away from the original starting point.
To the code in the previous section, we add the following event:
event swipe board: board(x,y,delta x, delta y)
var sprite := data → board → create ellipse(10,10)
sprite → set pos(x,y)
sprite → set speed(delta x, delta y)
In addition to the original position where the swipe started, the swipe event is also passed the delta, that is
the difference of the end and start position of the swipe. We can use this to give the new sprite some initial
speed.
6.4
Tap sprite Event
Your code can also respond to taps on specific sprites. In order to control which events respond to which
sprites, we use sprite sets. All sprite events are associated with a sprite set that you need to declare in the
data section. Once you have such a sprite set, you can add sprites to the set and your code can respond to
events on sprites in that set1 .
Let’s augment the previous sample to change the color of a sprite whenever you tap it. In the main
function, we define a sprite set all sprites that holds all our sprites, and in the tap and swipe events, we add
the new sprites to this set.
action main()
...
data → all sprites := data → board → create sprite set
event tap board: board(x,y)
...
data → all sprites → add(sprite)
event swipe board: board(x,y,delta x, delta y)
...
data → all sprites → add(sprite)
Now we add a tap handler for the sprite set all sprites. The event parameters include the sprite that was
tapped, the index of this sprite within the sprite set, and the board position of the tap. We change the color
of the tapped sprite to a random color.
event tap sprite in all sprites(sprite, index, x, y)
sprite → set color(colors → random)
Now tapping on a sprite changes its color randomly.
6.5
Swipe sprite Event
Suppose we want to change the direction and speed of an existing sprite. We can use the swipe sprite event
for this purpose. It triggers when a sprite is not tapped, but swiped, i.e., the position where you lift the
finger is different from the original position where you touched the screen. We use the same sprite set we
already have to hook into this event:
1 Yes,
sprites can appear in multiple sets at once and all events for that sprite will trigger.
9
event swipe sprite in all sprites(sprite, index, x, y, delta x, delta y)
sprite → set speed(delta x, delta y)
6.6
Drag sprite Event
The last event on sprites is the drag event. It fires during dragging and gives you the current position and
current drag amount. We can use this event to temporarily set the speed of the dragged sprite to 0 and to
display it at the current drag position:
event drag sprite in all sprites(sprite, index, x, y, delta x, delta y)
sprite → set speed(0, 0)
sprite → set pos(x, y)
This way, the sprite will appear under your finger while dragging and stay there. Once you lift your finger,
the swipe event will fire and the sprite will be given the speed corresponding to your overall drag amount.
7
Physics
Sprites are moved based on their speed and the acceleration from gravity or springs whenever the board → evolve
property is invoked in your script. The amount of movement depends on the time dT between successive
calls to board → evolve. The board API internally keeps track of time and moves objects according to dT . If
the game loop runs slowly however, we cap the maximum time dT for a step to 0.1 seconds.
7.1
Position and Speed Integration
Internally, the computation of new positions and speed uses the standard Runga-Kutta method for computing
the integral of the current position and speed based on previous speed and acceleration.
7.2
Collision Response
Currently, collision responses are only computed between walls and sprites, not between sprites themselves.
So sprites can overlap and go through each other. However, sprites will bounce off of walls. The collision
response when a sprite hits a wall depends on the orientation of the wall, the speed vector of the sprite, and
the elasticity of the wall and sprite. The orientation (rotation) of the sprite is ignored.
The only control over wall collision response currently available is the elasticity of the wall and the sprite.
The resulting speed magnitude of the sprite after collision is the product of the incoming speed magnitude,
the elasticity of the wall and the elasticity of the sprite.
This approach doesn’t really correspond to real physical properties of material, but makes it easy to
model sticky walls and objects, as well as bouncy walls and objects.
References
[1] N. Tillmann, M. Moskal, J. de Halleux, and M. Fahndrich. TouchDevelop—Programming CloudConnected Mobile Devices via Touchscreen. Technical Report MSR-TR-2011-49, Microsoft Research,
Apr. 2011.
10