zoukankan      html  css  js  c++  java
  • 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)静态障碍物哪去了?

  • 相关阅读:
    关于 Wordpress安装时出现“Warning: Cannot modify header information – headers already sent by….”
    C#、.Net经典面试题集锦(二)
    .net 中的事务总结
    什么是webservice
    Web Service与 .NET Remoting
    动态语句exec与sp_executesql执行计划区别
    SQL2005以上版本派生表更新
    清理sql server 2005 服务器名称列表
    如何卸载VS2008
    [怎樣處理]SQL2008、SQL2005類型判斷出錯
  • 原文地址:https://www.cnblogs.com/Jax/p/1670795.html
Copyright © 2011-2022 走看看