Silverlight游戏研究笔记

最近研究深蓝色左手的《C#开发WPF/Silverlight动画及游戏系列教程》

http://www.cnblogs.com/alamiye010/archive/2009/06/17/1505346.html

笔记心得如下:

1.人物移动分成两部分:质心的移动,以及人物自身的移动动画,这不由让我想起了大四最后一门课《刚体力学》。

2.对于Image,在WPF是:

spirit.Source = new BitmapImage(new Uri(@"Player\" + count + ".png", UriKind.Relative));

而在Silverlight则是:

spirit.Source = new BitmapImage(new Uri(@"Player/" + count + ".png", UriKind.Relative));

3.A*算法

终于想明白为什么过去玩游戏,人物总是先走正方形的对角线,然后再走横边,原来是A*算法的一种实现方式。

 

4.要实现Save/Load,就要使用Memento

而要实现Record,就是实现Command,把需要存储的操作作为Command存储到集合中。

 

5.副本地图绝对是好东西,但要结合地图编辑器来实现。

 

6.SL中,使用副本地图。

获取指定图片的某像素点的颜色

private void img3_MouseMove(object sender, MouseEventArgs e)
{
            WriteableBitmap bitmap = new WriteableBitmap(img3, null);

int color = bitmap.Pixels[(int)e.GetPosition(img3).Y * (int)img3.ActualWidth + (int)e.GetPosition(img3).X];

// 将整型转换为字节数组
byte[] bytes = BitConverter.GetBytes(color);

// 将字节数组转换为颜色(bytes[3] - A, bytes[2] - R, bytes[1] - G, bytes[0] - B)
            lbl.Text = Color.FromArgb(bytes[3], bytes[2], bytes[1], bytes[0]).ToString();
        }

 

7.WPF中的System.Drawing.Point,在SL中则是new System.Windows.Point

8.silverlight技巧 获取鼠标滚轮事件 及 判断获取组合键的方法

 

9.貌似Resource对应的../ 但是只能是Clip这样的

而Content对应的/Images 用于WB

 

10.X=Left+CenterX

终于把怪物的坐标轴搞好了。就是把静止物体的公式中obj.CenterY全都改为obj.Y - obj.CenterY,当然CenterX如法炮制。

11.加了一个isMoving,用来控制将每秒都计算坐标修改为每次点击到运动结束。

12.貌似SL中不能控制DispatcherTimer优先级别

13.更新了精灵拾取方法SilverlightControl20_MouseMove,原有方法的for循环中比较次数太多

14.将VLift数组修改为VLift和VLifeMax两个属性

 

15.动态障碍物的修改

1)将martrix修改为fixedObstruction,并增加varyObstruction。

2)将InitMartrix方法修改为InitObstruction,并在结尾增加语句:

varyObstruction = fixedObstruction.Clone() as byte[,];

3)将寻路算法AStarMoveTo、鼠标左击Carrier_MouseLeftButtonDown方法、检查周边障碍物的WillCollide方法中的变量fixedObstruction,修改为varyObstruction

4)修改WillCollide方法,扩大障碍物范围(引进HoldWidth和HoldHeight)

5)建立后台辅助线程

//设置后台线程
backgroundWorker = new BackgroundWorker();
backgroundWorker.DoWork += new DoWorkEventHandler(backWorker_DoWork);

//设置游戏窗体辅助计时器
DispatcherTimer AuxiliaryThread = new DispatcherTimer();
AuxiliaryThread.Tick += new EventHandler(AuxiliaryThread_Tick);
AuxiliaryThread.Interval = TimeSpan.FromMilliseconds(1000);
AuxiliaryThread.Start();

16.升级到28章,图片路径变了

终于感受到SL中那么多函数,都是为了游戏开发而准备的了。

 

17.实现攻击动作

1)为spirit加上了若干属性。5维基本属性,以及20长度数组合成参数

2)修改spirit:

修改了Timer_Tick线程方法,当Action为Attrack的时候,调用新添加的DoInjure方法

添加了LockObject属性,获取或设置锁定的目标精灵对象 

添加了AttackRange属性,获取或设置物理攻击范围(距离) ,暂时只是leader使用

添加了EffectiveFrame属性,获取或设置各动作产生实际效果的帧序号

ChangeAction方法,添加攻击的动作上去,速度即interval,需要再次重构

 

3)修改主窗体

统一了InitFacePlate方法(原先是2个),为此,给QXRoleFace增加了FaceSign、PKMode、VSName和VLevel四个属性

扩充了IsOpposition方法

添加了监视对象:string watchObj = null; 并相应修改了UpdateObjectFacePlate方法

修改了UpdateObjectFacePlate和UpdateLeaderFacePlate方法,为此向QXRoleFace添加了4个属性,没收拾干净,但基本够用了

添加了WillAttack方法,判断是否将要向锁定对象发起攻击

将方法RefreshObstruction修改为RefreshObstructionAndFacePlate,在其中执行UpdateObjectFacePlate和UpdateLeaderFacePlate方法,最后判断是否进入攻击范围,设置LockObject对象

LockObject和watchObj是一个东西,设计冗余了,删除watchObj,把LockObject升级为QXSpirit对象

添加SpiritEdge方法,根据发起攻击者朝向获取被攻击者实际脚底边缘(攻击寻路判断用)

在AuxiliaryThread_Tick线程方法中,这里代码有问题,不该在这里同步刷新主角攻击对象并启动寻路(CompositionTarget里面做这个事情)

重构了SilverlightControl20_MouseMove,原先的代码不好调试

扩充了寻路逻辑

考虑到MouseMove先于MouseLeftDown执行,所以在MouseLeftDown方法中,不需要再次进行命中测试,可以直接使用hitSpriteList这个集合。

在MouseLeftDown方法中,假如点到了自己,直接无视之。

奇怪,在window的ctor中,window的width还未设置。

 

做是做好了,但是人物第二次攻击就会原地不动,这个bug记下来,以后再处理。

 

18.即28_1

添加新的功能

1)禁止右键

2)QXDecoration真神奇,出场特效、脚底光环、鼠标点击光环,分别对应Image\Item下的2、6、1

鼠标点击光环属于不可移动障碍物,要在AllMove中一起移动

3)背景音乐

4)设置spirit头顶上伤害输出动画:-100或Miss

 

重构旧的代码

1)将Super.GetImage(@"/Images/Cursors/0.png")抽象为GetCursor方法

2)使用Point动画,而不再是Double动画

3)为了方便,为spirit添加了RootCanvas和ParentCanvas,前者是当前的根Canvas,后者是父亲Canvas

4)修改了WillCollide的边界控制,因为多了HoldWidth和HoldHeight

5)QXGame_Silverlight3.dll编译动作设置为none

6)重新扩充了AllMove方法,为了支持鼠标光标

7)WillCollide方法要重新,原先的有问题,比如说,spirit进入到怪物障碍物附近攻击,这时候再移动到别处,执行到Carrier_MouseLeftButtonDown方法的

else if (!WillCollide())

这一句的时候,会发现周围是“障碍物”,从而不会移动。要相应替换为:

//点到的地方不是障碍物才能移动
if (varyObstruction[(int)(p.X / gridSizeX), (int)(p.Y / gridSizeY)] != 0)
{
……
}

才正确。

8)spirit貌似始终挡在monster前面哦,这是因为我在InitMonster方法中没有设置怪物的ZIndex。

9)leader或monster伤血时,头顶上会显示值:-100或Miss。

由于原先把这个动态文字显示放到了主窗体中,所以它要随着spirit的移动而需要在AllMove方法中额外处理。

如今我把它放到QXSpirit中,将它的位置修改为:

X = 0,
Y = 0 - Math.Abs(spirit.DescriptionTop), //垂直居顶处理

这样就好了。

10)修复了攻击怪物贾函但是怪物郭晓颖伤血的bug

11)修改主窗体的CompositionTarget_Rendering方法,当进入攻击范围的时候,不再进行循环——通过将isMoving设置为false。

//结束循环
isMoving = false;

 

19.添加死亡效果,项目编号29

怪物篇:

1)在ChangeAction方法中,添加分支

case Actions.Death:
    Timer.Interval = TimeSpan.FromMilliseconds(300);
    CurrentStartFrame = EachActionFrameRange[0] + EachActionFrameRange[1] + EachActionFrameRange[2] + EachActionFrameRange[3];
    CurrentEndFrame = EachActionFrameRange[0] + EachActionFrameRange[1] + EachActionFrameRange[2] + EachActionFrameRange[3] + EachActionFrameRange[4] - 1;
    OldAction = Actions.Death;
    break;

2)在DoInjure方法中,添加BattleHandle方法,进行战后处理

3)在spirit中,添加2个死亡标记:

bool isDeath = false;
int deathDelay = 0;

为防止和主线程不同步,在spirit的Timer_Tick方法的一开始就要判断,一旦挂了,就保存它的尸体,6次循环过后,再火化(执行RemoveObject方法)。这期间就有足够的延迟时间了,不会发生null的异常事件。

if (isDeath)
{
    deathDelay += 1;
    if (deathDelay == 6)
    {
        Super.RemoveObject(this, true);
    }
    return;
}

 

同时,在spirit的Timer_Tick方法中添加分支,设置isDeath变量——死了:

case Actions.Death:
    isDeath = true;
    break;

 

4)深蓝色右手设置死亡的操作,即spirit.Action = Actions.Stop;我找不到

我是在BattleHandle方法中直接设置的,这样做可以让spirit停下来,而不是继续鞭尸。

 

20.让怪物动起来,项目编号29_1

1)在Super中,为所有spirit建立storyboard字典

Super不能啥都干啊,抽象出StoryboardRegFactory

2)在RefreshObstructionAndFacePlate中,把刷新动态障碍物方法抽象为UpdateMonsters,并在其中添加逻辑,侦测怪物SeekRange,锁定怪物的LockObject

3)因为怪物也有AttackRange,所以leader不需要有脚底障碍物,holdwifth和holdheight都是0

4)需要添加spirit参数的方法:WillCollide、AStarMoveTo、MoveTo

抽象出ArriveTarget和AllActive方法

5)把unitMoveCost升级为spirit的VRunSpeed属性,leader为70,怪物的从xml中读取

6)杀死怪物或者点击到别处,怪物面板消失,分别在CompositionTarget_Rendering和Carrier_MouseLeftButtonDown方法中判断

7)isMoving标记不再需要了,不划算,还不如每次都AllMove

8)发现一个bug,怪物死后再次点击尸体位置(鞭尸),会死在Carrier_MouseLeftButtonDown的

if (IsEfficaciousSection(hitSprite[i].EfficaciousSection, e.GetPosition(hitSprite[i])))

这里的hitSprite[i]就是死去的怪物,还没火化,所以会报异常,e.GetPosition处理不了尸体。

为此修改了GetHitSprite方法,

//从2开始,因为还有两个对象在最早被发现
for (int i = 2; i < hitElements.Count; i++)
{
    if (hitElements[i] is QXSpirit29_1)
    {
        QXSpirit29_1 obj = hitElements[i] as QXSpirit29_1;
        if (obj.VLife != 0)
            hitSpriteList.Add(obj);
    }
}

这样就只把没死的怪物加进hitSpriteList中

 

21.项目29_2,再次重构

添加新功能

1)导入经验值和升级体系

数据:在XML中添加LevelUp节点,为怪物添加经验值

创建:单件LevelUpExperienceSingleton

读取:在xml读取LevelUpExperienceList数组

分配:为leader设置VExperience和VExperienceMax

使用:在QXSpirit中添加EarnExperience方法

显示:更新leader面板中的level和exp,考虑到面板上的活力值Energy没有用,将其修改为经验值

 

重构旧代码

1)把gridSize拆分为gridSizeX和gridSizeY

2)fixedObstruction数组的维度设定是有章可循的,为此添加GetMatrixSize方法

3)QXSpirit的DiscriptionTop的初始化

leader.DescriptionTop = (leader.Body.Height - 160) / 2 - 30;

 

22.项目30 施放魔法

设定leader的magic为600

ActiveMonster

 

 

 

重新理清人物动作逻辑

 

 

貌似怪物死了 没有移除相应的storyboard

 

 

23.第3次重构

1)将doubleAnimation修改为PointAnimation,为此修改接口IObject

机制为:Coordinate属性一旦改变,就会修改坐标对象的Left和Top属性

这样就省去了额外再设置Left和Top,自动callback蛮好的。

累死我了,改了144处,还要改掉AStarMove和NormalMove中的X和Y关键帧动画

 

不能这样写:

this.Coordinate.Y -= 3;

因为Coordinate是一个Point,它是一个结构体

 

leader.Coordinate = new Point(x, y); 这会导致触发回调函数ChangeCoordinateCallback——使用leader的CenterX和CenterY,更新leader的Top和Left。

我们是否可以写成:

leader = new QXSpirit()
{
    Name = "leader",
    Type = Spirit.Leader,
    Direction = direction,
    CenterX = 75,
    CenterY = 110,
    Coordinate = new Point(x, y),
};

 

为Coordinate设置值的时候,CenterX和CenterY已经有值了么?

测试发现,上述语句由于Coordinate = new Point(x, y),位于CenterX = 75, CenterY = 110, 之后,所以,触发回调函数ChangeCoordinateCallback的时候,CenterX和CenterY已经有值了。

如果把Coordinate = new Point(x, y),的位置上调到CenterX = 75, CenterY = 110, 之前,那么回调函数ChangeCoordinateCallback的触发会发生在CenterX = 75, CenterY = 110, 之前,而此时CenterX和CenterY还都是0。

同理,我要把ZIndex的设置放到 CenterX = 75, CenterY = 110, 之后

 

Left、Top也都不要了吧,ZIndex还需要

 

2)统一所有物体的坐标变量

3)将AllMove修改为Canvas移动

4)对Super进行重构,图像缓存独立出来,music扔回到主窗口,

5)重新规划IObject接口

6)去掉QXSpirit的Type属性

 

7)把魔法补齐,从xml读取数据,魔法伤害计算,右键点击与魔法施放位置调整

8)静态障碍物哪去了?

posted @ 2010-02-21 23:44  包建强  Views(1590)  Comments(0Edit  收藏  举报