- 好友
- 4
- 在线时间
- 0 小时
- 最后登录
- 2024-12-2
见习骑士
- UID
- 3199602
- 第纳尔
- 1162
- 精华
- 0
- 互助
- 36
- 荣誉
- 0
- 贡献
- 1
- 魅力
- 227
- 注册时间
- 2020-9-4
鲜花( 60) 鸡蛋( 0)
|
本帖最后由 奥杜因阿卡托什 于 2023-5-5 16:46 编辑
在写正文之前,先感谢@vegetto提供的灵感和指导,以及@huagao@黑暗路西法@战争傀儡阿格兰@无面的无名氏@阳光肥宅提供的协助,我能这么快把这个内容搓出来离不开和他们的集思广益。下面开始正文。
首先解释一下我做的是什么东西,简而言之,就是物理特技攻击的判定。这里的特技指的是按特殊键施放的主动技能,包括绝大多数人都用过的ctrl H、ctrl F4这种某种程度上也属于此列,我希望这些主动技能像魂系列的战技释放一样,做出一个特殊动作,对周围的敌人造成伤害。类似下图
但问题也随之而来,就是攻击的判定。众所周知,用set animation使出来的动作,只有其形,碰撞、伤害、触发那是一律没有,打在人身上直接穿体而过,什么事都没有,而攻击碰撞这块又是死代码中的死代码,几乎没有任何接口,所以想要让这些自己手搓的技能造成伤害,就必须自己写判定,判定命中后再用deliver damage等代码造成伤害。
事实上,可用的判定方法有很多。这段时间我搜集了不少,比如:
一、最简单的,触发动作的瞬间获取敌人和玩家的距离,小于多少就扣血。这种方式虽然是最简单,但作为法术来说或许算是合理,用在物攻上可就很难顶了。往前挥了一剑,结果周围一圈的人都死光了,别扭得不行。割草玩家或许会很喜欢这种技能,但要是NPC也能用,玩家成为被割草得一方时,恐怕没人能笑得出来了。
二、获取武器尖端的位置,当尖端的点与敌人相距小于一定值,就算击中。经过测试,这个方法在用单手剑时还行,但在挥舞长杆时,杆部将成为一个不可忽视的组份,导致很多紧贴攻击者的敌人直接穿杆而过,因为武器尖端在它背后一段距离,所以毫发无损。对于这点,黑暗路西法提供了一个改进思路:在武器上再多锁定几个点,靠这些点覆盖整个杆部,这个方案我还没试,有志之士可以试一下。
三、由老彼得友情提供的办法,获取武器尖端的位置,沿着武器杆往回射透明箭。这是个很聪明的办法,因为add_missile是一整个封装好的攻击,是玩家能使用的少数自带判定的攻击。这个思路让我想起了《刀剑神域》黑色子弹篇最后打死枪时,桐人手枪连续射击,用接连不断的子弹组成了第二把剑,和这个思路不谋而合:只要武器掠过敌人,武器上连续不断的透明剑就会捅它的腚眼子。但这个方案的问题也有,比如没有击中敌人的箭可能会飞到另一边去击中别人,会击中友军,本来应该对两个人造成伤害结果其中一人吃了两箭等等,对add_missile的参数设置精度要求比较高,需要大量的调试,这个我就没做了。
四、我自己寻思出的一种方法,只要敌人和武器尖端在同一个方向,而且距离小于武器长度,就算它被攻击到了。把攻击者周围的空间分成26个区域(分法参考三阶魔方,攻击者相当于最中间的机括),然后根据武器尖端和敌人与攻击者的相对位置判断它们在哪个区域,如果在同一区域,就算距离,在攻击范围内就判中。这个办法计算量虽大,但也算是兼顾了方向和距离,因为agent也是有体积的,基本上不是太特殊的动作都能勉强判定。最后我没用这个办法是因为找到了尚未替代,接下来放代码。
(补充一点,所有方法都有一件要注意的事,敌人的position获取之后并不是在头部或者胸口腹部等,而是在左脚底,这个从openbrf里也能看出来。所以计算是最好把它的坐标往z轴正方向移个1米,移动到差不多腹部的位置。)
我现在的办法是在四的基础上,改进了运算方法,本来是26分区的,现在直接用三维坐标系的向量点乘算出了武器和敌人的夹角。代码如下:
- #input pos1, pos2 and origin position pos3.
- #pos1 is weapon end point, pos2 is enemy position, pos3 is player position
- #If pos1 and pos2 are in same direction of pos1 and long enough, return reg1 = 1, means can hit.
- ("cf_position_check_hit", [
- (set_fixed_point_multiplier, 100),
- (assign, reg1, 0),
- (get_distance_between_positions, ":cur_distance_1", pos1, pos3),#weapon attack range
- (get_distance_between_positions, ":cur_distance_2", pos2, pos3),#enemy distance
- (ge, ":cur_distance_1", ":cur_distance_2"),#can hit
- (position_get_x, ":cur_x_1", pos1),
- (position_get_y, ":cur_y_1", pos1),
- (position_get_z, ":cur_z_1", pos1),
- (position_get_x, ":cur_x_2", pos2),
- (position_get_y, ":cur_y_2", pos2),
- (position_get_z, ":cur_z_2", pos2),
- (position_get_x, ":cur_x", pos3),
- (position_get_y, ":cur_y", pos3),
- (position_get_z, ":cur_z", pos3),
- (val_sub, ":cur_x_1", ":cur_x"),
- (val_sub, ":cur_y_1", ":cur_y"),
- (val_sub, ":cur_z_1", ":cur_z"),
- (val_sub, ":cur_x_2", ":cur_x"),
- (val_sub, ":cur_y_2", ":cur_y"),
- (val_sub, ":cur_z_2", ":cur_z"),
- (val_mul, ":cur_x_1", ":cur_x_2"),
- (val_mul, ":cur_y_1", ":cur_y_2"),
- (val_mul, ":cur_z_1", ":cur_z_2"),
- (store_add, ":numerator", ":cur_x_1", ":cur_y_1"),
- (val_add, ":numerator", ":cur_z_1"),#x1*x2+y1*y2+z1*z2, dot product
- (store_mul, ":denominator", ":cur_distance_1", ":cur_distance_2"),#magnitude1*magnitude2
- #cos=numerator/denominator. When cos equals 1, these two are in the same direction. We need their included angle to be within plus or minus 60 degrees, that is, cos must be greater than 0.5
- #But considering that Python may not be able to obtain 0.5, let's transform the inequality and apply 0.5 to the denominator side, that is, the numerator must be greater than the denominator multiplied by 0.5
- # (val_mul, ":denominator", 9),
- (val_div, ":denominator", 2),
- (ge, ":numerator", ":denominator"),
- (assign, reg1, 1),#can hit
- ]),
复制代码 里面写的注释可以看一下,因为怕打不中,我给这个夹角扩大到60度了,其实要更精确的话,改小一点也可以。
有了这个工具,就可以进行判定了,判定区块的代码如下:
- #use for weapon hit check
- ("AoM_weapon_hit_check", [
- (agent_get_bone_position, pos1, "$mission_player_agent", 19, 1),#item.R
- (agent_get_wielded_item, ":weapon_no", "$mission_player_agent", 0),
- (item_get_weapon_length, ":weapon_length", ":weapon_no"),#cm
- (try_begin),
- (item_get_type, ":type_no", ":weapon_no"),
- (eq, ":type_no", itp_type_polearm),
- (val_sub, ":weapon_length", 70),#0.7meter
- (try_end),
- (position_move_y, pos1, ":weapon_length"),
- (try_for_range, ":count_no", 0, 10),
- (store_add, ":slot_no", ":count_no", slot_agent_surrounded_enemy_1),
- (agent_get_slot, ":beattacked_agent_no", "$mission_player_agent", ":slot_no"),
- (gt, ":beattacked_agent_no", 0),
- (agent_is_alive, ":beattacked_agent_no"),
- (agent_get_position, pos2, ":beattacked_agent_no"),
- (position_move_z, pos2, 100),
- (call_script, "script_cf_position_check_hit"),
- (eq, reg1, 1),
- (agent_deliver_damage_to_agent, "$mission_player_agent", ":beattacked_agent_no"),
- (try_end),
- ]),
复制代码
有了判定区块还不算完,何时使用、对谁使用也有说法。不可能说没用一次就给全部agents循一次,要知道鉴于一个动作往往有多端伤害,这个东西可能每0.1秒就要触发一次,全部try for agents肯定是吃不消的。因此注意到上面代码中使用的slot_agent_surrounded_enemy_1一系列slot,在按下按键,开始动作时,会先循环一遍agent,把玩家攻击范围(可能会稍大一点)里的敌人选出来,储存在这些槽里,考虑到这种物理攻击应该不会打中太多人,就只留了10个槽。后续判定就只要从这十人里循环,压力大大减轻。
作为示范,我以一个高位回旋斩的动作作为示范。这个动作时在战风新纪元里找看到的,叫parry_attack_slashleft_onehanded_y,可以去看一看,基本是往左引一段距离蓄力,然后转体360度斩一圈。之前听无名氏说战风新是开源的,就用了,但在此还是不放出来了。我给这个动作设置的时间是1.6秒。因为在我的mod里,角色界面可以选择技能,所以给它套了一个item,储存技能图案、名字、介绍和一些数据。item是这样的
- ["active_sweep_away", "active_sweep_away", [("active_sweep_away", 0)], itp_unique, 0, <b>15131007</b>, weight(4)|abundance(10)|max_ammo(0)|food_quality(0), imodbits_none],
复制代码 item之中,金钱部分的15131007,其实表示该技能的四个判定点,分别是发动后的0.4秒、0.5秒(第一次前挥阶段)、0.7秒、0.9秒(转过360度后第二次前挥),怎么得出来的之后再说。
这一系列的东西最终指向肯定是mission trigger,这里会提供两个,第一个是计时器,第二个是工作的主要部分。
- (0, 0, 0.1, [],
- [
- (try_for_agents, ":agent_no"),
- (agent_is_alive, ":agent_no"),
- (agent_is_human, ":agent_no"),
- (agent_get_slot, ":timer_count", ":agent_no", slot_agent_skill_timer),
- (gt, ":timer_count", 0),
- (val_sub, ":timer_count", 1),
- (agent_set_slot, ":agent_no", slot_agent_skill_timer, ":timer_count"),
- (try_end),
- ]),
复制代码 计时器的原理是利用触发时间的最后一项制造0.1秒的延迟,然后每0.1秒所有的slot_agent_skill_timer都减少一。
然后是主要工作部分
- (0, 0, 0, [],
- [
- (try_begin), #activation part, only working at the point when the button is clicked or ai gets permission.
- (is_between, ":active_skill_no", "itm_active_skills_begin", "itm_active_skills_end"),
- (item_get_slot, ":attacker_anim_no", ":active_skill_no", slot_active_skill_attacker_anim),
- (gt, ":attacker_anim_no", 0),
- (set_fixed_point_multiplier, 100),
- (agent_get_position, pos1, ":attacker_agent_no"),
- (agent_get_team, "$mission_player_team", ":attacker_agent_no"),
- (agent_get_wielded_item, ":weapon_no", ":attacker_agent_no", 0),
- (item_get_weapon_length, ":weapon_length", ":weapon_no"),#cm
- (val_add, ":weapon_length", 150),
- # (val_div, ":weapon_length", 100),
- (assign, ":slot_no", slot_agent_surrounded_enemy_1),
- (try_for_agents, ":agent_no"),
- (agent_is_alive, ":agent_no"),
- (le, ":slot_no", slot_agent_surrounded_enemy_10),#at most ten enemies will be attacked
- (assign, ":continue", 0),
- (try_begin),
- (agent_is_human, ":agent_no"),
- (agent_get_team, ":team_no", ":agent_no"),
- (teams_are_enemies, "$mission_player_team", ":team_no"), #agent is hostile
- (assign, ":continue", 1),
- (else_try),
- (neg|agent_is_human, ":agent_no"),#horse
- (agent_get_rider, ":rider_agent_no", ":agent_no"),
- (le, ":rider_agent_no", 0),#have no rider
- (assign, ":continue", 1),
- (else_try),
- (neg|agent_is_human, ":agent_no"),#horse
- (gt, ":rider_agent_no", 0),#have rider
- (agent_get_team, ":team_no", ":rider_agent_no"),
- (teams_are_enemies, "$mission_player_team", ":team_no"), #rider is hostile
- (assign, ":continue", 1),
- (try_end),
- (eq, ":continue", 1),
- # (agent_get_position, pos2, ":agent_no"),
- # (get_distance_between_positions_in_meters, ":cur_distance", pos1, pos2),
- # (le, ":cur_distance", ":weapon_length"),
- (agent_set_slot, ":attacker_agent_no", ":slot_no", ":agent_no"),
- (val_add, ":slot_no", 1),
- (try_end),
- (agent_set_animation, ":attacker_agent_no", ":attacker_anim_no"),
- (item_get_max_ammo, ":max_timer", ":active_skill_no"),
- (agent_set_slot, ":attacker_agent_no", slot_agent_skill_timer, ":max_timer"),
- (agent_set_slot, ":attacker_agent_no", slot_agent_activiting_skill, ":active_skill_no"),
- (try_end),
- ]),
复制代码 第一个try_begin之后的区域,很显然是按数字键1激活该动作,包括我上面说的预选10个敌对agent。第二个区域看起来比较简短,主要是大部分都被塞进script了。可以看到其中设置timer的地方,明明这个动作是1.6秒,却设置了19。剩下这三行首先把时间获取了出来,然后call1了一个上面没出现过的script。这个script就是进行时间判断的部分,也是上面item value15131007表示0.4、0.5、0.7、0.9秒,以及timer从19开始的原因,代码如下
- #use for weapon hit check
- #input timer and skill id
- ("AoM_active_weapon_hit", [
- (store_script_param_1, ":timer_count"),
- (store_script_param_2, ":active_skill_no"),
- (store_item_value, ":attack_check_point", ":active_skill_no"),
- (agent_get_position, pos3, "$mission_player_agent"),
- (try_begin),
- (store_div, ":attack_point", ":attack_check_point", 100000000),
- (gt, ":attack_point", 0),
- (eq, ":attack_point", ":timer_count"),#can attack
- <b> (val_sub, ":timer_count", 1),
- (agent_set_slot, "$mission_player_agent", slot_agent_skill_timer, ":timer_count"),</b>
- (call_script, "script_AoM_weapon_hit_check"),
- (else_try),
- (val_mod, ":attack_check_point", 100000000),
- (store_div, ":attack_point", ":attack_check_point", 1000000),
- (gt, ":attack_point", 0),
- (eq, ":attack_point", ":timer_count"),#can attack
- <b> (val_sub, ":timer_count", 1),
- (agent_set_slot, "$mission_player_agent", slot_agent_skill_timer, ":timer_count"),</b>
- (call_script, "script_AoM_weapon_hit_check"),
- (else_try),
- (val_mod, ":attack_check_point", 1000000),
- (store_div, ":attack_point", ":attack_check_point", 10000),
- (gt, ":attack_point", 0),
- (eq, ":attack_point", ":timer_count"),#can attack
- <b> (val_sub, ":timer_count", 1),
- (agent_set_slot, "$mission_player_agent", slot_agent_skill_timer, ":timer_count"),</b>
- (call_script, "script_AoM_weapon_hit_check"),
- (else_try),
- (val_mod, ":attack_check_point", 10000),
- (store_div, ":attack_point", ":attack_check_point", 100),
- (gt, ":attack_point", 0),
- (eq, ":attack_point", ":timer_count"),#can attack
- <b> (val_sub, ":timer_count", 1),
- (agent_set_slot, "$mission_player_agent", slot_agent_skill_timer, ":timer_count"),</b>
- (call_script, "script_AoM_weapon_hit_check"),
- (else_try),
- (val_mod, ":attack_check_point", 100),
- (gt, ":attack_point", 0),
- (eq, ":attack_check_point", ":timer_count"),#can attack
- <b> (val_sub, ":timer_count", 1),
- (agent_set_slot, "$mission_player_agent", slot_agent_skill_timer, ":timer_count"),</b>
- (call_script, "script_AoM_weapon_hit_check"),
- (try_end),
- ]),
复制代码 这里面有五个结构基本相同的部分,是数位储存,上面那个15131007每两位表示一个时间,那么应该是1.5、1.3、1.0、0.7这四个点,因为时间是从大往小减的,设总时间为X,那这四个判定时刻分别是X-1.5,X-1.3,X-1和X-0.7——当然实际情况没有这么简单。实操中发现一个问题,为了保证触发的灵敏,工作部分的触发器设置的是(0,0,0,这个反应是比0.1秒还快的,差不多0.015秒左右有一次,这导致在四个判定点本来应该只判定一次的,因为它响应得快,在下一个0.1秒到来前又多判定了六七次,导致攻击也多了六七次,这肯定不行。因此我这里判定结束后直接把timer又减了1,强制它提前进去下一个0.1秒,避免重复判定。这么做的结果就是1.5、1.3、1.0这三个点各快进了0.1秒,实际上对应的时刻应该是X-1.5,X-1.3-0.1,X-1-0.1-0.1和X-0.7-0.1-0.1-0.1,成了0.4秒、0.5秒、0.7秒、0.9秒。整个timer的时间,自然也从1.6秒增加到了1.9秒。很简单的小学数学,细想一下就能明白了。
最后把新增的common也放出来,自然都是agent部分的
- slot_agent_skill_timer = 80
- slot_agent_surrounded_enemy_1 = 81
- slot_agent_surrounded_enemy_2 = 82
- slot_agent_surrounded_enemy_3 = 83
- slot_agent_surrounded_enemy_4 = 84
- slot_agent_surrounded_enemy_5 = 85
- slot_agent_surrounded_enemy_6 = 86
- slot_agent_surrounded_enemy_7 = 87
- slot_agent_surrounded_enemy_8 = 88
- slot_agent_surrounded_enemy_9 = 89
- slot_agent_surrounded_enemy_10 = 90
复制代码 开始mission前记得把这些都归零。
- (ti_on_agent_spawn, 0, 0, [],
- [
- (store_trigger_param_1, ":agent_no"),
- (assign, ":slot_begin", slot_agent_skill_timer),
- (store_add, ":slot_end", ":slot_begin", 11),
- (try_for_range, ":slot_no", ":slot_begin", ":slot_end"),#slot_agent_surrounded_enemy
- (agent_set_slot, ":agent_no", ":slot_no", -1),
- (try_end),
- ]),
复制代码 这次没有放出插入即用的傻瓜代码,主要是这个实在是一个特别大的系统,横跨的部分太多,从openbrf里的技能图片到prsnt的新角色界面到anim的新动作到新粒子效果,东西太多。不过上述的代码基本也是完成度95%以上的,只要把anim部分自己完善一下就能用。希望能对正在做类似机制的moder和有志于mod的汽油有所帮助。
由于中文站传不了太大的视频,所以把视频传干杯站了,链接是https://www.bilibili.com/video/B ... 13c7736d290eab41395,想看效果的可以去看看。
可以看出这个攻击虽然没有什么额外的伤害加成,但是是多段的,角度对的话,可以让一名敌人吃下最多四次攻击。这就是我整这个的目的,毕竟要是都是获取距离然后直接扣血,那除了远程游斗和肉装站撸外没有别的玩法了,现在这么改进一下,如果敌人对玩家使用这一招,玩家有机会可以躲它的攻击,如果是玩家使用这个技能,也可以通过一定时间的练习,配合走位把尽可能多段的攻击打在同一个人身上。比如这个技能一号判定点是在正前方、三号判定点是在左前方,如果玩家打出第一击后朝前方踏出一步,然后立即左转,可以让某个敌人吃完1、3、4号三次攻击,这个操作我视频里好像也搓出来了一次。后续应该会在训练场或竞技场里新加一个训练室,专门给玩家熟悉这些特技。
5月5日更新
我发现try for range本身就可以只检测某个位置周围的单位,也就是说不用再遍历所有agent一个一个判断距离了。利用这点优化一下,虽然不知道能提升多少性能,但是蚊子再小也是肉,战团这么一点点算力,能优化多少算多少。
|
评分
-
查看全部评分
鲜花鸡蛋杰喵喵 在2024-1-14 17:52 送朵鲜花 并说:我非常同意你的观点,送朵鲜花鼓励一下 huagao 在2023-5-7 21:46 送朵鲜花 并说:直接偷了 黑暗路西法 在2023-4-25 11:12 送朵鲜花 并说:奥杜因的手,好汉
|