Mobile Best Practices
Optimize your game and BZZE Ads integration for the best mobile experience and maximum revenue.
1. Responsive Design
Ensure your game works perfectly on all mobile screen sizes and orientations.
Viewport Configuration
HTML
<!-- Always include this in your HTML -->
<meta name="viewport"
content="width=device-width,
initial-scale=1.0,
maximum-scale=1.0,
user-scalable=no,
viewport-fit=cover">
CSS Media Queries
CSS
/* Mobile-first approach */
.game-container {
width: 100%;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
/* Adjust for small phones */
@media (max-width: 375px) {
.game-ui {
font-size: 14px;
}
}
/* Adjust for tablets */
@media (min-width: 768px) {
.game-container {
max-width: 768px;
margin: 0 auto;
}
}
/* Handle notches (iPhone X+) */
@supports (padding: max(0px)) {
.game-header {
padding-top: max(10px, env(safe-area-inset-top));
padding-left: max(10px, env(safe-area-inset-left));
padding-right: max(10px, env(safe-area-inset-right));
}
}
Test Both Orientations
JavaScript
// Detect orientation changes
window.addEventListener('orientationchange', function() {
console.log('Orientation:', screen.orientation.type);
// Adjust game layout if needed
resizeGame();
});
// Or force specific orientation in config.xml (Cordova/Capacitor)
// <preference name="orientation" value="portrait" />
2. Ad Integration Best Practices
Initialize Early
JavaScript
// Initialize as soon as DOM is ready
document.addEventListener('DOMContentLoaded', function() {
RewardedAd.init({
appId: "YOUR_APP_ID",
apiKey: "YOUR_API_KEY",
userId: getUserId(), // Persistent user ID
autoPreload: true, // Preload ads automatically
preloadCount: 2, // Keep 2 ads ready
onReward: handleReward,
onNoFill: handleNoFill,
onError: handleError
});
});
// Start preloading even before user clicks button
setTimeout(function() {
console.log('Ads ready:', RewardedAd.isAdAvailable());
}, 3000);
Show Ads at Natural Breaks
JavaScript
// GOOD: Show ads at natural game breaks
function onLevelComplete() {
showLevelCompleteScreen();
// Offer ad reward for bonus
showAdButton("Watch ad for 2x rewards!");
}
function onGameOver() {
showGameOverScreen();
// Offer ad for continue
showAdButton("Watch ad to continue?");
}
// BAD: Don't interrupt gameplay
function onEnemySpawned() {
RewardedAd.showAd(); // ← Never do this!
}
Provide Clear Value Proposition
JavaScript
// Show what user will get
<button onclick="showRewardedAd()">
🎁 Watch Ad for 500 Coins
</button>
// Vague or no benefit
<button onclick="showRewardedAd()">
Watch Video
</button>
Handle Network Issues Gracefully
JavaScript
RewardedAd.init({
appId: "YOUR_APP_ID",
apiKey: "YOUR_API_KEY",
userId: getUserId(),
onNoFill: function() {
// No ads available
showMessage("No ads available right now. Try again later!");
},
onError: function(error) {
// Network or other error
console.error('Ad error:', error);
if (error.code === 'NETWORK_ERROR') {
showMessage("Please check your internet connection");
} else if (error.code === 'RATE_LIMIT_EXCEEDED') {
showMessage("You've watched too many ads. Come back later!");
} else {
showMessage("Something went wrong. Please try again.");
}
}
});
// Check network status before showing ad
function showRewardedAd() {
if (!navigator.onLine) {
showMessage("Please connect to the internet to watch ads");
return;
}
if (!RewardedAd.isAdAvailable()) {
showMessage("Ads are loading... Please wait a moment");
return;
}
RewardedAd.showAd();
}
3. Performance Optimization
Reduce Asset Sizes
Bash
# Compress images
# Use tools like TinyPNG, ImageOptim, or:
npm install -g imagemin-cli
imagemin assets/*.png --out-dir=assets/optimized
# Use WebP for better compression
# Convert images: cwebp input.png -o output.webp
# Minify JavaScript
npm install -g terser
terser game.js -o game.min.js -c -m
# Minify CSS
npm install -g clean-css-cli
cleancss -o style.min.css style.css
Lazy Load Resources
JavaScript
// Load game assets progressively
const gameAssets = {
level1: ['bg1.jpg', 'player.png', 'enemy1.png'],
level2: ['bg2.jpg', 'enemy2.png'],
level3: ['bg3.jpg', 'boss.png']
};
function loadLevel(levelNum) {
const assetsToLoad = gameAssets['level' + levelNum];
assetsToLoad.forEach(asset => {
const img = new Image();
img.src = 'assets/' + asset;
});
}
// Only load level 1 initially
loadLevel(1);
Optimize for Low-End Devices
JavaScript
// Detect device performance
function getDevicePerformance() {
const memory = navigator.deviceMemory || 4; // GB
const cores = navigator.hardwareConcurrency || 2;
if (memory >= 4 && cores >= 4) {
return 'high';
} else if (memory >= 2 && cores >= 2) {
return 'medium';
} else {
return 'low';
}
}
// Adjust game quality
const performance = getDevicePerformance();
if (performance === 'low') {
// Reduce particle effects
particleSystem.maxParticles = 50;
// Lower frame rate
game.setFrameRate(30);
// Disable shadows
renderer.shadows = false;
} else if (performance === 'high') {
particleSystem.maxParticles = 500;
game.setFrameRate(60);
renderer.shadows = true;
}
4. User Experience
Touch-Friendly Buttons
CSS
/* Minimum touch target size: 44x44px (Apple HIG) */
.game-button {
min-width: 44px;
min-height: 44px;
padding: 12px 24px;
font-size: 16px;
border-radius: 8px;
/* Prevent text selection on touch */
-webkit-user-select: none;
user-select: none;
/* Prevent tap highlight */
-webkit-tap-highlight-color: transparent;
}
/* Add visual feedback */
.game-button:active {
transform: scale(0.95);
opacity: 0.8;
}
Prevent Accidental Clicks
JavaScript
// Add confirmation for important actions
function showRewardedAd() {
const confirmed = confirm(
"Watch a 30-second ad to earn 500 coins?"
);
if (confirmed) {
RewardedAd.showAd();
}
}
// Or use custom modal
function showAdConfirmation() {
showModal({
title: "Watch Ad?",
message: "Watch a short video to earn 500 coins",
buttons: [
{ text: "Cancel", style: "secondary" },
{ text: "Watch Ad", style: "primary", onClick: showAd }
]
});
}
Loading States
JavaScript
const adButton = document.getElementById('ad-button');
// Show loading state
adButton.addEventListener('click', function() {
adButton.disabled = true;
adButton.textContent = "Loading ad...";
RewardedAd.showAd();
});
// Reset button after ad closes
RewardedAd.init({
// ... other config
onClose: function() {
adButton.disabled = false;
adButton.textContent = "Watch Ad for Reward";
}
});
5. Data & Storage Management
Persist User ID
JavaScript
// Generate persistent user ID
function getUserId() {
let userId = localStorage.getItem('bzze_user_id');
if (!userId) {
userId = 'user_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
localStorage.setItem('bzze_user_id', userId);
}
return userId;
}
// Use in SDK init
RewardedAd.init({
appId: "YOUR_APP_ID",
apiKey: "YOUR_API_KEY",
userId: getUserId() // ← Consistent across sessions
});
Handle Storage Quotas
JavaScript
// Check storage availability
if ('storage' in navigator && 'estimate' in navigator.storage) {
navigator.storage.estimate().then(estimate => {
const percentUsed = (estimate.usage / estimate.quota) * 100;
console.log(`Storage used: ${percentUsed.toFixed(2)}%`);
if (percentUsed > 80) {
console.warn('Storage nearly full!');
// Clean up old game saves or cached data
cleanupOldData();
}
});
}
// Clear old data periodically
function cleanupOldData() {
const keysToClean = [];
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
// Keep BZZE Ads data
if (key.startsWith('bzze_')) continue;
// Remove old game saves (example)
if (key.startsWith('save_')) {
const saveData = JSON.parse(localStorage.getItem(key));
const age = Date.now() - saveData.timestamp;
// Remove saves older than 30 days
if (age > 30 * 24 * 60 * 60 * 1000) {
keysToClean.push(key);
}
}
}
keysToClean.forEach(key => localStorage.removeItem(key));
}
6. Testing & Debugging
Enable Debug Mode
JavaScript
// Development build
const isDevelopment = window.location.hostname === 'localhost' ||
window.location.hostname === '127.0.0.1';
RewardedAd.init({
appId: "YOUR_APP_ID",
apiKey: "YOUR_API_KEY",
userId: getUserId(),
debug: isDevelopment // ← Auto-enable debug mode in dev
});
// Or manually enable for testing
if (isDevelopment) {
RewardedAd.enableDebug();
console.log('Debug mode enabled');
}
Test on Multiple Devices
Device Testing Checklist:
- Small phone (iPhone SE, older Android)
- Large phone (iPhone 14 Pro Max, Galaxy S23 Ultra)
- Tablet (iPad, Android tablet)
- 🔄 Portrait orientation
- 🔄 Landscape orientation
- 📶 Slow 3G connection (throttle in DevTools)
- ✈️ Offline mode (airplane mode)
- 🔋 Low battery mode (iOS)
- 🌐 Different OS versions (iOS 14+, Android 8+)
Monitor Performance
JavaScript
// Track ad performance
let adMetrics = {
shown: 0,
completed: 0,
revenue: 0
};
RewardedAd.init({
appId: "YOUR_APP_ID",
apiKey: "YOUR_API_KEY",
userId: getUserId(),
onAdShown: function() {
adMetrics.shown++;
console.log('Total ads shown:', adMetrics.shown);
},
onReward: function(reward) {
adMetrics.completed++;
adMetrics.revenue += 0.05; // Estimate
console.log('Completion rate:',
(adMetrics.completed / adMetrics.shown * 100).toFixed(1) + '%');
}
});
// Log metrics every 5 minutes
setInterval(function() {
console.log('Ad Metrics:', adMetrics);
}, 5 * 60 * 1000);
7. Privacy & Compliance
Implement GDPR Consent (EU)
JavaScript
// Show consent dialog for EU users
function isEUUser() {
const euCountries = ['AT','BE','BG','HR','CY','CZ','DK','EE','FI','FR','DE','GR','HU','IE','IT','LV','LT','LU','MT','NL','PL','PT','RO','SK','SI','ES','SE'];
const userCountry = getUserCountry(); // Detect from IP or user input
return euCountries.includes(userCountry);
}
if (isEUUser() && !hasGivenConsent()) {
showGDPRDialog();
}
function showGDPRDialog() {
const consent = confirm(
"We use ads to support this free game. " +
"Do you consent to personalized ads? " +
"(You can still play with non-personalized ads)"
);
localStorage.setItem('gdpr_consent', consent ? 'yes' : 'no');
// Initialize SDK with consent flag
RewardedAd.init({
appId: "YOUR_APP_ID",
apiKey: "YOUR_API_KEY",
userId: getUserId(),
gdprApplies: true,
gdprConsent: consent
});
}
Implement COPPA Compliance (Kids Apps)
JavaScript
// For apps targeting children under 13 (US)
RewardedAd.init({
appId: "YOUR_APP_ID",
apiKey: "YOUR_API_KEY",
userId: getUserId(),
coppaCompliant: true, // ← Enable COPPA mode
isChildDirected: true // ← Flag as child-directed
});
// This ensures:
// - No personalized ads
// - No user tracking
// - COPPA-compliant ad networks only
See full compliance guides: GDPR | COPPA
8. App Store Guidelines
iOS App Store
- Clearly disclose that your app contains ads
- Set appropriate age rating based on ad content
- Don't show ads that don't match your app's rating
- Provide a way to restore purchases (if using IAP to remove ads)
- Don't mislead users about ad frequency
- Don't show ads that cover UI elements unexpectedly
Google Play Store
- Comply with Google Play's ads policy
- Disclose ads in your app description
- Implement "Designed for Families" requirements if targeting kids
- Don't show ads on lockscreen or notification bar
- Don't use deceptive ad placements
- Don't incentivize ad clicks (click fraud)
Quick Checklist
Before Launch:
- Test on at least 3 different devices
- Test both portrait and landscape
- Test with slow/no internet connection
- Verify ads show correctly
- Verify rewards are granted
- Check analytics in Publisher Dashboard
- Implement GDPR consent (if targeting EU)
- Implement COPPA compliance (if targeting kids)
- Add "Contains Ads" to store listing
- Set appropriate age rating
- Test with debug mode disabled
- Compress all assets
- Remove console.log statements
- Test payment flow (if selling ad removal)