TCJSGame Camera Class: Complete Reference Guide
Introduction to the Camera Class
TCJSGame's Camera Class provides powerful viewport control for creating scrolling worlds, following players, and managing large game environments. The camera system allows you to create games that extend beyond the visible screen area.
🎥 Basic Camera Setup
Camera Initialization
// Camera is automatically created with Display
const camera = display.camera;
// Access camera properties
console.log("Camera position:", camera.x, camera.y);
console.log("World size:", camera.worldWidth, camera.worldHeight);
// Manual camera creation (if needed)
const customCamera = new Camera(0, 0, 2000, 1500);
display.camera = customCamera;
Constructor Parameters
class Camera {
constructor(x = 0, y = 0, worldWidth = 1000, worldHeight = 1000) {
this.x = x; // Current X position
this.y = y; // Current Y position
this.target = null; // Component to follow
this.speed = 5; // Movement speed
this.worldWidth = worldWidth; // Total world width
this.worldHeight = worldHeight; // Total world height
}
}
🎯 Camera Follow Systems
Basic Object Following
// Follow a component directly (no smoothing)
camera.follow(player, false);
// Follow with smooth movement (lerping)
camera.follow(player, true);
// Example: Different follow styles for different game types
function setupPlatformerCamera() {
// Platformer: Smooth follow on X, direct follow on Y
camera.follow(player, false);
}
function setupTopDownCamera() {
// Top-down: Smooth follow in both directions
camera.follow(player, true);
}
function setupStrategyCamera() {
// Strategy: No automatic follow, manual control
camera.target = null;
}
Advanced Follow Configurations
// Custom follow function with offset
function customCameraFollow() {
if (camera.target) {
// Center on target with offset
const targetX = camera.target.x - display.canvas.width / 2 + 100;
const targetY = camera.target.y - display.canvas.height / 2 - 50;
if (camera.smooth) {
// Smooth interpolation
camera.x += (targetX - camera.x) * 0.1;
camera.y += (targetY - camera.y) * 0.1;
} else {
// Direct positioning
camera.x = targetX;
camera.y = targetY;
}
// Apply world bounds
clampCameraToWorld();
}
}
function clampCameraToWorld() {
camera.x = Math.max(0, Math.min(camera.x, camera.worldWidth - display.canvas.width));
camera.y = Math.max(0, Math.min(camera.y, camera.worldHeight - display.canvas.height));
}
🌍 World Boundaries and Constraints
Setting World Size
// Define your game world boundaries
camera.worldWidth = 3000; // 3000 pixels wide
camera.worldHeight = 2000; // 2000 pixels tall
// Example: Progressive world expansion
function expandWorld(newWidth, newHeight) {
camera.worldWidth = newWidth;
camera.worldHeight = newHeight;
console.log(`World expanded to ${newWidth}x${newHeight}`);
}
// Dynamic world sizing based on level
function setupLevelCamera(level) {
const levelSizes = {
1: { width: 800, height: 600 },
2: { width: 1200, height: 800 },
3: { width: 2000, height: 1200 }
};
const size = levelSizes[level] || levelSizes[1];
camera.worldWidth = size.width;
camera.worldHeight = size.height;
}
Boundary Enforcement
// Manual boundary checking
function enforceCameraBounds() {
const maxX = camera.worldWidth - display.canvas.width;
const maxY = camera.worldHeight - display.canvas.height;
camera.x = Math.max(0, Math.min(camera.x, maxX));
camera.y = Math.max(0, Math.min(camera.y, maxY));
}
// Example: Update in game loop
function update() {
// Your game logic...
enforceCameraBounds();
}
🚀 Camera Movement and Control
Manual Camera Positioning
// Direct camera control
camera.x = 500; // Move camera to X position 500
camera.y = 300; // Move camera to Y position 300
// Relative movement
camera.x += 10; // Pan right
camera.y -= 5; // Pan up
// Example: Camera shake effect
function cameraShake(intensity = 10, duration = 500) {
const originalX = camera.x;
const originalY = camera.y;
let elapsed = 0;
const shakeInterval = setInterval(() => {
elapsed += 50;
camera.x = originalX + (Math.random() - 0.5) * intensity;
camera.y = originalY + (Math.random() - 0.5) * intensity;
if (elapsed >= duration) {
clearInterval(shakeInterval);
camera.x = originalX;
camera.y = originalY;
}
}, 50);
}
// Trigger camera shake on events
function playerHit() {
cameraShake(15, 300);
}
Smooth Camera Transitions
// Smooth movement to target position
function smoothCameraTo(x, y, duration = 1000) {
const startX = camera.x;
const startY = camera.y;
const startTime = Date.now();
function animate() {
const currentTime = Date.now();
const elapsed = currentTime - startTime;
const progress = Math.min(elapsed / duration, 1);
// Easing function for smooth movement
const ease = progress < 0.5 ? 2 * progress * progress : -1 + (4 - 2 * progress) * progress;
camera.x = startX + (x - startX) * ease;
camera.y = startY + (y - startY) * ease;
if (progress < 1) {
requestAnimationFrame(animate);
}
}
animate();
}
// Example: Transition between areas
function moveToBossArena() {
smoothCameraTo(1500, 800, 2000);
}
🎮 Practical Camera Systems
Platformer Camera Behavior
class PlatformerCamera {
constructor() {
this.camera = display.camera;
this.leadDistance = 100; // How far ahead to look
this.deadZone = 50; // Area where camera doesn't move
}
update(player) {
const targetX = player.x - display.canvas.width / 2;
const targetY = player.y - display.canvas.height / 2;
// Horizontal leading (look ahead in movement direction)
if (player.speedX > 0) {
targetX += this.leadDistance;
} else if (player.speedX < 0) {
targetX -= this.leadDistance;
}
// Vertical adjustment (only follow when falling or jumping high)
if (player.y < this.camera.y + this.deadZone) {
targetY = player.y - this.deadZone;
} else if (player.y > this.camera.y + display.canvas.height - this.deadZone - player.height) {
targetY = player.y - display.canvas.height + this.deadZone + player.height;
}
// Smooth camera movement
this.camera.x += (targetX - this.camera.x) * 0.1;
this.camera.y += (targetY - this.camera.y) * 0.08;
// Enforce bounds
this.enforceBounds();
}
enforceBounds() {
this.camera.x = Math.max(0, Math.min(this.camera.x, this.camera.worldWidth - display.canvas.width));
this.camera.y = Math.max(0, Math.min(this.camera.y, this.camera.worldHeight - display.canvas.height));
}
}
// Usage
const platformerCam = new PlatformerCamera();
function update() {
platformerCam.update(player);
}
Top-Down Adventure Camera
class TopDownCamera {
constructor() {
this.camera = display.camera;
this.smoothness = 0.1;
this.zoom = 1.0;
}
update(player) {
// Center camera on player
const targetX = player.x - display.canvas.width / 2;
const targetY = player.y - display.canvas.height / 2;
// Smooth interpolation
this.camera.x += (targetX - this.camera.x) * this.smoothness;
this.camera.y += (targetY - this.camera.y) * this.smoothness;
this.enforceBounds();
}
setZoom(newZoom, duration = 1000) {
// Smooth zoom transition
const startZoom = this.zoom;
const startTime = Date.now();
function animateZoom() {
const progress = (Date.now() - startTime) / duration;
const currentZoom = startZoom + (newZoom - startZoom) * Math.min(progress, 1);
// Apply zoom by scaling canvas (simplified)
display.canvas.style.transform = `scale(${currentZoom})`;
this.zoom = currentZoom;
if (progress < 1) {
requestAnimationFrame(animateZoom);
}
}
animateZoom();
}
enforceBounds() {
const maxX = this.camera.worldWidth - display.canvas.width;
const maxY = this.camera.worldHeight - display.canvas.height;
this.camera.x = Math.max(0, Math.min(this.camera.x, maxX));
this.camera.y = Math.max(0, Math.min(this.camera.y, maxY));
}
}
🔧 Advanced Camera Techniques
Multiple Camera Targets
class MultiTargetCamera {
constructor() {
this.camera = display.camera;
this.targets = [];
this.mode = 'average'; // 'average', 'focus', 'dynamic'
}
addTarget(target) {
this.targets.push(target);
}
removeTarget(target) {
const index = this.targets.indexOf(target);
if (index > -1) {
this.targets.splice(index, 1);
}
}
update() {
if (this.targets.length === 0) return;
let targetX, targetY;
switch(this.mode) {
case 'average':
// Average position of all targets
const avgX = this.targets.reduce((sum, t) => sum + t.x, 0) / this.targets.length;
const avgY = this.targets.reduce((sum, t) => sum + t.y, 0) / this.targets.length;
targetX = avgX - display.canvas.width / 2;
targetY = avgY - display.canvas.height / 2;
break;
case 'focus':
// Focus on primary target
const primary = this.targets[0];
targetX = primary.x - display.canvas.width / 2;
targetY = primary.y - display.canvas.height / 2;
break;
case 'dynamic':
// Dynamic zoom to fit all targets
const bounds = this.calculateTargetBounds();
targetX = bounds.centerX - display.canvas.width / 2;
targetY = bounds.centerY - display.canvas.height / 2;
this.adjustZoom(bounds);
break;
}
// Smooth movement
this.camera.x += (targetX - this.camera.x) * 0.05;
this.camera.y += (targetY - this.camera.y) * 0.05;
this.enforceBounds();
}
calculateTargetBounds() {
let minX = Infinity, maxX = -Infinity;
let minY = Infinity, maxY = -Infinity;
this.targets.forEach(target => {
minX = Math.min(minX, target.x);
maxX = Math.max(maxX, target.x);
minY = Math.min(minY, target.y);
maxY = Math.max(maxY, target.y);
});
return {
minX, maxX, minY, maxY,
width: maxX - minX,
height: maxY - minY,
centerX: (minX + maxX) / 2,
centerY: (minY + maxY) / 2
};
}
}
Camera Effects and Transitions
class CameraEffects {
constructor() {
this.camera = display.camera;
this.effects = new Map();
}
// Screen shake effect
shake(intensity = 10, duration = 500) {
const effectId = 'shake_' + Date.now();
const originalX = this.camera.x;
const originalY = this.camera.y;
let elapsed = 0;
const effect = setInterval(() => {
elapsed += 50;
this.camera.x = originalX + (Math.random() - 0.5) * intensity;
this.camera.y = originalY + (Math.random() - 0.5) * intensity;
if (elapsed >= duration) {
clearInterval(effect);
this.camera.x = originalX;
this.camera.y = originalY;
this.effects.delete(effectId);
}
}, 50);
this.effects.set(effectId, effect);
}
// Zoom effect
zoomTo(scale, duration = 1000) {
const effectId = 'zoom_' + Date.now();
const startScale = 1.0;
const startTime = Date.now();
function animateZoom() {
const progress = (Date.now() - startTime) / duration;
const currentScale = startScale + (scale - startScale) * Math.min(progress, 1);
// Apply zoom (you'd need to implement actual zoom functionality)
console.log("Zoom scale:", currentScale);
if (progress < 1) {
requestAnimationFrame(animateZoom);
} else {
this.effects.delete(effectId);
}
}
animateZoom();
}
// Fade to color effect
fadeToColor(color, duration = 1000) {
const overlay = new Component(display.canvas.width, display.canvas.height, color, 0, 0, "rect");
overlay.alpha = 0;
display.add(overlay);
let alpha = 0;
const fadeInterval = setInterval(() => {
alpha += 0.02;
overlay.alpha = Math.min(alpha, 1);
if (alpha >= 1) {
clearInterval(fadeInterval);
setTimeout(() => {
display.add(overlay, 1); // Remove from current scene
}, 500);
}
}, duration / 50);
}
}
🎯 Camera Utility Functions
Screen-to-World Coordinate Conversion
// Convert screen coordinates to world coordinates
function screenToWorld(screenX, screenY) {
return {
worldX: screenX + display.camera.x,
worldY: screenY + display.camera.y
};
}
// Convert world coordinates to screen coordinates
function worldToScreen(worldX, worldY) {
return {
screenX: worldX - display.camera.x,
screenY: worldY - display.camera.y
};
}
// Example: Click on world objects
function handleClick(screenX, screenY) {
const worldPos = screenToWorld(screenX, screenY);
// Check if click hit any objects in world space
gameObjects.forEach(obj => {
if (worldPos.worldX >= obj.x && worldPos.worldX <= obj.x + obj.width &&
worldPos.worldY >= obj.y && worldPos.worldY <= obj.y + obj.height) {
obj.onClick();
}
});
}
Viewport Culling for Performance
// Only render objects within camera viewport
function isInViewport(component) {
return component.x + component.width >= display.camera.x &&
component.x <= display.camera.x + display.canvas.width &&
component.y + component.height >= display.camera.y &&
component.y <= display.camera.y + display.canvas.height;
}
// Optimized rendering system
class OptimizedRenderer {
constructor() {
this.visibleObjects = [];
}
updateVisibleObjects(allObjects) {
this.visibleObjects = allObjects.filter(obj => isInViewport(obj));
}
render() {
this.visibleObjects.forEach(obj => {
obj.update(display.context);
});
}
}
// Usage
const renderer = new OptimizedRenderer();
function update() {
renderer.updateVisibleObjects(allGameObjects);
// Only visible objects will be rendered
}
🎮 Complete Game Integration
Platformer Game with Advanced Camera
class PlatformerGame {
constructor() {
this.display = new Display();
this.display.start(800, 600);
this.player = new Component(30, 30, "blue", 100, 100, "rect");
this.player.physics = true;
this.player.gravity = 0.5;
this.display.add(this.player);
this.camera = this.display.camera;
this.camera.worldWidth = 2000;
this.camera.worldHeight = 1200;
this.platforms = this.createPlatforms();
this.setupCameraSystem();
}
setupCameraSystem() {
// Custom camera behavior for platformer
this.camera.follow(this.player, true);
// Camera constraints for platformer
this.cameraConstraints = {
minY: 0,
maxY: this.camera.worldHeight - this.display.canvas.height
};
}
update() {
// Player movement
if (this.display.keys[37]) this.player.speedX = -5;
if (this.display.keys[39]) this.player.speedX = 5;
if (this.display.keys[38] && this.player.gravitySpeed === 0) {
this.player.speedY = -12;
}
// Custom camera update
this.updateCamera();
}
updateCamera() {
// Platformer-specific camera logic
const targetX = this.player.x - this.display.canvas.width / 2;
const targetY = this.player.y - this.display.canvas.height / 2;
// Only follow player vertically when falling or jumping high
const verticalThreshold = 100;
if (this.player.y < this.camera.y + verticalThreshold ||
this.player.y > this.camera.y + this.display.canvas.height - verticalThreshold) {
this.camera.y += (targetY - this.camera.y) * 0.05;
}
// Always follow horizontally
this.camera.x += (targetX - this.camera.x) * 0.1;
// Enforce camera bounds
this.enforceCameraBounds();
}
enforceCameraBounds() {
this.camera.x = Math.max(0, Math.min(this.camera.x,
this.camera.worldWidth - this.display.canvas.width));
this.camera.y = Math.max(this.cameraConstraints.minY,
Math.min(this.camera.y, this.cameraConstraints.maxY));
}
}
⚡ Performance Optimization
Camera Update Optimization
class OptimizedCamera {
constructor() {
this.camera = display.camera;
this.lastUpdate = 0;
this.updateInterval = 2; // Update every 2 frames
}
update() {
// Skip update if not enough frames have passed
if (display.frameNo - this.lastUpdate < this.updateInterval) {
return;
}
this.lastUpdate = display.frameNo;
// Perform camera calculations
if (this.camera.target) {
const targetX = this.camera.target.x - display.canvas.width / 2;
const targetY = this.camera.target.y - display.canvas.height / 2;
this.camera.x += (targetX - this.camera.x) * 0.1;
this.camera.y += (targetY - this.camera.y) * 0.1;
this.enforceBounds();
}
}
enforceBounds() {
this.camera.x = Math.max(0, Math.min(this.camera.x,
this.camera.worldWidth - display.canvas.width));
this.camera.y = Math.max(0, Math.min(this.camera.y,
this.camera.worldHeight - display.canvas.height));
}
}
📚 Conclusion
TCJSGame's Camera Class provides:
- Smooth object following with lerping support
- World boundary management for large game environments
- Flexible camera control for various game types
- Performance optimization through viewport culling
- Advanced effects like shaking and transitions
Key camera patterns for different game genres:
- Platformers: Horizontal following with vertical constraints
- Top-down games: Centered following with smooth movement
- Strategy games: Manual camera control with edge scrolling
- Multiplayer games: Dynamic zoom to fit all players
The camera system works seamlessly with TCJSGame's rendering pipeline, automatically applying transformations to create immersive scrolling worlds. Combine camera control with TCJSGame's other systems to build professional-quality 2D games with engaging camera behaviors!
Top comments (0)