posts - 72,  comments - 4,  trackbacks - 0
 
由于想在游戏中(非mfc程序)调用一个dll用来监控游戏中一些数据变化,而这些数据可以触发式变动,所以想到用mfc来弄,
但网上查的
“非mfc程序调用mfc扩展dll”似乎有点麻烦,也没有什么成样的例子。
后来还是自己对比正常的mfc程序加载方式修改出来了,步骤如下:
1.  
exe工程要设置成Use Standard Windows Libraries或者Use MFC in a Shared DLL(因为我们不用mfc,所以设置成前者)
2. 
dll工程用vc向导创建mfc扩展dll
3. 在dll工程中拷贝相关的app,mainfrm和view以及doc框架(可以用vc向导创建一个正常mfc框架程序用来拷贝)
然后在dllmain的attach中加入如下语句即可:
//----------------------------------------
// AFX internal initialization
if (!AfxWinInit(hInstance, 0, "", 1))
return 0;
// Register the doc templates we provide to the app
CWinApp* pApp = AfxGetApp();
ENSURE(pApp != NULL);
pApp->InitApplication();
pApp->InitInstance();
//----------------------------------------
4. 如果是隐式调用的话,在exe中加入:
#define AFX_EXT_API  __declspec(dllexport)
#include "..\mfcdll\mfcdll.h"  // 这个是dll要导出的东西
这样就可以了

5.显式调用的话,用LoadLibrary先装载,再获取对应导出函数调用就应该也可以了(这个没试)

6.上述处理后只能显示,但按键接收还不行,因为消息循环没有地方调用。。。它原来是系统调用app->run(),在其中循环处理的。
如果直接在dll中调用app的run()的话,mainfrm的PreTranslateMessage就能收到按键消息了。但这样由于是主线程中调用app->run(),而run()中
是消息死循环,所会exe会没机会响应。。。所以解决办法是模拟app->run()写一个runoneframe()函数,然后每次刷新一下(或者创建新线程来刷新, 临时试了一下会crash,我没用这种办法,先不查了,不过理论上应该可以的)
这样就ok了(view中按键自己再处理吧,这里不提了)
posted @ 2012-09-14 15:56 flipcode 阅读(1292) | 评论 (0)编辑 收藏
火矩之光中的导航图:
火矩之光 中已经抛弃高位图地形之类的作法,换用了模型,为了能作出迷宫,把地图分块,每块是一个模型,
每个模型有几条通路,这样各通路可以据算法得出随机组合的谜宫,为了丰富,每个入路有好几套模型可以随机
选取,然后根据所选择的模型再随机组合出复杂的谜宫。
而对于路点,则是平均格子求得的,这点跟普通网游的作法还是有点类似的,
不是真正意义上的3维寻路,还是2维的, 作法应该是从上往下跟碰撞模型进行垂直求交得出路点,
然后再对路点进行通路测试,产生导航图。(它不是用几何网格生成的导航图)

看下图我的一个测试:
左边是一个模型,右边是个有楼梯的另一个模型(它是个入口)。蓝色格是导航点。蓝色块是寻出的路点。

posted @ 2012-08-27 10:35 flipcode 阅读(213) | 评论 (0)编辑 收藏
泰坦之旅的ai

titan quest的AI用的是切换式的状态机,而寻路用的是path engine第3方库,游戏中有一个任务编辑器主要是生成每一个任务,每个任务中可以生成多个触发器(trigger),每个触发器可以生成一系列条件(condition),并可生成条件成立时要触发的动作(action). 这个有点类似war3的事件编辑器。

以下是切换式状态机跟踪的一些记录,很乱,没写总结,只是用于备忘。。。
AI移动跟踪
WinMain消息循环中的Game::Run()中先
1. gGameEngine->GetFrustumForPlayer(updateFrustum, player->GetCoords().origin); 得到frustum

2. gEngine->Update(&updateFrustum);进行更新, 其中
   调用 world->Update(worldFrustumList);

   然后遍历每个frustum取得对应region进行更新

   a. 看看当前region是否与frustum相交,如果是则load,否则扩大一点再相交,这时如果相交则将添加到后台加载。
   b. 查看portal相关region进行更新
   3. 查看connectedRegions(在地图装载得到玩家出生位置后进行玩家所在region扩大后跟其它region判断相交所得)进行更新 

