您的位置:首页 > 其它

Silverlight MMORPG网页游戏开发课程[一期] 第十三课:战斗系统之技能/魔法攻击

2010-12-09 11:21 429 查看
引言

游戏因为华丽而精彩!这是所有游戏开发者发自肺腑的不懈追求!绚丽的技能/魔法效果将游戏的内涵渲染得淋漓尽致,本节我将继续拓展游戏中的战斗系统,以最简单直接的方式实现超酷的技能/魔法攻击效果。

13.1战斗系统之技能/魔法攻击(交叉参考:[b]大法师 - 华丽经典之轮回 超酷万变的矢量魔法 雷、混、冰、毒、火、风 - 幻化中的魔法魅力!锦上添花之魔法特效装饰 落雷!治疗!陷阱!连锁闪电!多段群伤!魔法之终极五重奏① 落雷!治疗!陷阱!连锁闪电!多段群伤!魔法之终极五重奏② )[/b]

传统即时类RPG游戏通常以右键作为技能/魔法的触发,战士类职业以技能为主,法师类职业以魔法为主,区别在于近身与远距离之分。这样我们大致可将其进行如下归类:近/远距离单体/群体技能攻击、近/远距离单体/群体魔法攻击;其中的单体又可分为速效型、持续型或按受益性质化分可分为伤害型、辅助型和特殊型等;而群体则大多以伤害范围区分类型,常见的有圆形、扇型、十字、蝶形、随机路径、特殊图形等。

自Silverlight4开始已能完美的支持鼠标右键事件,于是作为施放Magic(技能/魔法)的开始,我们在游戏LayoutRoot的右键事件中编写如下代码,告诉主角开始执行Casting动作了,并将Magic参数MagicArgs传递过去:

/// <summary>
/// 游戏中鼠标右键点击
/// </summary>
void LayoutRoot_MouseRightButtonDown(object sender, MouseButtonEventArgs e) {
e.Handled = true;
Point p0 = e.GetPosition(scene);
Point p1 = Scene.GetWindowCoordinate(hero.Coordinate);
hero.Target = null;
hero.SetDirection(p1, p0);
hero.Casting(new MagicArgs() {
Code = Convert.ToInt32(((ComboBoxItem)comboBox3.SelectedItem).Tag),
Coordinate = Convert.ToInt32(((ComboBoxItem)comboBox2.SelectedItem).Tag) == 0 ? p1 : p0,
Target = p0,
Z = -1,
Level = Convert.ToInt32(((ComboBoxItem)comboBox1.SelectedItem).Tag),
});
}
其中的Magic类继承自Animation,区别在于它俩资源存放的路径不同,同时Magic在未被放出前仅仅以参数MagicArgs的形态出现:

代码

