Bangun Game Dua Pemain Berbasis Web dengan Arduino UNO & PHPoC WiFi Shield
Komponen dan Perlengkapan
 | | × | 1 | |
 | | PHPoC WiFi Shield untuk Arduino |
| × | 1 | |
| | × | 4 | |
 | | × | 1 | |
| | × | 1 | |
| | × | 1 | |
Tentang Proyek Ini
Jika Anda pemula, Anda dapat mempelajari tentang tombol di Arduino - Tutorial Tombol.
Saya telah membuat proyek serupa menggunakan perangkat keras yang lebih sederhana (hanya PHPoC) di sini.
Demo
Aliran Data
Arduino ---> PHPoC WiFi Shield ---> Browser web
Ada dua orang yang bermain game. setiap orang menggunakan dua tombol untuk mengontrol arah penjaga gawang. Oleh karena itu, kita membutuhkan empat tombol.
Arduino membaca status empat tombol, Jika salah satu berubah, Arduino akan menghitung ulang arah gerakan penjaga gawang dan mengirim nilai arah ke PHPoC WiFi Shield. Saat menerima nilai, PHPoC WiFi Shield mengirimkannya ke Browser Web melalui websocket. Fungsi JavaScript akan memperbarui arah gerakan penjaga gawang.
Program JavaScript akan terus-menerus memperbarui posisi bola, penjaga gawang dan halangan berdasarkan arah mereka serta memeriksa tabrakan.
Arah penjaga gawang berubah berdasarkan status tombol.
Catatan: PHPoC shield memiliki program bawaan untuk meneruskan data dari Arduino ke browser web. Oleh karena itu, kita tidak perlu memikirkan hal itu.
Apa yang Kita Lakukan
- Atur informasi WiFi untuk PHPoC shield (SSID dan kata sandi)
- Unggah UI baru ke PHPoC shield
- Tulis kode Arduino
Mengatur Informasi WiFi untuk PHPoC Shield
Lihat petunjuk ini.
Unggah Web UI baru ke PHPoC Shield
- Unduh kode sumber PHPoC remote_game.php (di bagian kode).
- Unggah ke PHPoC shield menggunakan PHPoC debugger sesuai petunjuk ini.
Saat menerima permintaan HTTP dari browser web, PHPoC Shield menafsirkan skrip PHP dalam file ini, dan kemudian mengirim file yang telah ditafsirkan ke browser web. File yang telah ditafsirkan (berisi kode HTML, CSS dan JavaScript) menyediakan UI (Tampilan Pengguna), memperbarui posisi bola, penjaga gawang dan halangan berdasarkan arah mereka serta memeriksa tabrakan. Ini juga menerima arah gerakan penjaga gawang dari websocket.
Tulis Kode Arduino
- Instal PHPoC Library untuk Arduino (lihat petunjuk ).
- Unggah kode Arduino (di bagian kode) ke Arduino
Pengujian
- Klik tombol serial di Arduino IDE untuk melihat alamat IP.
- Buka browser web, ketik
https:// ganti_alamat_ip/remote_game.php - Klik tombol connect dan coba
Kit Starter Arduino Terbaik untuk Pemula
Lihat The Best Arduino Kit for Beginners
Referensi Fungsi
- pinMode()
- digitalRead()
- Serial.begin()
- Serial.print()
- Serial.println()
Kode
- Kode Arduino
- Kode PHPoC Shield (remote_game.php)
Kode ArduinoArduino
#include "SPI.h" #include "Phpoc.h" PhpocServer server(80); boolean alreadyConnected = false; void setup() { Serial.begin(9600); while(!Serial) ; Phpoc.begin(PF_LOG_SPI | PF_LOG_NET); server.beginWebSocket("game"); Serial.print("WebSocket server address : "); Serial.println(Phpoc.localIP()); pinMode(6, INPUT); pinMode(7, INPUT); pinMode(8, INPUT); pinMode(9, INPUT); } int value_6 = digitalRead(6); int value_7 = digitalRead(7); int value_8 = digitalRead(8); int value_9 = digitalRead(9); int pre_dir_1 = 0; int pre_dir_2 = 0; int dir_1 = 0; int dir_2 = 0; void loop() { // when the client sends the first byte, say hello: PhpocClient client = server.available(); if (client) { value_6 = digitalRead(6); value_7 = digitalRead(7); value_8 = digitalRead(8); value_9 = digitalRead(9); dir_1 = value_7 - value_6; dir_2 = value_9 - value_8; if(dir_1 != pre_dir_1 || dir_2 != pre_dir_2) { pre_dir_1 = dir_1; pre_dir_2 = dir_2; String txtMsg = "[" + String(dir_1) + ", " + String(dir_2) + "]\r\n"; char buf[txtMsg.length()+ 1]; txtMsg.toCharArray(buf, txtMsg.length() + 1); server.write(buf, txtMsg.length()); } } } Kode PHPoC Shield (remote_game.php)PHP
<!DOCTYPE html> <html> <head> <title>PHPoC - Game</title> <meta name="viewport" content="width=device-width, initial-scale=0.7, maximum-scale=0.7"> <style> body { text-align: center; font-size: 15pt; font-family: Arial, Helvetica, sans-serif;} h1 { font-weight: bold; font-size: 25pt; } h2 { font-weight: bold; font-size: 15pt; } button { font-weight: bold; font-size: 15pt; } </style> <script> window.requestAnimFrame = (function(callback) { return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function(callback) { window.setTimeout(callback, 1000 / 60); }; })(); var cvs_width = 400, cvs_height = 500; var ball = {x:cvs_width / 2, y:cvs_height / 2 , radius:20, dir_x:1, dir_y:1, speed:3}; var obs_1 = { x: 100, y: 150, left: 0, // update later right: 0, // update later top: 0, // update later bottom: 0, // update later width: 105, height: 30, dir: 1, // up down direction speed: 2 }; var obs_2 = { x: 300, y: 350, left: 0, // update later right: 0, // update later top: 0, // update later bottom: 0, // update later width: 105, height: 30, dir: -1, // up down direction speed: 2 }; var obstacles = [obs_1, obs_2]; var keeper_1 = { x: cvs_width / 2, y: 15, left: 0, // update later right: 0, // update later top: 0, // update later bottom: 0, // update later width: 105, height: 30, dir: 0, // left right direction speed: 8 }; var keeper_2 = { x: cvs_width / 2, y: cvs_height - 15, left: 0, // update later right: 0, // update later top: 0, // update later bottom: 0, // update later width: 105, height: 30, dir: 0, // left right direction speed: 8 }; var keepers = [keeper_1, keeper_2]; var score = [0, 0]; var delay = 100; var ws = null; var ctx = null; function init() { var width = window.innerWidth; var height = window.innerHeight; var ratio_x = (width - 105) / (cvs_width); var ratio_y = (height - 200) / cvs_height; var ratio = (ratio_x < ratio_y) ? ratio_x : ratio_y; cvs_width *= ratio; cvs_height *= ratio; var canvas = document.getElementById("remote"); canvas.width = cvs_width + 105; canvas.height = cvs_height; ctx = canvas.getContext("2d"); ctx.translate(105, 0); ctx.lineWidth = 4; for( var i = 0; i < obstacles.length; i++) { obstacles[i].x *= ratio; obstacles[i].y *= ratio; obstacles[i].width *= ratio; obstacles[i].height *= ratio; obstacles[i].speed *= ratio; obstacles[i].left = obstacles[i].x - obstacles[i].width / 2; obstacles[i].right = obstacles[i].x + obstacles[i].width / 2; obstacles[i].top = obstacles[i].y - obstacles[i].height / 2; obstacles[i].bottom = obstacles[i].y + obstacles[i].height / 2; } for( var i = 0; i < keepers.length; i++) { keepers[i].x *= ratio; keepers[i].y *= ratio; keepers[i].width *= ratio; keepers[i].height *= ratio; keepers[i].speed *= ratio; keepers[i].left = keepers[i].x - keepers[i].width / 2; keepers[i].right = keepers[i].x + keepers[i].width / 2; keepers[i].top = keepers[i].y - keepers[i].height / 2; keepers[i].bottom = keepers[i].y + keepers[i].height / 2; } ball.x *= ratio; ball.y *= ratio; ball.radius *= ratio; ball.speed *= ratio; update_view(ctx); } function connect_onclick() { if(ws == null) { var ws_host_addr = "<?echo _SERVER("HTTP_HOST")?>"; ws = new WebSocket("ws://" + ws_host_addr + "/game", "text.phpoc"); document.getElementById("ws_state").innerHTML = "CONNECTING"; ws.onopen = ws_onopen; ws.onclose = ws_onclose; ws.onmessage = ws_onmessage; } else ws.close(); } function ws_onopen() { document.getElementById("ws_state").innerHTML = "<font color='blue'>CONNECTED</font>"; document.getElementById("bt_connect").innerHTML = "Disconnect"; ws.send("dummy\r\n"); } function ws_onclose() { document.getElementById("ws_state").innerHTML = "<font color='gray'>CLOSED</font>"; document.getElementById("bt_connect").innerHTML = "Connect"; ws.onopen = null; ws.onclose = null; ws.onmessage = null; ws = null; } function ws_onmessage(e_msg) { e_msg = e_msg || window.event; // MessageEvent console.log(e_msg.data); var arr = JSON.parse(e_msg.data); keepers[0].dir = parseInt(arr[0]); keepers[1].dir = parseInt(arr[1]); } function update_view(ctx) { ctx.clearRect(-105, 0, cvs_width, cvs_height); ctx.fillStyle = "black"; ctx.fillRect(0, 0, cvs_width, cvs_height); ctx.beginPath(); ctx.moveTo(-105, cvs_height / 2); ctx.lineTo(0, cvs_height / 2); ctx.stroke(); ctx.font = "120px Georgia"; ctx.textBaseline = "middle"; ctx.textAlign = "center"; var team_1 = score[0]; var team_2 = score[1]; ctx.fillStyle = "#00FF00"; ctx.fillText(team_1.toString(), -50, cvs_height / 2 - 70); ctx.fillStyle = "#0000FF"; ctx.fillText(team_2.toString(), -50, cvs_height / 2 + 50); ctx.fillStyle="#FF0000"; ctx.beginPath(); ctx.arc(ball.x, ball.y, ball.radius, 0, 2*Math.PI); ctx.fill(); for( var i = 0; i < obstacles.length; i++) ctx.fillRect(obstacles[i].left, obstacles[i].top, obstacles[i].width, obstacles[i].height); ctx.fillStyle="#00FF00"; ctx.fillRect(keeper_1.left, keeper_1.top, keeper_1.width, keeper_1.height); ctx.fillStyle="#0000FF"; ctx.fillRect(keeper_2.left, keeper_2.top, keeper_2.width, keeper_2.height); } function collision_detect(object) { var dist_x = Math.abs(ball.x - object.x); var dist_y = Math.abs(ball.y - object.y); var TOUCH_DIST_X = ball.radius + object.width / 2; var TOUCH_DIST_Y = ball.radius + object.height / 2; if(ball.x >= object.left && ball.x <= object.right) { if(dist_y <= TOUCH_DIST_Y) { ball.dir_y *= -1; if(ball.y < object.top) ball.y = object.top - ball.radius; else if(ball.y > object.bottom) ball.y = object.bottom + ball.radius; return true; } return false; } if(ball.y >= object.top && ball.y <= object.bottom) { if(dist_x <= TOUCH_DIST_X) { ball.dir_x *= -1; if(ball.x < object.left) ball.x = object.left - ball.radius; else if(ball.x > object.right) ball.x = object.right + ball.radius; return true; } return false; } if(dist_x < TOUCH_DIST_X && dist_y < TOUCH_DIST_Y) { dist_x -= object.width / 2; //distance to corner dist_y -= object.height / 2; //distance to corner if(dist_x == dist_y) { ball.dir_x *= -1; ball.dir_y *= -1; } else if(dist_x > dist_y) ball.dir_x *= -1; else ball.dir_y *= -1; return true; } } function check_edges() { if((ball.x + ball.radius) >= cvs_width || (ball.x - ball.radius) <= 0) ball.dir_x *= -1; if((ball.y - ball.radius) >= cvs_height || (ball.y + ball.radius) <= 0) { if((ball.y - ball.radius) >= cvs_height) score[0] += 1; else score[1] += 1; ball.dir_y *= -1; ball.x = cvs_width / 2; ball.y = cvs_height / 2; delay = 100; } } function check_keepers() { for( var i = 0; i < keepers.length; i++) { var obs = keepers[i]; collision_detect(obs); } } function check_obstacles() { for( var i = 0; i < obstacles.length; i++) { var obs = obstacles[i]; collision_detect(obs); } } function move_ball() { ball.x += ball.dir_x * ball.speed; ball.y += ball.dir_y * ball.speed; } function move_obstacles() { for( var i = 0; i < obstacles.length; i++) { obstacles[i].y += obstacles[i].dir * obstacles[i].speed; if(obstacles[i].dir == 1 && obstacles[i].y > (cvs_height - 8*ball.radius)) obstacles[i].dir = -1; else if(obstacles[i].dir == -1 && obstacles[i].y < (8*ball.radius)) obstacles[i].dir = 1; obstacles[i].top = obstacles[i].y - obstacles[i].height / 2; obstacles[i].bottom = obstacles[i].y + obstacles[i].height / 2; } } function move_keepers() { for( var i = 0; i < keepers.length; i++) { keepers[i].x += keepers[i].dir*keepers[i].speed; if(keepers[i].right > cvs_width && keepers[i].dir == 1) { keepers[i].dir *= 0; keepers[i].x = cvs_width - keepers[i].width / 2 } if(keepers[i].left < 0 && keepers[i].dir == -1) { keepers[i].dir = 0; keepers[i].x = keepers[i].width / 2 } keepers[i].left = keepers[i].x - keepers[i].width / 2; keepers[i].right = keepers[i].x + keepers[i].width / 2; } } function animate(ctx) { if(ws != null) { move_keepers(); if(!delay) { move_ball(); move_obstacles(); check_edges(); check_keepers(); check_obstacles(); } else delay--; update_view(ctx); } // request new frame requestAnimFrame(function() { animate(ctx); }); } setTimeout(function() { animate(ctx); }, 100); window.onload = init; </script> </head> <body> <center> <p> <h1>PHPoC - Web-based Game</h1> </p> <canvas id="remote" width="400" height="500"></canvas> <h2> <p> WebSocket : <span id="ws_state">null</span> </p> <button id="bt_connect" type="button" onclick="connect_onclick();">Connect</button> </h2> </center> </body> </html> Skematik
1. Tempatkan PHPoC wifi shield atau PHPoC shield di atas Arduino
2. Rangkai seperti gambar di bawah