3. Region的更新中进行level更新:
  level->Update(frustumList, numFrustums, elapsedTime);
  在其中先取得在frustum中的Entity, 然后再遍历更新所有Entity,
  之后再更新Entity所在4叉树空间.

4. 角色的更新:
   Entity的更新中, 先UpdateSelf再UpdateAttachedEntities
   (Entity中有PhysicsRigidBody成员physicsObject)

5. UpdateSelf会跑到Character::PreAnimationUpdate中执行 baseController->Update(localTimeDelta);从而跳到ContrallerAI中执行GetExecutingState()->OnUpdate(deltaTime); 从而到达ControllerNpcStateIdle的更新中进行状态切换到SetState("Wander", ControllerAIStateData(0, 0, 0)); 
之后再跑到ControllerNpcStateWander::OnBegin()
处理:
    int closestPoint = GetClosest(GetController().GetWanderPoints());
    GetController().SetCurrentWanderPoint(closestPoint);
    if (!MoveToCurrentWanderPoint())
    {
        SetState("Idle", ControllerAIStateData());
        return;
    }
这个在MoveToCurrentWanderPoint()函数里从队列中取出当前目标点并ControllerAI::WalkTo
其中会GetController().WalkTo(location, target);即ControllerAI::WalkTo(。。), 这会执行:
 HandleAction(new WalkAction(GetParentId(), GetAI()->GetPathPosition(), location, target));
这个会执行:
        SetCurrentAction(action);
        GetCurrentAction()->Execute();从而运行了WalkAction::Execute(), 这其中又调用了Character::WalkTo
 这又会:
    movementMgr->SetNewPathTarget进行处理


最后在CharacterMovementManager::Update()中进行角色位置更新:

        CreateLocalPath(deltaTime, speed);

        if (!MoveDownPath(deltaTime, speed))
        {
            return false;
        }
        UpdateCharacterPosition(deltaTime, speed);

void UIDialogWindow::OnOpen()会调用 myNpc->AddSocialTarget( target );
在void ControllerNpcStateIdle::OnUpdate(Time deltaTime)中判断如果有SocialTarget则进入Chat状态处理

【状态处理例子】
Monster的初始状态是Idle,在Monster的更新函数里:
一)。进行搜敌,并切换成pursue状态
,并调追捕状态的OnBegin()函数处理,如果Monster不能行走则切回Idle状态,否则如果搜不到敌
人则切换到Return状态,否则根据当前技能id找出要移到的位置点.
------------------------------------------------------
【注意】找到要移到的位置点细节:
I. (Character::GetMoveToPoint)找出目标点:
  1. 目标是自己则直接return自己位置
  2. 没有目标则保存goalPoint=目标点,distance=技能施放范围,待后处理.
  3. 目标是FixedItem则return FixedItem->GetMoveToPoint(..)里进行处理
  4. 目标是StrategicMovementBase,则return sm->GetMoveToPoint(..)里进行处理
  5. 目标是Entity,则goalPoint = entity->GetCoords().origin;并且如果entity阻挡则让goalPoint回移一点以免浮点出错?否则distance=GetExtents() + entity-

>GetExtents();待后处理
  6. 目标是Character,则,
     a. 如果是朋友
       1)如果当前是移动状态,则要求目标给出DefenseSlot(防御位置点)作为goalPoint直接返回.
       2)否则如果能直线通路到目标点的话就直接返回离目标比较近的一点(去掉半径),不能直通则返回0点  
     b. 如果是敌人
       1)如果没有技能,则goalPoint=目标点,distance=GetExtents() + target->GetExtents();待后处理
       2)如果技能不需要AttackSlot或者this是Player, 则
        goalPoint=目标点,distance = GetExtents() + target->GetExtents() + skill->GetRange();待后处理
       3)否则直接返回目标算出的AttackSlot攻击点位置.
  上述2和5以及6中的b.的1)和2)需要待后处理的最后通过 
    WorldVec3 finalPoint = movementMgr->GetPointAwayFromGoal(goalPoint, distance);
  得到最终位置, 这个位置还要特殊判断一下如果不在Region中或者路径不能到达的话,则直接用TranslateToFloor到goalPoint.

【说明】:
  1. 什么是AttackSlot/DefenseSlot:
每个角色可以有n个x距离的AttackSlot/DefenseSlot,它会在周围x半径的圆上平分出n个位置点,当有其它人要攻击它或者要来帮助(防御)它时,它就会在旁边找一个较近的还

