iNTERACT

Typografie reagiert auf Bewegung: Durch das Kippen und Halten eines physischen Objekts verändert sich die Schrift im Browser. So wird aus einem Bewegungssensor ein spielerisches Interface zwischen Körper, Code und Lesbarkeit.

Kurzbeschreibung

Für das Projekt wurde ein Arduino mit MPU-6050 Bewegungssensor mit einer p5.js-Anwendung verbunden. Die Neigungsdaten werden über einen Node.js-Server an den Browser gesendet und dort in Echtzeit verarbeitet.

Die Anwendung besteht aus drei aufeinanderfolgenden Spielen. Im ersten Spiel ordnen sich Buchstaben aus einem chaotischen Feld heraus. Wird das Objekt schräg abgestellt, aktiviert sich ein typografischer Bildschirmschoner. Im zweiten Spiel verändert die Bewegung Schriftstärke, Breite und Kursivstellung. Im dritten Spiel werden Buchstaben gesammelt, um das Wort „LESBARKEIT“ zu vervollständigen.

Idee und Konzept

Ausgangspunkt war die Frage, wie Schrift nicht nur statisch dargestellt, sondern körperlich beeinflusst werden kann. Statt Maus oder Tastatur wird ein physisches Objekt bewegt. Durch Kippen, Halten oder Ablegen verändert sich die Typografie auf dem Bildschirm.

Dabei geht es um den Wechsel zwischen Störung und Ordnung. Wenn keine aktive Eingabe stattfindet, bewegen sich Buchstaben frei und ungeordnet. Durch gezielte Bewegung entsteht wieder Struktur und Lesbarkeit.

Technischer Aufbau

Das Projekt besteht aus mehreren verbundenen Ebenen:

MPU-6050 Bewegungssensor

Arduino
↓ USB / serieller Port
Node.js Server
↓ Socket.io
Browser mit p5.js

interaktive Typografie

Der Sensor misst die Lage des Objekts. Der Arduino berechnet daraus Werte wie pitch, roll, diffPitch und diffRoll. Diese Werte werden als JSON-Daten über den seriellen Port an den Computer gesendet.

Eine typische Datenzeile sieht ungefähr so aus:

{
  "tilt": "STRAIGHT",
  "pitch": 1.86,
  "roll": 2.60,
  "diffPitch": -0.16,
  "diffRoll": -0.31
}

Diese Daten werden anschließend im Browser genutzt, um die typografische Darstellung in Echtzeit zu verändern.

Verbindung zwischen Arduino und Browser

Die Verbindung läuft über eine Datei namens server.js. Dort wird zuerst ein lokaler Server gestartet:

server.listen(3000, () => {
  console.log("Server läuft auf http://localhost:3000");
  connectArduino();
});

Dadurch kann die Anwendung im Browser über diese Adresse geöffnet werden:

http://localhost:3000

Der Server sucht automatisch nach einem passenden Arduino-Port. Dadurch muss der Port nicht immer manuell geändert werden, wenn der Arduino neu eingesteckt wird:

const ports = await SerialPort.list();

const arduino = ports.find((p) => {
  return (
    p.path.includes("usbmodem") ||
    p.path.includes("usbserial")
  );
});

Wenn ein passender Port gefunden wurde, wird die Verbindung geöffnet:

port = new SerialPort({
  path: arduino.path,
  baudRate: 115200
});

Im Terminal sieht man dann, ob alles funktioniert:

Server läuft auf http://localhost:3000
Arduino verbunden auf /dev/cu.usbmodem1101

Während der Entwicklung kamen teilweise unvollständige Datenzeilen an. Deshalb wurde der Code so angepasst, dass fehlerhafte Zeilen ignoriert werden und nur gültige JSON-Daten an den Browser weitergeleitet werden:

try {
  const data = JSON.parse(line);
  io.emit("arduinoData", data);
} catch (error) {
  // kaputte Zeilen ignorieren
}

Das war wichtig, damit die Anwendung stabil läuft und das Terminal nicht dauerhaft Fehlermeldungen ausgibt.

Verarbeitung der Sensordaten in p5.js

In der Datei sketch.js werden die Daten aus dem Server empfangen:

