Tutorial 17: Launcher
You will build: a shooter that spins a flywheel, sets the hood angle from the distance, and feeds a piece only when everything is ready. The main track builds a shooter with less code: A shooter, a camera and a turret.
You will learn: how a flywheel shooter is controlled (speed, ready, feeding), how to build a
shot table from real shots, and how Launcher makes sure a piece is only fed when everything is ready.
You need: motor flywheel (with its encoder cable), servo hood, motor feeder. Tutorials 03, 14.
Safety glasses: a flywheel throws things.
Full program: Tut17Launcher.java
Reference: launcher.md
1. How a flywheel shot works
Section titled “1. How a flywheel shot works”A spinning wheel grabs the game piece and throws it. Two things decide where it lands: the wheel’s speed (RPM: turns per minute) and the angle it leaves at (the hood). Both depend on distance. And one rule matters more than any other: never feed a piece into a wheel that is not up to speed, or it falls short.
2. The flywheel
Section titled “2. The flywheel”Flywheel flywheel = Flywheel.create(hardwareMap, "flywheel") .countsPerRev(28) // encoder counts per ONE flywheel turn .pidf(10, 3, 0, 12) // the hub's speed control .tolerance(75) // "at speed" = within 75 rpm... .settleTime(0.15); // ...for 0.15 spidf sets the gains of the hub’s own speed control (P, I, D and F, like Tutorial 07). The hub
holds the speed itself (fast and smooth). “Ready” needs the speed to STAY in the band for
0.15 s, so one lucky reading during spin-up does not count.
countsPerRev: a bare goBILDA/REV motor encoder gives 28 counts per motor turn. If the flywheel turns at the motor’s speed, that is 28. Geared: multiply by motor turns per flywheel turn. An encoder on the flywheel shaft (plugged into this motor’s encoder port): its own counts per turn.
Check the reading: run at 3000 rpm and compare with a tachometer (a tool that measures turning speed) (or count turns in a slow-motion video). If they disagree, countsPerRev is wrong.
3. The hood and the feeder
Section titled “3. The hood and the feeder”PositionServo hood = PositionServo.create(hardwareMap, "hood") .degrees(0.10, 0, 0.90, 25) // two measured points .travelTime(0.4);MotorFeeder feeder = MotorFeeder.create(hardwareMap, "feeder").power(1.0).feedTime(0.25);Measure the hood: move the servo with the Hardware Check, write down the raw position at the lowest and the highest angle, and measure those angles (a phone level app works).
4. Fill the shot table
Section titled “4. Fill the shot table”Measure these numbers with the Shot Tuner and the flywheel gains with the Flywheel Finder
(tutorial 21): it shows the new ShotTable() lines to copy.
ShotTable table = new ShotTable() .add(24, 2600, 6) .add(48, 3100, 14) .add(72, 3700, 21);How to get YOUR numbers:
- Press X on gamepad 1: manual rpm mode. D-pad left/right = rpm -/+ 100.
- Put the robot 24 in from the target (tape measure, to the same point on the robot every time).
- Find the rpm and hood angle where 5 shots in a row score. Write them down.
- Repeat at 3-5 distances across where you will shoot from.
- Put the points in the table. Between them the table draws straight lines.
Different game pieces (2026-27: POLLEN and NECTAR) may need their own tables.
5. The launcher
Section titled “5. The launcher”Launcher launcher = new Launcher(flywheel, feeder) .hood(hood) .table(table) .shotInterval(0.3) .readyTimeout(2.5);...launcher.setDistance(distanceIn); // every loop: rpm + hood from the tablelauncher.update();.bindPress(Pad.G2, Btn.A, launcher::spinUp).bindPress(Pad.G2, Btn.RB, () -> launcher.shoot(1)).bindPress(Pad.G2, Btn.LB, () -> launcher.shoot(3)).bindPress(Pad.G2, Btn.B, launcher::idle)shoot(3) does: spin up if needed; wait until the flywheel is ready AND the hood has arrived (AND
any requireReady checks, like an aimed turret); feed one; wait shotInterval; repeat. If it is
not ready within readyTimeout, it gives up and says why: “gave up: flywheel”.
Run it
Section titled “Run it”- Safety glasses. No game piece first. START.
- A: the flywheel spins up; telemetry shows about “2850 / 2850 rpm READY”. (The program starts at 36 in, halfway between the 24 and 48 in rows of the table.)
- RB without a piece: the feeder runs once. LB: three times, 0.3 s apart.
- With pieces: D-pad up/down changes the distance; the hood moves; shoot.
- Stop the OpMode and unplug the flywheel’s encoder cable. Run again and
shoot(1): the flywheel can never read “ready”, so it waits, then shows “gave up: flywheel”. That is the safety rule working. Plug the cable back in.
If something goes wrong
Section titled “If something goes wrong”| Problem | Fix |
|---|---|
| Never READY | countsPerRev wrong (speed reads wrong), or F gain too low (never reaches the speed) |
| Speed overshoots then settles slowly | lower P; tune F first so it gets close by itself |
| Shots fall short when shooting fast | shotInterval too short: the wheel has not recovered |
| Hood never “arrives” | travelTime too long; measure it |
| “gave up: hood” | the hood’s travelTime is longer than readyTimeout |
Try this
Section titled “Try this”- Add
.requireReady("Driver", () -> gamepad2.right_trigger > 0.5): shots only while the trigger is held. - Add
.idleRpm(1500): a slow idle makes spin-up faster. Measure the difference. - A second table for another game piece, switched with a button.