没其它人用过的slot分给这个其它人。
  2. movementMgr->GetPointAwayFromGoal()函数细节:
    先是FindPath(目标)得出path,再用path->Advance(pathLength - distance)得到回退一点的位置。

II. 找到目标点后,还要调用movementGoalManager->GetClosestMovePoint(目标点) 进行处理:
    这个函数主要是给范围武器用的,如果不是使用范围武器的Monster则不会调整目标点。
    如果是的话则遍历全局对象movementGoalManager中的m_MoveGoalMap目标点映射表,求出其它在同一region中的Monster
    所在目标点跟当前Monster所在目标点的距离,如果距离比较近则调用GetPointAwayFromGoal(目标点, 3.0);调整当前Monster的目标点回退一点,并将些处理后的位置及些

Monsterid映射到m_MoveGoalMap目标点映射表中。这样遍历过所有其它Monster的目标点进行一一检测处理后就会尽量避免与其它Monster挤到一起。
------------------------------------------------------

找到要移到的位置点之后,
1. 用(CloseEnoughToUseSkill(GetCurrentEnemy(), GetCurrentSkillID()))判断是否在技能攻击范围内,
如果在则用IsPathClear(GetCurrentEnemy())来判断是不是到直通目标,是则切换成Attack状态后返回,否则切换成NavigateObstacle状态后返回。

2. 否则敌人不在攻击范围内就看当前是否已站在目标点,是则切回Idle状态后返回

3. 不在目标点则看是不是能够移到目标点,不能则切回Idle状态后返回。

4. 能移到目标点则MoveTo到目标点.
   这个MoveTo会调用
   HandleAction(new MoveToAction(GetParentId(), GetAI()->GetPathPosition(), location, target, GetAI()->GetSkillReferenceNumber(skill), 1.0, animType));
   这个会执行到MoveToAction, 其中会转调:
        monseter->SetCurrentAttackTarget(targetId, location, skillNumber);
        monseter->SkillWarmUp( skillNumber, false );
        monseter->MoveTo(location, GetBlendTime(), animType);
        monseter->PlayLoopingRunningSound();
  而monseter->MoveTo又会调用 movementMgr->SetNewPathTarget(movementMgr->GetPathPosition(), surfacePoint, alreadyThere))
  然后再用SetActionState(Character_ActionState_Move);设置Action的状态为Move,并通过PlayAnimation播放run动作(即调用GetAnimationBase( type ).PlayAnimation( 

actor, selection, speedModifier, loop, iteration ),这个可以参考我另一个动画跟踪文档看细节)

二)。搜敌后会接着调用ControllerAI::Update()更新函数:
   1. 先进行当前状态更新()
      由于前面Monster切换到了pursue追捕状态,所以执行到
      ControllerMonsterStatePursue::OnUpdate(),其中:
      a. 追捕所用时间过了,则切换回return状态
      b. 重新选择技能时间到了则重新找出一个bestSkill.(这也避免了万一当前技能是melee,而玩家总是绕着Monster转,怪就会不停地追不上而没法肉搏攻击)
      c. 用CloseEnoughToUseSkill判断是否够技能施放距离,够的话用IsPathClear判断攻击方向是否可通,是则转Attack状态,不通则转NavigateObstacle状态.

   2. 再遍历执行m_PreloadQuestActionList中action.

