Feature/Touch support (#4)
All checks were successful
Build and Publish Docker Image / Build and Validate (push) Successful in 8s
Build and Publish Docker Image / Publish to Registry (push) Successful in 6s

# Add Touch Support and Documentation

## 📋 Summary

This PR adds comprehensive touch support for mobile devices and tablets, enabling players to control the game using touch gestures. Additionally, a complete README.md file has been added to document the game features, controls, and gameplay mechanics.

## 🎯 Changes Made

###  New Features
- **Touch Controls**: Full touch support for smartphones and tablets
  - Touch and drag anywhere on the screen to move the player
  - Movement direction calculated relative to screen center
  - Smooth, responsive touch input handling
  - Multi-touch support with proper touch ID tracking

- **Documentation**: Comprehensive README.md
  - Game overview and features
  - Detailed controls (keyboard and touch)
  - Game mechanics explanation
  - Technical details and architecture
  - Getting started guide
  - Tips for high scores

### 🔧 Technical Implementation

#### Touch Event Handling
- Added touch event listeners (`touchstart`, `touchmove`, `touchend`, `touchcancel`)
- Implemented touch position tracking relative to screen center
- Converted touch input to WASD key states for seamless integration
- Proper touch ID tracking to handle multiple simultaneous touches
- Touch movement threshold (10px) to prevent accidental movements

#### Code Structure
- Added touch-related properties to `Game` class:
  - `touchActive`, `touchStartX/Y`, `touchCurrentX/Y`, `touchId`
- New methods:
  - `setupTouchControls()`: Initializes touch event listeners
  - `handleTouchStart()`: Handles touch initiation
  - `handleTouchMove()`: Updates touch position during drag
  - `handleTouchEnd()`: Resets touch state
  - `updateTouchMovement()`: Converts touch input to movement keys

#### UI Updates
- Updated instructions to mention touch controls
- Responsive design maintained for mobile devices

## 📊 Statistics

- **Files Changed**: 2
  - `index.html`: +123 lines, -1 line
  - `README.md`: +171 lines (new file)
- **Total Changes**: +293 insertions, -1 deletion
- **Commits**: 2

## 🧪 Testing

### Desktop Testing
-  Keyboard controls (WASD and Arrow Keys) still work correctly
-  No regression in existing functionality
-  Game performance unchanged

### Mobile Testing
-  Touch controls work on smartphones
-  Touch controls work on tablets
-  Movement is smooth and responsive
-  Touch input properly resets on touch end
-  Multiple touches handled correctly (only first touch tracked)

## 📱 Mobile Compatibility

The game now fully supports:
- iOS devices (iPhone, iPad)
- Android devices (phones and tablets)
- Other touch-enabled devices

Touch controls use the center of the screen as a reference point, making it intuitive for users to drag in any direction to move.

## 🎮 User Experience Improvements

1. **Accessibility**: Game is now playable on mobile devices without keyboard
2. **Intuitive Controls**: Touch and drag is natural for mobile users
3. **Documentation**: README provides clear instructions for all users
4. **No Visual Clutter**: Touch controls work without on-screen joystick

## 🔍 Code Quality

- Clean, maintainable code structure
- Proper event handling with `preventDefault()` to avoid browser defaults
- Touch events use `{ passive: false }` for proper scroll prevention
- Consistent with existing code style and patterns
- No breaking changes to existing functionality

## 📝 Notes

- Touch controls integrate seamlessly with existing keyboard controls
- Both input methods can work simultaneously (though not recommended)
- Touch movement uses the same movement system as keyboard input
- README includes comprehensive documentation for future developers

##  Checklist

- [x] Touch controls implemented and tested
- [x] Mobile compatibility verified
- [x] README.md created with comprehensive documentation
- [x] No regression in desktop functionality
- [x] Code follows existing patterns and style
- [x] Instructions updated to mention touch controls

---

**Ready for Review** 🚀

Reviewed-on: #4
Co-authored-by: Juan Sebastian Montoya <juansmm@outlook.com>
Co-committed-by: Juan Sebastian Montoya <juansmm@outlook.com>
This commit is contained in:
Juan Sebastián Montoya 2025-11-26 01:31:15 -05:00 committed by Juan Sebastián Montoya
parent 4bf8897370
commit dfe87d63cc
2 changed files with 293 additions and 1 deletions

View file

@ -72,6 +72,11 @@
text-align: center;
text-shadow: 2px 2px 4px rgba(0,0,0,0.5);
}
@media (max-width: 768px) {
#instructions {
font-size: 14px;
}
}
</style>
</head>
<body>
@ -87,7 +92,7 @@
</div>
<div id="instructions">
<p><strong>Controls:</strong> WASD or Arrow Keys to move | Collect yellow coins | Avoid red obstacles!</p>
<p><strong>Controls:</strong> WASD or Arrow Keys to move | Touch and drag to move (mobile) | Collect yellow coins | Avoid red obstacles!</p>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
@ -319,6 +324,12 @@
this.keys = {};
this.coins = [];
this.obstacles = [];
this.touchActive = false;
this.touchStartX = 0;
this.touchStartY = 0;
this.touchCurrentX = 0;
this.touchCurrentY = 0;
this.touchId = null;
this.init();
this.setupEventListeners();
@ -427,6 +438,111 @@
});
window.addEventListener('resize', () => this.onWindowResize());
document.getElementById('restartBtn').addEventListener('click', () => this.restart());
// Touch event listeners
this.setupTouchControls();
}
setupTouchControls() {
const canvas = this.renderer.domElement;
// Prevent default touch behaviors
canvas.addEventListener('touchstart', (e) => {
e.preventDefault();
this.handleTouchStart(e);
}, { passive: false });
canvas.addEventListener('touchmove', (e) => {
e.preventDefault();
this.handleTouchMove(e);
}, { passive: false });
canvas.addEventListener('touchend', (e) => {
e.preventDefault();
this.handleTouchEnd(e);
}, { passive: false });
canvas.addEventListener('touchcancel', (e) => {
e.preventDefault();
this.handleTouchEnd(e);
}, { passive: false });
}
handleTouchStart(e) {
if (this.touchActive) return;
const touch = e.touches[0];
const rect = this.renderer.domElement.getBoundingClientRect();
// Use center of screen as reference point
this.touchStartX = rect.left + rect.width / 2;
this.touchStartY = rect.top + rect.height / 2;
this.touchId = touch.identifier;
this.touchCurrentX = touch.clientX;
this.touchCurrentY = touch.clientY;
this.touchActive = true;
}
handleTouchMove(e) {
if (!this.touchActive) return;
const touch = Array.from(e.touches).find(t => t.identifier === this.touchId);
if (!touch) return;
this.touchCurrentX = touch.clientX;
this.touchCurrentY = touch.clientY;
this.updateTouchMovement();
}
handleTouchEnd(e) {
if (!this.touchActive) return;
// Check if the touch that ended is our tracked touch
const touch = Array.from(e.changedTouches).find(t => t.identifier === this.touchId);
if (!touch) return;
this.touchActive = false;
this.touchId = null;
this.touchCurrentX = this.touchStartX;
this.touchCurrentY = this.touchStartY;
this.updateTouchMovement();
}
updateTouchMovement() {
if (!this.touchActive) {
// Reset all movement keys
this.keys['w'] = false;
this.keys['s'] = false;
this.keys['a'] = false;
this.keys['d'] = false;
return;
}
const deltaX = this.touchCurrentX - this.touchStartX;
const deltaY = this.touchCurrentY - this.touchStartY;
const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
// Normalize movement direction
const threshold = 10; // Minimum movement to register
if (distance < threshold) {
this.keys['w'] = false;
this.keys['s'] = false;
this.keys['a'] = false;
this.keys['d'] = false;
return;
}
// Calculate direction
const normalizedX = deltaX / distance;
const normalizedY = deltaY / distance;
// Update movement keys based on direction
this.keys['w'] = normalizedY < -0.3;
this.keys['s'] = normalizedY > 0.3;
this.keys['a'] = normalizedX < -0.3;
this.keys['d'] = normalizedX > 0.3;
}
updatePlayer() {
@ -516,6 +632,11 @@
animate() {
requestAnimationFrame(() => this.animate());
// Update touch movement if active
if (this.touchActive) {
this.updateTouchMovement();
}
this.updatePlayer();
this.updateCoins();
this.updateObstacles();