引言
通常来说RPG游戏中的战斗系统按类型(AttackType)大致可划分为普通攻击和技能攻击两大类。其中较常见的普通攻击有近距离单体物理攻击、远距离单体物理攻击和远距离单体魔法攻击。本节,我们将通过为精灵控件添加一些新的事件和属性,轻松实现RPG战斗系统中的简单AI及普通攻击战斗效果。
12.1战斗系统之普通攻击(交叉参考:经典式属性设计及完美的物理攻击系统 战斗前夜之构建动态障碍物系统 人工智能(AI)之追踪者 锦上添花之魔法特效装饰 远距离单体攻击与单体魔法)
11.1中我们利用Silverlight中的命中测试来获取鼠标划过时的精灵对象,ARPG中的战斗操作大都需要通过鼠标左、右键的点击实现普通攻击和技能攻击,因此本节我们首先为游戏的LayoutRoot注册鼠标左键点击(路由)事件:
/// <summary>
/// 游戏中鼠标左键点击
/// </summary>
void LayoutRoot_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) {
Point p = e.GetPosition(scene);
List<Sprite> hitSprites = HitSprite(e.GetPosition(LayoutRoot));
bool IsHitSprite = false;
for (int i = 0; i < hitSprites.Count; i++) {
if (hitSprites[i].InPhysicalRange(e.GetPosition(hitSprites[i]))) {
bool isHostile = hero.IsHostileTo(hitSprites[i].Camp);
if (leftButtonClickSprite != hitSprites[i]) {
if (leftButtonClickSprite != null) {
//移除上一次点中精灵选中特效
leftButtonClickSprite.RemoveSelectedEffect();
//移除上一次点中精灵的事件
leftButtonClickSprite.LifeChanged -= UpdateTargetInfoLife;
leftButtonClickSprite.LifeMaxChanged -= UpdateTargetInfoLife;
}
leftButtonClickSprite = hitSprites[i];
//注册选中精灵更新生命值事件
hitSprites[i].LifeChanged += UpdateTargetInfoLife;
hitSprites[i].LifeMaxChanged += UpdateTargetInfoLife;
//更新HUD中目标精灵资料
targetInfo.Update(hitSprites[i].FullName, hitSprites[i].Avatar, hitSprites[i].Level, hitSprites[i].Life, hitSprites[i].LifeMax);
//添加选中特效
Animation animation = new Animation() { Code = isHostile ? 5 : 4, Coordinate = hitSprites[i].Center, Z = -1 };
EventHandler handler = null;
animation.Disposed += handler = delegate {
animation.Disposed -= handler;
hitSprites[i].RemoveSelectedEffect();
};
hitSprites[i].AddSelectedEffect(animation);
}
//主角向敌对目标精灵移动
if (isHostile) {
hero.Target = hitSprites[i];
hero.FightDetect -= sprite_FightDetect;
hero.FightDetect += sprite_FightDetect;
if (hero.InCircle(hero.Target.Coordinate, hero.AttackDistance)) {
hero.TowardTo(hero.Target);
hero.Attack();
} else {
hero.RunTo(Scene.GetWindowCoordinate(hero.Target.Coordinate));
}
}
targetInfo.Visibility = Visibility.Visible;
IsHitSprite = true;
break;
}
}
if (!IsHitSprite) {
hero.Target = null;
hero.FightDetect -= sprite_FightDetect;
//主角向目标点移动
hero.RunTo(p);
//加入点击水滴
Animation water = new Animation() { Code = 3, Coordinate = p, Z = -1 };
EventHandler handler = null;
water.Disposed += handler = delegate {
water.Disposed -= handler;
scene.RemoveAnimation(water);
};
scene.AddAnimation(water);
}
}
战斗存在一个前置条件,主角将要面对的精灵是否处于敌对阵营?于是我们还需为精灵添加一个描述阵营的属性Camp:
/// 获取或设置阵营
/// </summary>
public Camps Camp { get; set; }
/// <summary>
/// 阵营
/// </summary>
public enum Camps {
/// <summary>
/// 正义
/// </summary>
Justice = 0,
/// <summary>
/// 中立
/// </summary>
Neutrality = 1,
/// <summary>
/// 邪恶
/// </summary>
Eval = 2
}
当鼠标左键击中某个精灵对象时,通过分析主角与对方的阵营关系从而得出是否应该对其发动攻击,如果是则设置主角的攻击对象(Target)为该精灵,并向其冲去(RunTo)。
主角的这些逻辑全都在鼠标左键点击事件中实现,那么如果是非主角(比如怪物类)的精灵呢?它们该如何自动(非控)的产生类似的攻击意识和行为?这就要游戏将要涉及到的AI逻辑。
接下来,我为精灵再添上了3个关于攻击这方面的重要事件:
/// 环境侦测
/// </summary>
public event EventHandler Detect;
/// <summary>
/// 战斗侦测
/// </summary>
public event EventHandler FightDetect;
/// <summary>
/// 普通攻击
/// </summary>
public event EventHandler DoAttack;
它们所触发的位置如下:
//ObjectTracker.Track(this);
this.CacheMode = new BitmapCache();
this.Children.Add(fullName);
lifePanel.Children.Add(lifeBorder);
lifeBorder.Child = lifeRect;
this.Children.Add(lifePanel);
auxiliary.Interval = TimeSpan.FromMilliseconds(2000);
auxiliary.Tick += new EventHandler(auxiliaryTick);
auxiliary.Start();
}
Frame frame;
Frames frames;
Dictionary
protected override void HeartTick(object sender, EventArgs e) {
string key = string.Format("{0}-{1}-{2}", (int)State, (int)Direction, frame.Current);
if (IsResReady) {
BodySource = Global.GetWebImage(string.Format("Sprite/{0}/{1}.png", Code, key));
BodyPosition = frameOffset[key];
} else {
BodySource = Global.GetProjectImage(string.Format("Model/Sprite/{0}/{1}.png", ModelCode, key));
BodyPosition = new Point2D(0, 0);
}
frame.Current++;
if (frame.Current > frame.Total) {
switch (State) {
case States.Attack:
if (DoAttack != null) { DoAttack(this, e); }
break;
case States.Injure:
case States.Casting:
Stand();
break;
}
frame.Current = 0;
}
//进行攻击侦测
if (FightDetect != null) { FightDetect(this, e); }
}
bool auxiliaryTicked = false;//测试用
void auxiliaryTick(object sender, EventArgs e) {
//间隔环境侦测
if (Detect != null) { Detect(this, e); }
auxiliaryTicked = true;
}
且听我一一道来。
首先是Detect事件,每隔2秒触发一次(实际开发中至少1秒用处会更广,比如中毒伤血处理等)。它的作用是实现精灵每隔一段时间对周围的环境进行侦测,包括判断自身是否已处于屏幕内,主角是否处于自己的视线范围中等等;从而作出相关反应,比如:隐藏掉自己(提升性能)、巡逻(原地随机走动)和追击。
/// 侦测
/// </summary>
void sprite_Detect(object sender, EventArgs e) {
Sprite sprite = sender as Sprite;
//异步判断精灵是否处于屏幕显示区域中,是的话且无目标则进行索敌判断:在视线范围内则追击,否则警戒
this.Dispatcher.BeginInvoke(() => {
sprite.IsVisible = sprite.InScreen(hero.Coordinate);
if (sprite.IsVisible && sprite.Target == null) {
//目前单机版怪物的敌人只有主角
if (sprite.InCircle(hero.Coordinate, sprite.SightDistance)) {
sprite.Target = hero;
sprite.RunTo(Scene.GetWindowCoordinate(hero.Coordinate));
} else {
sprite.GuardCenter = hero.Coordinate;
sprite.Guard();
}
}
});
}
当然,这些功能的实现仍然需要一些新属性的支持:
/// 警戒中心
/// </summary>
public Point GuardCenter { get; set; }
/// <summary>
/// 获取或设置警备距离
/// </summary>
public int GuardDistance { get; set; }
/// <summary>
/// 获取或设置视线距离(索敌距离)
/// </summary>
public int SightDistance { get; set; }
/// <summary>
/// 获取或设置物理攻击距离
/// </summary>
public int AttackDistance { get; set; }
/// <summary>
/// 获取或设置施法距离
/// </summary>
public int CastingDistance { get; set; }
然后是FightDetect事件,我把它放在精灵动作帧间隔里,即精灵每切换一张动作帧图片时触发一次。它在游戏中主要用作对正处于视线范围及攻击范围内的敌人进行相应的战斗处理。
/// 战斗侦测
/// </summary>
void sprite_FightDetect(object sender, EventArgs e) {
Sprite sprite = sender as Sprite;
//精灵处于屏幕中且存在目标则判断是否在攻击范围内进行攻击;否则追击;超出视线范围则停止
if (sprite.IsVisible && sprite.Target != null) {
if (sprite.InCircle(sprite.Target.Coordinate, sprite.AttackDistance)) {
sprite.TowardTo(sprite.Target);
sprite.Attack();
} else if (sprite.InCircle(sprite.Target.Coordinate, sprite.SightDistance)) {
sprite.RunTo(Scene.GetWindowCoordinate(sprite.Target.Coordinate));
} else {
sprite.Target = null;
sprite.Stand();
}
}
}
最后是DoAttack事件,当精灵攻击动作播放到起效帧时触发,根据精灵普通攻击类型进行相应的处理,比如近距离单体物理攻击则直接产生伤害;远距离单体物理攻击则发射一箭支飞向目标,到达后再对目标产生伤害:
/// 开始普通攻击
/// </summary>
void sprite_DoAttack(object sender, EventArgs e) {
Sprite attacker = sender as Sprite;
Sprite defencer = attacker.Target;
//攻击起效时如果在屏幕中且目标存在则对其进行伤害
if (attacker.IsVisible && defencer != null) {
attacker.TowardTo(defencer);
switch (attacker.AttackType) {
case AttackTypes.Close:
attacker.AttackToHurt(defencer);
break;
case AttackTypes.Distance:
Arraw arraw = new Arraw() {
Code = 0,
Z = defencer.Z,
Launcher = attacker,
Target = defencer,
};
EventHandler handler = null;
arraw.MoveCompleted += handler = delegate {
arraw.MoveCompleted -= handler;
if (arraw.Launcher != null && arraw.Target != null) {
arraw.Launcher.AttackToHurt(arraw.Target);
}
arraw.Launcher = null;
arraw.Target = null;
scene.RemoveUIElement(arraw);
};
scene.AddUIElement(arraw);
arraw.Move(new