In diesem Guide lernst du die ersten Schritte im Umgang mit der BangleJs Hackable Smart Watch und erfährst, wie du eigene Apps damit programmierst.
In einem Zeitalter, in dem sogar Toaster wesentlich smarter sind als der beste Computer vor 50 Jahren, ist es nicht verwunderlich, dass Uhren mit dem Trend mitgehen. Smart Watches haben längst den Markt erobert und die Idee des intelligenten Helfers am Handgelenk ist nicht mehr nur Science-Fiction. Für uns Maker und Tinkerer gibt es hierbei nur ein Problem: Aufgrund von proprietärer Software ist es meist sehr schwer, die eigenen Ideen und Projekte mit der Watch umzusetzen.
Nicht so mit der BangleJs Hackable Smart Watch. Die clevere Armbanduhr gibt dir die Möglichkeit, deine eigenen Apps ganz einfach mit JavaScript auf deinem Handgelenk umzusetzen. Wie der Name schon vermuten lässt, handelt es sich bei der gesamten Codebase um eine Free & Open Source Software (MIT License).
Jetzt die BangleJs Hackable Smart Watch einfach bei uns im Shop bestellen
WARENKORBBangleJs Hackable Smart Watch: First Impressions
Beim ersten Öffnen erhält man einen durchaus positiven Eindruck. Die Uhr wird zusammen mit einem passenden magnetischen Ladekabel geliefert und ist mit einem Durchmesser von ca. 5 cm nicht allzu klein. Das Display löst mit 240 x 240 Pixeln auf und hat eine Diagonale von 1,3″. Außerdem verfügt es über zwei

berührungsempfindliche Zonen. Gesteuert wird hauptsächlich mithilfe der drei Tasten auf der rechten Seite. Die Uhr kommuniziert mit der Umgebung via Bluetooth Low Energy und ist mit folgenden Sensoren ausgestattet:
- GPS-Empfänger
- Puls-Monitor
- 3 Achsen-Magnetometer
- 3 Achsen-Accelerometer
Des Weiteren gibt es einen Vibrationsmotor, Piezo Summer und ein IP68-Rating, was Tauchgänge bis zu einer Tiefe von 10 m ermöglicht. Nach dem Auspacken sollte direkt die Schutzfolie auf der Unterseite entfernt werden, da diese meiner Erfahrung nach den Puls-Monitor extrem einschränkt. Die 350-mAh-Batterie verspricht Woche Standby-Zeit; in der Praxis hat sich jedoch gezeigt, dass ein solcher Wert eher eine Laufzeit von drei bis vier Tagen ermöglicht. Das ist aber nicht wirklich schlimm, denn nach einer halben Stunde ist die volle Kapazität wieder erreicht.
Verbesserungswürdig ist meiner Meinung nach die Funktionsweise des GPS, die vor allem in geschlossenen Räumen an seine Grenzen kommt.
Erste Schritte

Für die Interaktion mit der Uhr sind größtenteils die Buttons verantwortlich, daher findest du hier eine kurze Erklärung ihrer Funktion:
- BTN1: Hoch / Zurück zu den Menüs und „Power on“
- BTN2: Öffnen des jeweiligen Menüs im Uhrmodus.
- BTN3: Runter bzw. nächstes Menü und bei längerem Drücken zurück zur Uhr
Wenn du starten willst, solltest du sicherstellen, dass BLE zum Programmieren aktiviert ist. Hierfür einmal Button 2 (in der Mitte) drücken, um ins Menü zu gelangen, und dort „Settings“ auswählen. Dann den ersten Punkt („Make connectable“) klicken.
Starte jetzt die Bluetooth-Funktion deines Laptops und öffne Chrome. Falls du Linux-User bist, sind noch weitere Schritte erforderlich .
Navigiere nun zum App Loader von BangleJs.

Um dich anschließend via Web Bluetooth mit der Uhr zu verbinden, klickst du auf „Connect“. Jetzt sollte sich ein Pop-up öffnen, in dem nach kurzer Zeit deine BangleJs angezeigt wird. Sollte das nicht der Fall sein, startest du die Uhr noch einmal neu.

Sobald die Verbindung hergestellt ist, kannst du damit beginnen, die Zeit zu einzustellen. Wähle hierfür den Menüpunkt „About“ und klicke danach auf „Set Bangle.Js Time“.