//开始技能/施法攻击
void sprite_DoCasting(object sender, DoCastingEventArgs e) {
Sprite caster = sender as Sprite;
List<Sprite> sprites = new List<Sprite>();
//创建主魔法
Magic magic = new Magic() { Args = e.MagicArgs };
if (magic.IsConfigReady) {
EventHandler handler = null;
magic.Disposed += handler = delegate {
magic.Disposed -= handler;
scene.RemoveAnimation(magic);
};
scene.AddAnimation(magic);
} else{
magic.Dispose();
}
switch (magic.Args.MagicType) {
case MagicTypes.Circle:
//捕获圆形范围内将要伤害的精灵表
for (int i = 0; i < scene.sprites.Count; i++) {
if (scene.sprites[i].IsVisible && caster.IsHostileTo(scene.sprites[i].Camp)) {
if (scene.sprites[i].InCircle(Scene.GetGameCoordinate(magic.Args.Coordinate), magic.Args.Radius)) {
sprites.Add(scene.sprites[i]);
}
}
}
break;
case MagicTypes.Sector:
double angle = Global.GetAngle(Global.GetRadian(Scene.GetWindowCoordinate(caster.Coordinate), magic.Args.Target)) + 180;
Point p = new Point(magic.Coordinate.X + magic.Args.Radius * Math.Cos(Global.GetRadian(angle)), magic.Coordinate.Y + magic.Args.Radius * Math.Sin(Global.GetRadian(angle)));
magic.Move(magic.Coordinate, p, 2, MoveModes.Normal);
//其他额外魔法
double singleAngle = 18;
double startAngle = angle - ((magic.Args.Number - 1) / 2 * singleAngle);
double endAngle = angle + ((magic.Args.Number - 1) / 2 * singleAngle);
for (int i = 0; i < magic.Args.Number; i++) {
double otherAngle = startAngle + singleAngle * i;
if (otherAngle == angle) { continue; }
Magic otherMagic = new Magic() { Args = e.MagicArgs };
if (otherMagic.IsConfigReady) {
EventHandler handler = null;
otherMagic.Disposed += handler = delegate {
otherMagic.Disposed -= handler;
scene.RemoveAnimation(otherMagic);
};
scene.AddAnimation(otherMagic);
} else {
otherMagic.Dispose();
}
p = new Point(otherMagic.Coordinate.X + magic.Args.Radius * Math.Cos(Global.GetRadian(otherAngle)), otherMagic.Coordinate.Y + magic.Args.Radius * Math.Sin(Global.GetRadian(otherAngle)));
otherMagic.Move(otherMagic.Coordinate, p, 2, MoveModes.Normal);
}
double s0 = 0, s1 = 1, e0 = 0, e1 = 0;
if (startAngle < 180 && endAngle > 180) {
s0 = startAngle; e0 = 180; s1 = -180; e1 = endAngle - 360;
} else if (startAngle >= 180) {
s0 = startAngle - 360; e0 = endAngle - 360;
} else {
s0 = startAngle; e0 = endAngle;
}
for (int i = 0; i < scene.sprites.Count; i++) {
if (scene.sprites[i].IsVisible && caster.IsHostileTo(scene.sprites[i].Camp)) {
double spriteAngle = Global.GetAngle(Global.GetRadian(Scene.GetWindowCoordinate(scene.sprites[i].Coordinate), magic.Args.Coordinate));
if ((spriteAngle >= s0 && spriteAngle <= e0) || (spriteAngle >= s1 && spriteAngle <= e1)) {
sprites.Add(scene.sprites[i]);
}
}
}
break;
case MagicTypes.Cross:
angle = Global.GetAngle(Global.GetRadian(Scene.GetWindowCoordinate(caster.Coordinate), magic.Args.Target)) + 180;
//测试
magic.RenderTransform = new RotateTransform() {
CenterX = 75,
CenterY = 115,
Angle = angle
};
singleAngle = 360 / magic.Args.Number;
for (int i = 1; i < magic.Args.Number; i++) {
Magic otherMagic = new Magic() { Args = e.MagicArgs };
if (otherMagic.IsConfigReady) {
EventHandler handler = null;
otherMagic.Disposed += handler = delegate {
otherMagic.Disposed -= handler;
scene.RemoveAnimation(otherMagic);
};
scene.AddAnimation(otherMagic);
} else {
otherMagic.Dispose();
}
otherMagic.RenderTransform = new RotateTransform() {
CenterX = 75,
CenterY = 115,
Angle = angle + singleAngle * i
};
}
//捕获十字(圆)范围内将要伤害的精灵表
for (int i = 0; i < scene.sprites.Count; i++) {
if (scene.sprites[i].IsVisible && caster.IsHostileTo(scene.sprites[i].Camp)) {
if (scene.sprites[i].InCircle(Scene.GetGameCoordinate(magic.Args.Coordinate), magic.Args.Radius)) {
sprites.Add(scene.sprites[i]);
}
}
}
break;
case MagicTypes.MultipleCircle:
angle = Global.GetAngle(Global.GetRadian(magic.Args.Coordinate, magic.Args.Target)) + 180;
int count = 0;
int number = 4;
double width = 80;
magic.Dispose();
EventHandler timerHandler = null;
DispatcherTimer timer = new DispatcherTimer() { Interval = TimeSpan.FromMilliseconds(100) };
timer.Tick += timerHandler = delegate {
if (count == magic.Args.Number) {
timer.Tick -= timerHandler;
timer.Stop();
} else {
count++;
number += 2;
width += 80;
createCircleMagic(e.MagicArgs, count, number, angle, width);
}
};
timer.Start();
//捕获圆范围内将要伤害的精灵表
for (int i = 0; i < scene.sprites.Count; i++) {
if (scene.sprites[i].IsVisible && caster.IsHostileTo(scene.sprites[i].Camp)) {
if (scene.sprites[i].InCircle(Scene.GetGameCoordinate(magic.Args.Coordinate), magic.Args.Radius)) {
sprites.Add(scene.sprites[i]);
}
}
}
break;
}
//对精灵表中所有精灵进行魔法/技能伤害
foreach (Sprite sprite in sprites) {
caster.CastingToHurt(sprite, magic.Args);
}
sprites.Clear();
}

