Feature/Touch support (#4)
# 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:
parent
4bf8897370
commit
dfe87d63cc
2 changed files with 293 additions and 1 deletions
123
index.html
123
index.html
|
|
@ -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();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue