# Pong
In this document you will create a clone of the classic game of Pong by Atari. You will learn the basics of rendering entities and moving them through physical properties like mass and restitution. Then, you will be able to detect collisions and add gameplay features accordingly.
# Five influential games
As example games we have selected some of the most influential, iconic and successful games in their era. Small in scope but still interesting enough to illustrate the different aspects the language M is capable of.
You can start with the classic game of Pong, developed by Atari in 1972, where you will learn the basics of entity and system creation and physics interactions.
Next is the Space Invaders game, developed by Tomohiro Nishikado and distributed by Taito in 1978. You will learn about batch creation of entities, creation and destruction of entities in runtime.
For the 80's era we have chosen the well known Pacman and Mario. In Pacman you can learn how to use reinforcement learning AI techniques and in Mario how to design diverse levels.
For the 90's we enter the era of 3D graphics. To showcase this ability, we have created a demo of Crash Bandicoot, developed by Naughty Dog, that combines all the concepts in a three dimensional game.
# Prerequisites
Before starting this tutorial we recommend checking the [rendering] and [physics] tutorials.
# Final result
You can download the resulting project in each interface here:
- [English text]
- [Euskara]
- [Blockly blocks]
- [Unity zip]
# Let's start
As discussed in [key concepts], the entities store all the data of the game. This includes visual entities such as the ball but also non visual elements like the game rules.
First, we will identify all the entities in Pong. We have the ball, the two paddles that the players can control, the top and bottom wall, the two goals, the score counters and an extra entity for holding the game rules.
Some of these entities have a lot of data in common. For example, the top and bottom wall differ in their position, but their scale and physical properties are the same. Instead of creating all entities from scratch, we will define a prefab and create two variants of that prefab as our final walls.
What data do we need to define a wall? This depends on the abstraction we make. In this example, we have decided that we will need a position, scale and a sprite. Since the position changes in the instances we will add the common elements:
We will start by defining the visual properties of the entities. Then, continue with the physical ones.
"wall"
{
scale 100 2, sprite "wall"
}
Now our walls will be visible. To interact with the walls, we will need to add physical properties to them. We will add a box collider to detect collisions with other physical bodies and avoid penetrations and a restitution value of 1 to make the collision bouncy.
wall has
{
scale 100 2, sprite wall,
extent 1 1, restitution 1
}
Notice that the size of the box collider is relative to the scale of the entity.
Let us continue with the ball.
ball has
{
scale 2 2, sprite ball,
radius 0.5, restitution 1
}
It's like a wall, except it uses a circle collider with a radius that fits its scale. The ball takes part in more interactions, and as such we must add more data to it.
Tag components are useful to mark entities that take part in certain interactions. We can distinguish if a system will process an entity by looking whether it contains a tag or not. In pong, we want the ball to be part of the serving, scoring and spawning interactions. For simplicity, we will add a tag for each of those interactions.
ball has
{
scale 2 2, sprite ball,
radius 0.5, restitution 1,
serveTag, scoreTag, spawnTag,
spawnPosition 0 1
}
Ending the components with the word Tag is not necessary, it's a naming convention. The particular thing about tags is that they don't carry any data, making them specially efficient to filter.
For the paddle, we need to include some sort of input controls to control its movement. We will create a component that holds the name of the input that will control the paddle, in this case, the left stick Y axis of the gamepad. Also, the direction component will control in which direction moves this paddle, its modulus being the speed.
paddle has
{
scale 2 5, sprite paddle,
extent 1 1, restitution 1,
motion gamepad left Y, direction 0 10
}
Make sure that the code compiles and you can open the project in the game engine. You should see the entity prefabs under the Design/Prefabs/ folder. Next we will add our first system for interaction:
control
{
for all entity paddle
{
paddle.velocity = paddle.direction * paddle.motionValue
}
}
spawn
{
for all entity collision with enterCollision
{
if has(collision.first, spawnTag)
{
collision.first.position = collision.first.spawnPosition
}
}
}
serve
{
for all entity ball with serveTag
{
angle = random(ball.angleRange)
ball.velocity = join(cos(angle),sin(angle)) * random(ball.lengthRange)
}
}
score
{
for all entity collision with enterCollision
{
if has(collision.first, scoreTag)
{
for all entity board
{
if board.team = collision.second.team
{
board.score += collision.second.worth
}
}
}
}
}
restart
{
for all entity rules
{
for all entity board
{
if board.score >= rules.maximumScore
{
count++
}
}
if count >= rules.minimumBoardsOverScore
{
for all entity any
{
destroy(any)
}
create(rules.scene)
}
}
}
# New way
control ≔
[
∀ a
[
a.velocity ≔ a.baseVelocity × a.inputValue
]
]
serve ≔
[
∀ a | has[serveSignal, a]
[
α ≔ random[a.serveAngle]
l ≔ random[a.serveLength] + a.z
a.velocity ≔ [cos[α], sin[α], a.z] × l
remove[serveSignal, a]
]
]
respawn ≔
[
∀ a | has[respawnApplier, a]
[
∀ b | b ∈ a.collisions
[
b.position ≔ b.respawnPosition
add[serveSignal, b]
]
]
]
score ≔
[
∀ a | has[scoreApplier, a]
[
∀ b | b ∈ a.collisions
[
b.number ≔ b.number + b.worth
]
]
]
restart ≔
[
A ≔ { z | z.number > z.maxNumber }
∀ a | #A > a.requiredBoardsToReset
[
∀ b
[
b.number ≔ b.resetNumber
]
]
]
# What's next
Offer little exercises or the next tutorial.
# Have questions
Post them somewhere, open an issue?