文章目录
一、创建准心UI
- 调整摄像机位置:现在游戏角色位于镜头的正中间,这样会在操作中遮挡玩家的视线。 而通常在第三人称游戏中,人物通常位于画面的偏左或者偏右位置。
- 进入
Player
的蓝图编辑器,选择弹簧臂组件SpringArmComp
,调整其中的"摄像机 "属性。通过设置"长度 "可以变化摄像机与角色的距离,设置"插槽偏移"从而在改变相机位置时保持弹簧臂碰撞检测的功能。 - 回到
Player
蓝图中,将准星添加到视口
二、调整发射代码
- 在我们之前角色的发射代码中,为了简单起见,是直接使用的
GetActorRotation
获得角色的旋转方向作为粒子的发射方向。但是我们现在添加了十字准星,要实现指哪打哪,得沿着玩家视角即Controller
视角发射,所以首先需要将GetActorRotation
替换为GetControllerRotation
为什么原来发射方向是相对于
Actor
,而现在可以相对于Controller
?
- 我的理解是:既然我们做了准心,也就是我们希望实际落点是我们可以控制的,那它就需要Controller玩家输入决定,而我们从枪口(手中)发射出去东西,扔出去的方向可以上任意的(想想游戏中扔手榴弹),所以可以做到与Actor无关,由准心决定了落点
GetControlRotation()
和GetViewRotation()
在这里是一样的,但实际它们有所不同:
(1)GetControllerRotation
通常从玩家的控制器获取旋转信息,通常与玩家的输入设备(如鼠标或游戏手柄)直接相关。这个旋转角度通常用于确定玩家意图面向的方向,无论其角色的视觉表示如何。(即使角色模型因动画或物理影响而朝向不同方向,控制器的旋转仍然保持玩家的输入方向。)
(2)GetViewRotation
则是从玩家的视角或摄像机视角获取旋转信息。在使用第三人称摄像机时GetViewRotation
可能会与GetControllerRotation
不同,因为摄像机可能会围绕角色旋转,提供不同的视角。
(3)举例一:吃鸡游戏中,控制器输入控制玩家的行进方向,而拖动小眼睛改变视口方向可以观察四周的环境。
(4)举例二:塔防或策略游戏,控制器决定场景中小单位的移动方向,而视口方向一直是俯视全场
(5)举例三:在VR应用中,控制器方向和视角方向的区别尤为明显:
①控制器输入方向:控制器在物理空间中的实际方向,可能用于指向或交互。
②摄像机方向:与用户头部的方向一致,用户头部的转动直接改变视角,与手中控制器的方向可能完全不同。- 问题一:正脸面向镜头,技能会打到自己
- 解决办法:触发OnActorOverlap事件后,先判断是否是
Instigator
(自己)
cpp
void ASurMagicProjectile::OnActorOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
//避免攻击者被自己的粒子伤害
if (OtherActor && OtherActor != GetInstigator()) {
//获得AttributeComp
USurAttributeComponent* AttributeComp = Cast<USurAttributeComponent>(OtherActor->GetComponentByClass(USurAttributeComponent::StaticClass()));
// 再次判空,可能碰到的是墙壁、箱子等没有血量的物体
if (AttributeComp) {
// 魔法粒子造成20血量伤害
AttributeComp->ApplyHealthChange(-20.0f);
// 一旦造成伤害就销毁,避免穿过角色继续计算
Destroy();
}
}
}
- 问题二:只要发射位置不在屏幕中心(相机位置),落点就会有偏差
准心 是什么,就是最后玩家希望子弹落点的位置。为打到它,枪口时可以任意旋转的这点不用太纠结。我们要的是指哪打哪的视觉效果。
- 解决方案:所以在做是否可以击中目标的测试中,我们可以从相机位置出发,按照控制器方向(玩家视角)做射线检测 ,再根据落点结果,用向量减法计算 枪口朝向作为投掷物生成方向分为两种情况:
(1)可以碰撞到指定目标 :重写玩家想命中的点为目标end
点
(2)没有碰撞到目标 :按照原来设定好的最远的end
同样计算得到发射方向
注意:因为涉及到射线检测,所以传入参数时要考虑想打到的目标是什么类型,否则可能会存在检测不到直接穿过的情况
cpp
void ASCharacter::SpawnProjectile(TSubclassOf<AActor> ClassToSpawn)
{
if (ensureAlways(ClassToSpawn))
{
//FVector HandLocation = GetMesh()->GetSocketLocation("Muzzle_01");
FVector HandLocation = GetMesh()->GetSocketLocation(HandSocketName);
FActorSpawnParameters SpawnParams;
SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
SpawnParams.Instigator = this;
FCollisionShape Shape;
Shape.SetSphere(20.0f);
// Ignore Player
FCollisionQueryParams Params;
Params.AddIgnoredActor(this);
FCollisionObjectQueryParams ObjParams;
ObjParams.AddObjectTypesToQuery(ECC_WorldDynamic);
ObjParams.AddObjectTypesToQuery(ECC_WorldStatic);
ObjParams.AddObjectTypesToQuery(ECC_Pawn);
FVector TraceStart = CameraComp->GetComponentLocation();
// endpoint far into the look-at distance (not too far, still adjust somewhat towards crosshair on a miss)
FVector TraceEnd = CameraComp->GetComponentLocation() + (GetControlRotation().Vector() * 5000);
FHitResult Hit;
// returns true if we got to a blocking hit
if (GetWorld()->SweepSingleByObjectType(Hit, TraceStart, TraceEnd, FQuat::Identity, ObjParams, Shape, Params))
{
// Overwrite trace end with impact point in world
TraceEnd = Hit.ImpactPoint;
}
// find new direction/rotation from Hand pointing to impact point in world.
FRotator ProjRotation = FRotationMatrix::MakeFromX(TraceEnd - HandLocation).Rotator();
FTransform SpawnTM = FTransform(ProjRotation, HandLocation);
GetWorld()->SpawnActor<AActor>(ClassToSpawn, SpawnTM, SpawnParams);
}
}