你如何使用精灵,为什么它们在老游戏中被如此广泛地使用?
How do you work with sprites, and why were they used so widely in older games?
我和我的朋友决定一起创建一个平台游戏作为我们的计算机科学项目。
我目前正在使用 p5.js 库中的图像函数在图像之间切换并创建行走、跳跃等动画。
我查看了以前在 Sega Mega Drive 和 NES 等系统上的 2d 游戏是如何完成的,并且了解到当时几乎所有用于 2D 图形的东西 space 都是精灵。
经过进一步研究,我发现精灵被做成一个很大的图像文件,上面有很多不同的帧动画,而像NES这样的系统甚至可以将这些部分的图像倒转和倒转!我什至读到过您可以将同一个精灵重新着色为不同的调色板!
我查找了 p5.js 库具有 sprite 功能,但是当我四处查看它们的工作原理时,它们总是只用于制作彩色方块,而没有展示如何将它们与将存储所有图像文件的图像文件一起使用里面有图片。
我的问题是:
- 根据定义,精灵是否具有执行所有操作的能力,例如更改精灵的调色板、将其向后或上下颠倒等?
- 与使用图像显示功能和仅使用 png 相比,使用 sprite 有什么优势吗?
- 为什么它们在旧硬件中得到如此广泛的应用,而如今它们仍在现代复古风格的游戏(Showelknight、Dead Cells 等)中使用?
简要回答您的精灵 sheet(也称为纹理图集)问题:
Do sprites by definition have the capabilities to do all the things like changing the color palette of a sprite, turning it backward or upside-down, etc?
不,您仍然需要手动编程。 (NES 有帮助说明,p5.js 目前没有 flip/rotate90 度函数作为 p5.Image AFAIK 的一部分,但是你可以 "cheat" 并使用 PGraphics绘制应用变换的缓冲区(translate()/rotate()/scale()
实现翻转和旋转)
Is there any advantage to using sprites as opposed to using image display functions and just using png?
你会为精灵分配内存 sheet 一次,然后简单地引用它的区域,因为稍后需要复制帧(而不是数组中的许多独立图像,具有 load/decode资产多次)。每个 character/game 对象的帧数和更多的游戏对象能够有效地打包像素,真正节省了 RAM,让您可以将其用于更有趣的游戏机制和效果,而不仅仅是原始资产。
Why is it that they were so widely used in older hardware, and are they still used today in modern retro-styled games(Showelknight, Dead Cells, etc)?
那时候是硬件的限制,所以必须尽可能地节省资产,以便能够通过严密的 controls/gameplay 机制和故事来吸引观众。它们今天仍在用于 3D 视频游戏和实时图形:GPU 需要 2 个纹理的能力。即使是完全相同的东西,即使是现代游戏仍然包含应用于 3D 模型的 2D 纹理。
除了视频游戏之外,精灵 sheet 还在网络上找到了另一种用途。
就在我们面前的例子是 StackExchange favicon spritesheet
原因相似但不同:
- 相似,因为它仍然是一个优化
- 不同是因为我们可以轻松加载每个单独的图标,这意味着为每个单独的图标发出多个单独的 HTTP 请求(初始化连接、等待来自服务器的确认、获取数据、缓存数据)。
执行单个请求并轻松使用 CSS 为正确的图标显示一幅图像的各个部分效率更高。
请注意,spreadsheet 可以进一步优化,因为元图标是主要站点的灰度版本,并且有一个灰度 css 过滤器,但这可能会使整个代码库有点难以阅读和管理,并允许具有不一定是原始灰度副本的元图标的灵活性。这说明了他们正在优化请求的数量,而不一定是文件大小和内存分配。
对于您自己的游戏,您可以在尽可能紧密地优化游戏与尽可能灵活地使用代码库之间找到这种微妙的平衡。
回到 p5.js 这将是使用 2 个图像的问题:一个加载的精灵 sheet 和一个单独的较小图像分配给 copy() 个精灵像素。
Here的一个非常简单的示例显示了马里奥精灵的几帧:
参考代码也在这里:
你可以运行它如下:
// full spritesheet
var spriteSheet;
// a sprite sampling from sprite sheet
var mario;
// 8 frames in the spritesheet
var numSprites = 8;
// each sprite in the sheet has this bounding box
var spriteWidth = 18;
var spriteHeight = 24;
// start frame
var spriteIndex = 1;
function setup(){
createCanvas(150,150);
frameRate(24);
noSmooth();
noFill();
spriteSheet = loadImage("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJAAAAAYCAYAAAAVpXQNAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTQwIDc5LjE2MDQ1MSwgMjAxNy8wNS8wNi0wMTowODoyMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTggKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MzE1ODI1MkNDQ0MzMTFFOEJFNjA5ODI5Q0U0NzlGOEEiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MzE1ODI1MkRDQ0MzMTFFOEJFNjA5ODI5Q0U0NzlGOEEiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpFM0U1NkY3RkNDQTMxMUU4QkU2MDk4MjlDRTQ3OUY4QSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpFM0U1NkY4MENDQTMxMUU4QkU2MDk4MjlDRTQ3OUY4QSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PszND6MAAAXWSURBVHja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");
// create an image to draw a single sprite into
mario = createImage(spriteWidth,spriteHeight);
}
// set all pixels (R,G,B,A) to the same value (e.g. clear image with a colour)
function setAllPixels(image,brightness){
// prep. pixels for manipulation
image.loadPixels();
let numPixels = image.pixels.length;
// loop through all pixels (spriteWidth * spriteHeight * colourChannels(4))
for(let i = 0 ; i < numPixels; i++){
image.pixels[i] = brightness;
}
// commit value changes to image: updates it all in one go, more efficient than set()
image.updatePixels();
}
function draw(){
// clear frame
background(255);
// display the whole sprite sheet
image(spriteSheet,0,0);
// increment sprite index
spriteIndex++;
// reset sprite index if out of bounds
if(spriteIndex >= numSprites){
spriteIndex = 0;
}
// visualise sprite copy rect
rect(spriteIndex * spriteWidth,0,spriteWidth,spriteHeight);
// clear mario image
setAllPixels(mario,255);
// copy pixels from sprite sheet into sprite
// copy (source image, source coordinates(x,y,w,h), destination coordiantes (x,y,w,h) )
mario.copy(spriteSheet,
spriteIndex * spriteWidth,0,spriteWidth,spriteHeight,
0 ,0,spriteWidth,spriteHeight);
// display mario sprite
image(mario,mouseX,mouseY+spriteHeight);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.3/p5.min.js"></script>
我正在使用 base64 编码的字符串来避免 CORS 问题,但您应该能够使用 preload()
and loadImage()
来使用您自己的精灵 sheet。
在 NES 游戏方面,我建议您查看 Writing NES Games! With Assembly!! and How we fit an NES game into 40 Kilobytes。它们都是令人印象深刻的技术成就,并且在可视化精灵 sheet 和平台上的调色板限制方面做得非常好。
您不必像之前看到的那样克服这些障碍并理解 binary/bytes 就可以在 p5.js 中使用,但是了解这些旧的约束以构建高效的框架很有趣游戏。
在软件方面,有多种选择。
即使我没有得到认可,我也可以推荐 Texture Packer。
您现在可以在线试用一个简单的网络应用程序版本:SpriteSheetPacker and they have a couple of silly informercial like animations: SpriteSheets - The Movie Part 1
和 Sprite Sheets - The Movie Pt. 2 - Performance
在 actionscript 时代,有几个非常好的以像素为中心的游戏引擎:Flixel(used for the original Canabalt) and FlashPunk. There are HaXe ports available: such as HaxeFlixel and HaxePunk as well as other native JS ones (e.g. PixelJS, phaser, ImpactJS,等等)。
最近看到使用 2D WebGL 引擎的 PixelArt 风格游戏很有趣,例如 PixiJS. Although very commercial and simple in terms of game mechanics, here's a nicely rendered game by Stink Digital Studios: Miu Miu Twist
p5.js 非常适合完全理解一些在 loading/handling 资产、使用像素、处理输入等方面至关重要的基本概念。
因为它是一个相当广泛的库,所以请记住它可能仅针对游戏进行了优化。开始的好方法!
我和我的朋友决定一起创建一个平台游戏作为我们的计算机科学项目。 我目前正在使用 p5.js 库中的图像函数在图像之间切换并创建行走、跳跃等动画。 我查看了以前在 Sega Mega Drive 和 NES 等系统上的 2d 游戏是如何完成的,并且了解到当时几乎所有用于 2D 图形的东西 space 都是精灵。
经过进一步研究,我发现精灵被做成一个很大的图像文件,上面有很多不同的帧动画,而像NES这样的系统甚至可以将这些部分的图像倒转和倒转!我什至读到过您可以将同一个精灵重新着色为不同的调色板!
我查找了 p5.js 库具有 sprite 功能,但是当我四处查看它们的工作原理时,它们总是只用于制作彩色方块,而没有展示如何将它们与将存储所有图像文件的图像文件一起使用里面有图片。
我的问题是:
- 根据定义,精灵是否具有执行所有操作的能力,例如更改精灵的调色板、将其向后或上下颠倒等?
- 与使用图像显示功能和仅使用 png 相比,使用 sprite 有什么优势吗?
- 为什么它们在旧硬件中得到如此广泛的应用,而如今它们仍在现代复古风格的游戏(Showelknight、Dead Cells 等)中使用?
简要回答您的精灵 sheet(也称为纹理图集)问题:
Do sprites by definition have the capabilities to do all the things like changing the color palette of a sprite, turning it backward or upside-down, etc?
不,您仍然需要手动编程。 (NES 有帮助说明,p5.js 目前没有 flip/rotate90 度函数作为 p5.Image AFAIK 的一部分,但是你可以 "cheat" 并使用 PGraphics绘制应用变换的缓冲区(
translate()/rotate()/scale()
实现翻转和旋转)Is there any advantage to using sprites as opposed to using image display functions and just using png?
你会为精灵分配内存 sheet 一次,然后简单地引用它的区域,因为稍后需要复制帧(而不是数组中的许多独立图像,具有 load/decode资产多次)。每个 character/game 对象的帧数和更多的游戏对象能够有效地打包像素,真正节省了 RAM,让您可以将其用于更有趣的游戏机制和效果,而不仅仅是原始资产。
Why is it that they were so widely used in older hardware, and are they still used today in modern retro-styled games(Showelknight, Dead Cells, etc)?
那时候是硬件的限制,所以必须尽可能地节省资产,以便能够通过严密的 controls/gameplay 机制和故事来吸引观众。它们今天仍在用于 3D 视频游戏和实时图形:GPU 需要 2 个纹理的能力。即使是完全相同的东西,即使是现代游戏仍然包含应用于 3D 模型的 2D 纹理。
除了视频游戏之外,精灵 sheet 还在网络上找到了另一种用途。 就在我们面前的例子是 StackExchange favicon spritesheet 原因相似但不同:
- 相似,因为它仍然是一个优化
- 不同是因为我们可以轻松加载每个单独的图标,这意味着为每个单独的图标发出多个单独的 HTTP 请求(初始化连接、等待来自服务器的确认、获取数据、缓存数据)。
执行单个请求并轻松使用 CSS 为正确的图标显示一幅图像的各个部分效率更高。
请注意,spreadsheet 可以进一步优化,因为元图标是主要站点的灰度版本,并且有一个灰度 css 过滤器,但这可能会使整个代码库有点难以阅读和管理,并允许具有不一定是原始灰度副本的元图标的灵活性。这说明了他们正在优化请求的数量,而不一定是文件大小和内存分配。
对于您自己的游戏,您可以在尽可能紧密地优化游戏与尽可能灵活地使用代码库之间找到这种微妙的平衡。
回到 p5.js 这将是使用 2 个图像的问题:一个加载的精灵 sheet 和一个单独的较小图像分配给 copy() 个精灵像素。
Here的一个非常简单的示例显示了马里奥精灵的几帧:
参考代码也在这里:
你可以运行它如下:
// full spritesheet
var spriteSheet;
// a sprite sampling from sprite sheet
var mario;
// 8 frames in the spritesheet
var numSprites = 8;
// each sprite in the sheet has this bounding box
var spriteWidth = 18;
var spriteHeight = 24;
// start frame
var spriteIndex = 1;
function setup(){
createCanvas(150,150);
frameRate(24);
noSmooth();
noFill();
spriteSheet = loadImage("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJAAAAAYCAYAAAAVpXQNAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTQwIDc5LjE2MDQ1MSwgMjAxNy8wNS8wNi0wMTowODoyMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTggKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MzE1ODI1MkNDQ0MzMTFFOEJFNjA5ODI5Q0U0NzlGOEEiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MzE1ODI1MkRDQ0MzMTFFOEJFNjA5ODI5Q0U0NzlGOEEiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpFM0U1NkY3RkNDQTMxMUU4QkU2MDk4MjlDRTQ3OUY4QSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpFM0U1NkY4MENDQTMxMUU4QkU2MDk4MjlDRTQ3OUY4QSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PszND6MAAAXWSURBVHja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");
// create an image to draw a single sprite into
mario = createImage(spriteWidth,spriteHeight);
}
// set all pixels (R,G,B,A) to the same value (e.g. clear image with a colour)
function setAllPixels(image,brightness){
// prep. pixels for manipulation
image.loadPixels();
let numPixels = image.pixels.length;
// loop through all pixels (spriteWidth * spriteHeight * colourChannels(4))
for(let i = 0 ; i < numPixels; i++){
image.pixels[i] = brightness;
}
// commit value changes to image: updates it all in one go, more efficient than set()
image.updatePixels();
}
function draw(){
// clear frame
background(255);
// display the whole sprite sheet
image(spriteSheet,0,0);
// increment sprite index
spriteIndex++;
// reset sprite index if out of bounds
if(spriteIndex >= numSprites){
spriteIndex = 0;
}
// visualise sprite copy rect
rect(spriteIndex * spriteWidth,0,spriteWidth,spriteHeight);
// clear mario image
setAllPixels(mario,255);
// copy pixels from sprite sheet into sprite
// copy (source image, source coordinates(x,y,w,h), destination coordiantes (x,y,w,h) )
mario.copy(spriteSheet,
spriteIndex * spriteWidth,0,spriteWidth,spriteHeight,
0 ,0,spriteWidth,spriteHeight);
// display mario sprite
image(mario,mouseX,mouseY+spriteHeight);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.3/p5.min.js"></script>
我正在使用 base64 编码的字符串来避免 CORS 问题,但您应该能够使用 preload()
and loadImage()
来使用您自己的精灵 sheet。
在 NES 游戏方面,我建议您查看 Writing NES Games! With Assembly!! and How we fit an NES game into 40 Kilobytes。它们都是令人印象深刻的技术成就,并且在可视化精灵 sheet 和平台上的调色板限制方面做得非常好。
您不必像之前看到的那样克服这些障碍并理解 binary/bytes 就可以在 p5.js 中使用,但是了解这些旧的约束以构建高效的框架很有趣游戏。
在软件方面,有多种选择。 即使我没有得到认可,我也可以推荐 Texture Packer。 您现在可以在线试用一个简单的网络应用程序版本:SpriteSheetPacker and they have a couple of silly informercial like animations: SpriteSheets - The Movie Part 1 和 Sprite Sheets - The Movie Pt. 2 - Performance
在 actionscript 时代,有几个非常好的以像素为中心的游戏引擎:Flixel(used for the original Canabalt) and FlashPunk. There are HaXe ports available: such as HaxeFlixel and HaxePunk as well as other native JS ones (e.g. PixelJS, phaser, ImpactJS,等等)。
最近看到使用 2D WebGL 引擎的 PixelArt 风格游戏很有趣,例如 PixiJS. Although very commercial and simple in terms of game mechanics, here's a nicely rendered game by Stink Digital Studios: Miu Miu Twist
p5.js 非常适合完全理解一些在 loading/handling 资产、使用像素、处理输入等方面至关重要的基本概念。 因为它是一个相当广泛的库,所以请记住它可能仅针对游戏进行了优化。开始的好方法!