socket.on("arduinoData", function (data) {
  arduinoData = data;
});

Die Werte werden nicht direkt benutzt, sondern zuerst geglättet. Dadurch bewegt sich die Typografie weicher und weniger ruckartig:

smoothPitch = lerp(smoothPitch, calibratedPitch, 0.08);
smoothRoll = lerp(smoothRoll, calibratedRoll, 0.08);

Zusätzlich gibt es eine Kalibrierung. Mit der Taste C wird die aktuelle Lage des Objekts als Nullposition gespeichert:

if (key === "c" || key === "C") {
  zeroPitch = getSensorPitch();
  zeroRoll = getSensorRoll();

  smoothPitch = 0;
  smoothRoll = 0;
}

Dadurch kann das Objekt flach hingelegt werden und die Werte starten ungefähr bei Pitch 0.00 und Roll 0.00.

Spiel 1: Drift und Bildschirmschoner

Das erste Spiel funktioniert als Übergang und Bildschirmschoner. Es besteht aus einem Raster aus Buchstaben. Wenn das Objekt ruhig und gerade gehalten wird, erscheint nach einigen Sekunden ein Fortschrittskreis. Nach fünf Sekunden wechselt die Anwendung automatisch zu Spiel 2.

Dieser Übergang wird über einen Timer gesteuert:

if (isStraightAndCalm) {
  if (straightStartTime === 0) {
    straightStartTime = millis();
  }

  if (millis() - straightStartTime > straightToNextDelay) {
    scene = 2;
  }
}

Wenn das Objekt dagegen aufgestellt und für längere Zeit nicht bewegt wird, startet der Bildschirmschoner. Dafür wird geprüft, ob der Pitch-Wert deutlich größer ist:

function isObjectStanding() {
  let rawPitch = getSensorPitch();
  let rawRoll = getSensorRoll();

  return abs(rawPitch) > standingPitchThreshold && abs(rawRoll) < standingRollMax;
}

Im Bildschirmschoner bewegen sich die Buchstaben frei. Dafür werden Sinus- und Noise-Werte genutzt:

let driftX =
  sin(frameCount * 0.012 + l.seed) * (24 + chaosAmount * 42) +
  noise(l.seed, frameCount * 0.006) * 35 * chaosAmount;

So entsteht eine lose, instabile Buchstabenfläche. Sobald das Objekt wieder bewusst bewegt oder gerade gehalten wird, ordnet sich die Typografie wieder.

Spiel 2: Variable Typografie

Im zweiten Spiel steht ein Wort beziehungsweise Satz im Mittelpunkt. Die Schrift reagiert auf die Bewegung des Objekts. Die Neigung nach vorne oder hinten verändert das Schriftgewicht. Die Bewegung nach links oder rechts verändert Kursivstellung, Breite und Laufweite.

Die Werte werden aus smoothPitch und smoothRoll berechnet:

let currentWeight = floor(map(-pitchAmount, -25, 25, 200, 900));
let currentSlant = map(rollAmount, -25, 25, -0.22, 0.22);
let currentWidthScale = map(rollAmount, -25, 25, 0.92, 1.10);

Die Schrift wird dann mit Canvas gezeichnet:

drawingContext.font = weight + " " + size + "px " + fontFamily;
drawingContext.fillText(ch, x, y);

Ursprünglich gab es in dieser Szene zusätzliche Überlagerungen und Linien. Diese wurden später wieder reduziert, weil die Typografie dadurch zu unruhig und teilweise doppelt wirkte. In der aktuellen Version ist die Darstellung schlichter, damit die Veränderung der Schrift klarer sichtbar bleibt.

Wenn das Objekt fünf Sekunden in einer Position gehalten wird, friert die aktuelle typografische Form ein:

if (millis() - game2HoldStart > game2HoldDelay) {
  freezeGame2Typography();
}

Dabei werden die aktuellen Werte gespeichert:

frozenWeight = currentWeight;
frozenSlant = currentSlant;
frozenWidthScale = currentWidthScale;
frozenTracking = currentTracking;

Nach dem Einfrieren bleibt die Schriftform stehen. Nach weiteren fünf Sekunden wechselt die Anwendung automatisch zu Spiel 3.

