第 5 章XNA里的2D动画进阶
5.1 游戏精灵的动画切换
上一章,我们做出了可控制移动的精灵。精灵是行走状态,实际游戏里一个游戏人物可跳可走,可跑,收到攻击会受伤,还会死亡。那么这些都是人物的状态在改变,相应的我们要改变精灵播放的动画帧。
1.先定义一个枚举
public enum PersonState
{
ran,
walk,
beattacked,
dead
}
2.我们给精灵加一个状态属性:
PersonState State{get;set;}
3.然后我们修改update函数里的代码:
if(sprite.State==PersonState.ran)
{
sprite.Textures= RunTextures;
} else
if(sprite.State==PersonState.walk)
{
sprite.Textures= WalkTextures;
}
......
在游戏运行时,只要改变精灵对象sprite的State属性就可以控制精灵的动画切换了。
//以下为伪代码
if(sprite.beattacked)
//如果精灵收到攻击
{
sprite.state=PersonState.beattacked;
}
if(sprite.isDead)
{
sprite.state=PersonState.dead;
}
4.在Draw函数里改变响应的Texture2D数组,来改变动画帧。
按照这个思路,我们开始完善我们的游戏代码吧:
1. 定义Sprite类:
/// <summary>
/// 游戏实体对象,相当于精灵实体
/// </summary>
public class Sprite
{
/// <summary>
/// 所在位置
/// </summary>
public Vector2 Position;
/// <summary>
/// 目标位置
/// </summary>
public Vector2 EndPosition;
/// <summary>
/// 运动矢量
/// </summary>
public Vector2 Velocity;
/// <summary>
/// 奔跑动画帧数
/// </summary>
public int RunFrameCount;
/// <summary>
/// 死亡动画帧数
/// </summary>
public int DeadFrameCount;
/// <summary>
/// 被攻击动画帧数
/// </summary>
public int BeAttackFrameCount;
/// <summary>
/// 攻击动画帧数
/// </summary>
public int AttackFrameCount;
/// <summary>
/// 贴图
/// </summary>
public Texture2D Texture;
/// <summary>
/// 跑动动画贴图
/// </summary>
public Texture2D[] RunTextures;
/// <summary>
/// 死亡动画贴图
/// </summary>
public Texture2D[] DeadTextures;
/// <summary>
/// 被攻击动画贴图
/// </summary>
public Texture2D[] BeattackTextures;
/// <summary>
/// 攻击动画贴图
/// </summary>
public Texture2D[] AttackTextures;
/// <summary>
/// 生命值
/// </summary>
public float Life;
/// <summary>
/// 是否死亡
/// </summary>
public bool IsAlive;
/// <summary>
/// 精灵状态
/// </summary>
public PersonState
State;
}
2. 定义精灵状态枚举
public enum PersonState
{
Ran,
Attack,
Beattacked,
Dead
}
3. 添加一个“A”键贴图,并且输入到屏幕右下角,如图5-1-1:
图5-1-1 带攻击键的游戏界面
4.修改LoadContent函数为:
public override void LoadContent()
{
base.LoadContent();
playerTexture = ScreenManager.Game.Content.Load("Player/1");
scrossTexure = ScreenManager.Game.Content.Load("UI/scross");
aTexture = ScreenManager.Game.Content.Load("UI/a");
solider.RunFrameCount = 12;
solider.AttackFrameCount = 10;
solider.State = PersonState.Ran;
solider.RunTextures = new Texture2D[12];
solider.AttackTextures = new Texture2D[10];
for (int i = 0; i < 12;i++ )
{
solider.RunTextures[i] = creenManager.Game.Content.Load("Enemy/Run/"+(i+1));
}
for (int j= 0;j< 10; j++)
{
solider.AttackTextures[j] = ScreenManager.Game.Content.Load("Enemy/Attack/" + (j + 1));
}
solider.Texture=solider.RunTextures[0];//预先给士兵贴图一个初始值
}
5.修改Update函数里的代码,当用户触碰A键的时候,改变士兵的状态为攻击状态。
public override void Update(GameTime gameTime, bool otherScreenHasFocus, bool coveredByOtherScreen)
{
base.Update(gameTime, otherScreenHasFocus, coveredByOtherScreen);
float elapsedTime = (float)gameTime.ElapsedGameTime.TotalSeconds;
updateCount++;
if (updateCount > timeSpan) //TimeSpan为时间间隔量
{
if (solider.State == PersonState.Ran)
{
solider.RunFrameCount += 1;
if (solider.RunFrameCount > solider.RunTextures.Length - 1)//如果播完最后一帧
{
solider.RunFrameCount = 0; //就回到第一帧
}
solider.Texture = solider.RunTextures[solider.RunFrameCount];
}
if (solider.State == PersonState.Attack)
{
solider.AttackFrameCount += 1;
if (solider.AttackFrameCount > solider.AttackTextures.Length - 1)//如果播完最后一帧
{
solider.AttackFrameCount = 0; //就回到第一帧
}
solider.Texture = solider.AttackTextures[solider.AttackFrameCount];
}
updateCount = 0;
}
endPosition += elapsedTime * speed;
if (endPosition.X>800)
{
endPosition = new Vector2(0,200);
}
}
6.修改Draw函数里代码:
public override void Draw(GameTime gameTime)
{
float elapsedTime = (float)gameTime.ElapsedGameTime.TotalSeconds;
float totalTime = (float)gameTime.TotalGameTime.TotalSeconds;
ScreenManager.SpriteBatch.Begin();
ScreenManager.SpriteBatch.Draw(solider.Texture, endPosition, Color.White);
ScreenManager.SpriteBatch.Draw(scrossTexure, new Vector2(2,380), Color.White);
ScreenManager.SpriteBatch.Draw(aTexture, new Vector2(722, 380), Color.White);
ScreenManager.SpriteBatch.End();
}
7.在模拟器里运行效果如图5-1-2:
图5-1-2 操控士兵行走方向
5.2游戏精灵的碰撞计算
在一个战斗游戏里,光有一个主角是不太可能的,在上一章节里,我们给主角精灵加了一个攻击动画。如果让主角对敌人做出攻击动作,敌人会出现被攻击状态,直到死亡。那么如何判断主角进入到能对敌人进行攻击的范围呢,这就需要用到游戏精灵的碰撞计算。
为了简化理解,我们看到主角有高度,有宽度,因为这是个2D的世界。那么我们就可以把主角在平面坐标里用一个矩形表示,矩形的高度近似等于主角的高度,矩形的宽度也近似等于主角的宽度。对敌人而已也是一样的,这样两个精灵物体的碰撞,我们就能变成2D世界里两个矩形是否相交汇。
在XNA游戏框架里,Rectangle有一个专门的函数Intersects来判断两个矩形是否相交汇。
这样我们就解决了精灵的碰撞问题,我们来做一个敌人让主角攻击,如果在主角的攻击范围内,也就是两个游戏精灵能进行碰撞。为了完美表现这个过程,我们需要引入精灵的被攻击动画和死亡动画。
1.由于主角和敌人都是同一个精灵创建而成,我们可以用同一个函数CreatePerson来实现这个过程:
Sprite CreatePerson(Vector2 Position)
{
Sprite person = new Sprite();
person.Position = Position;
person.RunFrameCount = 12;
person.AttackFrameCount = 10;
person.BeAttackFrameCount = 7;
person.DeadFrameCount = 10;
person.State = PersonState.Ran;
person.RunTextures = new Texture2D[12];
person.AttackTextures = new Texture2D[10];
person.DeadTextures = new Texture2D[10];
person.BeattackTextures = new Texture2D[7];
for (int i = 0; i < 12; i++)
{
person.RunTextures[i] = ScreenManager.Game.Content.Load("Enemy/Run/" + (i + 1));
}
for (int i = 0; i < 7; i++)
{
person.BeattackTextures[i] = ScreenManager.Game.Content.Load("Enemy/Beattacked/" + (i + 1));
}
for (int j = 0; j < 10; j++)
{
person.AttackTextures[j] = ScreenManager.Game.Content.Load("Enemy/Attack/" + (j + 1));
person.DeadTextures[j] = ScreenManager.Game.Content.Load("Enemy/Dead/" + (j + 1));
}
person.Texture = person.RunTextures[0];
return person;
}
2.创建主角和敌人两个精灵:
Sprite solider,enemy;
solider= CreatePerson(soliderPosition);
enemy = CreatePerson(enemyPosition);
3.修改Draw函数如下:
public override void Draw(GameTime gameTime)
{
float elapsedTime = (float)gameTime.ElapsedGameTime.TotalSeconds;
float totalTime = (float)gameTime.TotalGameTime.TotalSeconds;
ScreenManager.SpriteBatch.Begin();
ScreenManager.SpriteBatch.Draw(solider.Texture, solider.Position, Color.White);
ScreenManager.SpriteBatch.Draw(enemy.Texture, enemy.Position, Color.White);
ScreenManager.SpriteBatch.Draw(scrossTexure, new Vector2(2,380), Color.White);
ScreenManager.SpriteBatch.Draw(aTexture, new Vector2(722, 380), Color.White);
ScreenManager.SpriteBatch.End();
}
4.增加一个执行精灵动画的方法DoSpriteAnimation,并修改Update函数,如下:
///
/// 执行精灵动画-
///
///
void DoSpriteAnimation(Sprite solider)
{
if (solider.State == PersonState.Ran)
{
solider.RunFrameCount += 1;
if (solider.RunFrameCount > solider.RunTextures.Length - 1)//如播完最后一帧
{
solider.RunFrameCount = 0; //就回到第一帧
}
solider.Texture = solider.RunTextures[solider.RunFrameCount];
}
if (solider.State == PersonState.Attack)
{
solider.AttackFrameCount += 1;
if (solider.AttackFrameCount > solider.AttackTextures.Length - 1)//如果播完最后一帧
{
solider.AttackFrameCount = 0; //就回到第一帧
}
solider.Texture = solider.AttackTextures[solider.AttackFrameCount];
}
if (solider.State == PersonState.Dead)
{
solider.DeadFrameCount += 1;
if (solider.DeadFrameCount > solider.DeadTextures.Length - 1)//如果播完最后一帧
{
solider.DeadFrameCount = 0; //就回到第一帧
}
solider.Texture = solider.DeadTextures[solider.DeadFrameCount];
}
if (solider.State == PersonState.Beattacked)
{
solider.BeAttackFrameCount += 1;
if (solider.BeAttackFrameCount > solider.BeattackTextures.Length - 1)//如果播完最后一帧
{
solider.BeAttackFrameCount = 0; //就回到第一帧
}
solider.Texture = solider.BeattackTextures[solider.BeAttackFrameCount];
}
}
public override void Update(GameTime gameTime, bool otherScreenHasFocus, bool coveredByOtherScreen)
{
base.Update(gameTime, otherScreenHasFocus, coveredByOtherScreen);
float elapsedTime = (float)gameTime.ElapsedGameTime.TotalSeconds;
updateCount++;
if (updateCount > timeSpan) //TimeSpan为时间间隔量
{
DoSpriteAnimation(solider);
DoSpriteAnimation(enemy);
updateCount = 0;
}
solider.Position += elapsedTime * speed;
if (soliderPosition.X>800)
{
soliderPosition = new Vector2(0,200);
}
}
5. 写一个判断精灵碰撞的函数IsSpriteHit:
/// <summary>
/// 两个精灵是否相碰
/// </summary>
/// <paramname="solider">主角</param>
/// <paramname="enemy">敌人</param>
/// <returns>是或者否</returns>
bool IsSpriteHit(Sprite solider,Sprite enemy)
{
Rectangle soliderRect = new Rectangle()
{
Location = new Point()
{
X = (int)solider.Position.X, Y = (int)solider.Position.Y
},
Width = solider.Texture.Width,
Height = solider.Texture.Height
};
Rectangle enemyRect = new Rectangle()
{
Location = new Point()
{
X = (int)enemy.Position.X, Y = (int)enemy.Position.Y
},
Width = enemy.Texture.Width,
Height = enemy.Texture.Height
};
return soliderRect.Intersects(enemyRect);
}
2. 在Update函数增加如下代码:
if(IsSpriteHit(solider,enemy)&&solider.State== PersonState.Attack)
{
enemy.State = PersonState.Beattacked;
}
3. 在模拟器执行上述代码效果如图5-2-1:
图5-2-1 士兵战斗碰撞判断
这一章的内容讲到这里就玩了,不过两个精灵战斗必然会有伤亡,这就需要我们加上伤害,防御,血量等数据,以及判断精灵是否死亡,精灵死亡后播放死亡动画,这就留给读者继续完善本章节的这个游戏。