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,通过这一节,我们实现了游戏中精灵动画,并添加到游戏页面,实现图如下

原文地址:https://www.cnblogs.com/zcftech/p/3002080.html