上一章中我们成功的将2D精灵绘制在了窗口中。那么接下来我们尝试来解锁关于精灵的更多姿势。
- 立好flag,我们要开始啦。本次目标:
- 改变2D精灵的位置、角度
- 改变2D精灵的透明度
- 精灵动画
- 排序绘制精灵
前置知识
在上一章中我们通过调用SpriteBatch
实例的Draw(Texture2D texture, Vector2 position, Color color)
方法,将2D精灵加载到窗口上;而这个方法其实有六种重载。
-
Draw(Texture2D texture, Vector2 position, Rectangle? sourceRectangle, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects effects, float layerDepth)
-
Draw(Texture2D texture, Rectangle destinationRectangle, Rectangle? sourceRectangle, Color color, float rotation, Vector2 origin, SpriteEffects effects, float layerDepth)
-
Draw(Texture2D texture, Vector2 position, Rectangle? sourceRectangle, Color color)
-
Draw(Texture2D texture, Rectangle destinationRectangle, Rectangle? sourceRectangle, Color color)
-
Draw(Texture2D texture, Vector2 position, Color color)
-
Draw(Texture2D texture, Rectangle destinationRectangle, Color color)
接下来,解释一下这几个重载中出现的参数含义。
- Texture2D texture => 一张待绘制的2D精灵材质
- Vector2 position => 绘制到窗口的2维坐标(精灵的左上角放至该坐标),默认为窗口左上角为原点(0,0)
- Rectangle? sourceRectangle => 需要绘制2D精灵的区域,当传null时将整个2D精灵绘制到绘制目标
- Color color => 颜色融合,当颜色为
Color.White
表示不进行颜色融合 - float rotation => 2D精灵的旋转角度(以弧度为单位,使2D精灵围绕原点进行旋转)
- Vector2 origin => 2D精灵的原点,默认为窗口左上角为原点(0,0)
- Vector2 scale => 控制2D精灵的缩放(倍数),X方向和Y方向。默认为Vector2D.One (1,1)
- SpriteEffects effects => 可设置是否按照某一轴向反转,默认为SpriteEffects.None
- float layerDepth => 对2D精灵进行深度排序(0-1之间的浮点数),数值越大显示越前
- Rectangle destinationRectangle => 将2D精灵绘制到指定大小的矩形中,若此矩形大小与2D精灵大小不一致,则对2D精灵进行缩放
好啦,现在我们尝试使用Draw方法的重载来实现我们的小目标。
图片我们使用tuzi.jpg
和lufei.jpg
:
示例1 - 改变2D精灵的位置、角度
我尝试使用第一种重载完成操作:
将2D精灵(tuzi.jpg)绘制在窗口中心,并沿y轴方向旋转30°
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.White);
// 开始绘制
_spriteBatch.Begin();
//计算窗口中心x坐标
var x = Window.ClientBounds.Width / 2 - _img.Width / 2;
//计算窗口中心y坐标
var y = Window.ClientBounds.Height / 2 - _img.Height / 2;
//将30°转换为弧度
var angle = ((float)(Math.PI/180.0f))*30.0f;
//我们只改变绘制坐标与角度,并传入材质,其它参数保持默认值
_spriteBatch.Draw(_img, new Vector2(x,y), null,Color.White,angle,Vector2.Zero,Vector2.One,SpriteEffects.None,default);
// 结束绘制
_spriteBatch.End();
base.Draw(gameTime);
}
绘制结果:
示例2 - 改变2D精灵的透明度
通过上面的Draw
重载,我们可以发现,其中是没有直接操作透明度的方法。但是存在几个方法和颜色融合有关。那么我们就可以尝试通过操作融合颜色的Alpha通道数值来改变精灵透明度的效果。
我选取Draw方法重载其中最简单--重载5 Draw(Texture2D texture, Vector2 position, Color color)
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.White);
// 开始绘制
_spriteBatch.Begin();
//定义一个能设置混合颜色alpha的本地函数,取值范围为(0f-1.0f)
Color SetColorAlpha(float alpha)
{
return new Color(1.0f * alpha, 1.0f * alpha, 1.0f * alpha, 1.0f * alpha);
}
//计算窗口中心x坐标
var x = Window.ClientBounds.Width / 2 - _img.Width / 2;
//计算窗口中心y坐标
var y = Window.ClientBounds.Height / 2 - _img.Height / 2;
//设置透明度
var transparency = SetColorAlpha(0.5f);
//改变混合颜色中Alpha值(默认取值在0-255之间,0表示完全透明,255表示完全不透明)
//作为对比,我们将在窗口原点处绘制一张不透明的精灵,窗口中心绘制一张半透明图片
_spriteBatch.Draw(_img, Vector2.Zero, Color.White);
_spriteBatch.Draw(_img, new Vector2(x, y), transparency);
// 结束绘制
_spriteBatch.End();
base.Draw(gameTime);
}
绘制结果:
示例3 - 精灵动画
制作精灵动画一般是使用大量独立图片,并按照某种特定的顺序绘制,也叫帧动画。
既然如此,我们要绘制精灵动画只需要知道绘制图片的位置和绘制顺序即可。
综上所述,Draw方法重载中最简单也最符合我们要求应该就是重载3了Draw(Texture2D texture, Vector2 position, Rectangle? sourceRectangle, Color color)
我们使用的图片是lufei.jpg
,这组精灵图(也能称为一张精灵表)为4*4个精灵,每行都是一组动作;这次主要尝试实现一下第一行的动作--走
我们首先将第一张精灵提取出来(加载精灵的操作就略过,和加载tuzi.jpg一样)
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.White);
// 开始绘制
_spriteBatch.Begin();
//这张精灵表4行4列
var sheetSize = new Point(4, 4);
//获取每张精灵宽高(像素)
var frameSize = new Point(_lufei.Width/ sheetSize.X, _lufei.Height/ sheetSize.Y);
//将精灵表按每张精灵一个坐标分隔,当前播放坐标(0,0),也就是第一张
var currentFrame = Point.Zero;
//计算需要绘制的源区域
var drawArea = new Rectangle(currentFrame.X * frameSize.X,currentFrame.Y * frameSize.Y,frameSize.X,frameSize.Y);
_spriteBatch.Draw(_lufei, Vector2.Zero, drawArea, Color.White);
// 结束绘制
_spriteBatch.End();
base.Draw(gameTime);
}
当我们运行程序就能发现,我们成功获取了第一张精灵。接下来我们让它动起来,实际上通过改变坐标即可做到
//将精灵表按每张精灵一个坐标分隔,当前播放坐标(0,0),也就是第一张
Point currentFrame = Point.Zero;
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.White);
// 开始绘制
_spriteBatch.Begin();
//这张精灵表4行4列
var sheetSize = new Point(4, 4);
//获取每张精灵宽高(像素)
var frameSize = new Point(_lufei.Width/ sheetSize.X, _lufei.Height/ sheetSize.Y);
//计算需要绘制的源区域
var drawArea = new Rectangle(currentFrame.X * frameSize.X,currentFrame.Y * frameSize.Y,frameSize.X,frameSize.Y);
_spriteBatch.Draw(_lufei, Vector2.Zero, drawArea, Color.White);
totalTime += gameTime.ElapsedGameTime.Milliseconds;
//计算下一帧绘制的精灵坐标
currentFrame.X += 1;
if (currentFrame.X >= sheetSize.X)
{
currentFrame.X = 0;
}
// 结束绘制
_spriteBatch.End();
base.Draw(gameTime);
}
当我们运行程序就能发现,咦,动了它动了,但这也太快了。所以这个需要控制帧率。
//将精灵表按每张精灵一个坐标分隔,当前播放坐标(0,0),也就是第一张
Point currentFrame = Point.Zero;
//定义时间间隔(ms)
int perFramTime = 300;
//定义继上次刷新后的时间累计值(ms)
int totalTime = 0;
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.White);
// 开始绘制
_spriteBatch.Begin();
//这张精灵表4行4列
var sheetSize = new Point(4, 4);
//获取每张精灵宽高(像素)
var frameSize = new Point(_lufei.Width/ sheetSize.X, _lufei.Height/ sheetSize.Y);
//计算需要绘制的源区域
var drawArea = new Rectangle(currentFrame.X * frameSize.X,currentFrame.Y * frameSize.Y,frameSize.X,frameSize.Y);
_spriteBatch.Draw(_lufei, Vector2.Zero, drawArea, Color.White);
totalTime += gameTime.ElapsedGameTime.Milliseconds;
if (totalTime>perFramTime)
{
totalTime -= perFramTime;
//计算下一帧绘制的精灵坐标
currentFrame.X += 1;
if (currentFrame.X >= sheetSize.X)
{
currentFrame.X = 0;
}
}
// 结束绘制
_spriteBatch.End();
base.Draw(gameTime);
}
案例4 - 排序绘制精灵
对于精灵排序实际上我们可以通过控制绘制顺序实现,也可以通过重载1和重载2都提供的支持实现,这里我选择重载1Draw(Texture2D texture, Vector2 position, Rectangle? sourceRectangle, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects effects, float layerDepth)
实现兔子在路飞前面的效果。
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.White);
// 开始绘制
_spriteBatch.Begin(SpriteSortMode.FrontToBack);
_spriteBatch.Draw(_img, new Vector2(10,10), null, Color.White, 0.0f, default, Vector2.One, default,1.0f);
_spriteBatch.Draw(_lufei, Vector2.Zero, null, Color.White,0.0f,default,Vector2.One,default,0.0f);
// 结束绘制
_spriteBatch.End();
base.Draw(gameTime);
}
这里我故意将兔子先于路飞绘制。这里需要注意的是当需要排序时,一定要在绘制开始时(_spriteBatch.Begin方法中)指定精灵排序模式。
SpriteSortMode的几种枚举值
- Deferred 顺序绘制(默认)
- Immediate 即时绘制
- Texture 排序绘制
- BackToFront 排序绘制,存在多个含透明值精灵且需要排序时推荐(排序值由小到大)
- FrontToBack 排序绘制,存在多个含透明值精灵且需要排序时推荐(排序值由大到小)
绘制效果: