MonoGame-入门笔记(2)-好玩的精灵

上一章中我们成功的将2D精灵绘制在了窗口中。那么接下来我们尝试来解锁关于精灵的更多姿势。

  • 立好flag,我们要开始啦。本次目标:
    • 改变2D精灵的位置、角度
    • 改变2D精灵的透明度
    • 精灵动画
    • 排序绘制精灵

前置知识

在上一章中我们通过调用SpriteBatch实例的Draw(Texture2D texture, Vector2 position, Color color)方法,将2D精灵加载到窗口上;而这个方法其实有六种重载。

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

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

  3. Draw(Texture2D texture, Vector2 position, Rectangle? sourceRectangle, Color color)

  4. Draw(Texture2D texture, Rectangle destinationRectangle, Rectangle? sourceRectangle, Color color)

  5. Draw(Texture2D texture, Vector2 position, Color color)

  6. 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.jpglufei.jpg:
MonoGame-入门笔记(2)-好玩的精灵MonoGame-入门笔记(2)-好玩的精灵

示例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);
        }

绘制结果:
MonoGame-入门笔记(2)-好玩的精灵

示例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);
        }

绘制结果:
MonoGame-入门笔记(2)-好玩的精灵

示例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 排序绘制,存在多个含透明值精灵且需要排序时推荐(排序值由大到小)

绘制效果:
MonoGame-入门笔记(2)-好玩的精灵

源码

上一篇:Java设计模式-工厂模式


下一篇:Android Canvas绘图文本和事后更改文本