上述都在【更祥细一点】中1。Character::UpdateSelf()中进行
接着会到【更祥细一点】中2。 Update subsystems:中的FollowPath()进行真正的移动处理.
posted @ 2012-08-13 10:09 flipcode 阅读(337) | 评论 (0)编辑 收藏
dota中的道具/技能及动作状态机 相关原型 设计备忘::
//xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
// dota中的道具/技能及动作状态机 相关原型 设计备忘::
/*
                                             flipcode@qq.com 的草案备忘(测试通过)
   商店用拥有很多kToyItem供购买,角色通过购买也可以得到很多kToyItem。
   kToyItem用于kToy物品类的显示封装,对应有一个物品类kToy以及储存在哪个格子中,价格多少等信息。
   kToy中包含3种类型:1.kEquip(装备),2.kUse(使用), 3.kSkill(技能)
   这3个类中各自包含一个listAction(动作列表),列表中存放的是kAction动作基类。
   kAction类中有Select()和IsValid()以及Execute()函数;
   1. kEquip中的Update()中调用listAction中每一个IsValid()函数来判断是否执行对应的Execute();
      
   2. kUse中的Use()函数中调用listAction中每一个IsValid()函数来判断是否执行对应的Execute();
   
   3. kSkill中的select()函数遍历listAction中每一个Select()函数来判断是否能选择该技能,都通过才选择。
      kSkill中的Start()函数中调用listActionStart中每一个IsValid()函数来判断是否执行对应的Execute();
      kSkill中的Cast()函数中调用listActionCast中每一个IsValid()函数来判断是否执行对应的Execute();
  说明:
  kSkill设计时考虑到施放前摇,故用了start()函数来播放前摇动作/特效,然后等待前摇时间完成再调用cast()函数来播放对应的施放动作/特效。
  当特效完成后再处理listAction(动作列表)。dota中还有后摇,我理解成技能冷却时间(不知道对否),当cast()时,skill就可以开始冷却了。
   [说明]
   角色属性有:基础属性,附加属性(直接+),加成属性(*(1+比例)),而
   kAction的派生类kActionProperty专用于处理kEquip(装备)提高属性: 
   它用VARY_TYPE类型指明针对某种属性, 如: str, int, dex, HP/MP(min/max), atk(min/max)。。。等.
   它用PROPERTY_METHOD区别是直接加还是比例乘:add/rate。
   这样角色如果拥有kToyItem的话,那么在更新中就会调用每个物品的kEquip的Update,从而把属性更新到角色的add_data[类型]和rate_data[类型]中,
   接着角色的更新就会用(base_data[]+add_data[])*(1+rate_data[])的公式来处理之(当然,dota的一些特殊的属性使用另外的计算方法)
   kAction可以方便地扩充很多普通派生处理类,只要在相应的Execute()进行处理即可实现想要的功能,比如可能派生一个名为kActionHurt类,
   然后把它加入到kUse/kSkill中的listAction列表中,这样只要点击使用,即可Execute中处理加血/扣血。
   另外从kAction中派生的还有一些特殊类型:
   1. kActionEffect(包含listAction成员):
      kActionEffect执行Execute()时会调用全局的kEffectManager(特效管理器)的PlayEffect(effect_class_name)函数来产生一个kEffect的派生类对象, 并转让listAction给它。
      kEffectManager用来管理listEffect列表,更新处理其中每一个kEffect。
      kEffect类中包含一个listAction(动作列表)指针,它是由kActionEffect在产生它时传递过来的,这样在特效完成并且条件成立时(比如命中敌人)调用listAction中每一个IsValid()函数来判断是否执行对应的Execute();
   2. kActionState(包含listAction成员):
      kActionState执行Execute()时会调用全局的kStateManager(状态管理器)的PlayState(state_class_name)函数来产生一个kState的派生类对象, 并转让llistAction给它。
      kStateManager用来管理listState列表,更新处理其中每一个kState。
      kState类中包含一个listAction(动作列表)指针, 它是由kActionState在产生它时传递过来的,这样在该状态完成时会调用listAction中每一个IsValid()函数来判断是否执行对应的Execute();
  注意: kState队列的执行优先于kAIState队列,只有kState列表为空时,kAIState才有机会执行.
  关于kState和kAIState的区别:
  a. 动作触发的状态机:
  kState主要是kAction(动作)触发引起角色的一些临时被动行为,比如kState被击退状态,被晕状态,状态之间可以并行,或者串行(通过kState中的listAction列表挂接,在kState完成时遍历调用listAction)。
  比如:
     可以将kActionHurt加入到kStateStun(被晕)状态中的listActon中,然后再把kStateStun加到kStateThrustBack(被击退)中,然后再把kStateThrustBack加入到kShotEffect的listAction中,接着给kActionEffect设置对应的kShotEffect
  并把kActionEffect加入到kToy中的kSkill的listActon中.这样,当技能使用时就会触发一个kActionEffect播放kShotEffect,这个kShotEffect播放完成时会触发kStateThrustBack将角色击退到一边,退到一边完成后接着触发kStateStun让角色晕上一会。
  而如果前面的kShotEffect是范围特效的话,那在特效伤害范围的角色都被击退后再晕上一会(由于击退和晕是动作状态优先于角色的AI状态,所以这时角色的AI是不运行的,只有等击退后晕完了AI才醒过一继续)。
  b. AI行为状态机:
  kAIState主要是由kAIBrain(大脑)思考引起的一些主动的AI行为,比如kAIRoam漫步, kAIPursue追捕等,各AI状态之间不能并行或串行只能切换。在任何时候包括在kAction的处理中也可以进行角色的kAIState切换。
  
   3. kActionTrigger:
      kActionTrigger中拥有一个事件名称列表,当该action被execute时会通过kEventManager->PostEvent(Event_Name)来发出事件消息。这时事件监听列表中的对应事件号的触发器先判断kCondition是否成立,是则调用相应的触发器的kAction动作。
   关于触发器:
   kTrigger触发器,拥有listEventName, kCondition, kAction。事件管理器kEventManager可以创建kTrigger,并添加触发器监听的事件列表(listEventName),条件(kCondition),以及动作(kAction)。 
   当事件发生时可随时调用kEventManager->PostEvent(Event_Name)来发出消息,事件的监听者kTrigger先判断kCondition是否成立,是则调用kAction。
   调用的kAction即前面所说的动作基类,当动作完成时可以再次PostEvent(...)以便触发另外的触发器。
   4. kActionPose:
      这个Action只是简单地调用一下角色的动作播放。
   5. kActionSleep(包含listAction成员):
      这个kAction只是延迟一段时间,时间到了再调用listAction各成员的execute()函数.
   由以上设计可以知道每一个kToy道具均可以由不同的处理函数并行及串行(注意:这里说的串/并行跟多线程无关,概念不一样)组合而成,这样就可以实现动作或特效的串行/并发执行,以及触发相应的处理。
*/
//xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
posted @ 2012-08-13 09:58 flipcode 阅读(815) | 评论 (0)编辑 收藏

