TurboSprite:.NET Framework 2.0 上的简单 2D 精灵引擎






4.96/5 (24投票s)
.NET 2.0 上一个简单、面向对象的 2D 精灵引擎。
引言
TurboSprite 是一组组件,可为您的 .NET 应用程序提供完整的 2D 动画精灵引擎。“精灵”是游戏或其他支持动画的应用程序中的可移动对象。在 TurboSprite 中,您可以使用几种不同类型的精灵,并创建自己的派生类型来展现您需要的确切外观和行为。TurboSprite 完全用 C# 托管代码编写。主要组件是
SpriteSurface
- 精灵动画发生的视觉画布。Sprite
- 一个抽象基类,用于封装精灵。TurboSprite 包含几个具体的派生精灵类,例如BitmapSprite
、PolygonSprite
和AnimatedBitmapSprite
。SpriteEngine
- 添加到SpriteEngine
的精灵将出现在SpriteSurface
上。SpriteEngine
会通知您精灵之间的碰撞。SpriteEngine
的派生类允许您定义它们所包含的精灵的不同移动能力。TurboSprite 中包含的DestinationSpriteMover
派生类允许您以不同的速度将精灵移动到目标点。
背景
TurboSprite 最初是在 20 世纪 90 年代中期作为一个用 Borland Delphi 编写的 Windows 精灵引擎开始的。当时,在 Windows 中生成响应式动画非常困难,TurboSprite 依赖于特殊的汇编语言编码函数直接绘制到图像缓冲区内存。现在计算机速度更快,TurboSprite 的最新版本利用 GDI+ 的绘图能力来执行渲染。这使得我能够完全用 C# 托管代码开发该包。对于简单的 2D 游戏和其他应用程序,性能非常可接受!TurboSprite for .NET 保留了原始库的简单、面向对象的设计模式。
TurboSprite 组件
SpriteSurface
SpriteSurface
是精灵移动和其他动画效果发生的可见动画表面。将 SpriteSurface
放置在窗体上后,设置 DesiredFPS
属性以确定您希望实现的动画每秒帧数。在代码中,将 Active
属性设置为 true
来开始动画。您可以随时通过检查 ActualFPS
属性来检查 SpriteSurface
的实际动画速率。
您可以使用多种不同的方式通过 SpriteSurface
产生动画。一种方法是使用 SpriteEngine
s 和 Sprite
s(见下文)。当您将 SpriteEngine
s 连接到 SpriteSurface
时,SpriteEngine
s 中包含的精灵将在 SpriteSurface
上渲染和处理。
SpriteSurface
还提供事件和属性,您可以在动画周期的各个点处理它们。其中一些事件使用 PaintEventArgs
,因此您可以使用事件中包含的 Graphics
对象在表面上绘图。请记住,下面事件的顺序在每个动画周期中都会发生。您基本上在每个周期中绘制完整的帧。正是每一帧中绘制内容的细微变化可以产生动画效果。
BeforeAnimationCycle
事件在每个动画帧之前发生。- 如果将
AutoBlank
属性设置为true
,则在每个动画周期中发生的第一件事就是使用AutoBlankColor
属性清除表面。 BeforeSpriteRender
事件在任何精灵渲染到表面之前发生,但在 AutoBlank 处理之后。使用Graphics
对象在每个动画帧中执行任何自定义渲染到表面的操作。AfterSpriteRender
事件在精灵渲染之后发生,并允许您使用传递的Graphics
对象绘制到表面。
SpriteSurface
具有像任何其他 Windows Forms 控件一样的 Width
和 Height
属性,但它还有一个“虚拟”尺寸的概念,即控件的可见部分是完整虚拟区域的视口。要启用此功能,请将 UseVirtualSize
属性设置为 true
,并使用 VirtualSize
属性设置虚拟尺寸。当虚拟尺寸模式生效时,您使用 OffsetX
和 OffsetY
属性来读取和设置到虚拟空间中的左上角的偏移量。使用虚拟尺寸时,请记住精灵的 X 和 Y 坐标是以完整的虚拟空间而不是 SpriteSurface
的可见部分来表示的。
SpriteSurface
中有许多属性可以显示一个选择光标。如果 CursorVisible
为 true
,则光标将显示为矩形,无论您在表面上单击何处。光标的外观由 CursorColor
和 CursorWidth
属性控制,其位置由 CursorX
和 CursorY
属性控制。
此外,如果 SelectionBandVisible
为 true
,则当您在 SpriteSurface
上拖动鼠标时,将出现一个选择框。选择框的颜色由 SelectionBandColor
属性控制。当用户绘制选择框时,SpriteSurface
会触发 RangeSelected
事件。
Wraparound
属性决定当精灵对象移出 SpriteSurface
的虚拟尺寸时会发生什么。如果 Wraparound
为 false
,则允许精灵移出实际尺寸,只是不进行渲染。如果为 true
,则超出表面一侧的精灵将在另一侧出现,从而有效地环绕表面。
Sprite
Sprite
类定义了一个可以移动并在 SpriteSurface
上渲染的对象。Sprite
是一个抽象类,它定义了一个 Position
属性、一个 Width
和 Height
,以及一些对所有精灵通用的其他属性。TurboSprite 包含许多具体的派生精灵类
BitmapSprite
使用Bitmap
来渲染自身。AnimatedBitmapSprite
继承自BitmapSprite
,并允许您指定一组Bitmap
,这些Bitmap
定义了用于渲染精灵的动画帧。PolygonSprite
根据您定义的一组点来渲染自身,并支持绕其轴旋转。ParticleExplosionSprite
生成基于粒子的爆炸,允许您控制大小、速度和颜色范围。ShockwaveSprite
生成由向外辐射的圆组成的爆炸效果。StarFieldSprite
重现了经典的街机游戏星场超空间效果。
鼓励您将这些用作示例,并创建自己的 Sprite
派生类。
Sprite
类包含两个重要的方法,值得在此进一步讨论
Process
方法是虚拟的,如果您愿意,可以在具体的派生类中重写它。TurboSprite 在每个动画帧之前调用精灵的Process
方法,无论精灵是否在虚拟区域的可见部分内。在这里,您可以执行任何您希望在每个动画帧中应用于精灵的处理。这可以包括粒子分散、递减生命计数器等。Render
方法是抽象的,因此必须在派生类中实现。它包含实际将精灵绘制到SpriteSurface
上的代码。Render
提供了一个Graphics
对象,您应该使用它来渲染精灵。
渲染精灵时,使用精灵的 Location
,减去 SpriteSurface
当前的 OffsetX
和 OffsetY
,以确定在 Graphics
对象上的绘制位置。下面的示例是 BitmapSprite
的 Render
方法,它使用 Bitmap
来渲染自身。请注意,代码在渲染时还减去了 Bitmap
大小的一半,确保 Bitmap
以精灵的实际位置为中心。
//Render it on the sprite surface
protected internal override void Render(Graphics g)
{
g.DrawImage(Bitmap, X - WidthHalf - Surface.OffsetX,
Y - HeightHalf - Surface.OffsetY);
}
X
和 Y
是 Sprite
类的属性,让您可以访问其位置。如上所示,Sprite
类通过 Surface
属性提供对其 SpriteSurface
的访问。
在 Sprite
派生类中,告诉 TurboSprite 精灵的大小很重要。TurboSprite 使用此信息以及精灵的 Position
来确定是否需要在每个动画帧中渲染它。精灵的 Shape
属性(一个 RectangleF
)应该在 Sprite
派生类中分配,以定义其大小。Shape
属性的设置应与其位置无关。例如,如果一个精灵的大小为 20x20 像素,那么其 Shape
属性应设置为新的 RectangleF
(-10, -10, 10, 10);这将精灵的形状以其位置为中心。您应该设置 Shape
属性,使其大致对应于您在 Render
方法中绘制的图像。
下面我们看到 BitmapSprite
在分配 Bitmap
时设置了 Shape
属性
//The bitmap used to render the sprite
public Bitmap Bitmap
{
get
{
return _bitmap;
}
set
{
_bitmap = value;
if (_bitmap != null)
Shape = new RectangleF(-_bitmap.Width / 2, -_bitmap.Height / 2,
_bitmap.Width, _bitmap.Height);
}
}
TurboSprite 支持点击精灵并通过 SpriteClicked
事件通知客户端。您并不总是希望精灵的可见绘制区域来确定精灵被点击的区域。因此,Sprite
类还有一个 ClickShape
属性,如果您希望点击区域与 Shape
不同,可以设置它。默认情况下,ClickShape
假定 Shape
属性的设置值。
Sprite
类还定义了一些描述旋转的属性,包括 FacingAngle
、Spin
和 SpinSpeed
。如果 Spin
设置为 SpinType.ClockWise
或 SpinType.CounterClockwise
,则 TurboSprite 会在每个动画周期自动调整精灵的 FacingAngle
。由派生的 Sprite
类来利用 FacingAngle
来渲染精灵。要查看一个实现此功能的示例,请检查 PolygonSprite
类的代码。PolygonSprite
维护一个点的内部列表,这些点定义了精灵的多边形形状。在 Process
方法中,点根据精灵的 FacingAngle
进行旋转。
//Process the sprite on each animation cycle - handle rotation
protected internal override void Process()
{
//Process rotation of shape
if (FacingAngle != _lastAngle)
{
float sin = Sprite.Sin(FacingAngle);
float cos = Sprite.Cos(FacingAngle);
_lastAngle = FacingAngle;
for (int p = 0; p < _points.Length; p++)
{
_points[p].X = _unrotated[p].X * cos - _unrotated[p].Y * sin;
_points[p].Y = _unrotated[p].Y * cos + _unrotated[p].X * sin;
}
//This causes Shape to be correctly recalculated
Points = _points;
}
}
顺便说一句,Sprite
类创建了正弦和余弦值的静态查找表,这减少了动画周期中的计算开销,而 PolygonSprite
在上面的代码中使用了这些查找表。
SpriteEngine
要使您的精灵出现在 SpriteSurface
上,它们必须添加到 SpriteEngine
中。SpriteEngine
是一个非可视组件,用于管理精灵移动和碰撞检测。SpriteEngine
有一个 Surface
属性,应将其设置为其精灵将要绘制的 SpriteSurface
。您可以使用连接到同一 SpriteSurface
的多个 SpriteEngine
,为同一表面上的不同精灵组提供不同的行为。
SpriteEngine
的 Priority
属性决定了当多个 SpriteEngine
连接到 SpriteSurface
时精灵的渲染顺序。
精灵之间的碰撞检测通过设置 SpriteEngine
的 DetectCollisionSelf
和 DetectCollisionFlag
属性来处理。如果 DetectCollisionSelf
为 true
,则 SpriteEngine
中的精灵将检测它们之间的碰撞。当使用多个 SpriteEngine
时,DetectCollisionFlag
值相同的 SpriteEngine
将检测它们之间的碰撞。当检测到精灵碰撞时,SpriteSurface
会触发 SpriteCollision
事件,并将发生碰撞的两个 Sprite
对象传递给您。
移动精灵
除非有办法让精灵动起来,否则它们几乎一文不值!在 TurboSprite 中,这由派生自 SpriteEngine
的组件处理。SpriteEngine
提供了一个定义精灵移动的设计模式。派生组件可以利用此模式来创建不同的精灵移动方式。TurboSprite 包含一个这样的派生组件,即 SpriteEngineDestination
组件,它以特定速度将精灵移动到特定目标。
这里的设计利用了 Sprite
类中的一个特殊标签属性,称为 MovementData
。SpriteEngine
派生组件可以根据需要将任何对象分配给此属性,以跟踪和/或控制精灵的移动。设置此值的地方是 SpriteEngine
的 InitializeSprite
方法,该方法应在派生类中重写。SpriteEngineDestination
重写此方法以创建 DestinationMover
对象实例,并将其分配给 Sprite
的 MovementData
属性。
//Create a DestinationMove object and attach it to the sprite
protected override void InitializeSprite(SCG.TurboSprite.Sprite sprite)
{
sprite.MovementData = new DestinationMover(sprite);
}
SpriteEngineDestination
提供了一个名为 GetMover
的公共方法,该方法将 DestinationMover
的实例返回给客户端,用于特定的 Sprite
对象。DestinationMover
类本身包含描述精灵速度、目的地以及精灵到达目的地后是否应停止移动的属性。因此,客户端代码可以通过设置 DestinationMover
对象的 Speed
和 Destination
属性来使精灵移动。
TurboSprite 在调用 SpriteEngine
的 MoveSprite
方法时执行移动逻辑。这发生在每个动画周期中。SpriteEngineDestination
重写了 MoveSprite
方法并采用了其自定义移动逻辑。有关精灵移动设计模式的更详细了解,请参阅 SpriteEngineDestination
的代码及其注释。虽然这可能显得繁琐,但它为将来可以添加的不同移动行为提供了灵活性。
TurboSprite 演示应用程序
源代码中包含一个完整的演示应用程序,它演示了如何创建精灵、将它们添加到 SpriteEngine
s 以及移动它们。它还涉及 TurboSprite 的一些其他优点,例如 GamePieceBitmapFactory
组件,该组件允许您轻松访问包含在单个较大 Bitmap
中的单个精灵图形,甚至可以将其着色为任何特定颜色。
这是当单击“位图精灵”按钮时执行的代码。它创建一个 BitmapSprite
实例,并将其投入到 SpriteSurface
中。
private void btnAddSprite_Click(object sender, EventArgs e)
{
//Create a BitmapSprite
BitmapSprite s = new BitmapSprite((Bitmap)picGlyph.Image);
s.Bitmap.MakeTransparent(Color.Black);
//Center it on the SpriteSurface
s.Position = new Point(surface.Width / 2, surface.Height / 2);
//Add it to the SpriteEngine
engineDest.AddSprite(s);
//Set its speed and destination
DestinationMover dm = engineDest.GetMover(s);
dm.Speed = rnd.Next(10) + 1;
dm.Destination = new Point(rnd.Next(surface.Width), rnd.Next(surface.Height));
dm.StopAtDestination = false;
}
TurboSprite 实战
TurboSprite 是我免费的实时策略游戏 Solar Vengeance 中使用的动画引擎。Solar Vengeance 使用了 TurboSprite 的许多功能,其中一些功能包含在此包中但本文未涉及。
- 使用许多自定义
Sprite
派生类来渲染星舰和星系。 - 使用
AnimatedBitmapSprite
类来渲染虫洞。 - 使用
ParticleExplosionSprite
和ShockwaveSprite
s 来渲染爆炸。 - 使用
SquareGridSpriteSurface
作为主表面。这是一个派生自基本SpriteSurface
的组件,但提供了额外功能,允许您使用方形网格。 - 使用
SpriteEngineDestination
在游戏场地上移动精灵。
访问 Silicon Commander Games 网站,了解更多关于 TurboSprite、Solar Vengeance 以及我们开源的 .NET 多人游戏和聊天组件包 PrismServer 的信息。