void createCircleMagic(MagicArgs magicArgs, int count, double number, double angle, double width) {
for (int i = 0; i < number; i++) {
//if (count == 0 && i == 0) { continue; }
Magic otherMagic = new Magic() { Args = magicArgs };
if (otherMagic.IsConfigReady) {
EventHandler handler = null;
otherMagic.Disposed += handler = delegate {
otherMagic.Disposed -= handler;
scene.RemoveAnimation(otherMagic);
};
scene.AddAnimation(otherMagic);
} else {
otherMagic.Dispose();
}
double tempAngle = angle + i * (360 / number);
double tempRadian = Global.GetRadian(tempAngle);
double x = width * Math.Cos(tempRadian) + otherMagic.Coordinate.X;
double y = width * Math.Sin(tempRadian) + otherMagic.Coordinate.Y;
otherMagic.Coordinate = new Point(x, y);
}
}
绚,缘自对游戏设计的狂热;魔法都做烂了,那么换一下吧。以上代码将为大家演绎维美而精彩的战士技能:









能够征服玩家的Magic必须拥有强而有力且富含打击质感的体验,辅以百态特效比如:通过Storyboard的CubicEase缓动效果实现的震飞,还有挑高、冰冻、下毒、定身(麻痹)、迟缓等等,配上不同等级、不同模式时的各异姿态体现,魅力超级无限!

当然,到此为止还需要完善的内容有很多,如魔法效果的拓展、进一步封装等等;而最大的问题出在:为什么第一次放魔法时常常会没有效果出现?也不产生任何伤害?问题关键在于Magic的xml配置是动态下载的,所以实际开发需要将xml配置文件一开始就全部加载好(可适当考虑使用独立存储)。比方说假如游戏是SLG回合战斗RPG,可以在每场战斗初始化时分析所有角色所掌握的技能一并下载完成后再回合Start。而即时类的就比较麻烦了,通常的做法是游戏开始时创建一个可维护的队列,一旦有新的对象(新场景、新精灵、新魔法、新界面等)加入到游戏中,则将它所需要的一切资源配置信息添加到队列的末尾或开头,依次类推。

Silverlight 5 Beta明年春天即将发布,从新特性上我们不难看出MS在Silverlight于网页方面的应用相当给力,外加[u]微软Silverlight团队开始大规模全球招聘[/u]!!种种迹象不难看出Silveright 5在实际发布时誓将给世界一个大大的惊叹!性能的进一步提升和3D骨骼动画的API支持,哇塞!绝对的翘首以盼!

本课小结:关于RPG游戏中的战斗系统讲解到此已全部结束。或许你会觉得很简单;确实代码相当的少,Silverlight底层框架给了我们很大的支持,让我们可以把更多的时间花在具体功能的实现逻辑上;然而实际情况是极其复杂多变的,到底什么才能使得每天面对着同一款游戏的玩家恋恋不舍,茶余饭后回味谈资?中国的网游行业年复一年的在上演换一套UI就是一款游戏的贺岁强档,凭借《传奇》发家的盛大10多年了还依旧在吃着《传奇》饭,问题到底出在哪?

经典不需要Copy,也没必要More,玩家真正需要的是一部旷世之作! - The Only One!

本课源码点击进入目录下载

参考资料:中游在线[WOWO世界]Silverlight C# 游戏开发:游戏开发技术

教程Demo在线演示地址http://cangod.com
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