创建自定义精灵类
前面我们已经能创建精灵和动画,并且对精灵执行输入控制(鼠标和键盘),但是也许我们纵观上一节那个程序,发现变量种类繁多不利于管理,各种控制语句都在update方法里面,如果有几十个精灵,那这个程序岂不是臃肿不堪,所以我们就需要自定义精灵类,让各种精灵有序的工作,分门别类的进行管理,在XNA中还有一些游戏组件,这些组件更加方便了我们的管理,游戏组件将在下一节介绍,由于本节内容十分重要,所以为分为了两节!
在我们的游戏精灵中,虽然有很多精灵,但是说白了就只有两个种类,一个 用户控制的(如角色),一个是自动的(如敌人),但是这些精灵都拥有很多相同的属性,例如都会移动,都有坐标,都要绘制,都要更新,经过总结,他们的关系层次图如下:
所以在这里我们先在项目中添加一个新类,命名为Sprite,右键点击项目选项-添加-类,并且在sprite.cs中加入这样的包,以便使用XNA的功能
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
另外由于这个类只是基类,不是实际的类,所以我们修改他的类属性为抽象 abstract属性!
接下来就是要给类添加成员变量了,那么该添加些什么勒?我们在前面已经用到了很多了,这里贴出代码:
Texture2D textureImage; //各种精灵属性的成员变量
protected Point frameSize;
Point currentFrame;
Point sheetSize;
int collisionOffset;
int timeSinceLastFrame = 0;
int millisecondsPerFrame;
const int defaultMillisecondsPerFrame = 16;
protected Vector2 speed;
protected Vector2 position;
可以看到类成员变量中有 位图,控制精灵位图输出的三个point变量,控制精灵的帧率,速度,当前位置等等变量,应有尽有,这些都记载了精灵的属性,其中一些有默认值,一些没有,而且一些为protected属性,这是为了子类更好的使用,接下来看看我们的构造函数:
public sprite(Texture2D textureImage, //构造函数1,有默认参数
Vector2 position,
Point frameSize,
int collisionOffset,
Point currentFrame,
Point sheetSize,
Vector2 speed) : this(textureImage,
position, frameSize,
collisionOffset,
currentFrame,
sheetSize, speed,
defaultMillisecondsPerFrame) { }
public sprite(Texture2D textureImage, //构造函数2,没有默认参数
Vector2 position,
Point frameSize,
int collisionOffset,
Point currentFrame,
Point sheetSize,
Vector2 speed,
int millisecondsPerFrame)
{
this.textureImage = textureImage;
this.position = position;
this.frameSize = frameSize;
this.collisionOffset = collisionOffset;
this.currentFrame = currentFrame;
this.sheetSize = sheetSize;
this.speed = speed;
this.millisecondsPerFrame = millisecondsPerFrame;
}
两个构造函数,一个是有默认参数的,一个是没有默认参数的,接下来看看该类需要什么方法:
public virtual void Update(GameTime gameTime, Rectangle clientBounds) //精灵的绘制更新函数
{
timeSinceLastFrame += gameTime.ElapsedGameTime.Milliseconds;
if (timeSinceLastFrame > millisecondsPerFrame)
{
timeSinceLastFrame = 0; ++currentFrame.X;
if (currentFrame.X >= sheetSize.X)
{
currentFrame.X = 0;
++currentFrame.Y;
if (currentFrame.Y >= sheetSize.Y)
currentFrame.Y = 0;
}
}
}
这是update方法,就跟我们在Game类update方法中的一样,进行精灵位图的位置更新,设计为虚函数是为了可以被子类重写,另外参数列表中也多添加了一个矩形类的变量,这是为了在碰撞检测的时候使用的!
public virtual void Draw(GameTime gameTime, SpriteBatch spriteBatch) //draw方法中需要spritebatch对象,所以需要传递
{
spriteBatch.Draw(textureImage, position,
new Rectangle(currentFrame.X * frameSize.X,
currentFrame.Y * frameSize.Y, frameSize.X, frameSize.Y),
Color.White, 0, Vector2.Zero, 1f, SpriteEffects.None, 0);
}
这是我们的精灵类的draw方法,就是拷贝的原Game类的draw方法,绘制出精灵,由于在Game中已经有一个SpriteBatch变量,我们这里没有,所以需要传递一个spriteBatch变量,在绘制的时候使用!
public Rectangle collisionRect //返回精灵的包装矩形,用于碰撞检测
{
get
{
return new Rectangle((int)position.X + collisionOffset,
(int)position.Y + collisionOffset,
frameSize.X - (collisionOffset * 2),
frameSize.Y - (collisionOffset * 2));
}
}
该方法返回精灵的包装矩形,在碰撞检测的时候需要
public abstract Vector2 direction //精灵移动方向,子类都不相同,所以应该设置成抽象
{
get;
}
该方法为direction的访问器,获取精灵的移动方向,具体如何使用稍后会讲!
好了,我们的精灵基类就设计完成了,下面我们开始设计我们的角色类!
2:设计用户控制的角色类
同样的方法新建一个类,注明继承自Sprite类,并且添加XNA的包,与上面不同的是这里需要该类获取到用户的控制,所以还要多加一行
using Microsoft.Xna.Framework.Input;
这行就可以提供获取用户输入的控制,然后添加构造函数,几乎都跟基类一样,一个一个赋值而已!
public UserControlledSprite( //带默认参数的构造函数 调用了基类构造函数
Texture2D textureImage,
Vector2 position,
Point frameSize,
int collisionOffset,
Point currentFrame,
Point sheetSize,
Vector2 speed) : base(textureImage,
position,
frameSize,
collisionOffset,
currentFrame,
sheetSize,
speed)
{ }
public UserControlledSprite( //一般的构造函数 调用了基类构造函数
Texture2D textureImage,
Vector2 position,
Point frameSize,
int collisionOffset,
Point currentFrame,
Point sheetSize,
Vector2 speed,
int millisecondsPerFrame) : base(
textureImage,
position,
frameSize,
collisionOffset,
currentFrame,
sheetSize,
speed,
millisecondsPerFrame)
{ }
接下来先实现基类中的抽象方法drection:
public override Vector2 direction //游戏方向的确定,由用户输入和自定义的速度共同决定
{
get
{
Vector2 inputDirection = Vector2.Zero;
if (Keyboard.GetState( ).IsKeyDown(Keys.Left))
inputDirection.X -= 1;
if (Keyboard.GetState( ).IsKeyDown(Keys.Right))
inputDirection.X += 1;
if (Keyboard.GetState( ).IsKeyDown(Keys.Up))
inputDirection.Y -= 1;
if (Keyboard.GetState( ).IsKeyDown(Keys.Down))
inputDirection.Y += 1;
return inputDirection * speed;
}
}
这个方向矢量表示了精灵的移动距离,在以前我们按下左键精灵直接移动2,按多少次就移动多少个2,这样就无法中途更改移动速度,所以这里用方向乘以速度来决定位移量,如果没有按键,返回的是0!
public override void Update(GameTime gameTime, Rectangle clientBounds)
{
position += direction;
MouseState currMouseState = Mouse.GetState( ); //鼠标控制 就不在使用方向了
if (currMouseState.X != prevMouseState.X || currMouseState.Y != prevMouseState.Y)
{
position = new Vector2(currMouseState.X, currMouseState.Y);
}
prevMouseState = currMouseState;
if (position.X < 0) //保持精灵始终在窗口中间
position.X = 0;
if (position.Y < 0)
position.Y = 0;
if (position.X > clientBounds.Width - frameSize.X)
{
position.X = clientBounds.Width - frameSize.X;
}
if (position.Y > clientBounds.Height - frameSize.Y)
{
position.Y = clientBounds.Height - frameSize.Y;
}
base.Update(gameTime, clientBounds);
}
这里我们也知道,鼠标控制是要检测上一帧鼠标位置和当前帧有没有发生改变,所以需要一个prevmousestate变量,这个需要在写update方法的时候加入到类成员变量中,设为私有的!
由于draw方法不需要做改变,所以我们这里就不需要重写draw方法了,现在我们的角色类也就已经写完了。
3:设计自动的游戏精灵类
同上建立一个继承自sprite的类,并且加入XNA的包,这里不需要用户输入,所以可以不加第三个包,
这个类设计很简单,我就不用多说了
class AutomatedSprite:sprite
{
public AutomatedSprite
(Texture2D textureImage,
Vector2 position,
Point frameSize,
int collisionOffset,
Point currentFrame,
Point sheetSize,
Vector2 speed) : base(
textureImage,
position,
frameSize,
collisionOffset,
currentFrame,
sheetSize,
speed) { }
public AutomatedSprite(
Texture2D textureImage,
Vector2 position,
Point frameSize,
int collisionOffset,
Point currentFrame,
Point sheetSize,
Vector2 speed,
int millisecondsPerFrame) : base(
textureImage,
position,
frameSize,
collisionOffset,
currentFrame,
sheetSize,
speed,
millisecondsPerFrame) { }
public override Vector2 direction
{
get
{
return speed; //他没有方向的控制,返回值直接就是速度
}
}
public override void Update(GameTime gameTime, Rectangle clientBounds)
{
position += direction;
base.Update(gameTime, clientBounds); //别忘记调用基类的方法,以便不影响精灵位图的更新
}
}
我们的三个类都设计完了,本节的工作也就完成了,为了需要我这里把前面两个类的完整代码贴出来
Sprite类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace WindowsGame6
{
abstract class sprite
{
Texture2D textureImage; //各种精灵属性的成员变量
protected Point frameSize;
Point currentFrame;
Point sheetSize;
int collisionOffset;
int timeSinceLastFrame = 0;
int millisecondsPerFrame;
const int defaultMillisecondsPerFrame = 16;
protected Vector2 speed;
protected Vector2 position;
public sprite(Texture2D textureImage, //构造函数1,有默认参数
Vector2 position,
Point frameSize,
int collisionOffset,
Point currentFrame,
Point sheetSize,
Vector2 speed) : this(textureImage,
position, frameSize,
collisionOffset,
currentFrame,
sheetSize, speed,
defaultMillisecondsPerFrame) { }
public sprite(Texture2D textureImage, //构造函数2,没有默认参数
Vector2 position,
Point frameSize,
int collisionOffset,
Point currentFrame,
Point sheetSize,
Vector2 speed,
int millisecondsPerFrame)
{
this.textureImage = textureImage;
this.position = position;
this.frameSize = frameSize;
this.collisionOffset = collisionOffset;
this.currentFrame = currentFrame;
this.sheetSize = sheetSize;
this.speed = speed;
this.millisecondsPerFrame = millisecondsPerFrame;
}
public virtual void Update(GameTime gameTime, Rectangle clientBounds) //精灵的绘制更新函数
{
timeSinceLastFrame += gameTime.ElapsedGameTime.Milliseconds;
if (timeSinceLastFrame > millisecondsPerFrame)
{
timeSinceLastFrame = 0; ++currentFrame.X;
if (currentFrame.X >= sheetSize.X)
{
currentFrame.X = 0;
++currentFrame.Y;
if (currentFrame.Y >= sheetSize.Y)
currentFrame.Y = 0;
}
}
}
public virtual void Draw(GameTime gameTime, SpriteBatch spriteBatch) //draw方法中需要spritebatch对象,所以需要传递
{
spriteBatch.Draw(textureImage, position,
new Rectangle(currentFrame.X * frameSize.X,
currentFrame.Y * frameSize.Y, frameSize.X, frameSize.Y),
Color.White, 0, Vector2.Zero, 1f, SpriteEffects.None, 0);
}
public abstract Vector2 direction //精灵移动方向,子类都不相同,所以应该设置成抽象
{
get;
}
public Rectangle collisionRect //返回精灵的包装矩形,用于碰撞检测
{
get
{
return new Rectangle((int)position.X + collisionOffset,
(int)position.Y + collisionOffset,
frameSize.X - (collisionOffset * 2),
frameSize.Y - (collisionOffset * 2));
}
}
}
}
UserControlledSprite类
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; //用户控制的类,需要从设备获取数据
namespace WindowsGame6
{
class UserControlledSprite:sprite
{
private MouseState prevMouseState;
public UserControlledSprite( //带默认参数的构造函数 调用了基类构造函数
Texture2D textureImage,
Vector2 position,
Point frameSize,
int collisionOffset,
Point currentFrame,
Point sheetSize,
Vector2 speed) : base(textureImage,
position,
frameSize,
collisionOffset,
currentFrame,
sheetSize,
speed)
{ }
public UserControlledSprite( //一般的构造函数 调用了基类构造函数
Texture2D textureImage,
Vector2 position,
Point frameSize,
int collisionOffset,
Point currentFrame,
Point sheetSize,
Vector2 speed,
int millisecondsPerFrame) : base(
textureImage,
position,
frameSize,
collisionOffset,
currentFrame,
sheetSize,
speed,
millisecondsPerFrame)
{ }
public override Vector2 direction //游戏方向的确定,由用户输入和自定义的速度共同决定
{
get
{
Vector2 inputDirection = Vector2.Zero;
if (Keyboard.GetState( ).IsKeyDown(Keys.Left))
inputDirection.X -= 1;
if (Keyboard.GetState( ).IsKeyDown(Keys.Right))
inputDirection.X += 1;
if (Keyboard.GetState( ).IsKeyDown(Keys.Up))
inputDirection.Y -= 1;
if (Keyboard.GetState( ).IsKeyDown(Keys.Down))
inputDirection.Y += 1;
return inputDirection * speed;
}
}
public override void Update(GameTime gameTime, Rectangle clientBounds)
{
position += direction;
MouseState currMouseState = Mouse.GetState( );
if (currMouseState.X != prevMouseState.X || currMouseState.Y != prevMouseState.Y)
{
position = new Vector2(currMouseState.X, currMouseState.Y);
}
prevMouseState = currMouseState;
if (position.X < 0)
position.X = 0;
if (position.Y < 0)
position.Y = 0;
if (position.X > clientBounds.Width - frameSize.X)
{
position.X = clientBounds.Width - frameSize.X;
}
if (position.Y > clientBounds.Height - frameSize.Y)
{
position.Y = clientBounds.Height - frameSize.Y;
}
base.Update(gameTime, clientBounds);
}
}
}
下一节我们介绍如何利用游戏组件,把这些类有机的组合在一起,他们的使用会让游戏程序变得那么的面向对象化!