Wie bei jedem App Store kannst du jetzt damit beginnen, verschiedene Anwendungen auf die Uhr zu laden. Für Android-User kann ich hier vor allem die Gadgetbridge App empfehlen. Diese kann aus dem F-Droid Repo installiert werden und zeigt Benachrichtigungen und den aktuellen Song auf der Uhr an.
Let’s get hacking

Kommen wir nun aber zum wirklich spannenden Teil, nämlich: Wie erstellt man seine eigene Anwendung? Hierzu schließt du den App Loader wieder und öffnest die Espruino Chrome IDE. Diese ermöglicht es dir, im Browser einen Code zu erstellen und diesen direkt auf die Uhr zu laden. Auch hier wird im ersten Schritt eine Verbindung mit der Uhr hergestellt: Klicke dazu oben links auf das Stecker Symbol, selektiere Web Bluetooth als Schnittstelle und wähle in der Liste die BangleJs Hackable Smart Watch aus.

Jetzt kannst du über die Konsole bereits erste Befehle ausführen lassen, die anschließend direkt vom Espruino Interpreter umgesetzt werden. Die Uhr besitzt eine Klasse, die verschiedene Methoden offenlegt, so kann zum Beispiel mit folgendem Method Call der Vibrationsmotor getriggert werden:
Bangle.buzz();
Ähnliches gilt für den Piezo mit diesem Aufruf:
Bangle.beep();

Wie auf dem Screenshot zu sehen ist, geben beide Methoden JavaScript Promises zurück, die natürlich auch verkettbar sind. Benutze für das nächste Beispiel den Code Editor und klicke bei vollständiger Eingabe auf das kleine RAM-Symbol.
Bangle.buzz().then(() => {
Bangle.beep();
});
Um Text auf dem Bildschirm darzustellen, kannst du folgende Methode anwenden:
g.clear();
E.showMessage("Welt", "Hallo");
Grundlegend kann an dieser Stelle gesagt werden, dass alle auf die Uhr bezogenen Methoden dem Bangle-Objekt zuzuordnen sind, allgemeinere dem E- (für Espruino) und alle grafikrelevanten Methoden dem g-Objekt.
Um auf Button Clicks zu reagieren, bietet die API eine Async-Möglichkeit:
setWatch(() => {
Bangle.buzz();
E.showMessage("You\npressed\nthe middle\nbutton!");
setTimeout(()=>g.clear(), 1000);
}, BTN2, {repeat:true});
Immer wenn BTN2 gedrückt wird, feuert der Inhalt in den geschweiften Klammern, was zur Folge hat, dass die Uhr vibriert. Anschließend wird eine Nachricht angezeigt, bevor nach einer Sekunde der Bildschirm zurückgesetzt wird.
Grafik
In diesem Schritt schauen wir uns an, wie man ganz einfach Grafiken auf dem integrierten Display darstellen kann und bewegte Animationen umsetzt. Wie bereits erwähnt, bietet hier die Graphics-Klasse mit dem g-Objekt diverse Möglichkeiten. Zunächst einige Grundlagen:
Jedes Display ist im Endeffekt eine 2D-Matrix von Pixeln, in unserem Fall sind es jeweils 240. Der Ursprung befindet sich allerdings im Gegensatz zu einem mathematischen Graphen links oben und hat die Koordinaten 0, 0. Der letzte Pixel rechts unten hat somit die Koordinaten 239, 239. Um dies zu veranschaulichen, lernen wir nun unsere erste Methode, mittels der wir eine Linie von Punkt a zu Punkt b zeichnen.
g.clear();
//g.drawLine(a1, a2, b1, b2);
g.drawLine(0, 0, 239, 239);

In diesem Fall steht a1 für die X-Koordinate des ersten Punkts und a2 für die Y-Koordinate. Wenn wir also alles richtig gemacht haben, sollte eine diagonale Linie von links oben nach rechts unten über den gesamten Bildschirm verlaufen.
Nach ähnlichem Prinzip kann auch ein Kreis am Punkt a mit Radius r gezeichnet werden:
g.clear();
//g.drawCircle(a1, a2, r);
g.drawCircle(g.getWidth() / 2, g.getHeight() / 2, 50);

