Robotics
Build a Webots football pitch, drive one robot with your keyboard, and code an AI opponent that hunts the ball and shoots for goal.
Two e-puck robots. One ball. One goal each. You drive one — the computer drives the other, and it is genuinely trying to score against you.
Everything you need is on this page. Build the pitch, paste in two programs, and play a real match.
🏟️ Steps 1-4 🤖 Steps 5-6 💻 Steps 7-8 ⚽ Steps 9-10
The pitch ───▶ The players ───▶ The brains ───▶ Kick off
arena, ball, two robots on your code and play, then
goals the field the AI's coach the AI
File → New → New Project Directory...
Name it robot_football, tick all the boxes (especially Add a rectangle arena), and click Finish.
Now click RectangleArena in the scene tree on the left and set:
| field | value |
|---|---|
floorSize |
1 1 |
wallHeight |
0.08 |
✅ Done when: you see a square pitch about one metre across with low walls round the edge.
Press the Add button at the top of the scene tree, then choose Base nodes → Solid.
Open your new Solid and fill it in:
| field | what to do |
|---|---|
translation |
type 0 0 0.03 |
children |
Add → Shape. Inside it: geometry → Sphere, radius 0.03 |
the Shape's appearance |
Add → PBRAppearance, baseColor 1 1 1 (white) |
boundingObject |
Add → Sphere, radius 0.03 |
physics |
Add → Physics, then set mass to 0.05 |
✅ Done when: you press ▶ and the ball sits on the floor. Hold Alt and drag it — it rolls away like a real ball.
🛟 Stuck? Ball falling through the floor? You missed the
boundingObject. Ball frozen in place? You missed thephysics. It needs both: one to have a surface, one to have weight.
🤔 Think about it (Q1): what would happen if you gave the ball a boundingObject but no Physics node? (Answer at the bottom.)
The AI needs to find the ball in its code. We do that by giving it a label.
Right-click your ball Solid in the scene tree and choose DEF. Type exactly this — all capitals:
BALL
✅ Done when: the scene tree shows DEF BALL Solid instead of just Solid.
⭐ Capital letters matter here.
BALLworks.Ballandballdo not. If the AI later says it can't find the ball, this line is the first place to look.
A football pitch needs a goal at each end. In Webots we just paint a coloured stripe on the floor — a thin, flat box lying down.
We make two stripes, one at a time. They are only paint, so neither of them gets a boundingObject and neither gets a physics node. The ball will roll straight over the top of them, which is exactly what we want.
🧭 Which goal is whose? Think of it like a real match. You stand at one end, the other team stands at the other end, and each team tries to put the ball in the goal at the far end.
- The red stripe is at the far end from the AI robot. The AI tries to push the ball onto the red stripe. So red is the goal you have to defend.
- The blue stripe is at the far end from your robot. You try to push the ball onto the blue stripe. So blue is the goal you are attacking.
Easy way to remember it: you score on blue, you defend red.
Solid appears at the bottom of the scene tree. Click the small arrow next to it to open it up.translation field and type exactly:-0.48 0 0.001
children field, press Add, choose Base nodes, and double-click Shape.geometry field, press Add, choose Base nodes, and double-click Box.size field and type exactly:0.02 0.36 0.002
appearance field. Press Add, choose Base nodes, and double-click PBRAppearance.baseColor field and type exactly:1 0 0
boundingObject and physics fields empty. Do not touch them.✅ Done when: a thin red stripe is lying flat on the floor at one end of the pitch.
🛟 Stuck? Can't see the stripe? The three numbers in
translationmean left-right, forward-back, and up-down. The last one,0.001, lifts the stripe one millimetre off the floor so it doesn't disappear into it. If you typed0there, change it to0.001.
Do exactly the same eleven steps again for the second stripe. Only two numbers are different.
Add another Solid, and this time translation is:
0.48 0 0.001
Give it a Shape → Box with size:
0.02 0.36 0.002
Give the Shape a PBRAppearance with baseColor:
0 0 1
Again, leave boundingObject and physics empty.
✅ Done when: you have a red stripe at one end of the pitch and a blue stripe at the other. The middle is empty and ready for the ball.
🛟 Stuck? Both stripes ended up in the same place? Check the very first number of
translation. One must be minus-0.48and the other must be plus0.48. The minus sign is what puts them at opposite ends.
🤔 Think about it (Q2): both stripes are 0.02 0.36 0.002 — very thin, quite wide, and almost completely flat. Why make them so flat?
Press Add → PROTO nodes → robots → gctronic → e-puck → E-puck
Set these fields:
| field | value |
|---|---|
translation |
-0.25 0 0 |
name |
player |
Leave controller alone for now — we'll write it in step 7.
✅ Done when: an e-puck is standing on your half of the pitch.
Add a second E-puck the same way, then set:
| field | value |
|---|---|
translation |
0.25 0 0 |
rotation |
0 0 1 3.14159 |
name |
ai |
supervisor |
TRUE ← don't miss this one |
✅ Done when: two robots face each other across the ball, and the second one has supervisor set to TRUE.
👁️ What does supervisor mean? A normal robot only knows what its own sensors tell it — it's like playing football blindfolded, feeling around with your hands.
A supervisor robot is allowed to ask the simulator directly: "where is the ball right now?" Like having a drone camera above the pitch.
Real RoboCup teams do exactly this — an overhead camera tells every robot where everything is.
🤔 Think about it (Q3): why does only the AI robot need supervisor TRUE, and not yours?
File → New → New Robot Controller...
player_strikerWebots creates the file for you at:
robot_football/controllers/player_striker/player_striker.py
Delete everything already in that file — we're writing our own.
✅ Done when: an empty editor tab called player_striker.py is open on your right.
Type or paste all of this into player_striker.py:
"""
player_striker.py - YOUR robot
Controls: UP forward DOWN reverse LEFT/RIGHT turn SPACE turbo
"""
from controller import Robot, Keyboard
TIME_STEP = 64
SPEED = 5.0
TURN = 2.5
TURBO = 6.28
robot = Robot()
left = robot.getDevice('left wheel motor')
right = robot.getDevice('right wheel motor')
left.setPosition(float('inf'))
right.setPosition(float('inf'))
keyboard = robot.getKeyboard()
keyboard.enable(TIME_STEP)
while robot.step(TIME_STEP) != -1:
key = keyboard.getKey()
if key == Keyboard.UP:
left.setVelocity(SPEED)
right.setVelocity(SPEED)
elif key == Keyboard.DOWN:
left.setVelocity(-SPEED)
right.setVelocity(-SPEED)
elif key == Keyboard.LEFT:
left.setVelocity(-TURN)
right.setVelocity(TURN)
elif key == Keyboard.RIGHT:
left.setVelocity(TURN)
right.setVelocity(-TURN)
elif key == ord(' '):
left.setVelocity(TURBO)
right.setVelocity(TURBO)
else:
left.setVelocity(0.0)
right.setVelocity(0.0)
Save with Ctrl+S.
Now connect it: click the player robot in the scene tree → its controller field → Select... → choose player_striker.
✅ Done when: you press ▶, click inside the 3D window, and your robot moves with the arrow keys.
🛟 Stuck? Keys doing nothing? Click inside the 3D window first. If the editor has focus, the arrow keys just move your text cursor around.
🤔 Think about it (Q4): in the LEFT arrow part of your code, the left wheel gets -TURN and the right wheel gets +TURN. Why opposite signs?
🤔 Think about it (Q5): the very last else sets both wheels to 0.0. What's that for?
File → New → New Robot Controller... again.
ai_strikerWebots creates it at:
robot_football/controllers/ai_striker/ai_striker.py
Clear out whatever is in there.
✅ Done when: an empty ai_striker.py tab is open.
💭 How the AI will think It does not just drive at the ball. If it did, it would shove the ball the wrong way half the time — think about what happens if it approaches from the wrong side.
Instead, every single step it works out a spot behind the ball, on the far side from the goal. It drives to that spot first, lines itself up, and only then charges straight through the ball.
That's three lines of maths, and it makes the robot look like it has a plan.
goal ◀─────────────── ⚪ ball ◀── 🤖 lines up HERE first
(where the AI shoots) (behind the ball, on the far side)
Put all of this into ai_striker.py:
"""
ai_striker.py - the autonomous opponent
Needs: supervisor TRUE on this robot, and DEF BALL on the ball.
"""
from controller import Supervisor
import math
TIME_STEP = 64
# ---- CHANGE THIS TO MAKE IT EASIER OR HARDER ----
SKILL = 0.6 # 0.3 = easy 0.6 = fair 1.0 = brutal
# -------------------------------------------------
GOAL_X = -0.5 # the goal the AI shoots at
GOAL_WIDTH = 0.18
STANDOFF = 0.10 # how far behind the ball it lines up
TURN_LIMIT = 0.25 # how straight it must be before charging
robot = Supervisor()
me = robot.getSelf()
ball = robot.getFromDef('BALL')
ball_pos = ball.getField('translation')
left = robot.getDevice('left wheel motor')
right = robot.getDevice('right wheel motor')
left.setPosition(float('inf'))
right.setPosition(float('inf'))
def wheels(l, r):
left.setVelocity(l * SKILL)
right.setVelocity(r * SKILL)
def turn_needed(target_x, target_y, my_x, my_y, facing):
"""How far to turn to point at the target. Answer is -pi to +pi."""
err = math.atan2(target_y - my_y, target_x - my_x) - facing
while err > math.pi:
err -= 2 * math.pi
while err < -math.pi:
err += 2 * math.pi
return err
def kick_off():
"""Put the ball back in the middle after a goal."""
ball_pos.setSFVec3f([0.0, 0.0, 0.03])
ball.resetPhysics()
score = 0
while robot.step(TIME_STEP) != -1:
# where is everyone?
mx, my, _ = me.getPosition()
bx, by, _ = ball.getPosition()
o = me.getOrientation()
facing = math.atan2(o[3], o[0])
# did the AI score?
if bx < GOAL_X + 0.06 and abs(by) < GOAL_WIDTH:
score += 1
print("GOAL for the AI! score:", score)
kick_off()
continue
# find the spot behind the ball
gx = bx - GOAL_X
gy = by
dist = math.hypot(gx, gy)
if dist < 0.000001:
dist = 0.000001
target_x = bx + (gx / dist) * STANDOFF
target_y = by + (gy / dist) * STANDOFF
# already lined up? then go straight for the ball
if math.hypot(target_x - mx, target_y - my) < 0.07:
target_x, target_y = bx, by
# turn towards it, or charge
err = turn_needed(target_x, target_y, mx, my, facing)
if err > TURN_LIMIT:
wheels(-2.0, 2.0)
elif err < -TURN_LIMIT:
wheels(2.0, -2.0)
else:
wheels(6.0, 6.0)
Save with Ctrl+S.
Connect it: click the ai robot → controller field → Select... → ai_striker.
✅ Done when: you press ▶ and the AI robot starts moving by itself, curving around towards the ball.
🛟 Stuck? Error mentioning
NoneType? That meansgetFromDef('BALL')found nothing — go back to step 3 and check the DEF name is exactlyBALLin capitals.
🤔 Think about it (Q6): find the line if math.hypot(target_x - mx, target_y - my) < 0.07:. It checks whether the AI is close to its lining-up spot. What does it do once it gets there?
🤔 Think about it (Q7): what do you think kick_off() is for?
Press ▶, click inside the 3D window, and defend your goal.
Watch the console at the bottom — the AI prints GOAL for the AI! each time it scores, and resets the ball by itself.
First to 3 goals wins.
✅ Done when: you've played a full match against the AI.
💡 Tactic that genuinely works: don't chase the ball. Sit in front of your own goal and let the AI come to you. Save the SPACE turbo for when the ball is right in front of you.
Go back to ai_striker.py and try changing these, one at a time. Save, then press ▶ again each time.
| line | try | what you should notice |
|---|---|---|
SKILL = 0.6 |
0.3 |
slow and clumsy, easy to beat |
SKILL = 0.6 |
1.0 |
very hard to stop |
STANDOFF = 0.10 |
0.25 |
takes much wider approaches |
TURN_LIMIT = 0.25 |
0.05 |
aims very precisely but wastes time turning |
✅ Done when: you've found the SKILL setting where you win roughly half your matches.
🤔 Think about it (Q8): you set SKILL = 1.0 and the AI got much better. Looking at the wheels() function, what is SKILL actually doing?
Challenge 1 — Make the AI a defender. Turn the AI into a defender instead of a striker: it should shoot at the other end, which means it spends the whole match clearing the ball away from you.
Hint: there's exactly one number that decides which goal it attacks. Expected: the AI pushes the ball towards the blue end instead of the red one.
Challenge 2 — Add a scoreboard for you. Right now only the AI's goals get counted. Add a score for you too.
Hint: the AI checks if bx < GOAL_X + 0.06. Your goal is the blue stripe at x = 0.5, so you need the mirror of that test.
Expected: the console prints your goals as well as the AI's.
DEF BALL so code could find it.player_striker.py and drove your robot with the keyboard.ai_striker.py — a robot that lines up behind the ball and scores.Q1. It would sit there like a statue — solid, but impossible to move. No physics means no weight and no movement. (With no boundingObject it would fall through the floor instead.)
Q2. So they are just paint on the floor and the ball can roll over them. A tall box would be a wall, not a goal.
Q3. Because you can already see the ball with your own eyes on the screen — you're the sensor for your robot.
Q4. Opposite directions make the robot spin on the spot instead of driving. One wheel forward, one back = a spin. (Try making both positive and see what happens!)
Q5. So the robot stops when you're not pressing anything. Without it the robot would keep rolling forever.
Q6. It switches its target from the spot to the ball itself, and charges. Line up first, then attack.
Q7. It puts the ball back in the middle after someone scores — otherwise the ball would sit in the goal forever.
Q8. It multiplies every wheel speed, so the whole robot moves faster. Its thinking never changes, only its speed.
Challenge 1 solution:
GOAL_X = 0.5
One number. From your side of the pitch it now looks like a defender constantly booting the ball away.
Challenge 2 solution: add my_score = 0 next to score = 0 near the top, then put this just below the AI's goal check inside the loop:
if bx > 0.44 and abs(by) < GOAL_WIDTH:
my_score += 1
print("GOAL for you! score:", my_score)
kick_off()
continue
Right now the AI cheats slightly: it asks the simulator where the ball is.
Next lesson we take that away and give it a camera instead. It has to find the ball by hunting for the colour white — the way a real robot has to.
💡 Did you know? There's a real world championship for this called RoboCup, running since 1997. Their official goal is a robot team that can beat the human World Cup winners by 2050. You just wrote your first robot footballer. ⚽