Создадим онлайн сетевую игру для двоих игроков на основе аппаратной платформы PHPoC и веб-интерфейса.
Комплектующие
Мы будем делать игру на основе аппаратной платформы PHPoC о которой мы сегодня уже говорили и сделали про нее подробный обзор.
Для создания проекта нам понадобятся следующие комплектующие:
- PHPoC Blue × 1
- Кнопка × 4
- Макет × 1
- Резистор 10 кОм
PHPoC Blue (P4S-342) - это программируемая плата для беспроводной локальной сети со встроенным интерпретатором PHP. Поддерживает различные интерфейсы для связи с датчиками и устройствами.
Помимо различных интерфейсов для взаимодействия с датчиками и исполнительными устройствами, PHPoC Blue оснащен возможностью подключения Wi-Fi.
Он поддерживает различные сетевые протоколы, протоколы безопасности, алгоритмы аутентификации и шифрования, отвечая всем требованиям для создания проектов и устройств интернета вещей (IoT).
PHPoC Blue использует язык программирования PHP, который создатели платформы так и называют PHPoC, т.е. PHP on Chip.
Платформа оснащена веб-сервером и сервером WebSocket, позволяет пользователям разрабатывать веб-приложения в реальном времени, чего нет в Ардуино. PHPoC Blue можно использовать в сочетании с платой расширения для добавления возможностей и гибкости.
Схема соединения
Все наши комплектующие мы подключаем согласно схеме ниже:
Поток данных
PHPoC ---> Веб-браузер
В игру играют два человека. Каждый игрок использует две кнопки для управления условными "вратарями". Для этого нам нужны четыре кнопки.
PHPoC считывает состояния четырех кнопок. Если состояние какой-либо кнопки изменено, то PHPoC пересчитает направление движения "вратаря" и отправляет значение направления в веб-браузер через веб-сокет. Функция JavaScript будет обновлять направление движения "вратарей".
Программа JavaScript будет постоянно обновлять положение мяча, "вратарей" и препятствий в зависимости от ситуации, а также проверять столкновения. Направление вратарей меняется в зависимости от состояния кнопок.
Исходный код
Исходный код включает в себя два файла, код каждого файла приведен ниже.
index.php
Этот файл является кодом на стороне клиента. При получении HTTP-запроса от веб-браузера PHPoC интерпретирует PHP-скрипт в этом файле, а затем отправляет интерпретированный файл в веб-браузер.
Интерпретированный файл (содержит HTML, CSS и код JavaScript) предоставляет пользовательский интерфейс, обновляет положение мяча, "вратарей" и препятствий на основе их направления, а также проверяет столкновения. Он также получает направление движения вратарей от websocket.
<!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"; } 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>
task0.php
Этот файл является кодом на стороне сервера. Он запускается в бесконечном цикле, чтобы прочитать состояния кнопок и отправить их в веб-браузер через WebSocket.
<?php include_once "/lib/sd_340.php"; include_once "/lib/sn_tcp_ws.php"; uio_setup(0, 12, "in_pd"); uio_setup(0, 13, "in_pd"); uio_setup(0, 20, "in_pd"); uio_setup(0, 21, "in_pd"); ws_setup(0, "game", "text.phpoc"); $pre_dir_1 = 0; $pre_dir_2 = 0; while(1) { if(ws_state(0) == TCP_CONNECTED) { $value_12 = uio_in(0, 12); $value_13 = uio_in(0, 13); $value_20 = uio_in(0, 20); $value_21 = uio_in(0, 21); $dir_1 = $value_13 - $value_12; $dir_2 = $value_21 - $value_20; if($dir_1 != $pre_dir_1 || $dir_2 != $pre_dir_2) { $pre_dir_1 = $dir_1; $pre_dir_2 = $dir_2; ws_write(0, "[$dir_1, $dir_2]"); } } } ?>
Итоговый результат
Вот такой результат мы получили. В ближайших уроках мы реализуем эту игру на основе платы Ардуино. Следите за новыми уроками на нашем сайте.