• XNA之RGP游戏开发教程之七


    这节中我们将会为游戏中添加一个动态精灵;实际上精灵移动的实现跟我们最初的动画实现相似,都是通过重复绘制精灵使得产生一种精灵运动的错觉,当绘制速度达到一定值后就会实现动态精灵。先从http://xnagpa.net/xna4/downloads/playersprites.zip上下载精灵图片

    在EyesOfTheDragonContent项目下添加一个新的文件夹PlayerSprites,将精灵图片添加进这个文件夹。有了图片接下来就是构建一个动画类Animation,来作为动态精灵的基类

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using Microsoft.Xna.Framework;
    namespace XRpgLibrary.SpriteClasses
    {
    public enum AnimationKey { Down, Left, Right, Up }//动态精灵所对应的方向
    public class Animation : ICloneable
     {
     #region Field Region
    Rectangle[] frames;//精灵帧的矩形框
    int framesPerSecond;//帧频率变量
    TimeSpan frameLength;//相邻帧之间的时间间隔
    TimeSpan frameTimer;
    int currentFrame;//当前帧的索引
    int frameWidth;//帧的宽度和高度
    int frameHeight;
     #endregion
     #region Property Region
    public int FramesPerSecond
     {
    get { return framesPerSecond; }
    set
     {
    //对帧频的限定,要保持在1到60之间
    if (value < 1) framesPerSecond = 1; else if (value > 60) framesPerSecond = 60; else framesPerSecond = value; frameLength = TimeSpan.FromSeconds(1 / (double)framesPerSecond);//初始化帧之间的时间间隔 } } public Rectangle CurrentFrameRect { get { return frames[currentFrame]; } } public int CurrentFrame { get { return currentFrame; } set { currentFrame = (int)MathHelper.Clamp(value, 0, frames.Length - 1);//限定当前帧索引的范围 } } public int FrameWidth { get { return frameWidth; } } public int FrameHeight { get { return frameHeight; } } #endregion #region Constructor Region其中xOffset和yOffset的作用是实现在不同行不同列上截取图片
    public Animation(int frameCount, int frameWidth, int frameHeight, int xOffset, int yOffset) { frames = new Rectangle[frameCount];//初始化该动画的帧集 this.frameWidth = frameWidth; this.frameHeight = frameHeight; for (int i = 0; i < frameCount; i++)//根据选定的行来实例化动画中的帧图 { frames[i] = new Rectangle( xOffset + (frameWidth * i), yOffset, frameWidth, frameHeight); } FramesPerSecond = 5;//设置帧频 Reset(); }
    //直接用动画来初始化
    private Animation(Animation animation) { this.frames = animation.frames; FramesPerSecond = 5; } #endregion #region Method Region动画中帧的更新 public void Update(GameTime gameTime) { frameTimer += gameTime.ElapsedGameTime; if (frameTimer >= frameLength) { frameTimer = TimeSpan.Zero; currentFrame = (currentFrame + 1) % frames.Length;//当时间间隔超过设定值时间,翻到下一帧 } } public void Reset() { currentFrame = 0;//重置当前的帧索引和时间间隔的值 frameTimer = TimeSpan.Zero; } #endregion #region Interface Method Region public object Clone() { Animation animationClone = new Animation(this); animationClone.frameWidth = this.frameWidth; animationClone.frameHeight = this.frameHeight; animationClone.Reset(); return animationClone; } #endregion } }

    在以上代码中要说明两个地方,一个就是第一个构造函数,其中通过xOffset和yOffset来选择不同行的帧图片,从精灵图中可以看到,各个方向的图片在同一行中,所以对于图片的加载是以行为单位,我们可以通过设定构造函数的参数为3,32,32,0,0来获取第一行的三个精灵图片,也可以通过参数3,32,32,0,32来获取第二行的三个精灵图片...第二个要说明的地方就是Update方法中通过模运算,使得图片索引在0,1,2之间交替,对应的是精灵图片中一行的三幅图片

    有了动画的基类,接下来就是构建活动的精灵类

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Graphics;
    using XRpgLibrary.TileEngine;
    namespace XRpgLibrary.SpriteClasses
    {
    public class AnimatedSprite
     {
     #region Field Region
    Dictionary<AnimationKey, Animation> animations;//字典类,键值对存储
    AnimationKey currentAnimation;//精灵当前的状态
    bool isAnimating;//精灵当前是否活动
    Texture2D texture;//精灵图片
    Vector2 position;//精灵位置 Vector2 velocity;//精灵移动方向向量
    float speed = 2.0f;//精灵移动速度 #endregion #region Property Region public AnimationKey CurrentAnimation { get { return currentAnimation; } set { currentAnimation = value; } } public bool IsAnimating { get { return isAnimating; } set { isAnimating = value; } } public int Width { get { return animations[currentAnimation].FrameWidth; } } public int Height { get { return animations[currentAnimation].FrameHeight; } } public float Speed { get { return speed; } set { speed = MathHelper.Clamp(speed, 1.0f, 16.0f); } } public Vector2 Position { get { return position; } set { position = value; } } public Vector2 Velocity { get { return velocity; } set { velocity = value; if (velocity != Vector2.Zero) velocity.Normalize();//方向向量要单位化后才能乘以速度,表示精灵的移动 } } #endregion #region Constructor Region public AnimatedSprite(Texture2D sprite, Dictionary<AnimationKey, Animation> animation) { texture = sprite; animations = new Dictionary<AnimationKey, Animation>(); foreach (AnimationKey key in animation.Keys) animations.Add(key, (Animation)animation[key].Clone()); } #endregion #region Method Region public void Update(GameTime gameTime) { if (isAnimating) animations[currentAnimation].Update(gameTime); }
    //关于精灵的绘制要注意一点,在地图的绘制中要减去Camera的位置坐标,所以在绘制地图中的任何对象时都要减去Camera的位置坐标
    public void Draw(GameTime gameTime, SpriteBatch spriteBatch, Camera camera) { spriteBatch.Draw( texture, position - camera.Position, animations[currentAnimation].CurrentFrameRect, Color.White); }
    //锁定精灵的位置,防止其移出游戏窗体
    public void LockToMap() { position.X = MathHelper.Clamp(position.X, 0, TileMap.WidthInPixels - Width); position.Y = MathHelper.Clamp(position.Y, 0, TileMap.HeightInPixels - Height); } #endregion } }

     创建好精灵类后,在GamePlayScreen中添加活动精灵,在LoadContent方法中加载所要的资源

    using XRpgLibrary.SpriteClasses;
    AnimatedSprite sprite;
    protected override void LoadContent()
    {
    Texture2D spriteSheet = Game.Content.Load<Texture2D>(@"PlayerSprites\malefighter");//加载活动精灵图片
    Dictionary<AnimationKey, Animation> animations = new Dictionary<AnimationKey, Animation>();//定义活动精灵动画集合
    Animation animation = new Animation(3, 32, 32, 0, 0);//选取图片中的第一行来构造Animation对象,其对应的是Down状态的精灵
     animations.Add(AnimationKey.Down, animation);//将活动精灵动画对象加入集合,用于创建精灵
     animation = new Animation(3, 32, 32, 0, 32);//图片第二行
     animations.Add(AnimationKey.Left, animation);
     animation = new Animation(3, 32, 32, 0, 64);//图片第三行
     animations.Add(AnimationKey.Right, animation);
     animation = new Animation(3, 32, 32, 0, 96);//图片第四行
     animations.Add(AnimationKey.Up, animation);
     sprite = new AnimatedSprite(spriteSheet, animations);//创建精灵
    base.LoadContent();
    //以上是代码增加部分 Texture2D tilesetTexture
    = Game.Content.Load<Texture2D>(@"Tilesets\tileset1"); Tileset tileset1 = new Tileset(tilesetTexture, 8, 8, 32, 32); tilesetTexture = Game.Content.Load<Texture2D>(@"Tilesets\tileset2"); Tileset tileset2 = new Tileset(tilesetTexture, 8, 8, 32, 32); List<Tileset> tilesets = new List<Tileset>(); tilesets.Add(tileset1); tilesets.Add(tileset2); MapLayer layer = new MapLayer(40, 40); for (int y = 0; y < layer.Height; y++) { for (int x = 0; x < layer.Width; x++) { Tile tile = new Tile(0, 0); layer.SetTile(x, y, tile); } } MapLayer splatter = new MapLayer(40, 40); Random random = new Random(); for (int i = 0; i < 80; i++) { int x = random.Next(0, 40); int y = random.Next(0, 40); int index = random.Next(2, 14); Tile tile = new Tile(index, 0); splatter.SetTile(x, y, tile); } splatter.SetTile(1, 0, new Tile(0, 1)); splatter.SetTile(2, 0, new Tile(2, 1)); splatter.SetTile(3, 0, new Tile(0, 1)); List<MapLayer> mapLayers = new List<MapLayer>(); mapLayers.Add(layer); mapLayers.Add(splatter); map = new TileMap(tilesets, mapLayers); }

    更新Update和Draw方法如下

    public override void Update(GameTime gameTime)
    {
     player.Update(gameTime);
     sprite.Update(gameTime);//更新精灵
    base.Update(gameTime);
    }
    public override void Draw(GameTime gameTime)
    {
     GameRef.SpriteBatch.Begin(
    SpriteSortMode.Immediate,
    BlendState.AlphaBlend,
    SamplerState.PointClamp,null,
    null,
    null,
    Matrix.Identity);
     map.Draw(GameRef.SpriteBatch, player.Camera);
     sprite.Draw(gameTime, GameRef.SpriteBatch, player.Camera);//绘制精灵
    base.Draw(gameTime);
     GameRef.SpriteBatch.End();
    }

    这个时候精灵还是不会动,它还是位于地图的左上角,我们即想让精灵可以自由的移动,也想通过将精灵和Camera关联,实现通过精灵扩充地图,这样Camera就会有两种状态:Free或者Fellow,在Free状态下Camera还是通过玩家按上下左右键来拓展地图,游戏精灵通过自己的方向按键(通常定义为w,a,s,d)来移动,这样的话会看到精灵移出窗体;在Fellow状态下,Camera和游戏精灵关联,通过控制精灵来拓展地图;修改Camera类代码如下

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Graphics;
    using Microsoft.Xna.Framework.Input;
    using XRpgLibrary.SpriteClasses;
    namespace XRpgLibrary.TileEngine
    {
    public enum CameraMode { Free, Follow }//camera的两种状态
    public class Camera
     {
     #region Field Region
    Vector2 position;
    float speed;
    float zoom;
    Rectangle viewportRectangle;
    CameraMode mode;//camera状态的类级变量
     #endregion
     #region Property Region
    public Vector2 Position
     {
    get { return position; }
    private set { position = value; }
     }
    public float Speed
     {
    get { return speed; }
    set
     {
     speed = (float)MathHelper.Clamp(speed, 1f, 16f);
     }
     }
    public float Zoom {
    get { return zoom; }
     }
    public CameraMode CameraMode
     {
    get { return mode; }
     }
     #endregion
     #region Constructor Region
    public Camera(Rectangle viewportRect)
     {
     speed = 4f;
     zoom = 1f;
     viewportRectangle = viewportRect;
     mode = CameraMode.Follow;//初始化摄像头为Follow状态
     }
    public Camera(Rectangle viewportRect, Vector2 position)
     {
     speed = 4f;
     zoom = 1f;
     viewportRectangle = viewportRect;
     Position = position;
     mode = CameraMode.Follow;//
     }
     #endregion
     #region Method Region
    public void Update(GameTime gameTime)
     {
    if (mode == CameraMode.Follow)//如果为Follow状态,不再执行Camera自身的Update方法
    return;
    Vector2 motion = Vector2.Zero;
    if (InputHandler.KeyDown(Keys.Left) ||
    InputHandler.ButtonDown(Buttons.RightThumbstickLeft, PlayerIndex.One))
     motion.X = -speed;
    else if (InputHandler.KeyDown(Keys.Right) ||
    InputHandler.ButtonDown(Buttons.RightThumbstickRight, PlayerIndex.One))
     motion.X = speed;
    if (InputHandler.KeyDown(Keys.Up) ||
    InputHandler.ButtonDown(Buttons.RightThumbstickUp, PlayerIndex.One))
     motion.Y = -speed;
    else if (InputHandler.KeyDown(Keys.Down) ||
    InputHandler.ButtonDown(Buttons.RightThumbstickDown, PlayerIndex.One))
     motion.Y = speed;
    if (motion != Vector2.Zero)
     {
     motion.Normalize();
     position += motion * speed;
     LockCamera();
     } 
     }
    private void LockCamera()
     {
     position.X = MathHelper.Clamp(position.X,
     0,
    TileMap.WidthInPixels - viewportRectangle.Width);
     position.Y = MathHelper.Clamp(position.Y,
     0,TileMap.HeightInPixels - viewportRectangle.Height);
     }
    //将Camera的坐标与精灵坐标关联起来
    public void LockToSprite(AnimatedSprite sprite) { position.X = sprite.Position.X + sprite.Width / 2 - (viewportRectangle.Width / 2);//当精灵移动超过当前Camera视场宽度的一半,Camera的位置才改变,才会水平方向重绘地图 position.Y = sprite.Position.Y + sprite.Height / 2 - (viewportRectangle.Height / 2); LockCamera(); }
    //Camera状态的变换方法
    public void ToggleCameraMode() { if (mode == CameraMode.Follow) mode = CameraMode.Free; else if (mode == CameraMode.Free) mode = CameraMode.Follow; } #endregion } }

    做了这么多,现在精灵还是不能动,最后一步就是要在GameplayScreen类中修改update方法,加入精灵移动控制代码

    public override void Update(GameTime gameTime)
    {
     player.Update(gameTime);
     sprite.Update(gameTime);
     Vector2 motion = new Vector2();//方向向量
    //按W键,精灵向上移动
    if (InputHandler.KeyDown(Keys.W) || InputHandler.ButtonDown(Buttons.LeftThumbstickUp, PlayerIndex.One)) { sprite.CurrentAnimation = AnimationKey.Up; motion.Y = -1; }
    //按S键,精灵向下移动
    else if (InputHandler.KeyDown(Keys.S) || InputHandler.ButtonDown(Buttons.LeftThumbstickDown, PlayerIndex.One)) { sprite.CurrentAnimation = AnimationKey.Down; motion.Y = 1; } if (InputHandler.KeyDown(Keys.A) || InputHandler.ButtonDown(Buttons.LeftThumbstickLeft, PlayerIndex.One)) { sprite.CurrentAnimation = AnimationKey.Left; motion.X = -1; } else if (InputHandler.KeyDown(Keys.D) || InputHandler.ButtonDown(Buttons.LeftThumbstickRight, PlayerIndex.One)) { sprite.CurrentAnimation = AnimationKey.Right; motion.X = 1; } if (motion != Vector2.Zero) { sprite.IsAnimating = true;//精灵发生移动,在Update方法中需要更新 motion.Normalize(); sprite.Position += motion * sprite.Speed;//单位化方向向量,并乘以速度,重置精灵位置 sprite.LockToMap();//控制精灵的位置,防止移出地图 if (player.Camera.CameraMode == CameraMode.Follow)//如果当前Camera的状态是Follow,则将精灵和Camera关联起来,Camera的位置会跟着sprite的位置移动 player.Camera.LockToSprite(sprite); } else { sprite.IsAnimating = false; }
    //按F键,实现Camera状态的切换
    if (InputHandler.KeyReleased(Keys.F) || InputHandler.ButtonReleased(Buttons.RightStick, PlayerIndex.One)) { player.Camera.ToggleCameraMode();
    //如果切换后的Camera状态变为Follow,将其与sprite关联
    if (player.Camera.CameraMode == CameraMode.Follow) player.Camera.LockToSprite(sprite); } if (player.Camera.CameraMode != CameraMode.Follow) { if (InputHandler.KeyReleased(Keys.C) || InputHandler.ButtonReleased(Buttons.LeftStick, PlayerIndex.One)) { player.Camera.LockToSprite(sprite); } } base.Update(gameTime); }

    OK,通过这一节,我们实现了游戏中精灵动画,并添加到游戏页面,实现图如下

  • 相关阅读:
    Objective-C 资源收藏
    坑爹的高德地图API
    nginx的location root alias指令以及区别
    Java Swing 界面中文乱码问题解决(Idea环境)
    不同的国家/地区与语言缩写代码
    IDEA编译时出现 Information:java: javacTask: 源发行版 1.8 需要目标发行版 1.8
    请重视!请重视!请重视!百度熊掌号之搜索资源平台体验
    php7带来的性能升级
    详解Asp.Net Core 2.1+的视图缓存(响应缓存)
    Xshell5 提示要继续使用此程序,您必须应用最新的更新或使用新版本
  • 原文地址:https://www.cnblogs.com/zcftech/p/3002080.html
Copyright © 2020-2023  润新知