mythos中的ai是并发的栈式状态机:

拿wolf作为例子:
一。配置文件:
在ai目录中的wolf.xml配置它所有的行为(包括每个行为发生机率,参数,以及执行函数):
其中执行函数以及对应skillid如下:
1.
  <nBehaviorId>move - approach target</nBehaviorId>
  <nSkillId>monster melee</nSkillId>
2.
  <nBehaviorId>skill - do skill</nBehaviorId>
  <nSkillId>monster melee</nSkillId>
3.
  <nBehaviorId>skill - do skill</nBehaviorId>
  <nSkillId>Fidget</nSkillId>
4.
  <nBehaviorId>move - wander</nBehaviorId>
  <nSkillId/>

二。ai更新:
   SrvGameTick--》GameEventsProcess--》AI_Update()
  在AI_Update中遍历执行该unit的所有行为(behavior) :
  1. sBehaviorApproachTarget()
     查找对应的目标,发出朝它移动指令
    
  1. sBehaviorDoSkill()
     如果有目标则执行sSkillExecute进行技能施放
    
  2. ...类似功能处理
 
  。。。
 
  【说明】:
    mythos的行为是并发的栈式,最大可以有5个栈, 用
    int nIndex = tContext.pnStack[ tContext.nStackCurr ]这样的结构进行处理,
    通过pTable->pBehaviors[ nIndex ]得到对应的行为处理函数进行执行处理。
       
    a. 并发:
    每执行完一个行为函数后 tContext.pnStack[ tContext.nStackCurr ]++; 这样就换到unit的下一个行为函数再执行。
   
    b. 进入栈(子函数):
    tContext.nStackCurr为栈下标,初始tContext.nStackCurr=0,即为第0个栈,
    可以设置tContext.nStackCurr++;并且tContext.pnStack[ tContext.nStackCurr ]=nBranchTo(要走的分支号)。这样来
    执行分支函数。 当分支函数完成时tContext.nStackCurr--再回来上一级。
   
    另外有些行为函数是执行一次的,执行完了就会把它从列表中删除。下次不会再遍历到.


posted @ 2012-08-13 09:51 flipcode 阅读(175) | 评论 (0)编辑 收藏
gunz的ai处理(备忘)
[更新]
一个Actor更新中:
1. m_TaskManager.Run(fDelta);
2. ProcessNetwork(fDelta);
3. ProcessAI(fDelta);
   处理actor->Brain->Think()中
bool bFind = FindTarget();
if ( bFind)
{
  ProcessBuildPath( fDelta);
  ProcessAttack( fDelta);
}
4. ProcessMovement(fDelta);
5. ProcessMotion(fDelta);
一。ProcessAI()
    actor->Brain->Think()处理中产生一个Task
    一个任务有 移动,攻击,延时,转向等,其中攻击有肉搏,范围,技能
    比如:actor->Brain->Think()中发了现敌人后在ProcessAttack()中产生一个skilltask存入到
    actor->m_TaskManager中
二。m_TaskManager.Run()
   在在taskMgr中处理m_pCurrTask的3个步骤(start,run,complete):
   举例: 如ZTask_Skill:
   1. OnStart()中
     a.parent(是一个actor)->Skill(m_nSkill); // 进行动作输入(动作用了m_AniFSM进行管理)
     b.ZPostNPCSkillStart(ownerid,nSkill,Targetid,TargetPos);
       这会生成一个id为MC_QUEST_PEER_NPC_SKILL_START的cmd并push到全局的m_CommandManager中
   2. OnRun()中
       if时间到了则ZPostNPCSkillExecute(ownerid,nSkill,Targetid,TargetPos);
       产生的命令id==MC_QUEST_PEER_NPC_SKILL_EXECUTE;
   3 . OnComplete()中
       ZTask_Skill中无处理.
三。m_pGameInterface->Update()中从m_CommandQueue中取出cmd, 对其:
    1. SendCommand(pCommand);
    2. OnCommand(pCommand);
       switch(cmd->id)调用对应的处理函数,
      例如:
      a. id==MC_QUEST_PEER_NPC_SKILL_START时将调用OnPeerNPCSkillStart()
         在其中通过uidOwner找得actor得到它的ZModule_Skills处理模块传入cmd的参数处理之.
         这会进行发起特效处理
      b. id==MC_QUEST_PEER_NPC_SKILL_EXECUTE时时将调用OnPeerNPCSkillExecute()
         在其中通过uidOwner找得actor得到它的ZModule_Skills处理模块传入cmd的参数处理之.
         这会进行攻击特效处理:
         1). 如果是bHitCheck标志的特效,则转让全局的武器管理类处理:
             m_WeaponManager.AddMagic( this, vMissilePos, vMissileDir, m_pOwner);进行处理
    这里判断如果循环次数减到0,则m_bEnable = false;
         2).否则:
           a. 如果有相机震动标志,则通知照相机震动处理
             GetCamera()->Shock( fPower*fDefaultPower, fDuration, rvector( 0.0f, 0.0f, -1.0f));
           b. 遍历所有角色,检测是否范围内以及抗性没抵消完伤害:
              (1)是攻击则
                 pObject->OnDamaged(..)
                 否则               
pObject->OnHealing(..)
              (2)限速:
                 pMovableModule->SetMoveSpeedRestrictRatio( 0, fDuration);
               
              (3)击退:
                 pObject->AddVelocity( m_pDesc->fModKnockback * 7.f * -dir);
          3). 特效燃烧效果:
if(m_pDesc->szCastingEffect[0]) {
    if(type != eq_parts_pos_info_etc)
    efgmgr->AddPartsPosType(m_pDesc->szCastingEffect, Targetid,type,m_pDesc->nEffectTime);
else 
efgmgr->Add(m_pDesc->szCastingEffect, vPos,vDir,OwnerID,m_pDesc->nEffectTime);
}
          
if(m_pDesc->szCastingEffectSp[0]) {
ZGetEffectManager()->AddSp(m_pDesc->szCastingEffectSp,m_pDesc->nCastingEffectSpCount,
vPos,vDir,m_pOwner->GetUID());
}
           3). 提示信息显示处理
           4). 声音播放处理.
四。ZWeaponMgr处理:
    上述的OnPeerNPCSkillExecute()处理中发现是飞行特效转交给ZWeaponMgr来处理,所以这里说一下
   在这里会处理特效模型飞行并处理Explosion( type, pPickObject,pickdir);爆炸伤害!转调用到pTarget->OnDamaged(..)
posted @ 2012-08-13 09:49 flipcode 阅读(281) | 评论 (0)编辑 收藏
posted @ 2012-07-03 10:55 flipcode 阅读(177) | 评论 (0)编辑 收藏

载图,角色用的gunz模型测试:


界面用群雄逐鹿的测试:
posted @ 2012-05-10 15:29 flipcode 阅读(169) | 评论 (0)编辑 收藏

posted @ 2012-04-01 20:04 flipcode 阅读(194) | 评论 (0)编辑 收藏




图:

posted @ 2012-03-23 21:26 flipcode 阅读(187) | 评论 (0)编辑 收藏
仅列出标题
共8页: 1 2 3 4 5 6 7 8