一直在想应该用什么作为《Silverlight 2.5D RPG游戏技巧与特效处理系列教程》的终结,既要实用而不拖泥带水;又要通用而不哗众取宠。于是一不小心便成就了我一个未了心愿:一切基于动态绘制路径而生成的万象动画作为这又一部作品的谢幕,足矣。
还记得我们小时候玩的《坦克大战》、《雷电》吗?在那狭小的弹粒缝隙间躲闪追逐成为了每每课后最快乐的回忆:
还记得高三那年的春天吗?每次丢下书包第一时间总会跑进网吧与同学们联网《星级争霸》、《帝国时代》;4V4、罗马战车VS投石车,炮火与弓箭的洗礼下,我们幸福的青春就这样被无限的释放:
大学里,《博德之门》、《奇迹》、《半条命》、《破碎银河系》等N多顶级游戏/网游让我短暂的忘却了时代赋予大学生的苦闷,人生除了现实,其实成为一名虚拟的王者未必不是今生一件幸事。“魔法飞弹”横空掠过、“蓝翎弩”三重穿梭、“手雷”华丽的抛物线、“跟踪弹”锲而不舍的精神如同网吧中弥漫着无上团结的凝聚力,走过了4年,也走过了最让我留念和感动的“兄弟年代”:
直至今日,当我们坐在凳子上开始编写属于自己的游戏时,当年战斗的画面早已模糊不清,残留的仅剩无数的弹粒在天空中你来我往,毫无眷恋。一直在想是否能够通过一块画板外加几个选项来完成所有一切的自定义路径动画,从而让游戏设计中那些如此神秘而又变幻莫测的“弹道轨迹”变得简单而轻松?
之前便有很多人公开了他们的相关成果,关于Silverlight中的路径动画(Path Animation)实现,比如下面两个链接:
http://www.codeproject.com/KB/silverlight/PathAnimation.aspx
http://geekswithblogs.net/cskardon/archive/2010/09/21/path-animation-in-silverlight.aspx
很可惜它们均为外国人所作,第一篇重新封装了一个名为PathAnimation的类,基于Blend绘制Path坐标数据实现路径动画;而后者则完全是通过Blend绘制的路径动画。虽然均能达到效果,但易用性及拓展性显然不够强。想想,怎样的自定义路径动画才能最大化适应当下绝大多数(各类型)游戏的需要?尤其在RPG游戏中,能为之增添无限乐趣和优秀的玩家视觉体验。
思路再一次回到那块画板上,通过鼠标在画板上移动,然后将鼠标所经过的轨迹以一定的密度绘制在其上并按顺序保存到List<Point>中,最后通过Storyboard的关键帧动画将所有的Point连成一个完整的路径动画。其中通过简单的顺时针/逆时针判断公式及匀加/减速度计算公式(任何公式都可以随意拓展)来分别实现动画延路径移动时的动态朝向及变速运动效果;当然,再配合上坐标缩放系数,从而最终整个自定义路径动画所实现的效果可完美无缝的移植到任何有相关需求的Silverlight游戏中:
bullet.Center = bullet.Offset;
tabCanvas2.Children.Add(bullet);
Storyboard storyboard = new Storyboard();
PointAnimationUsingKeyFrames pointAnimationUsingKeyFrames = new PointAnimationUsingKeyFrames();
Storyboard.SetTarget(pointAnimationUsingKeyFrames, bullet);
Storyboard.SetTargetProperty(pointAnimationUsingKeyFrames, new PropertyPath( " Position " ));
DoubleAnimationUsingKeyFrames doubleAnimationUsingKeyFrames = new DoubleAnimationUsingKeyFrames();
Storyboard.SetTarget(doubleAnimationUsingKeyFrames, bullet);
Storyboard.SetTargetProperty(doubleAnimationUsingKeyFrames, new PropertyPath( " Angle " ));
double speed = pathAnimationPainter[n].Rate;
double a = 0.002 ; // 加速系数
double timeSpanTemp = 0 ;
double durationTemp = 0 ;
double angleTemp = 0 ;
int circle = 0 ;
if (pathAnimationPainter[n].Easing == 2 ) { // 加速逆行即减速
for ( int i = 0 ; i < pathAnimationPainter[n].Path.Count; i ++ )
{ speed += a * i; }
}
for ( int i = 0 ; i < pathAnimationPainter[n].Path.Count; i ++ ) {
TimeSpan timeSpan = new TimeSpan();
Point lastPath = (i == 0 ? pathAnimationPainter[n].Path[ 0 ] : pathAnimationPainter[n].Path[i - 1 ]);
Point nowPath = pathAnimationPainter[n].Path[i];
Point nextPath = (i == pathAnimationPainter[n].Path.Count - 1 ? pathAnimationPainter[n].Path[pathAnimationPainter[n].Path.Count - 1 ] : pathAnimationPainter[n].Path[i + 1 ]);
switch (pathAnimationPainter[n].Easing) {
case 0 :
timeSpanTemp = GlobalMethod.GetDistance(lastPath, nowPath) / speed;
durationTemp += timeSpanTemp;
timeSpan = TimeSpan.FromMilliseconds(durationTemp);
break ;
case 1 :
speed += a * i;
timeSpanTemp = GlobalMethod.GetDistance(lastPath, nowPath) / speed;
durationTemp += timeSpanTemp;
timeSpan = TimeSpan.FromMilliseconds(durationTemp);
break ;
case 2 :
speed -= a * (pathAnimationPainter[n].Path.Count - 1 - i);
timeSpanTemp = GlobalMethod.GetDistance(lastPath, nowPath) / speed;
durationTemp += timeSpanTemp;
timeSpan = TimeSpan.FromMilliseconds(durationTemp);
break ;
}
pointAnimationUsingKeyFrames.KeyFrames.Add(
new LinearPointKeyFrame() {
KeyTime = KeyTime.FromTimeSpan(timeSpan),
Value = new Point() { X = nowPath.X * pathAnimationPainter[n].Proportion, Y = nowPath.Y * pathAnimationPainter[n].Proportion }
}
);
double angle = GlobalMethod.GetAngle(nowPath.Y - lastPath.Y, nowPath.X - lastPath.X) + 360 * circle;
// check大于0为顺时针个方向
double check = (nowPath.X - lastPath.X) * (nextPath.Y - nowPath.Y) - (nowPath.Y - lastPath.Y) * (nextPath.X - nowPath.X);
if (check > 0 ) {
if (angleTemp > 360 * circle && angle < 360 * circle) { angle += 360 ; } else if (angleTemp > 360 * circle + 180 && angle < angleTemp) { angle += 360 ; circle ++ ; }
} else {
if (angleTemp < 360 * circle && angle > 360 * circle) { angle -= 360 ; } else if (angleTemp < 360 * circle - 180 && angle > angleTemp) { angle -= 360 ; circle -- ; }
}
angleTemp = angle; // 用于旋转时的角度衔接
doubleAnimationUsingKeyFrames.KeyFrames.Add(
new LinearDoubleKeyFrame() {
KeyTime = KeyTime.FromTimeSpan(timeSpan),
Value = angle
}
);
}
pointAnimationUsingKeyFrames.Duration = new Duration(TimeSpan.FromMilliseconds(durationTemp));
storyboard.Children.Add(pointAnimationUsingKeyFrames);
if (pathAnimationPainter[n].Direction == 0 ) { storyboard.Children.Add(doubleAnimationUsingKeyFrames); }
EventHandler handler = null ;
storyboard.Completed += handler = delegate {
storyboard.Completed -= handler;
tabCanvas2.Children.Remove(bullet);
bullet.Move_Completed(bullet, null );
};
storyboard.Begin();
还在羡慕《倩女幽魂Online》交织穿梭的吸血法术吗?
还在嫉妒《星辰变》中的移动粒子施法吗?还在恨2D/2.5D无法实现类似3D游戏中那些高随意性的路径动画吗?
有了本节的自定义路径动画攻略,大家只需一个描述路径的List<Point>,配合上几个参数而已,一切效果随手创造:
看到这你是否开始心动了?没错,尤其是在角色位置相对固定的回合制、SLG等类型游戏中,在发动者与它的目标之间创造出你认为最华丽的路径,无论魔法的走位还是角色的各类移动等效果都将变得轻而易举,这就是Silverlight给我们游戏开发者所带来的奇迹~!
本系列源码请到目录中下载
在线演示地址:http://silverfuture.cn