【虚幻引擎】实现锁定敌人与切换锁定
2023-02-18 16:42:42 时间
实现效果
https://hctra.cn/usr/uploads/2022/09/3090895458.mp4
实现思路
锁定目标
用一个球形碰撞体来感知哪些敌人可锁定,碰撞体平时关闭,当玩家按下锁定键时,刷新碰撞体检测并开启一帧,在下一帧就知道哪些目标可锁定了,然后从这些可锁定的目标中找一个与摄像机正前方向量夹角最小的敌人作为锁定对象。 锁定目标后,让玩家视角固定,自身旋转实时朝向追踪敌人方向。
切换锁定目标
同样的,当玩家按下左或右键时,检测碰撞体开启一帧,然后在第二帧从除了当前锁定目标外所有可锁定敌人中,获取在玩家视角左/右边获取夹角最接近的敌人。夹角可以通过两向量点乘获取,然后通过公式:sign = (v1n.X - v1n.Y) * (v2n.Z - v2n.Y) - (v1n.Z - v1n.Y) * (v2n.X - v2n.Y)
,根据sign的正负得出是在左边还是右边。
实现步骤
因为我习惯用Lua所以代码部分使用的是Lua,如果你也想使用可以参照这篇文章: sluaunreal插件使用样例 sluaunreal插件的作用这里就不多介绍了,它的github地址是:https://github.com/Te...
首先在玩家身上挂一个碰撞体用于检测可锁定目标:
然后给也敌人挂上碰撞体,碰撞体的ObjectType为Enemy,玩家的检测碰撞体预设为只与Enemy发生Overlap:
最好是做成CollisionPresets,这里我就简单的手动调下。
核心代码
当按下锁定键时调用:
-- 尝试将视角锁定一个敌人
function cls:CheckLockEnemy_()
self.enlockEnemyList_ = {}
self:RefreshCheckEnemyCol_() --开启一帧碰撞检测
self:DelayCall(0.01, function()
local cmpFunc = function(cAngle, angle)
if angle == nil or math.abs(cAngle) < math.abs(angle) then
return true
end
return false
end
local lockEnemy = self:GetLockEnemyByCmp_(cmpFunc)
if lockEnemy ~= nil then
self:LockEnemy_(lockEnemy)
end
end)
end
当按下切换左/右目标键时,调用:
-- 切换锁定目标
if (key == GE.InputKey.Right or key == GE.InputKey.Left) and self.viewType_bp == CharacterEnums.ViewType.LockEnemy then
self:RefreshCheckEnemyCol_()
local cmpFunc = nil
if key == GE.InputKey.Right then
cmpFunc = function(cAngle, angle, cActor)
if angle == nil then
if cActor ~= self.lockEnemy_ and cAngle < 0 then
return true
else
return false
end
end
if cAngle < 0 and cAngle > angle and cActor ~= self.lockEnemy_ then
return true
end
return false
end
else
cmpFunc = function(cAngle, angle, cActor)
if angle == nil then
if cActor ~= self.lockEnemy_ and cAngle > 0 then
return true
else
return false
end
end
if cAngle > 0 and cAngle < angle and cActor ~= self.lockEnemy_ then
return true
end
return false
end
end
self:DelayCall(0.01, function()
local lockEnemy = self:GetLockEnemyByCmp_(cmpFunc)
if lockEnemy ~= nil then
self:LockEnemy_(lockEnemy)
end
end)
end
在Tick中:
function cls:ReceiveTick(deltaTime)
self:BP_Tick(deltaTime)
-- 如果锁视角,就根据目标手动刷新角色旋转
if self.viewType_bp == CharacterEnums.ViewType.LockEnemy then
local selfLoc, enemyLoc = GF:GetActorLocation(self), GF:GetActorLocation(self.lockEnemy_)
if GF:IsActorValid(self.lockEnemy_) == false or self.lockEnemy_:IsDeath() or
GF:VSize(selfLoc - enemyLoc) > self.lockEnemyDistance_bp then
self:UnLockEnemy_()
else
self:UpdateRotationByTarget_(self.lockEnemy_)
end
end
end
供调用的功能函数
-- 刷新检测碰撞体
function cls:RefreshCheckEnemyCol_()
self["bp_EnemyCheckCol"]:SetCollisionEnabled(GE.CollisionEnabled.QueryOnly)
local radius = self["bp_EnemyCheckCol"]:GetScaledSphereRadius()
self["bp_EnemyCheckCol"]:SetSphereRadius(0, true)
self["bp_EnemyCheckCol"]:SetSphereRadius(radius, true)
self["bp_EnemyCheckCol"]:SetCollisionEnabled(GE.CollisionEnabled.NoCollision)
end
-- 锁定目标
function cls:LockEnemy_(lockEnemy)
self.viewType_bp = CharacterEnums.ViewType.LockEnemy
if self.lockEnemy_ then
GD:Post(self, self.lockEnemy_, GEVT.LOCK_ENEMY, false)
end
GD:Post(self, lockEnemy, GEVT.LOCK_ENEMY, true)
self.lockEnemy_ = lockEnemy
self["bp_SpringArm"].bUsePawnControlRotation = false
self.movementComp_.bOrientRotationToMovement = false
end
-- 解锁
function cls:UnLockEnemy_()
self.viewType_bp = CharacterEnums.ViewType.Follow
GD:Post(self, self.lockEnemy_, GEVT.LOCK_ENEMY, false)
self.lockEnemy_ = nil
self["bp_SpringArm"].bUsePawnControlRotation = true
self.movementComp_.bOrientRotationToMovement = true
end
当发生碰撞时,将目标装入表:
self["bp_EnemyCheckCol"].OnComponentBeginOverlap:Add(function(overlappedComponent, otherActor, otherComp, otherBodyIndex, bFromSweep, sweepResult)
self.enlockEnemyList_[otherActor] = otherActor
end)
夹角计算: (GF库:)
-- 计算目标对象与自身的夹角
function cls:GetTargetActorAngle(selfActor, targetActor)
local v1 = self:GetForwardVector(self:GetActorRotation(selfActor))
local tLoc, sLoc = self:GetActorLocation(targetActor), self:GetActorLocation(selfActor)
tLoc.Z, sLoc.Z = 0, 0
local v2 = tLoc - sLoc
v1, v2 = self:Normal(v1), self:Normal(v2)
local rad = math.acos(self:Dot_VectorVector(v1, v2))
local angle = KismetMathLibrary:RadiansToDegrees(rad)
return angle
end
-- 计算目标对象与自身的夹角(带正负)
function cls:GetTargetActorAngleHaveSign(selfActor, targetActor)
local v1 = self:GetForwardVector(self:GetActorRotation(selfActor))
local tLoc, sLoc = self:GetActorLocation(targetActor), self:GetActorLocation(selfActor)
tLoc.Z, sLoc.Z = 0, 0
local v2 = tLoc - sLoc
local v1n, v2n = self:Normal(v1), self:Normal(v2)
local rad = math.acos(self:Dot_VectorVector(v1n, v2n))
local angle = KismetMathLibrary:RadiansToDegrees(rad)
local sign = (v1n.X - v1n.Y) * (v2n.Z - v2n.Y) - (v1n.Z - v1n.Y) * (v2n.X - v2n.Y)
-- print("hcDel sign angle = ", angle, v1n.X, v1n.Y, v1n.Z, v2n.X, v2n.Y, v2n.Z)
if sign < 0 then
angle = -1 * angle
end
return angle
end
-- 看向目标旋转
function cls:UpdateRotationByTarget_(target)
local curRot = GF:GetActorRotation(self)
local tarRot = GF:FindLookAtRotation(GF:GetActorLocation(self), GF:GetActorLocation(target))
curRot.Roll, curRot.Pitch = 0, 0
tarRot.Roll, tarRot.Pitch = 0, 0
local newRot = GF:RInterpTo(curRot, tarRot, GF:GetDeltaTime(), 5)
GF:SetActorRotation(self, newRot, false)
end
相关文章
- Java开发如何通过IoT边缘ModuleSDK进行协议转换?
- 0停机迁移Nacos?Java字节码技术来帮忙
- 视频 | ZYNQ开发板深度评测:高性能FPGA和双核ARM的强强联合!
- I²C协议官方标准文档2021最新版本下载
- 国产FPGA开发板上手体验:不足百元,集成ARM硬核处理器!
- 业内首发!感芯MC3172硬实时RISC-V芯片,还用啥RTOS!
- 从汇率转换通用解决方案到可复用设计思想
- 顶流选手专访 - 最具推广价值作品 - 背后故事
- 顶流选手专访 - 最佳展现创意作品 - 背后故事
- 【精华】顶流选手专访-最佳可视化展现作品(冠军)
- 冠军作品背后的故事会是什么呢
- IBM 人力资源综合分析案例
- 世界五百强财务高管数字化战群雄经典案例
- ZebraBI 6.0 发布,更高更快更强大
- DAX 引擎之父揭秘 DAX 引擎内部细节
- 脑图PPT - 让大脑高效流淌内容
- 融合最佳展现创意的企业智慧经营分析看板
- ArcPy将HDF格式栅格文件批量转为TIFF格式
- 用AvaSpec 2048便携式光谱仪测定地物高光谱曲线
- ArcPy依据成像时间分别批量拼接不同时相的遥感影像