找回密码
 点一下
查看: 6894|回复: 16

[教程&演示]演算体(Actor)相位技术系列其之一:为不同玩家异步显示/隐藏/销毁演算体

[复制链接]
发表于 2011-3-21 20:54:27 | 显示全部楼层 |阅读模式
前言:

这是我的“演算体(英文版中的Actor)相位技术”教程的第一篇,本系列预计将有三篇,将全面阐述我弄出来的这个“SC2 Actor相位技术”。

今天我要写的第一篇其实月初我已经发在老外的论坛过了:
http://forums.sc2mapster.com/res ... oy-any-given-actor/

反响不错,不过由于这个月事情实在太多,我之前一直没功夫写中文版。至于我为啥先发在老外那里?突发奇想而已,没啥特别理由。


所谓相位技术,玩过WOW的同学可能会有所了解,就是两个玩家观察同一个地点,却看到完全不同的东西。听到不同的声音。这样,每个玩家都相当于在玩单机角色扮演游戏。可以通过任务来对世界造成实际的变化和影响,但又不会影响到其余玩家的游戏进程。

这个技术是由异步地显示和隐藏视觉效果和声音效果而实现的。此前我在War3里搞过活动,让大家来比赛实现这个东西。也有不少不错的作品。

不过War3中的异步支持是很差的,除了极少一部分可以异步创建的效果外,其余基本都是模拟的,本质上就是什么东西都用单位来模拟,然后单位由于可以对某个玩家隐形,对其它玩家显形。这样就可以变相实现异步显隐。

但是这样确实是很土的,比方说你需要让子弹能异步就需要用单位来模拟war3里所有的子弹。你就算真的有毅力做到,实际上有点本末倒置,付出和收到的效果差距太大。要推广也是没可能的。扯远了,关于war3的话题到此为止。本教程是关于sc2的。

总而言之我在SC2里实现了相位技术,然后于是本教程就是关于如何实现异步演算体(Actor),异步地隐藏和显示任意指定的一个演算体。并附了一个演示地图,里面有两个自定义函数“Show/Hide Actor For Player Group(为指定玩家组显示/隐藏指定的演算体)”和"Destroy Actor For Player Group(为指定玩家组销毁指定的演算体)"。

这两个自定义函数可以让不同的玩家在同一个地方看到/听到不同的东西(包括单位、装饰物、头像、声音、效果、模型、文本、地面喷涂,甚至于地形变形都能异步)。另外别忘了,在SC2里,子弹、物品和可破坏物都已经包括在单位里了。

由于本教程是Actor中的高级应用,所以对Actor了解不深的同学可能会完全看不懂。不过没关系,反正教程就在这里,先看一遍加深点印象,至少知道Actor系统能做到这个,以后掌握了足够的知识再来回顾本文就好了。

另外,虽然我大部分时候要求大家能用数据编辑器实现的东西都用数据编辑器实现,不过现这个东西可不同,单独靠数据编辑器或者单独靠触发器都是不可能实现我们想要的效果的——想想就知道,数据编辑器里根本就无法指定玩家号,怎么可能对指定玩家进行操作呢?当然还有其余更深层次的原因,在教程中会慢慢提到的,于是我们开始吧:


实际上演算体系统本身是允许异步的。比方说,某些Actor或者粒子特效可能只能创建在拥有超级显卡的电脑上,或者游戏视频设置开得很高的电脑上。一台好的电脑和一台差的电脑联机,它们并不会因为好的那台电脑上多了一些特效而导致断线。因为Actor系统只影响视觉特效和声音,并不会真正改变游戏进程。

不过虽然如此,目前Actor系统没有任何官方手段让你只对指定玩家进行进行发送消息的操作。(我们现在有一个Actor-Filter属性,不过这个属性的过滤条件只有敌我中立全部四种,而且我们无法在运行时改变它,无法通用。而且只对效果域中的actor有效。对单位,文本什么的都没用。)也许以后资料片会加入异步Actor的功能也说不定,但是至少目前没有。所以我们需要用一些很特殊的方法来做到:



超越

尽管我们无法只对特定玩家发送Actor消息,Actor编辑器中的演算体条件(Actor Term)却允许我们对本机的视频设置(最低<->极限)进行判断,毕竟这个系统需要针对不同的视频设置来决定一些特效是否显示。那么如果我们有办法用触发器来动态改变某台特定电脑上的视频设置,然后用演算体条件来判断这个设置的值,我们不是就能曲线救国了么?

不过也很显而易见的,大部分视频设置是无法通过触发器在运行时动态修改的(否则我们就能很轻易做出一张地图来烧你显卡不是?),而非视频相关的游戏设置就算能改,Actor系统也不认。不过幸运的是,有一个而且仅有一个差点就不算视频设置的设置,可以让我们用触发器来修改,而且正巧又能用Actor Term来判断——“Show Flyer Help(显示飞行单位映射)”。

飞行单位映射是个什么东西?就是通常当你选中空中单位时,从机体延伸到地面的那根垂线(我方是绿色,敌方是红色,友方/中立为黄色)。这个是为了让你能正确目测飞行单位位置和高度用的。在游戏视频设置里你可以完全关闭这个功能,或者让它只在选中时显示,或者不管是否选中一直显示。也就是该设置有三个选项:全部、选中、无。

偏偏触发器里就有这么条动作,可以强制修改指定玩家组的飞行单位映射设置:“Lock Flyer Helper Display(锁定飞行单位映射显示)”。还有另一条动作"Unlock Flyer Helper Display(解锁飞行单位映射显示)",可以对指定玩家组使用,让该玩家电脑上的映射设置回到他们自己原本的设定。好方便,都省去了我们记录玩家设置并恢复的过程(其实我们甚至都记录不了,触发器无法判断这个设置的当前状态)。

于是我们的第一条思路会像是这样:

假设我们要对玩家1隐藏一个Actor那么——

在触发器模块中,我们使用触发器先将玩家1的飞行映射设为“全部”,然后千万别忘记也要将其余玩家的飞行映射设为“无”。然后用触发器中的发送演算体消息动作向我们要操作的演算体发送一个信号(比方说"Hide")。再解锁所有玩家的飞行映射设置。

在数据编辑器模块中,我们修改那个Actor,加入一个新的事件,捕捉触发器发来的那个信号,并使用条件来判断本机的飞行映射,如果是“全部”,则对自己发送"SetVisibility"信号来隐藏自己。Actor事件的内容会像是这样:
[codes=xml]
<On Terms="Signal.*.Hide; FlyerHelper All" Send="SetVisibility"/>
[/codes]

如果你想提高自己的Actor技术的话可以自己测试一下,不过限于篇幅问题,我这里直接就说答案了:完全有效,能够成功地对玩家1隐藏那个演算体。

我们达成了一次超越。鼓掌!啪嗒啪嗒...



于是本篇完结?才怪,这跟我们真正想要实现的效果还差得远呢。



超·超越

刚才的那次测试证明了,通过刷一些特殊手段,异步Actor消息是可行的。但是真要按照这个方法做,就需要你在数据编辑器里修改那个Actor,并添加Actor事件。而如果我们想要让“任意”Actor都能异步显隐,岂不是得把所有的Actor都改一遍?虽然工作量也不算多到不可能承担,虽然我们可以用宏(CActorEventMacro)来减少操作量,但起码也有两三千个对象要改,终究还是很土。超级浪费时间和空间。

所以我们需要一个通用的方案。说到底数据编辑器是死的,所以只有从触发器那边想办法。

不幸的是:一来我们没法用触发器来动态添加演算体事件;二来虽然我们可以用触发器来直接发送演算体消息,但是只能发送,无法进行条件判断,所以设置飞行映射就毫无意义。

两条都是死路,这岂不是无路可走了?

NO。当面临两条死路的时候,想想还有没有第三条路。当确实没有第三条路的时候,只有我们自己想办法砸出一条路来了。在这里,大家要提前感谢一下感谢演算体系统中的别名系统和引用系统。

重新审视一下整个问题,并考虑一下触发器和数据编辑器各自的优势,以及它们是否已经各尽所能了的话。那就是触发器的自由度极高。比如针对指定玩家,针对指定Actor进行操作,不靠触发器是做不到的。另一方面,数据编辑器也虽然比较死板,却执行效率高,而且有许多触发器无法实现的功能,比如说判断演算体条件。但在这里,数据编辑器另一项优势还没体现出来:数据编辑器不但可以判断演算体条件,决定是否发送消息,还能在发送消息的时候决定要把消息发送给谁——也就是说并不是只能发给自己啊。

数据编辑器里指定演算体消息发送目标的栏位可以直接填写目标的actor ID,也可以填写它们的别名或者引用。实在是相当方便。所以我们并不真的需要修改每一个Actor,只需要新建一个代理演算体,让它判断飞行映射条件,然后把消息转发给我们真正想要操作的演算体就好啦。

本着能省则省的原则,制作这样一个代理演算体,使用CActorSimple类就足够了。然后只要想办法把我们的想要操作的Actor弄成数据编辑器可以发送消息的目标就行了。

于是我们的第二条思路就类似这样:

在触发器模块中,我们使用触发器设好飞行映射。然后给我们想要进行操作的演算体发送"Create xxxx"(xxxx是我们的代理演算体的ID)消息。这样它就会创建一个代理演算体。然后我们立刻使用"Actor From Actor"函数,并使用系统引用"::LastCreatedActual"来捕捉到这个刚创建的代理。再发送消息给代理,然后销毁代理,解锁飞行映射。

在数据编辑器模块中,我们用CActorSimple新建一个代理演算体。捕捉触发器发来的信号,判断飞行映射,并决定是否发送相应消息。然后发送的目标写"::Creator"(创建当前演算体的那个演算体,也就是我们想要操作的那个演算体啦)。

如果这样能行的话,我们的目的就达到了呢。

可惜这样是不行的。这有个奇怪的bug,当你用触发器来发送"Create"消息的时候,"::Creator"这个系统引用不会被正确射为被创建的Actor的创建者,所以发送的消息无法达到我们要操作的目标——这是拦在我们面前的最后一道障碍了。

当时,这个障碍花了我3秒钟——都这么一路砸过来了,这样的障碍还拦得住我么?而且解决的方法有很多,比如说我们可以用触发器来发送演算体消息,来手动设置"::Creator"。当然我们也可以使用别名系统。都可以,只是使用别名的话,在数据编辑器里发送消息的目标也要改为相应的别名就是了。



异步显示/隐藏/销毁演算体的自定义函数示例及演示地图

大功告成,下面的事情就只是发个演示而已了。这张演示地图里有两个自定义函数"Show/Hide Actor For Player Group"和"Destroy Actor For Player Group"分别用来为不同玩家组显示、隐藏和销毁演算体。

在这两个函数里,我通过使用"AliasAdd"消息来给目标演算体加上临时别名"_TriggerTarget"的方式来为代理演算体指定发送目标。并在结束后使用"AliasRemove"来移除这个临时别名。而我创建的代理演算体的ID叫"TriggerPerPlayerActorAgent"。

"Show/Hide Actor For Player Group"函数的详情:

[trigger]
Show/Hide Actor For Player Group
    Options: Action
    Return Type: (None)
    Parameters
        Players = (Active Players) <Player Group>
        Actor <Actor>
        Show = Show <Show/Hide Option>
    Grammar Text: Show Actor For Players
    Hint Text: (None)
    Custom Script Code
    Local Variables
        Agent = No Actor <Actor>
        OtherPlayers = (Active Players) <Player Group>
    Actions
        Player Group - Remove all players in Players from OtherPlayers
        UI - Lock flyer helper display for players in Players to All
        UI - Lock flyer helper display for players in OtherPlayers to None
        Actor - Send message "Create TriggerPerPlayerActorAgent" to actor Actor
        Variable - Set Agent = (Actor connected to Actor via reference "::LastCreatedActual")
        Actor - Send message "AliasAdd _TriggerTarget" to actor Actor
        General - If (Conditions) then do (Actions) else do (Actions)
            If
                Show == Show
            Then
                Actor - Send message "Signal Show" to actor Agent
            Else
                Actor - Send message "Signal Hide" to actor Agent
        Actor - Send message "AliasRemove _TriggerTarget" to actor Actor
        Actor - Send message "Destroy" to actor Agent
        UI - Unlock flyer helper display for players in (All players)
[/trigger]


"Destroy Actor For Player Group"函数的内容基本和上一个函数一样,只是接收不同的信号,并发送不同的消息而已。
[trigger]
Destroy Actor For Player Group
    Options: Action
    Return Type: (None)
    Parameters
        Players = (Active Players) <Player Group>
        Actor <Actor>
        Immediate = Off <On/Off Option>
    Grammar Text: Destroy Actor For Players with Immediate Immediate
    Hint Text: (None)
    Custom Script Code
    Local Variables
        Agent = No Actor <Actor>
        OtherPlayers = (Active Players) <Player Group>
    Actions
        Player Group - Remove all players in Players from OtherPlayers
        UI - Lock flyer helper display for players in Players to All
        UI - Lock flyer helper display for players in OtherPlayers to None
        Actor - Send message "Create TriggerPerPlayerActorAgent" to actor Actor
        Variable - Set Agent = (Actor connected to Actor via reference "::LastCreatedActual")
        Actor - Send message "AliasAdd _TriggerTarget" to actor Actor
        General - If (Conditions) then do (Actions) else do (Actions)
            If
                Immediate == On
            Then
                Actor - Send message "Signal Destroy_Immediate" to actor Agent
            Else
                Actor - Send message "Signal Destroy_Normal" to actor Agent
        Actor - Send message "AliasRemove _TriggerTarget" to actor Actor
        Actor - Send message "Destroy" to actor Agent
        UI - Unlock flyer helper display for players in (All players)
[/trigger]


而代理演算体"TriggerPerPlayerActorAgent"的内容:
[codes=xml]
    <CActorSimple id="TriggerPerPlayerActorAgent">
        <On Terms="Signal.*.Show; FlyerHelper All" Target="_TriggerTarget" Send="SetVisibility 1"/>
        <On Terms="Signal.*.Hide; FlyerHelper All" Target="_TriggerTarget" Send="SetVisibility"/>
        <On Terms="Signal.*.Destroy_Immediate; FlyerHelper All" Target="_TriggerTarget" Send="Destroy Immediate"/>
        <On Terms="Signal.*.Destroy_Normal; FlyerHelper All" Target="_TriggerTarget" Send="Destroy"/>
    </CActorSimple>
[/codes]

区区四行,实在是简洁的要命,不是么?



关于演示地图:

地图里有个测试触发器,外加两个上面列出的自定义函数。

这张地图可以进两人,任意一个玩家按下ESC一次,就会执行以下操作:

1]销毁玩家1电脑上的轨道指挥部的主演算体(但是底座的演算体还在,所以你会看到一个黑色的方块底座)

2]为玩家1隐藏任意两个单位,显示任意两个单位。

3]为玩家2隐藏任意两个单位,显示任意两个单位。

4]分别为玩家1和玩家2隐藏特定的装饰物,比如玩家1隐藏的是Xel'Naga Hull Reactor(doodad), Fallen Asteroids Scattered(doodad), Char Arm(doodad)。而玩家2隐藏的是Store Front(doodad)。

这张地图可以本地测试也可以联机测试,我已经把这个演示传到美服战网上,美服的人搜索"Per Player Actor"就能测试了。


然后是大家来找茬——

图一是玩家1看到的情景:

Screenshot2011-03-07_19_17_15.jpg

图二是玩家2看到的情景:

Screenshot2011-03-07_19_16_51.jpg

最后来个预告:

本文只是我的Actor相位技术系列的第一篇。异步显隐和异步销毁在普通的相位技术应用里已经足够了。但是在第二篇里,我会带大家更深入一步,用一个单一函数实现任意演算体消息的异步发送。在本篇中,我们为了发送"SetVisibility"和"Destroy"消息而分别为他们制作了一个信号。但演算体消息的组合是无限的,如果我们想要异步发送任意消息,那么就需要再来二个“超”。且待下回分解。

Per_Player_Actor.SC2Map

25 KB, 下载次数: 66

发表于 2011-3-21 21:06:53 | 显示全部楼层
头目沙发!
恩~初步明白实现原理了,还得继续深入学习
这功能强大啊~
回复

使用道具 举报

 楼主| 发表于 2011-3-21 21:17:38 | 显示全部楼层
反正这东西是我自己整出来的,名字也就是随我起。于是这个东西以后就叫演算体相位技术就是了。
回复

使用道具 举报

 楼主| 发表于 2011-3-21 21:48:26 | 显示全部楼层
说起来有人在qq上提到了,于是说一下。图一上的黑色方块其实是轨道指挥部的底座贴图。不同于轨道指挥部的主演算体,而是另一一个独立的演算体。

由于主演算体被销毁,所以就剩下个底座了。我是故意把那底座留那里的,为了体现这个自定义函数可以在销毁指定演算体的同时,不会影响到其余的演算体,就算它们属于同一个单位也一样。

我的那两个函数同样也可以对那个底座进行操作,只要通过Actor from actor来获得那个底座就行了。
回复

使用道具 举报

发表于 2011-3-21 22:45:50 | 显示全部楼层
看头目的教程跟看小说似的
期待超展开。
回复

使用道具 举报

发表于 2011-3-21 23:16:44 | 显示全部楼层
隐形单位本来就是异步的,说不定哪天暴雪就会做个接口呢 - - 目前这方法好绕啊。。。
回复

使用道具 举报

 楼主| 发表于 2011-3-21 23:19:54 | 显示全部楼层
原理绕而已。真正用的时候其实直接用我这个自定义函数。一句话的事情,又用不着管原理,多方便。

我打算做进GAx3里去。


至于利用隐形单位来实现的话,只有自讨苦吃,毕竟只有单位才拥有玩家属性。就得和当年war3一样什么都用单位模拟,超渣的。而且sc2里每个单位占1M内存。就算你有毅力花一年模拟出整个系统来,结果还是卡到人死掉。而且根本不可能实现通用。
回复

使用道具 举报

 楼主| 发表于 2011-3-21 23:25:47 | 显示全部楼层
另外关于在war3时期用隐形单位尝试实现这个效果是多么的烦人。大家可以搜索一下。本论坛应该还有保留的帖子的。
回复

使用道具 举报

发表于 2011-3-21 23:36:28 | 显示全部楼层
又有新東西可以學習了~


                                  另求頭目QQ~~
回复

使用道具 举报

发表于 2011-3-22 10:03:27 | 显示全部楼层
这个演示地图是多少版本的?
回复

使用道具 举报

发表于 2011-3-22 18:25:28 | 显示全部楼层
超头目
回复

使用道具 举报

 楼主| 发表于 2011-3-22 18:35:37 | 显示全部楼层
引用第9楼woaibusi于2011-03-22 10:03发表的  :
这个演示地图是多少版本的?

这跟版本又有什么关系?
回复

使用道具 举报

发表于 2011-3-23 00:02:30 | 显示全部楼层
每个单位1M内存……………………………………………………………………
大量单位模拟的做法看来是要在SC2绝迹了……
回复

使用道具 举报

发表于 2011-3-23 03:09:49 | 显示全部楼层
本来就不需要。。。灭绝就灭绝了。。
回复

使用道具 举报

发表于 2011-3-23 05:58:31 | 显示全部楼层
我非常喜欢大量单位模拟啊……
一会排成一字型,一会排成人字形。
回复

使用道具 举报

发表于 2011-3-23 08:59:14 | 显示全部楼层
演算体隐藏了,不过碰撞问题能否解决呢?比如对某玩家隐藏了基地,但是玩家的单位还是会绕过那个基地的地区~~
碰撞能不能也针对玩家?A玩家能通过B玩家不能~
回复

使用道具 举报

 楼主| 发表于 2011-3-30 22:10:19 | 显示全部楼层
这些自然就是数据编辑器逻辑方面的问题了。都是可以通过buff和过滤之类的东西解决的。

不过这部分就已经脱离Actor的范畴了。本教程主要是讲演算体。异步演算体是相位技术的关键,其余部分的问题相对来说都是基础了。我这系列里不会涉及,不过也许以后会开个帖子提几笔注意事项。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 点一下

本版积分规则

Archiver|移动端|小黑屋|地精研究院

GMT+8, 2024-12-23 21:34 , Processed in 0.080879 second(s), 21 queries .

Powered by Discuz! X3.5

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表