वेब गेम्स में पिक्सेल आर्ट रेंडरिंग
पिक्सेल आर्ट सही तरीके से रेंडर होने पर शानदार दिखती है, और धुंधली होने पर बेहद खराब। यह ट्यूटोरियल क्रिस्प, ऑथेंटिक पिक्सेल ग्राफिक्स की तकनीकें बताता है।
1) धुंधलेपन की समस्या
डिफ़ॉल्ट रूप से, ब्राउज़र स्केल करते समय इमेज को स्मूद कर देते हैं। पिक्सेल आर्ट के लिए यह गलत है:
css
/* CSS solution */
canvas {
image-rendering: pixelated;
image-rendering: crisp-edges; /* Firefox fallback */
}2) Canvas context सेटिंग्स
js
const canvas = document.getElementById('game')
const ctx = canvas.getContext('2d')
// Disable smoothing for crisp pixels
ctx.imageSmoothingEnabled = falseइसे हर canvas resize या context बदलाव के बाद सेट करना ज़रूरी है।
3) इंटीजर स्केलिंग
पिक्सेल आर्ट इंटीजर स्केल (1x, 2x, 3x, 4x) पर सबसे अच्छी दिखती है:
js
const GAME_WIDTH = 160
const GAME_HEIGHT = 144 // Game Boy resolution
function resize() {
const maxScale = Math.floor(Math.min(
window.innerWidth / GAME_WIDTH,
window.innerHeight / GAME_HEIGHT
))
const scale = Math.max(1, maxScale)
canvas.width = GAME_WIDTH
canvas.height = GAME_HEIGHT
canvas.style.width = GAME_WIDTH * scale + 'px'
canvas.style.height = GAME_HEIGHT * scale + 'px'
ctx.imageSmoothingEnabled = false
}4) स्प्राइट को इंटीजर पोज़िशन पर ड्रॉ करना
सब-पिक्सेल पोज़िशन धुंधलापन पैदा करती हैं:
js
// Bad: causes blur
ctx.drawImage(sprite, 10.5, 20.3)
// Good: crisp pixels
ctx.drawImage(sprite, Math.floor(10.5), Math.floor(20.3))
// Or round to nearest
ctx.drawImage(sprite, Math.round(player.x), Math.round(player.y))5) स्प्राइट शीट एनिमेशन
js
class PixelSprite {
constructor(image, frameWidth, frameHeight) {
this.image = image
this.frameWidth = frameWidth
this.frameHeight = frameHeight
this.animations = {}
this.currentAnim = null
this.frame = 0
this.timer = 0
}
addAnimation(name, frames, fps = 8) {
this.animations[name] = { frames, duration: 1 / fps }
}
play(name) {
if (this.currentAnim !== name) {
this.currentAnim = name
this.frame = 0
this.timer = 0
}
}
update(dt) {
if (!this.currentAnim) return
const anim = this.animations[this.currentAnim]
this.timer += dt
if (this.timer >= anim.duration) {
this.timer = 0
this.frame = (this.frame + 1) % anim.frames.length
}
}
draw(ctx, x, y, flipX = false) {
if (!this.currentAnim) return
const anim = this.animations[this.currentAnim]
const frameIndex = anim.frames[this.frame]
const sx = (frameIndex % 16) * this.frameWidth
const sy = Math.floor(frameIndex / 16) * this.frameHeight
ctx.save()
if (flipX) {
ctx.scale(-1, 1)
x = -x - this.frameWidth
}
ctx.drawImage(
this.image,
sx, sy, this.frameWidth, this.frameHeight,
Math.round(x), Math.round(y), this.frameWidth, this.frameHeight
)
ctx.restore()
}
}
// Usage
const player = new PixelSprite(spriteSheet, 16, 16)
player.addAnimation('idle', [0, 1], 2)
player.addAnimation('walk', [2, 3, 4, 5], 8)
player.addAnimation('jump', [6], 1)6) पैलेट स्वैपिंग
स्प्राइट को दोबारा रंगने की क्लासिक तकनीक:
js
function createPaletteSwap(image, oldColors, newColors) {
const canvas = document.createElement('canvas')
canvas.width = image.width
canvas.height = image.height
const ctx = canvas.getContext('2d')
ctx.drawImage(image, 0, 0)
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
const data = imageData.data
for (let i = 0; i < data.length; i += 4) {
const r = data[i], g = data[i + 1], b = data[i + 2]
for (let j = 0; j < oldColors.length; j++) {
const old = oldColors[j]
if (r === old.r && g === old.g && b === old.b) {
const newC = newColors[j]
data[i] = newC.r
data[i + 1] = newC.g
data[i + 2] = newC.b
break
}
}
}
ctx.putImageData(imageData, 0, 0)
return canvas
}
// Create enemy variant
const blueEnemy = createPaletteSwap(
enemySprite,
[{ r: 255, g: 0, b: 0 }], // Red
[{ r: 0, g: 100, b: 255 }] // Blue
)7) टाइलमैप रेंडरिंग
js
class Tilemap {
constructor(tileSize, tilesetImage) {
this.tileSize = tileSize
this.tileset = tilesetImage
this.tilesPerRow = Math.floor(tilesetImage.width / tileSize)
this.data = []
}
setData(width, height, tiles) {
this.width = width
this.height = height
this.data = tiles
}
draw(ctx, cameraX = 0, cameraY = 0) {
const startCol = Math.floor(cameraX / this.tileSize)
const startRow = Math.floor(cameraY / this.tileSize)
const endCol = startCol + Math.ceil(ctx.canvas.width / this.tileSize) + 1
const endRow = startRow + Math.ceil(ctx.canvas.height / this.tileSize) + 1
for (let row = startRow; row < endRow; row++) {
for (let col = startCol; col < endCol; col++) {
if (row < 0 || row >= this.height || col < 0 || col >= this.width) continue
const tileIndex = this.data[row * this.width + col]
if (tileIndex < 0) continue
const sx = (tileIndex % this.tilesPerRow) * this.tileSize
const sy = Math.floor(tileIndex / this.tilesPerRow) * this.tileSize
const dx = Math.round(col * this.tileSize - cameraX)
const dy = Math.round(row * this.tileSize - cameraY)
ctx.drawImage(
this.tileset,
sx, sy, this.tileSize, this.tileSize,
dx, dy, this.tileSize, this.tileSize
)
}
}
}
}8) आउटलाइन इफेक्ट
बेहतर दिखने के लिए स्प्राइट में आउटलाइन जोड़ें:
js
function drawWithOutline(ctx, image, x, y, outlineColor = '#000') {
const offsets = [[-1, 0], [1, 0], [0, -1], [0, 1]]
// Draw outline
ctx.globalCompositeOperation = 'source-over'
for (const [ox, oy] of offsets) {
ctx.drawImage(image, Math.round(x + ox), Math.round(y + oy))
}
// Apply color to outline
ctx.globalCompositeOperation = 'source-in'
ctx.fillStyle = outlineColor
ctx.fillRect(x - 1, y - 1, image.width + 2, image.height + 2)
// Draw sprite on top
ctx.globalCompositeOperation = 'source-over'
ctx.drawImage(image, Math.round(x), Math.round(y))
}9) स्क्रीन शेक
क्लासिक गेम-फील इफेक्ट:
js
const camera = {
x: 0,
y: 0,
shakeIntensity: 0,
shakeDuration: 0,
shake(intensity, duration) {
this.shakeIntensity = intensity
this.shakeDuration = duration
},
update(dt) {
if (this.shakeDuration > 0) {
this.shakeDuration -= dt
}
},
getOffset() {
if (this.shakeDuration <= 0) return { x: 0, y: 0 }
return {
x: Math.round((Math.random() - 0.5) * 2 * this.shakeIntensity),
y: Math.round((Math.random() - 0.5) * 2 * this.shakeIntensity),
}
}
}
// In render:
function render() {
const shake = camera.getOffset()
ctx.save()
ctx.translate(shake.x, shake.y)
// Draw everything...
ctx.restore()
}
// On hit:
camera.shake(4, 0.2) // 4 pixel intensity, 0.2 seconds10) कलर पैलेट की सीमाएं
ऑथेंटिक पिक्सेल आर्ट सीमित पैलेट का इस्तेमाल करती है:
js
const PALETTES = {
gameboy: ['#0f380f', '#306230', '#8bac0f', '#9bbc0f'],
nes: ['#000000', '#fcfcfc', '#f8f8f8', '#bcbcbc', /* ... */],
pico8: [
'#000000', '#1d2b53', '#7e2553', '#008751',
'#ab5236', '#5f574f', '#c2c3c7', '#fff1e8',
'#ff004d', '#ffa300', '#ffec27', '#00e436',
'#29adff', '#83769c', '#ff77a8', '#ffccaa'
],
}
function quantizeColor(r, g, b, palette) {
let minDist = Infinity
let closest = palette[0]
for (const color of palette) {
const pr = parseInt(color.slice(1, 3), 16)
const pg = parseInt(color.slice(3, 5), 16)
const pb = parseInt(color.slice(5, 7), 16)
const dist = (r - pr) ** 2 + (g - pg) ** 2 + (b - pb) ** 2
if (dist < minDist) {
minDist = dist
closest = color
}
}
return closest
}संबंधित
- Canvas 2D गेम लूप
- रिस्पॉन्सिव गेम canvas
- स्ट्रीमिंग एसेट लोडिंग
- फ्री गेम एसेट कहां मिलें — स्प्राइट शीट, पिक्सेल आर्ट पैक और 2D एसेट के स्रोत
- गेम्स में टेक्स्ट रेंडरिंग — बिटमैप फॉन्ट जो आपकी पिक्सेल आर्ट शैली से मेल खाते हैं