Die Funktionen getWidth und getHeight ergeben Breite und Höhe des Displays. Geteilt durch zwei erhalten wir in der Mitte einen Kreis mit 50 Pixel Radius.
Farben können einfach als Rot-Grün-Blau-Werte (RGB) angegeben werden (jeweils von 0 bis 1). Der Kreis kann zum Beispiel mit folgender Methode ausgefüllt werden:
g.clear();
g.setColor(1, 0, 0.5);
g.fillCircle(g.getWidth() / 2, g.getHeight() / 2, 50);
Bei mehreren Objekten müssen alle Einstellungen, die für das Objekt gelten sollen, vor dem Zeichnen getroffen werden. Nach dem gleichen Prinzip kann auch Text dargestellt werden:
g.clear();
g.setFontVector(40);
g.setColor(1,0,0);
g.drawString("Hello",40,0); // 40px high in red
g.setFontVector(60);
g.setColor(0,1,0);
g.drawString("World",40,40); // 60px high in green

Um die Größe der Schriftart verändern zu können, empfehle ich, Vektorschriften zu benutzen. Dies wird durch setFontVector umgesetzt.
Animation
Kommen wir nun zu simplen Animationen: Im folgendem Sketch wird mit einer Framerate von 10 FPS in jedem Frame ein Kreis gezeichnet. Die Farbe des Kreises ist abhängig von dessen Position, somit entsteht nach ein paar Sekunden ein buntes Grid:
const FPS = 10;
const DELTA_T = Math.floor(1000 / FPS);
let pos = {x: 0, y: 0};
let speed = {x: 1, y: 1};
const speedF = 30;
const width = 20;
function show() {
// Der Kreis wird in jedem Frame neu gezeichnet, allerdings nie gelöscht
//g.clear(); //
g.setColor(pos.x/240, 1 - (pos.x + pos.y) / 480, pos.y / 240);
g.fillCircle(pos.x, pos.y, width);
}
function move() {
pos.x += speed.x;
pos.y += speed.y;
speed.x = (Math.random() >= 0.5 ? 1 : 0) * speedF;
speed.y = (Math.random() >= 0.5 ? 1 : 0) * speedF;
if (pos.x >= g.getWidth()) pos.x = 0;
if (pos.y >= g.getHeight()) pos.y = 0;
}
g.clear();
setInterval(() => {
//Das ist der Animations Loop
move();
show();
}, DELTA_T);

In jedem Frame wird zuerst mithilfe der move-Funktion die nächste Position berechnet, wobei x- und y-Geschwindigkeit entweder 0 oder 30 sein können. Danach wird der Kreis in der show-Funktion dargestellt. Die Farbe ist hierbei abhängig von der aktuellen Position: Links oben beträgt sie (0, 1, 0) und rechts unten (1, 0, 1).
Sensoren und Events
Wie kann man nun also auf einen der verschiedenen Sensoren zugreifen? Mit Event-Handlers geht das relativ leicht, ähnlich wie bei jQuery. Zuvor muss der Sensor allerdings aktiviert werden:
Bangle.setHRMPower(1); // Pulsmesser aktivieren
Bangle.on('HRM',function(hrm) { // Event Handler sobald neue Daten verfügbar sind
//hrm.bpm -> Nummer
//hrm.confidence -> Nummer von 0-100%
});
Das kann man mit dem bisher Gelernten ganz einfach darstellen:
const FPS = 10;
const DELTA_T = Math.floor(1000 / FPS); // Zeit zwischen zwei Frames
g.clear();
let bpm = 0;
Bangle.setHRMPower(1); // Pulsmesser aktivieren
Bangle.on('HRM',function(hrm) { // Event Handler sobald neue Daten verfügbar sind
bpm = hrm.bpm; // Neue Pulsrate wird in globaler Variable gespeichert
});
function showText() {
g.setColor(1, 0, 0);
g.setFontVector(40);
g.drawString(`${bpm} BPM`, 0, 100); // Pulsrate wird als Text dargestellt
}
function draw() {
g.clear();
showText();
}
setInterval(() => {
draw();
}, DELTA_T);

Erstes Spiel auf der BangleJs Hackable Smart Watch entwickeln
Abschließend möchte ich noch darauf eingehen, wie man ein einfaches Spiel umsetzen kann. Hierfür verwende ich einen objektorientierten Ansatz unter Berücksichtigung der in ES6 eingeführten Classes.
Vorbereitung: Classes
Um das Arbeiten mit Vektoren zu vereinfachen, erzeuge ich also zuerst eine Vektor-Klasse, die eine Position in einer zweidimensionalen Sphäre speichert und Methoden wie Addition, Subtraktion, Multiplikation und die Berechnung des Euklidischen Abstands implementiert:
class Vector {
constructor(x, y) {
if (x !== undefined && y !== undefined) {
this.x = x;
this.y = y;
}else if (arguments.length == 1){
this.x = x;
this.y = x;
}else {
this.x = 0;
this.y = 0;
}
}
add(b) {
this.x += b.x;
this.y += b.y;
return this;
}
sub(b) {
this.x -= b.x;
this.y -= b.y;
return this;
}
mult(n) {
this.x *= n;
this.y *= n;
return this;
}
dist(b) {
const dx = Math.pow(b.x - this.x, 2);
const dy = Math.pow(b.y - this.y, 2);
return Math.sqrt(dx + dy);
}
}
Im nächsten Schritt erzeuge ich eine Objektklasse, von der sowohl die Player als auch die Hindernisklasse ableiten. Diese erstellt die rudimentären Grundlagen einer Physics-Engine. Jedes Objekt besitzt also folgende Fähigkeiten und Eigenschaften:
- Positionsvektor
- Geschwindigkeitsvektor
- Beschleunigungsvektor
- Update Methode, die in jedem Frame die nächste Geschwindigkeit und die daraus resultierende Position berechnet.
- Methode, mit der wir Kräfte auf das Objekt einwirken lassen, was dem Prinzip der Force-Accumulation entspricht.
class Object {
constructor() {
this.pos = new Vector(width / 2, height / 2);
this.vel = new Vector();
this.acc = new Vector();
}
update() {
this.pos.add(this.vel);
this.vel.add(this.acc);
this.acc.mult(0);
}
show() {
}
applyForce(f) {
this.acc.add(f);
}
}
Player Klasse
Daraus ableitend erstelle ich die Spieler-Klasse, die die Fähigkeiten der Objektklasse um folgende Features erweitert:
- Radius (da Kreis)
- MoveForce: Kraft, die beim Tastendruck auf das Objekt einwirkt
- Geschwindigkeit wird in jedem Frame mit 0.7 multipliziert, was einem Luftwiderstand ähneln soll.
- Wenn sich die Position außerhalb der Sichtfläche befindet, erscheint sie auf der gegenüberliegenden Seite.
- Show-Methode, die einen Kreis an der aktuellen Position zeichnet.
class Blob extends Object{
constructor() {
super();
this.r = 10;
this.movForce = 40;
}
update() {
this.pos.add(this.vel);
this.vel.add(this.acc);
this.acc.mult(0);
this.vel.mult(0.7);
if (this.pos.x <= -(this.r * 1.5)) {
this.pos.x = width;
this.applyForce(new Vector(-this.movForce*0.7, 0));
}else if (this.pos.x >= width) {
this.pos.x = -(this.r * 1.5);
this.applyForce(new Vector (this.movForce*0.7, 0));
}
}
show() {
g.setColor(0.5, 0, 0.9);
g.fillCircle(Math.floor(this.pos.x), Math.floor(this.pos.y), this.r);
}
}
Hindernis Klasse
Um die Hindernisse darzustellen und zu kontrollieren, geht es nun an die Block-Klasse. Diese leitet ebenfalls von der Objekt-Klasse ab, fügt aber noch weitere Funktionen hinzu:
- Da Rechteck, feste Höhe von 10px und eine variable Höhe, die durch die getWidth-Methode erzeugt wird und sich zwischen minWidth und maxWidth befindet.
- Ein Color-Objekt, das nach einem Zufallsprinzip eine Farbe erzeugt.
- Position ist entweder linke oder rechte obere Ecke, was ebenfalls zufällig entschieden wird.
- Show-Methode, die ein Rechteck an der aktuellen Position zeichnet.
- Eine isOffScreen-Methode, die einen Boolean-Wert zurückgibt. Der wiederum gibt Aufschluss darüber, ob das Hindernis noch auf dem Screen zu sehen ist. Falls nicht, kann es gelöscht werden, um Ressourcen zu sparen.
- Am wichtigsten ist aber die detectCrash-Methode, dank der es jedem Hindernis möglich ist, zu berechnen, ob es gerade mit dem Player kollidiert (Falls ja -> Spiel zu Ende).
class Block extends Object {
constructor() {
super();
this.height = 10;
this.minWidth = 20;
this.maxWidth = 90;
this.width = this.getWidth();
this.pos.y = -this.height -10;
this.col = {r: Math.random(), g: Math.random(), b: Math.random()};
if (this.col.r + this.col.g + this.col.b <= 0.5) {
const index = Math.floor(Math.random()* 3);
const cols = ["r", "g", "b"];
this.col[cols[index]] = 0.8;
}
if (this.isLeft()) {
this.pos.x = 0;
}else {
this.pos.x = width - this.width - 1;
}
this.gravCounter = 0;
}
getWidth() {
let rand = Math.random();
rand += 0.5;
console.log(rand);
let twidth = level * 60;
console.log(twidth);
twidth += Math.random() * 100;
console.log(twidth);
if (twidth < this.minWidth) twidth = this.minWidth;
twidth *= rand;
if (blocks.blocks.length < 2) {
twidth *= 1.7;
if (twidth > width-80) twidth = width - 80;
}else {
if (twidth > this.maxWidth) twidth = this.maxWidth;
}
return twidth;
}
isLeft() {
return (Math.random() >= 0.5) ? 1 : 0;
}
show() {
g.setColor(this.col.r, this.col.g, this.col.b);
g.fillRect(this.pos.x, this.pos.y, this.pos.x + this.width, this.pos.y + this.height);
}
applyGrav(g) {
this.gravCounter++;
this.applyForce(new Vector(g.x, g.y).mult(this.gravCounter));
}
isOffScreen() {
return (this.pos.y >= height);
}
getCenter() {
return new Vector(this.pos.x + this.width / 2, this.pos.y + this.height / 2);
}
detectCrash(blob) {
const center = this.getCenter();
const distX = Math.abs(blob.pos.x - center.x);
const distY = Math.abs(blob.pos.y - center.y);
if (distX > (this.width / 2 + blob.r)) return false;
if (distY > (this.height / 2 + blob.r)) return false;
if (distX <= (this.width / 2)) return true;
if (distY <= (this.height / 2)) return true;
const dx = distX - this.width / 2;
const dy = distY - this.height / 2;
return (dx*dx + dy*dy <= (blob.r * blob.r));
}
}
Die letzte Klasse in diesem simplen Spiel ist eine Helferklasse, die es ermöglicht, eine Übersicht über die verschiedenen Hindernisse zu erhalten und diese dynamisch zu erzeugen:
- Blocks Array beinhaltet alle derzeit sichtbaren Hindernisse.
- passedBlocks zählt, wie vielen Hindernissen erfolgreich ausgewichen wurde.
- Der Max Member bestimmt, wie viele Hindernisse zu jedem Zeitpunkt auf dem Screen sichtbar sein dürfen (um den Schwierigkeitsgrad zu regeln).
- Create Methode erzeugt neuen Block.
- shouldCreate gibt zu 50 % Boolean True zurück, wenn weniger Blöcke als Max sichtbar sind.
- Update Methode looped durch alle Blöcke im Array, stellt diese dar, erzeugt gegebenenfalls neue und löscht Blöcke, die nicht mehr sichtbar sind.
class Blocks {
constructor() {
this.blocks = [];
this.passedBlocks = 0;
this.max = 1;
}
create() {
this.blocks.push(new Block());
}
shouldCreate() {
return (this.blocks.length < this.max && Math.random() <= 0.5);
}
update(blob) {
if (Math.floor(level * 1000) % this.max * 50 == 1) {
this.max++;
console.log("Updating Max ", this.max);
}
if (this.shouldCreate()) {
console.log("Creating");
this.create();
}
if (this.blocks.length == 0) return;
for (let i = this.blocks.length-1; i >= 0; i--) {
if (this.blocks[i].detectCrash(blob)) {
reset();
}
this.blocks[i].applyGrav(gravity);
this.blocks[i].update();
this.blocks[i].show();
if (this.blocks[i].isOffScreen()) {
this.passedBlocks++;
this.blocks.splice(i, 1);
}
}
}
}
Events & Button Press
Jetzt geht es nur noch darum, die Objekte aus den Klassen zu instanziieren und einen Gravity-Vektor zu erzeugen. Außerdem müssen wir noch Eventhandler für die Buttonpressed-Events erstellen:
- Es kann sowohl per Touch als auch mit den Buttons eins und drei gelenkt werden.
- Mit Button zwei kann das Spiel neu gestartet werden.
//Right
setWatch(() => {
//console.log("Right");
blob.applyForce(new Vector(blob.movForce, 0));
}, BTN5, {repeat:true});
//Left
setWatch(() => {
//console.log("Left");
blob.applyForce(new Vector(-blob.movForce, 0));
}, BTN4, {repeat:true});
setWatch(() => {
//console.log("Right");
blob.applyForce(new Vector(blob.movForce, 0));
}, BTN3, {repeat:true});
//Left
setWatch(() => {
//console.log("Left");
blob.applyForce(new Vector(-blob.movForce, 0));
}, BTN1, {repeat:true});
//Start
setWatch(() => {
//console.log("Left");
if (! playing) {
setup();
}
}, BTN2, {repeat:true});
Der gesamte Code für die BangleJs Hackable Smart Watch:
const width = g.getWidth();
const height = g.getHeight();
const FPS = 10;
const DELTA_T = 1000 / FPS;
//Classes
// Vector Klasse um Vector Mathematik zu vereinfachen.
class Vector {
constructor(x, y) {
if (x !== undefined && y !== undefined) {
this.x = x;
this.y = y;
}else if (arguments.length == 1){
this.x = x;
this.y = x;
}else {
this.x = 0;
this.y = 0;
}
}
add (b) {
this.x += b.x;
this.y += b.y;
return this;
}
sub (b) {
this.x -= b.x;
this.y -= b.y;
return this;
}
mult(n) {
this.x *= n;
this.y *= n;
return this;
}
dist(b) {
const dx = Math.pow(b.x - this.x, 2);
const dy = Math.pow(b.y - this.y, 2);
return Math.sqrt(dx + dy);
}
}
// Abstrakte Objektklasse, von der alle Spielobjekte ableiten um grundlegende Physikkonzepte umzusetzen
class Object {
constructor() {
this.pos = new Vector(width / 2, height / 2);
this.vel = new Vector();
this.acc = new Vector();
}
update() {
this.pos.add(this.vel);
this.vel.add(this.acc);
this.acc.mult(0);
}
show() {
}
applyForce(f) {
this.acc.add(f);
}
}
// Spieler Objekt Klasse
class Blob extends Object{
constructor() {
super();
this.r = 10;
this.movForce = 40;
}
update() {
this.pos.add(this.vel);
this.vel.add(this.acc);
this.acc.mult(0);
this.vel.mult(0.7);
if (this.pos.x <= -(this.r * 1.5)) {
this.pos.x = width;
this.applyForce(new Vector(-this.movForce*0.7, 0));
}else if (this.pos.x >= width) {
this.pos.x = -(this.r * 1.5);
this.applyForce(new Vector (this.movForce*0.7, 0));
}
}
show() {
g.setColor(0.5, 0, 0.9);
g.fillCircle(Math.floor(this.pos.x), Math.floor(this.pos.y), this.r);
}
}
//Hinderniss Objekt Klasse
class Block extends Object {
constructor() {
super();
this.height = 10;
this.minWidth = 20;
this.maxWidth = 90;
this.width = this.getWidth();
this.pos.y = -this.height -10;
this.col = {r: Math.random(), g: Math.random(), b: Math.random()};
if (this.col.r + this.col.g + this.col.b <= 0.5) {
const index = Math.floor(Math.random()* 3);
const cols = ["r", "g", "b"];
this.col[cols[index]] = 0.8;
}
if (this.isLeft()) {
this.pos.x = 0;
}else {
this.pos.x = width - this.width - 1;
}
this.gravCounter = 0;
}
getWidth() {
let rand = Math.random();
rand += 0.5;
console.log(rand);
let twidth = level * 60;
console.log(twidth);
twidth += Math.random() * 100;
console.log(twidth);
if (twidth < this.minWidth) twidth = this.minWidth;
twidth *= rand;
if (blocks.blocks.length < 2) {
twidth *= 1.7;
if (twidth > width-80) twidth = width - 80;
}else {
if (twidth > this.maxWidth) twidth = this.maxWidth;
}
return twidth;
}
isLeft() {
return (Math.random() >= 0.5) ? 1 : 0;
}
show() {
g.setColor(this.col.r, this.col.g, this.col.b);
g.fillRect(this.pos.x, this.pos.y, this.pos.x + this.width, this.pos.y + this.height);
}
applyGrav(g) {
this.gravCounter++;
this.applyForce(new Vector(g.x, g.y).mult(this.gravCounter));
}
isOffScreen() {
return (this.pos.y >= height);
}
getCenter() {
return new Vector(this.pos.x + this.width / 2, this.pos.y + this.height / 2);
}
detectCrash(blob) {
const center = this.getCenter();
const distX = Math.abs(blob.pos.x - center.x);
const distY = Math.abs(blob.pos.y - center.y);
if (distX > (this.width / 2 + blob.r)) return false;
if (distY > (this.height / 2 + blob.r)) return false;
if (distX <= (this.width / 2)) return true;
if (distY <= (this.height / 2)) return true;
const dx = distX - this.width / 2;
const dy = distY - this.height / 2;
return (dx*dx + dy*dy <= (blob.r * blob.r));
}
}
class Blocks {
constructor() {
this.blocks = [];
this.passedBlocks = 0;
this.max = 1;
}
create() {
this.blocks.push(new Block());
}
shouldCreate() {
return (this.blocks.length < this.max && Math.random() <= 0.5);
}
update(blob) {
if (Math.floor(level * 1000) % this.max * 50 == 1) {
this.max++;
console.log("Updating Max ", this.max);
}
if (this.shouldCreate()) {
console.log("Creating");
this.create();
}
if (this.blocks.length == 0) return;
for (let i = this.blocks.length-1; i >= 0; i--) {
if (this.blocks[i].detectCrash(blob)) {
reset();
}
this.blocks[i].applyGrav(gravity);
this.blocks[i].update();
this.blocks[i].show();
if (this.blocks[i].isOffScreen()) {
this.passedBlocks++;
this.blocks.splice(i, 1);
}
}
}
}
//Setup
let blob;
let gravity;
let blocks;
let level;
let loop;
let playing = false;
function setup() {
g.clear();
blob = new Blob();
gravity = new Vector(0, 0.1);
blocks = new Blocks();
level = 0.00001;
playing = true;
loop = setInterval(() => {
draw();
}, DELTA_T);
}
setup();
function draw() {
g.clear();
blob.update();
blob.show();
blocks.update(blob);
level *= 1.0001;
gravity.mult(1.01);
//blob.applyForce(gravity);
}
function reset() {
console.log("COLLISION");
clearInterval(loop);
playing = false;
}
//Events
//Right
setWatch(() => {
//console.log("Right");
blob.applyForce(new Vector(blob.movForce, 0));
}, BTN5, {repeat:true});
//Left
setWatch(() => {
//console.log("Left");
blob.applyForce(new Vector(-blob.movForce, 0));
}, BTN4, {repeat:true});
setWatch(() => {
//console.log("Right");
blob.applyForce(new Vector(blob.movForce, 0));
}, BTN3, {repeat:true});
//Left
setWatch(() => {
//console.log("Left");
blob.applyForce(new Vector(-blob.movForce, 0));
}, BTN1, {repeat:true});
//Start
setWatch(() => {
//console.log("Left");
if (! playing) {
setup();
}
}, BTN2, {repeat:true});
Wir hoffen, dass du in dem Tutorial einen ersten Umgang mit der BangleJs Hackable Smart Watch lernen konntest. In unserem Shop haben wir noch weitere Produkte der Espruino Familie, die alle nach dem selben Prinzip mit JavaScript programmiert werden können.
Falls dein Verlangen nach DIY und Tech immer noch nicht gestillt ist, kannst du dir hier unsere weiteren Tutorials anschauen!