こんにちは、ロボットエンジニアの田中章愛(@akichika)です。
年末年始、何か新しい自由研究やプログラミングを始めてみたいという方にぴったりの、手軽なロボット・IoT体験をご紹介します。
今回使うのは、ソニー・インタラクティブエンタテインメントのロボットトイ 「toio™(トイオ)」。
これをJavaScriptなクリエイティブコーディングライブラリ p5.js で動かせる「p5.toio」を使い、ブラウザだけで完結する2Dデジタルツイン環境(ここでは実世界と画面上の世界が完全同期するという意味で使用)を作ってみました。
1. どんなことができる?
PCとtoioをWebBluetooth経由で接続し、マット上の複数のtoioの位置や角度をリアルタイムに画面上に同期(デジタルツイン化)します。
実際のマット上の動きと画面がリンク!
- リアルタイム同期: 実際のマット上の位置をPC画面に表示。
- 複数台対応: 接続ボタンを押すだけで、2台目、3台目と簡単に追加可能。
- かんたん制御: キーボード(矢印キー)で動かしたり、「Mキー」でランダムな目的地へ自動移動させたりできます。
2. ここが便利!:インストール不要・ログイン不要
p5.jsを利用する最大のメリットは、「環境構築が不要」 なことです。
- ブラウザだけでOK: ChromeやEdgeなど、WebBluetooth対応ブラウザがあれば動作します(残念ながらiPadOS・iOSのChromeでは使えないのでご注意ください・・・iPadの場合はBluefyなどWebBluetoothに対応したブラウザをご利用ください)。
- インストール不要: アプリやエディタのインストールは必要ありません。
- ログイン不要: 下記「3.まずは動かしてみよう」のサンプルを使えば、アカウントを作らなくてもその場ですぐにコードを動かせます。なお、プロジェクトの保存にはアカウント作成が必要ですのでご注意ください。
このような特徴を活用し、高校・高専・大学の授業、ワークショップなど、「限られた時間で全員の環境を揃えたい」というシーンに適しているんじゃないかなと思います。
3. まずは動かしてみよう
以下のリンクから公開中のスケッチにアクセスできます。
👉 p5.toio 2Dデジタルツイン・サンプル(p5.js Web Editor)
使い方
- キューブの電源を入れ、青い接続待ち状態になっていることを確認。
- 上記URLを開き、左上の「▶」ボタンを押して実行。
- 「toioを接続する」ボタンを押し、ペアリング画面からtoioを選択。
- マットの上にtoioを置くと、画面内のマット上にキューブが表示されます。
- キーボード操作: 矢印キーで前後左右に移動。
- ランダム移動: 「M」キーを押すと、マット中央付近のランダムな位置へ移動します。
※複数のキューブの電源を同時に入れた場合は、接続したいキューブをPCに近づけるとアンテナの本数が変わるので、できるだけアンテナの本数が多い(電波強度が高い)キューブを選ぶと判別しやすいかと思います。
4. 必要な機材
これからtoioを始めるなら、スイッチサイエンスで販売されているセットが使いやすくおすすめです。
5. コードのポイントと生成AIの活用
今回のコードでは、p5.toioライブラリを使って座標変換(マット座標 → 画面座標)を行っています。下記のコードによりキューブの位置を画面上にリアルタイムに反映して描画します。
// マット上の座標を画面上の座標にマッピング
let displayX = map(cube.x, 98, 402, MAT_X, MAT_X + MAT_W);
let displayY = map(cube.y, 142, 358, MAT_Y, MAT_Y + MAT_H);
push(); //push/popで一時的に別座標でキューブの絵を描くことで描画コードをシンプルに
translate(displayX, displayY); //キューブの位置を起点に下記の絵を描画
rotate(cube.angle); //角度をキューブの向きに合わせる
rectMode(CENTER);
stroke(0);
strokeWeight(2);
fill(`white`);
rect(0, 0, cubeSize, cubeSize, 1);
pop(); //元の座標系に戻る
cube.x, cube.y, cube.angleの変数にリアルタイムなtoioの位置が反映されるので、ほとんど画面上のプログラミングと同じノリで作れます。
また、下記のコードでキューブをランダムな位置に移動させることができます。
const randX = floor(random(100)) - 50 + targetMat.centerX; //中心±50の乱数
const randY = floor(random(100)) - 50 + targetMat.centerY; //中心±50の乱数
cube.moveTo({ x: randX, y: randY }, 80);
全体のコードはこちらです。
/* toio 2D簡易デジタルツイン表示サンプル
Thanks to p5.toio https://tetunori.github.io/p5.toio/ */
const cubes = [];
const targetMat = P5tId.SimpleTileMat; //使うマットを設定
let connectBtn; //接続ボタン
let fsBtn; //全画面表示ボタン
// 元の設計サイズ
const BASE_W = 600;
const BASE_H = 500;
// マットの描画範囲
const MAT_X = 50;
const MAT_Y = 50;
const MAT_W = 500;
const MAT_H = 355;
const COLOR_MAIN = [0, 133, 250, 100]; //少し透明なシアン
function setup() {
//初期設定
createCanvas(windowWidth, windowHeight);
connectBtn = createButton("toioを接続する");
connectBtn.position(20, 20);
connectBtn.mousePressed(connectToio);
fsBtn = createButton("全画面表示");
fsBtn.position(150, 20);
fsBtn.mousePressed(toggleFullscreen);
}
function connectToio() {
//toioのキューブとの接続(複数対応)
P5tCube.connectNewP5tCube().then((cube) => {
cubes.push(cube);
cube.turnLightOn("white");
connectBtn.html("次のtoioを接続");
});
}
function toggleFullscreen() {
fullscreen(!fullscreen());
}
function windowResized() {
resizeCanvas(windowWidth, windowHeight);
}
function draw() {
//メインループ
background(240, 252, 257); //水色
let scaleFactor = min(width / BASE_W, height / BASE_H);
push(); //pushで別座標系(マット)に一時的に移動・描画してpopで元座標系に戻る
translate(width / 2, height / 2);
scale(scaleFactor);
translate(-BASE_W / 2, -BASE_H / 2);
drawMat();
if (cubes.length === 0) {
fill(100);
noStroke();
textSize(20);
textAlign(CENTER, CENTER);
text("左上のボタンから接続してください", BASE_W / 2, BASE_H / 6);
} else {
drawCubes();
}
pop();
}
function drawMat() {
//マットの表示
fill(`white`);
stroke(150);
strokeWeight(1);
rect(MAT_X, MAT_Y, MAT_W, MAT_H);
stroke(COLOR_MAIN);
strokeWeight(1);
if (targetMat === P5tId.SimpleTileMat) {
//簡易マットの場合はマスの線を引く
for (let i = 1; i < 7; i++) {
let x = MAT_X + (MAT_W / 7) * i;
line(x, MAT_Y, x, MAT_Y + MAT_H);
}
for (let j = 1; j < 5; j++) {
let y = MAT_Y + (MAT_H / 5) * j;
line(MAT_X, y, MAT_X + MAT_W, y);
}
}
noStroke();
fill(COLOR_MAIN);
circle(MAT_W / 2 + MAT_X, MAT_H / 2 + MAT_Y, 3);
textSize(8);
textAlign(CENTER, TOP);
text(
`${targetMat.centerX}, ${targetMat.centerY}`,
MAT_W / 2 + MAT_X,
MAT_H / 2 + MAT_Y
);
text(`${targetMat.maxX}, ${targetMat.maxY}`, MAT_W + MAT_X, MAT_H + MAT_Y);
textAlign(CENTER, BOTTOM);
text(`${targetMat.minX}, ${targetMat.minY}`, MAT_X, MAT_Y);
}
function drawCubes() {
//キューブの位置表示(複数対応)
for (let i = 0; i < cubes.length; i++) {
let cube = cubes[i];
if (typeof cube.x !== "number" || typeof cube.y !== "number") continue;
let displayX = map(cube.x, 98, 402, MAT_X, MAT_X + MAT_W);
let displayY = map(cube.y, 142, 358, MAT_Y, MAT_Y + MAT_H);
const cubeSize = 36;
push();
translate(displayX, displayY);
if (typeof cube.angle === "number") {
rotate(cube.angle);
}
rectMode(CENTER);
stroke(0);
strokeWeight(2);
fill(`white`);
rect(0, 0, cubeSize, cubeSize, 1);
fill(COLOR_MAIN);
noStroke();
rect(cubeSize / 3, 0, cubeSize / 4, cubeSize / 4);
fill(`black`);
if (typeof cube.angle === "number") {
rotate(-cube.angle);
}
textAlign(CENTER, CENTER);
textSize(10);
text(i + 1, 0, 0);
pop();
fill(0);
noStroke();
textAlign(CENTER, CENTER);
textSize(10);
// 角度の計算と表示
let angleString = "---";
if (typeof cube.angle === "number" && !isNaN(cube.angle)) {
let angleDeg = ((cube.angle * 180) / PI) % 360;
angleString = nf(angleDeg, 1, 2) + "°";
}
text(
`Cube ${i + 1}\nX: ${cube.x}, Y: ${cube.y}\n∠ ${angleString}`,
displayX,
displayY + 45
);
}
}
function keyPressed() {
//キーボード操作
if (key === "f" || key === "F") toggleFullscreen();
const cube = cubes[0]; //最初に接続されたキューブのみ操作対象にする
const s = 30,
d = 25;
if (!cube) return;
switch (keyCode) {
case UP_ARROW:
cube.move(s, s, d);
break;
case DOWN_ARROW:
cube.move(-s, -s, d);
break;
case LEFT_ARROW:
cube.move(-s, s, d);
break;
case RIGHT_ARROW:
cube.move(s, -s, d);
break;
}
if (key === "m" || key === "M") {
//Mキーでランダムな位置に動く
const randX = floor(random(100)) - 50 + targetMat.centerX; //中心±50の乱数
const randY = floor(random(100)) - 50 + targetMat.centerY; //中心±50の乱数
cube.moveTo({ x: randX, y: randY }, 80);
//cube.moveTo({x:targetMat.centerX,y:targetMat.centerY}, 80);
}
}
すぐに試す場合はこちらから:
再掲 👉 p5.toio 2Dデジタルツイン・サンプル(p5.js Web Editor)
6. 生成AIの雛形としても
このサンプルプログラムは構造がシンプルなため、ChatGPTやClaudeなどの生成AIに「このコードを雛形にして、〇〇な動きをさせて」と投げるだけで、簡単に作りたい作品にアレンジできると思います。
「2台のtoioがぶつからないように動かして」
「円を描くように動かして」
「特定の場所に入ったら色を変えて」
といった指示で、バイブコーディング(AIと一緒にプログラミング)を楽しむのも面白いと思います。
7. まとめ
物理的なロボットが画面と連動して動く様子は、実際やってみると結構楽しいです。ここからゲームやアート作品にも発展できそうな気がします。
この年末年始のお休みに、ぜひ手軽なロボットプログラミングに挑戦してみてください!
関連リンク
toio™ 公式サイト
p5.toio
toio™コア キューブ 技術仕様
お知らせ:武蔵小杉ロボットフェス2026 開催!
普段「CoderDojo武蔵小杉」というプログラミングクラブを運営していますが、その年に一度の特別企画でロボットや宇宙開発そして「魔改造」を間近で体験できるイベントを開催します。toioをはじめ、自作ロボットやプログラミング作品も集結します。ぜひ遊びに来てください!
-
詳細・お申し込み: 武蔵小杉ロボットフェス2026
-
イベント概要
- 日時: 2026年1月12日(祝)午前の部:9:20~11:00 (途中昼休み) 午後の部:13:30~16:00
- 場所:〒211-0004 神奈川県川崎市中原区新丸子東3丁目1100−12
午前の部:中原市民館2F 多目的ホール
午後の部:かわさき市民活動センター 会議室(同館1F) - 主催: CoderDojo武蔵小杉 ※2025度かわさき市民公益活動補助金事業
- 参加費:無料 (当日、運営サポートのためのカンパを募集予定です)


