跳至正文

对数螺线: 帧图绘制与用绘制整出的花活

你们好,我是向内延伸的对数螺线,也可以叫我阿汪(同学起的外号www

第一次写教程,欢迎指出需要改进的地方(

关于Draw的几个重载

简单绘制那篇教程中提到了SpriteBatch.Draw的六个重载,我先分别介绍一下每个重载吧(x

以下内容仅供参考,是我自己根据经验现象得到的理解性结论,不见得正确

以后或许会有更加详细而本质性的解释(?),不过不是我写

第一个是

public void Draw(Texture2D texture, Vector2 position, Color color)

position参数就是贴图左上角的屏幕坐标,接下来几个重载如果没有特别说明那就都是这个意思

比如这张截图

瞎写了些数据
public override bool PreDrawInInventory(SpriteBatch spriteBatch, Vector2 position,
Rectangle frame, Color drawColor, Color itemColor, Vector2 origin, float scale)
{
    spriteBatch.Draw(tex, new Vector2(495, 514), Color.White);
    return base.PreDrawInInventory(spriteBatch, 
    position, frame, drawColor, itemColor, origin, scale);  
}
我觉得用这个能整出很多骚操作(

ao,接下来是第二个重载

public void Draw(Texture2D texture, Vector2 position, Rectangle? sourceRectangle, Color color)

sourceRectangle 参数意味着你绘制了贴图的哪一部分,new Rectangle() 需要填入的四个参数x、y、width、height分别对应着贴图的使用部分的左上角坐标、使用部分的长宽

如果没理解什么意思的话,我截了张图(这里填的是 new Rectangle(1, 1, 44, 36),我截图里右上角那个像素点偏了(

所以如果用好这个参数,你甚至可以尝试一整个模组的贴图用一个png文件搞定(

这边是实际效果

这个参数会是这个教程的主角哦(就这吗

第三个

public void Draw(Texture2D texture, Rectangle destinationRectangle, Color color)

这个 destinationRectangle 虽然和上面那个是同一个类型,但是作用不一样,这时x、y对应的是屏幕坐标的x、y,width和height即是贴图被拉伸之后的长宽。比如这个截图(

这里的参数是new Rectangle(480, 280, 960, 560)

第四个是

public void Draw(Texture2D texture, Rectangle destinationRectangle, Rectangle? sourceRectangle, Color color)

这次同时出现了那两个Rectangle参数,根据前面的几个重载不难猜出分别是什么意思,不放截图了(x

第五个

public void Draw(Texture2D texture, Rectangle destinationRectangle, Rectangle? sourceRectangle, Color color, float rotation, Vector2 origin, SpriteEffects effects, float layerDepth)

//包括这个在内的前几个重载说实话我还没用过(x

这个重载可能是最复杂的那个,但愿我的理解是对的(effects那一项的解释很可能有问题,不过如果origin填写的是图片长宽的一半应该就没太大错误

参数一下子多起来了www,前四个和先前的是一个意思,第七个effects是控制水平翻转和垂直翻转,第八个 layerDepth是图层深度,先讲这两个是因为第五个和第六个需要用到第七个解释(

如上面所说,通过sourceRectangle参数能从贴图上切下一个小贴图,effect参数就是对这个小贴图进行翻转,不翻转是填0或者 SpriteEffects. None,水平翻转是SpriteEffects.FlipHorizontally,垂直翻转是 SpriteEffects. FlipVertically(翻转是第一步还是最后一步我其实还没试出来,一遍遍改参数猜具体什么用真的好累…

layerDepth 在默认的绘制顺序模式下无论填1922.1230f还是1991.1225f都没差(x

这里不打算细讲(填个0就好

第五个rotation是旋转量,表示顺时针旋转的弧度,第六个origin是绘制中心(以下暂时简称ori)

怎么理解这个绘制中心呢?刚刚提到你切了一块贴图下来并且翻转了,现在以那新的一小块贴图的左上角为参考系中心,这里的x、y就类似于sourceRectangle的x、y(这描述对吗www,我还是放张截图吧

所以这个绘制中心的贴图位置就是(sR.X + ori.X , sR.Y+ori.Y)

旋转即是以这个点为中心,而 destinationRectangle 参数的x,y就是这个点在屏幕中绘制出来的实际位置。

嗯,所以我绘制出了这么个诡异的图像

public Texture2D tex => Main.itemTexture[item.type];
public override bool PreDrawInInventory(SpriteBatch spriteBatch, Vector2 position, Rectangle frame, Color drawColor, Color itemColor, Vector2 origin, float scale)
{
    spriteBatch.Draw(tex, new Rectangle(480, 280, 960, 560),
    new Rectangle(0, 0, 40, 40), Color.White, MathHelper.Pi / 3, new Vector2(20, 24), SpriteEffects.FlipHorizontally, 0);
    return base.PreDrawInInventory(spriteBatch, position, frame, drawColor, itemColor, origin, scale);  
}
嗯,没毛病,应该就是这样

还是第六个和第七重载个比较友好(

public void Draw(Texture2D texture, Vector2 position, Rectangle? sourceRectangle, Color color, float rotation, Vector2 origin, float scale, SpriteEffects effects, float layerDepth)
public void Draw(Texture2D texture, Vector2 position, Rectangle? sourceRectangle, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects effects, float layerDepth)

它们只有第七个参数scale类型的不同,一起讲了吧。

上面那个scale就是相似比,填2就是长宽都放大2倍

而下面那个是长宽分别缩放,比如(1.5f,2f)就是长变为1.5倍,宽变为2倍(此处长指横向宽指纵向

绘制中心在贴图上的位置会一起被缩放

用第七个重载可以很轻松地绘制一段直线,像是这个拓展函数

上一个截图的那个白线是拿这个画的(
public static void DrawLine(this SpriteBatch spriteBatch, Vector2 start, Vector2 end, Color color, float width = 4f)
{
	spriteBatch.Draw(Main.magicPixel, (start + end) * .5f, new Rectangle(0, 0, 1, 1), color,
        (end - start).ToRotation(), new Vector2(.5f, .5f),
        new Vector2((start - end).Length(), 1), 0, 0);
}

帧图绘制

接下来进入正题

如何在屏幕上绘制帧图?得看你的帧图是横着画还是纵着画,假设是纵着画,每一帧一样大小,长w高h

那么 sourceRectangle 参数就填上(0,Frame * h,w,h)

按我上面所说的内容,不难猜到横着画就是 (Frame * w,0,w,h)

弹幕绘制里面Frame一般填写projectile.frame,不过你要是乐意用其它参数控制第几帧也没问题。

如果横向和纵向都想有怎么办?那就横纵分别设置一个Frame控制就是了(

projectile.frame的取值范围应该是[0,总帧数-1].

你可以考虑在AI()里面写

projectile.frame += (int)Main.time % time == 0 ? 1 : 0;
projectile.frame %= 总帧数;
这是我好久之前写的拓展函数(话说乱写这么多拓展函数真的好吗
public static void ProjFrameChanger(this Projectile projectile,int frames, int time) 
{
	Main.projFrames[projectile.type] = frames;
	projectile.frame += (int)Main.time % time == 0 ? 1 : 0;
	projectile.frame %= frames;
}

嗯,所以正文内容就这么点?

原版有个现成的 Terraria.Utils.Frame 函数,1.3和1.4的参数似乎不太一样,我这里讲1.3的怎么用,1.4的似乎只是多了横纵偏移量,最后两个参数分别加到width和height上

public static Rectangle Frame(this Texture2D tex, int horizontalFrames = 1, int verticalFrames = 1, int frameX = 0, int frameY = 0)

它是Texture2D类型的拓展函数,四个int参数分别是总共的横向帧数、总共的纵向帧数、当前处于横向第几帧、 当前处于纵向第几帧

这个大概是帧图绘制具体效果

我这里似乎没有什么比较好的拿来展示的例子(x
这里是我用的贴图

或者你们自己拿真空剑的弹幕贴图试试吧(

如果projectile.frame以二次函数增长

我丢一个自己用得比较多的模板在这里吧(x

spriteBatch.Draw(
    Main.projectileTexture[projectile.type],
    projectile.Center - Main.screenPosition, 
    new Rectangle(scale * (int)projectile.frame, 0, projectile.width, projectile.height),
    Color.White * Alpha,
    projectile.velocity.ToRotation() + rotation,
    new Vector2(projectile.width / 2, projectile.height / 2),
    Size,
    SpriteEffects.None, 0);

记得predraw里return false


再写点别的东西吧,免得不明不白就结束了(x

首先讲讲tml自带的弹幕绘制和NPC绘制

Main.projFrames[projectile.type] = 28; // 弹幕贴图平均分成28帧.
projectile.frameCounter++;
if(projectile.frameCounter >= 2)
{
    projectile.frameCounter=0;
    projectile.frame++;
    if (projectile.frame >= Main.projFrames[projectile.type])
    {
         projectile.frame = 0; // 到28帧的时候重置.也就是重新播放.
    }
}

这个大概是我见到的第一处给弹幕添加动画效果的代码,曾经的我只会复制粘贴调参

而现在的我有不少更好的写法(比如下面这个

int m = 28;
Main.projFrames[projectile.type] = m;
projectile.frame += (int)Main.time % 2 == 0 ? 1:0;//三元运算符,具体用法可以自己查
projectile.frame %= m;
//虽然现在弹幕帧数的切换和Main.time 相关了,但是省出了projectile.frameCounter这个字段可以用在别的地方(x

需要注意的是,tml自带的帧图效果是纵向贴图,并且每帧大小一致,如果自己重写绘制就没有这么多奇妙的限制

projectile.spriteDirection 这个字段与水平翻转相关,我记得只有-1和1两个取值,可以自己试着瞎改成别的

float num150 = (float)(Main.projectileTexture[projectile.type].Width - projectile.width) * 0.5f + (float)projectile.width * 0.5f;
Main.spriteBatch.Draw(
Main.projectileTexture[projectile.type],
new Vector2(projectile.position.X - Main.screenPosition.X + num150 +
(float)num149, projectile.position.Y - Main.screenPosition.Y + (float)(projectile.height / 2) + projectile.gfxOffY),
new Microsoft.Xna.Framework.Rectangle?(new Microsoft.Xna.Framework.Rectangle(0, y15, Main.projectileTexture[projectile.type].Width, num319)), projectile.GetAlpha(color25),
projectile.rotation,
new Vector2(num150, (float)(projectile.height / 2 + num148)),
projectile.scale,
spriteEffects,
0f);

这个应该就是原版自带的绘制函数了,咱翻译翻译就是

  1. 绘制的贴图采用默认弹幕贴图
  2. 绘制的位置的横向分量是弹幕碰撞判定长方形的左侧横坐标加上贴图的宽的一半
  3. 纵向分量是弹幕碰撞长方形的中心的纵坐标
  4. 切割的长方形如上面所说处理好了
  5. 颜色采用自然光照
  6. 旋转量是弹幕的rotation
  7. 绘制中心的横向分量是贴图宽度一半,而纵向分量是弹幕碰撞箱高度的一半

这几个意味着什么呢

意味着如果你只使用原版的谔谔绘制,不自己重写绘制的话,

无论你projecile.width和projectile.height与贴图的长宽不一致

还是scale不等于1

都会或多或少导致绘制的错位,判定中心和绘制出的贴图中心不是同一个

所以我不会重写绘制的时候是很纠结的

以下是我常用的写法,这种写法保证碰撞中心和贴图中心一致,旋转中心就是贴图中心

var tex = Main.projectileTexture[projectile.type];
const int f = 5;//你的帧数,这里假设有5帧
spriteBatch.Draw(tex,
 projectile.Center - Main.screenPosition,
tex.Frame(),
 projectile.GetAlpha(lightColor), 
projectile.rotation, 
tex.Size() / new Vector2(1,f)*.5f,
 projectile.scale,
 SpriteEffects.None, 
0f);//effects参数根据自己需要修改

嗯,好,接下来看看npc的绘制怎么整(之后更新)。

《对数螺线: 帧图绘制与用绘制整出的花活》有4个想法

  1. Pingback: TeddyTerri:使用绘制来实现影子拖尾 - 裙中世界

  2. Pingback: TeddyTerri:使用绘制实现影子拖尾 – TeddyTerri's Blog

发表回复