mbedJS+Wiiヌンチャクでスロットカーを作った
I2Cなデバイスで真っ先に思い付いたのが、Wiiヌンチャクだった訳で。
何かできないかなと考えた。
ヌンチャクを手に持って小一時間・・・この感覚、あれだ!車が走るやつ!
Note
編集中です。
【概要】
・ヌンチャクのスティックの押し込み具合で加速する車、スロットカーを作った。
wikiページ
・さすがにグラフィック液晶では迫力もスピード感も出ないので、今回はHTML5+JavaScriptで作ってみる。
・JSと言えばmbedJSがあるので、これを使う。
【使ったもの】
・mbed LPC1768 (青mbed) + ☆ボードオレンジ(LAN接続用)
・Wiiヌンチャク ×2本 (1本でも可、コースを作ってるうちに2レーンできていたので1本追加で買ってきた。)
・拡張コントローラー用の接続アダプタ
最近は海外通販で本物(?)が買えるらしいけど、ピッチが合わず使いにくそうなので、ヌンチャクアダプタがおススメ。
通販用URL
・プルアップ抵抗(10KΩ)
ヌンチャク1本につき2つ。計4つ。
【回路図】
・I2Cのうち、p9 p10 を青い車(1号車)、p28 p27を赤い車(2号車)に割り当てています。
回路図を書いて掲載する。
サンプルページ(mbedJSが接続中でIPが"192.168.0.39"の場合) /media/uploads/ban4jp/slotcar.html
サンプルページ(自動ループ) /media/uploads/ban4jp/slotcar_autoloop.html
素材データ一式 /media/uploads/ban4jp/slotcar.zip
【権利関係】
・素材データ(slotcar.zip)はすべて私(ban4jp)がゼロから作成したものです。
・素材データ(slotcar.zip)は自由に利用、改造してください。商用なども一切気にしません。
・ただし、"AS IS"です。何か問題があっても自己責任でお願いします。
【後で動画を張る】
<!doctype html> <html> <head> <meta charset="utf-8"> <title>スロットカー for mbedJS</title> <style type="text/css"> .course_box { position:relative; } .car { position:absolute; } </style> <script src="http://192.168.0.39/rom/m/mbedJS.all-min.js"></script> <script> function Nunchuk(_mcu, _pinnames) { this.joyX = 0; // ニュートラルポジションからの相対値 this.joyY = 0; var instance = this; var stat = 0; var neutralJoyX = 127; // ニュートラルポジション var neutralJoyY = 127; var timer_wait = 0; var timer = setInterval(function() { switch(stat){ case 1: case 4: stat++; i2c.write(0xA4, [0x00], false); break; case 98: // Read/Writeが失敗した場合、初期化からやり直す if (timer_wait > 0) { timer_wait--; break; } stat = 0; i2c.write(0xA4, [0x40, 0x00], false); break; case 99: // ステータス異常の場合、i2cを再スタートする if (timer_wait > 0) { timer_wait--; break; } stat = 0; console.log("restart:", stat); i2c.start(); break; } }, 1000/60); var i2c = new mbedJS.I2C(_mcu, _pinnames, { onNew:function() { i2c.frequency(400000); }, onStart:function() { i2c.frequency(400000); }, onFrequency:function() { i2c.write(0xA4, [0x40, 0x00], false); }, onWrite:function(v) { if (v != 0) { //console.log("write error(1):", stat, v); timer_wait = 60 * 0.25; stat = 98; return; } switch(stat){ case 0: stat++; break; case 2: case 5: stat++; i2c.read(0xA4, 6, false); break; default: i2c.stop(); console.log("stop(1):", stat); break; } }, onRead:function(v) { switch(stat){ case 3: // ヌンチャクの状態が異常となった場合、値が255になる if (instance.joyX == 255) { i2c.stop(); console.log("reconnect:", stat, v); break; } instance.neutralJoyX = (v.data[0]^0x17)+0x17; instance.neutralJoyY = (v.data[1]^0x17)+0x17; stat++; break; case 6: // ヌンチャクの状態が異常となった場合、値が255になる if (instance.joyX == 255) { i2c.stop(); console.log("reconnect:", stat, v); break; } // データをデコード instance.joyX = (v.data[0]^0x17)+0x17 - instance.neutralJoyX; instance.joyY = (v.data[1]^0x17)+0x17 - instance.neutralJoyY; // ボタンや加速度センサーの値が必要な場合は、ここに追記 stat = 4; break; default: i2c.stop(); console.log("stop(2):", stat); break; } }, onStop:function() { console.log("onStop:", stat); timer_wait = 60 * 1.5; stat = 99; } }); } Nunchuk.prototype = {}; var nun_b; var nun_r; var mcu = new mbedJS.Mcu('192.168.0.39', { onNew:function() { nun_b = new Nunchuk(mcu, [mbedJS.PinName.p9, mbedJS.PinName.p10]); nun_r = new Nunchuk(mcu, [mbedJS.PinName.p28, mbedJS.PinName.p27]); }, onClose:function(){ alert("mbedJS close."); }, onError:function(){ alert("mbedJS Error!"); } }); </script> <script> function Car(func) { this.pos = 250; // 走行位置 this.course_func = func; // 走行区間を計算する関数 this.x = 0; // X座標(車の引っ掛け部分の座標) this.y = 0; // Y座標 this.r = 0; // 回転角度 this.z = 0; // Z座標(0 or 1) this.accel = 0; // 加速度 this.inertia = 0; // 慣性 } Car.prototype = { setAccel:function(val) { this.accel = val; }, tick:function() { // アクセルの踏み具合から次の走行距離を計算し、コース上の車の位置を更新 this.inertia = (this.inertia * 7 + this.accel) / 8; this.pos += this.inertia / 6; this.course_func(this); }, course_line:function(x1, y1, x2, y2, rotate, func_nextarea, z_order) { // エリアの距離を計算 var l = Math.sqrt(Math.pow(Math.abs(x1-x2), 2) + Math.pow(Math.abs(y1-y2), 2)); // もし、走行距離がエリアの距離を超えていた場合、 // エリア分の走行距離を差し引いて次のエリアへ引き継ぎ if (this.pos >= l) { this.pos -= l; this.course_func = func_nextarea; this.z = z_order; this.course_func(this); return; } // 走行距離から、現在座標を計算 this.x = (x2-x1) * this.pos / l + x1; this.y = (y2-y1) * this.pos / l + y1; this.r = rotate; }, course_circle:function(x1, y1, z1, r1, r2, func_nextarea, z_order) { // エリアの距離を計算 var l = (z1 * 2 * Math.PI) * Math.abs(r1-r2) / (Math.PI * 2) // もし、走行距離がエリアの距離を超えていた場合、 // エリア分の走行距離を差し引いて次のエリアへ引き継ぎ if (this.pos >= l) { this.pos -= l; this.course_func = func_nextarea; this.z = z_order; this.course_func(this); return; } // 走行距離から、現在座標を計算 var r0 = r1 + (r2-r1) * this.pos / l; if (r2 > r1) { this.x = x1 - Math.cos(r0) * z1; this.y = y1 - Math.sin(r0) * z1; } else { this.x = x1 + Math.cos(r0) * z1; this.y = y1 + Math.sin(r0) * z1; } this.r = r0; } }; var car_b = new Car(area_b_1); var car_r = new Car(area_r_1); var screen_canvas; var course; var course_a_img; var course_b_img; var car_b_img; var car_r_img; var frame_timer; function onload() { console.log("onload() start"); screen_canvas = document.getElementById("screen_canvas"); course = screen_canvas.getContext("2d"); course_a_img = document.getElementById("course_a"); course_b_img = document.getElementById("course_b"); car_b_img = document.getElementById("car_b"); car_r_img = document.getElementById("car_r"); update(); frame_timer = setInterval(update, 1000 / 60); // 60Hz console.log("onload() end"); } function update() { if (nun_b != undefined) { var val; if (nun_b.joyY < 0) { val = 0; } else { val = nun_b.joyY; if (val > 90) val = 90; } car_b.setAccel(val); if (nun_r.joyY < 0) { val = 0; } else { val = nun_r.joyY; if (val > 90) val = 90; } car_r.setAccel(val); data.innerHTML = " val1 = " + nun_b.joyY + " accel1 = " + Math.round(car_b.accel * 100) / 100 + "<br />" + " val2 = " + nun_r.joyY + " accel2 = " + Math.round(car_r.accel * 100) / 100; } else { console.log("wait..."); } car_b.tick(); car_r.tick(); course.drawImage(course_a_img, 0, 0); if (car_b.z != 0) { course.save(); course.translate(car_b.x, car_b.y); course.rotate(car_b.r); course.drawImage(car_b_img, -11, -13); course.restore(); } if (car_r.z != 0) { course.save(); course.translate(car_r.x, car_r.y); course.rotate(car_r.r); course.drawImage(car_r_img, -11, -13); course.restore(); } course.drawImage(course_b_img, 394, 386); if (car_b.z == 0) { course.save(); course.translate(car_b.x, car_b.y); course.rotate(car_b.r); course.drawImage(car_b_img, -11, -13); course.restore(); } if (car_r.z == 0) { course.save(); course.translate(car_r.x, car_r.y); course.rotate(car_r.r); course.drawImage(car_r_img, -11, -13); course.restore(); } } //---------------------------------------- var CONST_R0 = 270 * Math.PI / 180; var CONST_R1 = 441 * Math.PI / 180; var CONST_R2 = 285 * Math.PI / 180; var CONST_R3 = 138 * Math.PI / 180; var CONST_R4 = 66 * Math.PI / 180; //---------------------------------------- function area_b_1(car) { car.course_line( 835 - Math.cos(CONST_R0) * 85, 620 - Math.sin(CONST_R0) * 85, 178 - Math.cos(CONST_R0) * 105, 600 - Math.sin(CONST_R0) * 105, CONST_R0, area_b_2, 0); } function area_b_2(car) { car.course_circle(178, 600, 105, CONST_R0, CONST_R1, area_b_3, 0); } function area_b_3(car) { car.course_line( 178 - Math.cos(CONST_R1) * 105, 600 - Math.sin(CONST_R1) * 105, 880 + Math.cos(CONST_R1) * 100, 280 + Math.sin(CONST_R1) * 100, CONST_R1, area_b_4, 0); } function area_b_4(car) { car.course_circle(880, 280, 100, CONST_R1, CONST_R2, area_b_5, 0); } function area_b_5(car) { car.course_line( 880 + Math.cos(CONST_R2) * 100, 280 + Math.sin(CONST_R2) * 100, 328 + Math.cos(CONST_R2) * 110, 148 + Math.sin(CONST_R2) * 110, CONST_R2, area_b_6, 0); } function area_b_6(car) { car.course_circle(328, 148, 110, CONST_R2, CONST_R3, area_b_7, 1); } function area_b_7(car) { car.course_line( 328 + Math.cos(CONST_R3) * 110, 148 + Math.sin(CONST_R3) * 110, 646 + Math.cos(CONST_R3) * 105, 492 + Math.sin(CONST_R3) * 105, CONST_R3, area_b_8, 0); } function area_b_8(car) { car.course_circle(646, 492, 105, CONST_R3, CONST_R4, area_b_9, 0); } function area_b_9(car) { car.course_line( 646 + Math.cos(CONST_R4) * 105, 492 + Math.sin(CONST_R4) * 105, 835 - Math.cos(CONST_R4) * 85, 620 - Math.sin(CONST_R4) * 85, CONST_R4, area_b_10, 0); } function area_b_10(car) { car.course_circle(835, 620, 85, CONST_R4, CONST_R0, area_b_1, 0); } //---------------------------------------- function area_r_1(car) { car.course_line( 835 - Math.cos(CONST_R0) * 110, 620 - Math.sin(CONST_R0) * 110, 178 - Math.cos(CONST_R0) * 130, 600 - Math.sin(CONST_R0) * 130, CONST_R0, area_r_2, 0); } function area_r_2(car) { car.course_circle(178, 600, 130, CONST_R0, CONST_R1, area_r_3, 0); } function area_r_3(car) { car.course_line( 178 - Math.cos(CONST_R1) * 130, 600 - Math.sin(CONST_R1) * 130, 880 + Math.cos(CONST_R1) * 75, 280 + Math.sin(CONST_R1) * 75, CONST_R1, area_r_4, 0); } function area_r_4(car) { car.course_circle(880, 280, 75, CONST_R1, CONST_R2, area_r_5, 0); } function area_r_5(car) { car.course_line( 880 + Math.cos(CONST_R2) * 75, 280 + Math.sin(CONST_R2) * 75, 328 + Math.cos(CONST_R2) * 85, 148 + Math.sin(CONST_R2) * 85, CONST_R2, area_r_6, 0); } function area_r_6(car) { car.course_circle(328, 148, 85, CONST_R2, CONST_R3, area_r_7, 1); } function area_r_7(car) { car.course_line( 328 + Math.cos(CONST_R3) * 85, 148 + Math.sin(CONST_R3) * 85, 646 + Math.cos(CONST_R3) * 80, 492 + Math.sin(CONST_R3) * 80, CONST_R3, area_r_8, 0); } function area_r_8(car) { car.course_circle(646, 492, 80, CONST_R3, CONST_R4, area_r_9, 0); } function area_r_9(car) { car.course_line( 646 + Math.cos(CONST_R4) * 80, 492 + Math.sin(CONST_R4) * 80, 835 - Math.cos(CONST_R4) * 110, 620 - Math.sin(CONST_R4) * 110, CONST_R4, area_r_10, 0); } function area_r_10(car) { car.course_circle(835, 620, 110, CONST_R4, CONST_R0, area_r_1, 0); } </script> </head> <body onLoad="onload()"> <div style="display:none"> <img src="course_a.png" id="course_a" class="course" width="1024" height="768" alt="コース" /> <img src="course_b.png" id="course_b" class="course" width="154" height="99" alt="コース影" /> <img src="car_b.png" id="car_b" class="car" width="23" height="47" alt="1号車" /> <img src="car_r.png" id="car_r" class="car" width="23" height="47" alt="2号車" /> </div> <canvas id="screen_canvas" width="1024" height="768"></canvas> <div id="data"></div> </body> </html>
【追伸】
・ゲーム性はありません。
・タイムアタックとか、対戦プレイとか面白そうです。誰か追加してください。
Please log in to post comments.