Spiel 3: Buchstaben sammeln

Im dritten Spiel wird ein Cursor über die Bewegung des Objekts gesteuert. Ziel ist es, die Buchstaben des Wortes LESBARKEIT in der richtigen Reihenfolge einzusammeln.

Der Cursor reagiert auf Pitch und Roll:

cursorX = constrain(cursorX + smoothRoll * 0.18, 35, width - 35);
cursorY = constrain(cursorY + smoothPitch * 0.18, 35, height - 35);

Alle Buchstaben liegen im Raum verteilt. Das Programm prüft, ob der Cursor nah genug am nächsten gesuchten Buchstaben ist:

let d = dist(cursorX, cursorY, l.x, l.y);

if (
  !l.noiseLetter &&
  l.targetIndex === nextCollectIndex &&
  d < 42
) {
  l.found = true;
  nextCollectIndex++;
}

Das Wort LESBARKEIT wird einmal grau angezeigt. Sobald ein Buchstabe gesammelt wurde, wird er schwarz dargestellt:

if (i < nextCollectIndex) {
  fill(0, 230);
} else {
  fill(0, 45);
}

Zusätzlich läuft ein Timer. Am Ende werden die eigene Zeit und der Highscore groß in der Mitte angezeigt:

let finalTime = (game3FinishTime - game3StartTime) / 1000;

if (game3Highscore === null || finalTime < game3Highscore) {
  game3Highscore = finalTime;
  localStorage.setItem("game3Highscore", game3Highscore);
}

Der Highscore wird im Browser gespeichert. Dadurch bleibt er auch nach einem Neustart der Seite erhalten.

Entwicklung und Iteraktion

Während der Entwicklung wurde das Projekt mehrfach angepasst. Am Anfang stand vor allem die technische Verbindung im Vordergrund. Es musste geprüft werden, ob der Arduino korrekt erkannt wird und ob die Daten im Browser ankommen.

Ein Problem waren wechselnde Ports. Teilweise erschien der Arduino unter:

/dev/cu.usbmodem1101

und später unter einem anderen Port. Deshalb wurde der Server später so angepasst, dass er automatisch nach einem passenden Port sucht.

Ein weiterer Entwicklungsschritt war die Nullposition. Am Anfang lagen Pitch und Roll nicht bei null, obwohl das Objekt gerade auf dem Tisch lag. Deshalb wurde die Kalibrierung über die Taste C eingebaut.

Auch gestalterisch wurde mehrfach überarbeitet. Spiel 2 war zuerst stärker abstrahiert und arbeitete mit mehreren typografischen Überlagerungen. Das führte aber zu einer unruhigen Darstellung. Deshalb wurde die Szene reduziert und stärker auf eine klare typografische Veränderung konzentriert.

Spiel 3 wurde um Timer, Highscore und eine bessere Fortschrittsanzeige ergänzt. Zuerst wurde das Wort teilweise doppelt angezeigt. Später wurde es so geändert, dass das Wort nur einmal grau sichtbar ist und eingesammelte Buchstaben schwarz gefüllt werden.

Fazit

Das Projekt verbindet Arduino, Bewegungssensor, Node.js und p5.js zu einer interaktiven typografischen Anwendung. Die Bewegung des Objekts wird zur Eingabe und verändert die Lesbarkeit der Schrift.

Besonders wichtig war dabei die Verbindung von Technik und Gestaltung. Die Sensordaten werden nicht nur als Zahlen angezeigt, sondern in typografische Zustände übersetzt: freie Bewegung, variable Schriftform und spielerisches Sammeln von Buchstaben.

Am Ende entstand eine Anwendung, in der Schrift nicht nur gelesen, sondern durch Bewegung erlebt wird.


© 0t1

Cookies

0t1 mag keine Kekse (und kein Tracking). Wir verwenden lediglich notwendige Cookies für essentielle Funktionen.

Wir verwenden Schriftarten von Adobe Fonts. Dafür stellt dein Browser eine Verbindung zu den Servern von Adobe in den USA her. Wenn du unsere Seite nutzen möchtest, musst du dich damit einverstanden erklären.

Weitere Informationen in unserer Datenschutzerklärung.