SonarGame Documentation
Overview
SonarGame is an interactive browser-based game that uses a sonar sensor connected to an Arduino to control gameplay. The game features two modes – Submarine and Exploration – where players navigate through underwater environments by physically moving their hand or an object in front of the sonar sensor.
System Requirements
- Computer with a modern web browser (Chrome recommended for WebSerial API)
- Arduino board (Uno or similar)
- HC-SR04 ultrasonic sensor
- USB cable to connect Arduino to computer
- Basic electronic components (jumper wires, optional breadboard)
Installation
Web Application Setup
- Download the game files to your local machine:
- index.html
- style.css
- sketch.js (main game file)
- webSerial.js (for Arduino communication)
- Set up the file structure:
sonar-game/
├── index.html
├── style.css
├── js/
│ ├── sketch.js
│ └── webSerial.js
Arduino Setup
- Connect the HC-SR04 ultrasonic sensor to your Arduino:
- VCC to 5V on Arduino
- GND to GND on Arduino
- TRIG to pin 9 on Arduino
- ECHO to pin 10 on Arduino
- Upload the Arduino code (see Arduino Code section)
Game Setup
HTML Structure
Create an index.html
file with the following structure:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Sonar Game</title>
<link rel="stylesheet" href="style.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
</head>
<body>
<div id="gameContainer">
<div id="gameCanvas"></div>
<div id="controls">
<div id="modeSelector">
<h2>Select Game Mode</h2>
<button id="submarineMode">Submarine Mode</button>
<button id="explorationMode">Exploration Mode</button>
</div>
<div id="instructions" style="display: none;">
<p id="gameInstructions"></p>
<button onclick="document.getElementById('instructions').style.display='none';document.getElementById('modeSelector').style.display='flex';">Back to Mode Selection</button>
</div>
<div id="gameInfo">
<div>Score: <span id="scoreDisplay">0</span></div>
<div>Level: <span id="levelDisplay">1</span></div>
<div>Lives: <span id="livesDisplay">3</span></div>
</div>
<div id="sonarControls">
<div id="sonarSimulator">
<label for="sonarRange">Simulate Sonar (0-400cm): <span id="sonarValue">200</span></label>
<input type="range" id="sonarRange" min="0" max="400" value="200">
</div>
<div id="arduinoControls">
<button id="toggleSimulator">Toggle Simulator</button>
<button id="connectArduino">Connect Arduino</button>
<button id="disconnectArduino">Disconnect Arduino</button>
</div>
</div>
</div>
</div>
<script src="js/webSerial.js"></script>
<script src="js/sketch.js"></script>
</body>
</html>
CSS Styling
Create a style.css
file:
body {
margin: 0;
padding: 0;
font-family: Arial, sans-serif;
background-color: #111;
color: #0f0;
}
#gameContainer {
display: flex;
flex-direction: column;
height: 100vh;
}
#gameCanvas {
flex-grow: 1;
}
#controls {
height: 60px;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 20px;
background-color: #222;
}
#modeSelector {
display: flex;
flex-direction: column;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: rgba(0, 0, 0, 0.8);
padding: 20px;
border-radius: 10px;
text-align: center;
}
#instructions {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: rgba(0, 0, 0, 0.8);
padding: 20px;
border-radius: 10px;
text-align: center;
}
button {
background-color: #0f0;
color: #000;
border: none;
padding: 8px 16px;
margin: 5px;
border-radius: 5px;
cursor: pointer;
}
button:hover {
background-color: #0c0;
}
#gameInfo {
display: flex;
gap: 20px;
}
#sonarControls {
display: flex;
align-items: center;
}
#sonarSimulator {
margin-right: 20px;
}
input[type="range"] {
width: 200px;
margin: 0 10px;
}
Game Mechanics
Game Modes
- Submarine Mode:
- Navigate a submarine through underwater caves
- Avoid obstacles like rocks, mines, and enemy submarines
- Collect power-ups for extra lives and points
- Exploration Mode:
- Explore ocean depths with diving gear
- Avoid sharks, jellyfish, and trash
- Collect treasures and discover sea creatures
Controls
The game is controlled using a sonar sensor. The distance measured by the sonar determines the vertical position of the player:
- Closer to the sensor = higher position on screen
- Further from sensor = lower position on screen
Scoring System
- Each obstacle successfully avoided: +1 point
- Power-up collection: +10 points
- Level increases every 20 points
- Starting with 3 lives
- Game over when lives reach 0
Game Elements
- Obstacles:
- Submarine mode: rocks, mines, enemy submarines
- Exploration mode: sharks, jellyfish, trash
- Colliding with obstacles reduces lives
- Power-ups:
- Extra life: Restores 1 life
- Extra points: Adds 10 points
- Visual effects:
- Sonar waves: Visually represent sonar activity
- Particles: Created on collisions and power-up collection
P5JS
Arduino Integration
Arduino Code
// SonarGame Arduino Code
// Connects HC-SR04 ultrasonic sensor to send distance readings to browser
// Pin definitions
const int trigPin = 9; // Trigger pin of the HC-SR04
const int echoPin = 10; // Echo pin of the HC-SR04
// Variables
long duration;
int distance;
void setup() {
// Initialize Serial communication
Serial.begin(9600);
// Configure pins
pinMode(trigPin, OUTPUT);
pinMode(echoPin, INPUT);
}
void loop() {
// Clear the trigger pin
digitalWrite(trigPin, LOW);
delayMicroseconds(2);
// Send a 10μs pulse to trigger
digitalWrite(trigPin, HIGH);
delayMicroseconds(10);
digitalWrite(trigPin, LOW);
// Read the echo pin, duration in microseconds
duration = pulseIn(echoPin, HIGH);
// Calculate distance in centimeters
// Speed of sound is 343m/s = 34300cm/s
// Duration is time for sound to travel to object and back
// So distance = (duration * 34300) / 2 / 1000000
distance = duration * 0.034 / 2;
// Limit range to 0-400cm to match game expectations
if (distance > 400) distance = 400;
if (distance < 0) distance = 0;
// Send the distance to the Serial port
Serial.println(distance);
// Small delay before next reading
delay(50);
}
WebSerial Integration
The game uses the WebSerial API to communicate with the Arduino. The webSerial.js
file should handle:
- Requesting port access from the browser
- Opening serial connection
- Reading and parsing serial data
- Handling connection errors
Here’s a sample implementation:
// webSerial.js - Handles communication with Arduino via WebSerial API
let port;
let serialActive = false;
let reader;
let readableStreamClosed;
async function setUpSerial() {
if ('serial' in navigator) {
try {
// Request port access
port = await navigator.serial.requestPort();
// Open the port with appropriate settings
await port.open({ baudRate: 9600 });
serialActive = true;
// Set up reading from the port
const decoder = new TextDecoder();
const readableStreamClosed = port.readable.pipeTo(new WritableStream({
write(chunk) {
// Process each chunk of data
let string = decoder.decode(chunk);
// Call the readSerial function from sketch.js
if (typeof readSerial === 'function') {
readSerial(string);
}
}
}));
console.log("Serial connection established successfully");
} catch (error) {
console.error("Error opening serial port:", error);
}
} else {
console.error("WebSerial API not supported in this browser");
alert("WebSerial is not supported in this browser. Please use Chrome or Edge.");
}
}
Customization
Adding New Obstacles
To add new obstacles, modify the createObstacle()
function in sketch.js:
function createObstacle() {
// Increase the range for obstacle types
let obstacleType;
if (gameMode === "submarine") {
obstacleType = floor(random(4)); // Increased from 3 to 4
} else {
obstacleType = floor(random(4)) + 4; // Adjusted accordingly
}
let obstacle = {
x: width + 50,
y: random(50, height - 50),
width: random(30, 80),
height: random(20, 60),
speed: 2 + level * 0.5,
type: obstacleType,
};
obstacles.push(obstacle);
}
// Then update the drawObstacles() function to handle the new type
function drawObstacles() {
for (let obstacle of obstacles) {
// ... existing code ...
if (gameMode === "submarine") {
// ... existing obstacle types ...
else if (obstacle.type === 3) {
// New obstacle type
// Add drawing code here
}
} else {
// ... existing code ...
else if (obstacle.type === 7) {
// New obstacle type for exploration mode
// Add drawing code here
}
}
}
}
Adjusting Difficulty
To adjust game difficulty, modify these variables:
- Initial values in the
resetGame()
function:javascriptfunction resetGame() { score = 0; level = 1; lives = 5; // Increased from 3 for easier gameplay // ... }
- Obstacle generation rate in the
updateGame()
function:javascriptif (millis() - lastObstacleTime > 2500 - level * 75) { // Adjusted timing createObstacle(); lastObstacleTime = millis(); }
- Obstacle speed in the
createObstacle()
function:javascriptlet obstacle = { // ... speed: 1.5 + level * 0.3, // Slower progression // ... };
Troubleshooting
Game Performance Issues
If the game is running slowly:
- Reduce the number of background elements
- Decrease particle effects
- Lower the frequency of sonar waves
function createBackgroundElements() {
const numElements = 30; // Reduced from 50
// ...
}
function createParticles(x, y, count, particleColor) {
count = Math.floor(count / 2); // Half the number of particles
// ...
}
Arduino Connection Problems
- Cannot connect to Arduino:
- Ensure Arduino is connected via USB
- Verify correct driver installation
- Try using a different USB port
- Restart browser and try again
- Erratic sonar readings:
- Check sensor wiring connections
- Ensure stable power supply
- Add smoothing to the readings:
// In Arduino code, add smoothing:
const int numReadings = 5;
int readings[numReadings];
int readIndex = 0;
int total = 0;
int average = 0;
void setup() {
// Initialize all the readings to 0
for (int i = 0; i < numReadings; i++) {
readings[i] = 0;
}
// ... existing setup code ...
}
void loop() {
// ... existing sonar reading code ...
// Subtract the last reading
total = total - readings[readIndex];
// Read from sensor
readings[readIndex] = distance;
// Add the reading to the total
total = total + readings[readIndex];
// Advance to the next position in the array
readIndex = (readIndex + 1) % numReadings;
// Calculate the average
average = total / numReadings;
// Send the smoothed value
Serial.println(average);
// ... delay code ...
}
- Browser compatibility:
- WebSerial API is only supported in Chromium-based browsers (Chrome, Edge)
- Ensure your browser is up to date