Unreal Engine 4 C++ PlatformerGame自定义角色控制器源代码分析

Unreal Engine 4 C++ PlatformerGame自定义角色控制器源代码分析

官方商店里有个PlatformerGame整个免费的游戏,是一个卷轴类的跑酷游戏。整个项目的角色控制器很有意思,可以跑、跳、滑行,很酷。这里来分析下它具体是如何实现的。

UCharacterMovementComponent这个类实现了角色控制器的大部分功能,PlatformerGame实现了一个自定义的MovmentComponent,叫UPlatformerPlayerMovementComp,它从标准的UCharacterMovementComponent继承,并扩展了它的行为。

1.构造函数

UPlatformerPlayerMovementComp::UPlatformerPlayerMovementComp(const class FPostConstructInitializeProperties& PCIP)
	: Super(PCIP)
{
	MaxAcceleration = 200.0f;
	BrakingDecelerationWalking = MaxAcceleration;
	MaxWalkSpeed = 900.0f;

	SlideVelocityReduction = 30.0f;
	SlideHeight = 60.0f;
	SlideMeshRelativeLocationOffset = FVector(0.0f, 0.0f, 34.0f);
	bWantsSlideMeshRelativeLocationOffset = true;
	MinSlideSpeed = 200.0f;
	MaxSlideSpeed = MaxWalkSpeed + 200.0f;

	ModSpeedObstacleHit = 0.0f;
	ModSpeedLedgeGrab = 0.8f;
}

MaxAcceleration是继承来的变量,它代表角色控制器的最大的加速度;

BrakingDecelerationWalking,这是角色在行走时,受到摩擦力的影响,产生的阻碍的加速度;

MaxWalkSpeed,角色行走的最大速度;

SlideVelicityReduction,角色在滑行时,单位时间内速度损失的量;

SlideHeight,角色在滑行的时候,角色控制器的高度;跟碰撞有关,滑行了,当人角色会矮一点,这样可以通过一些站立时不能通过的障碍;

SlideMeshRelativeLocationOffset,这是控制角色滑行,模型相对角色控制器的位移;

bWantsSlideMeshRelativeLocationOffset ,是否需要位移角色的模型;

MinSlideSpeed,角色必须初始有一个速度,才能够滑行起来,这是能够滑行的最小速度,低于这个速度是不能滑行;

MaxSlideSpeed,角色滑行所能够达到的最大速度;

ModSpeedObstacleHit,

ModSpeedLedgeGrab

2.重载StartFalling()

void UPlatformerPlayerMovementComp::StartFalling(int32 Iterations, float remainingTime, float timeTick, const FVector& Delta, const FVector& subLoc)
{
	Super::StartFalling(Iterations, remainingTime, timeTick, Delta, subLoc);

	if (MovementMode == MOVE_Falling && IsSliding())
	{
		TryToEndSlide();
	}
}

此方法是继承来的,它在角色从Walking模式切换到Falling模式时被调用。

我们假设,角色正在滑行,碰到前面一个悬崖,它开始下落,这时候,角色必须停止滑行,因为它马上就要开始下落了。以上代码便是实现此功能,在下落的时候,如果发现当前在滑行,就尝试停止滑行。

3.重载ScaleInputAcceleration,实现角色向右跑动

FVector UPlatformerPlayerMovementComp::ScaleInputAcceleration(const FVector& InputAcceleration) const
{
	FVector NewAccel = InputAcceleration;

	APlatformerGameMode* GI = GetWorld()->GetAuthGameMode<APlatformerGameMode>();
	if (GI && GI->IsRoundInProgress())
	{
		NewAccel.X = 1.0f;
	}

	return Super::ScaleInputAcceleration(NewAccel);
}

游戏一开始,角色就会一直朝右开始跑,玩家只需要控制在合适的时候跳和滑行就行了。那怎么实现不用输入,就一直朝右跑呢?一直给角色一个加速的输入即可。这里判断当前是不是在游戏过程中,是的话,就始终给原始的InputAcceleration参数一个X方向的输入。就好像你一直按着方向键一样。

4.重载PhysWalking()

void UPlatformerPlayerMovementComp::PhysWalking(float deltaTime, int32 Iterations)
{
	APlatformerCharacter* MyPawn = Cast<APlatformerCharacter>(PawnOwner);
	if (MyPawn)
	{
		const bool bWantsToSlide = MyPawn->WantsToSlide();
		if (IsSliding())
		{
			CalcCurrentSlideVelocityReduction(deltaTime);
			CalcSlideVelocity(Velocity);

			const float CurrentSpeedSq = Velocity.SizeSquared();
			if (CurrentSpeedSq <= FMath::Square(MinSlideSpeed))
			{
				// slide has min speed - try to end it
				TryToEndSlide();
			}
		}
		else if (bWantsToSlide)
		{
			if (!IsFlying() &&
				Velocity.Size() > MinSlideSpeed * 2.0f) // make sure pawn has some velocity
			{
				StartSlide();
			}
		}
	}

	Super::PhysWalking(deltaTime, Iterations);
}

每当需要更新角色的Walking状态时,PhysWalking都会被调用。这个方法的调用会比较频繁。它会根据角色的输入和当前的状态来更新当前角色的速度、是否滑行等状态。

首先,我们读取角色的输入bWantsToSlide,看是不是要滑行。

如果正在滑行,CalcCurrentSlideVelocityReduction(deltaTime)计算当前角色因为滑行而造成的速度损失。CalcSlideVelocity(),计算出当前滑行的速度。然后对当前的速度和能够滑行的最小速度进行比较,速度损失到无法再进行滑行的话,就停止滑行状态。

用户有输入,说开始滑行吧,那么检查下,看速度是不是达到了可以滑行的条件,再排除是不是在飞行,不是,那么StartSlide(),开始滑行吧。

5.CalcCurrentSlideVelocityReduction()计算滑行的速度损失

void UPlatformerPlayerMovementComp::CalcCurrentSlideVelocityReduction(float DeltaTime)
{
	float ReductionCoef = 0.0f;

	const float FloorDotVelocity = FVector::DotProduct(CurrentFloor.HitResult.ImpactNormal, Velocity.SafeNormal());
	const bool bNeedsSlopeAdjustment = (FloorDotVelocity != 0.0f);

	if (bNeedsSlopeAdjustment)
	{
		const float Multiplier = 1.0f + FMath::Abs<float>(FloorDotVelocity);
		if (FloorDotVelocity > 0.0f)
		{
			ReductionCoef += SlideVelocityReduction * Multiplier; // increasing speed when sliding down a slope
		}
		else
		{
			ReductionCoef -= SlideVelocityReduction * Multiplier; // reducing speed when sliding up a slope
		}+
	}
	else
	{
		ReductionCoef -= SlideVelocityReduction; // reducing speed on flat ground
	}

	float TimeDilation = GetWorld()->GetWorldSettings()->GetEffectiveTimeDilation();
	CurrentSlideVelocityReduction += (ReductionCoef * TimeDilation * DeltaTime);
}

先说下CurrentFloor是做什么用的,角色在Walking模式下,始终会站立在一个平面上,这个平面的数据就用CurrentFloor来描述。

const float FloorDotVelocity = FVector::DotProduct(CurrentFloor.HitResult.ImpactNormal, Velocity.SafeNormal());

这一行,其实是在求解当前速度的方向和平面垂直方向的夹角的余弦,向量点乘,应该不难理解。

滑行时速度的损失的程度,和地面与当前速度方向的夹角有关系。你往坡上滑行,速度损失多点,往坡下滑行,相应的损失会少点。如果两个是垂直的,也就说是在一个平坦的面上,那就不用调整了。bNeedsSlopeAdjustment,就是做这个用的。

ReductionCoef是计算出的速度损失系数,最后把它叠加到CurrentSlideVelocityReduction。

6.CalcSlideVelocity()计算滑行速度

void UPlatformerPlayerMovementComp::CalcSlideVelocity(FVector& OutVelocity) const
{
	const FVector VelocityDir = Velocity.SafeNormal();
	FVector NewVelocity = Velocity + CurrentSlideVelocityReduction * VelocityDir;

	const float NewSpeedSq = NewVelocity.SizeSquared();
	if (NewSpeedSq > FMath::Square(MaxSlideSpeed))
	{
		NewVelocity = VelocityDir * MaxSlideSpeed;
	}
	else if (NewSpeedSq < FMath::Square(MinSlideSpeed))
	{
		NewVelocity = VelocityDir * MinSlideSpeed;
	}

	OutVelocity = NewVelocity;
}

上面计算出了滑行的速度损失,保存在了CurrentSlideVelocityReduction。在叠加出新的速度后,这个速度可能大于最大速度,也可能达不到最小速度,需要对其分别操作。大于,新的速度就是最大滑行速度,小于,新的速度就是最小滑行速度。

7.StartSlide()开始滑行

void UPlatformerPlayerMovementComp::StartSlide()
{
	if (!bInSlide)
	{
		bInSlide = true;
		CurrentSlideVelocityReduction = 0.0f;
		SetSlideCollisionHeight();

		APlatformerCharacter* MyOwner = Cast<APlatformerCharacter>(PawnOwner);
		if (MyOwner)
		{
			MyOwner->PlaySlideStarted();
		}
	}
}

初始一些变量,设置角色的碰撞高度,然后通知Character播放开始滑行的动画,等等。

void UPlatformerPlayerMovementComp::TryToEndSlide()
{
	// end slide if collisions allow
	if (bInSlide)
	{
		if (RestoreCollisionHeightAfterSlide())
		{
			bInSlide = false;

			APlatformerCharacter* MyOwner = Cast<APlatformerCharacter>(PawnOwner);
			if (MyOwner)
			{
				MyOwner->PlaySlideFinished();
			}
		}
	}
}

这个方法尝试结束滑行状态,需要把之前的碰撞高度还原,然后通知Character播放滑行结束的动画。

8.SetSlideCollisionHeight()和RestoreCollisionHeightAfterSlide()

void UPlatformerPlayerMovementComp::SetSlideCollisionHeight()
{
	if (!CharacterOwner || SlideHeight <= 0.0f)
	{
		return;
	}

	// Do not perform if collision is already at desired size.
	if (CharacterOwner->CapsuleComponent->GetUnscaledCapsuleHalfHeight() == SlideHeight)
	{
		return;
	}

	// Change collision size to new value
	CharacterOwner->CapsuleComponent->SetCapsuleSize(CharacterOwner->CapsuleComponent->GetUnscaledCapsuleRadius(), SlideHeight);

	// applying correction to PawnOwner mesh relative location
	if (bWantsSlideMeshRelativeLocationOffset)
	{
		ACharacter* DefCharacter = CharacterOwner->GetClass()->GetDefaultObject<ACharacter>();
		const FVector Correction = DefCharacter->Mesh->RelativeLocation + SlideMeshRelativeLocationOffset;
		CharacterOwner->Mesh->SetRelativeLocation(Correction);
	}
}

此方法修改碰撞高度,然后根据需要,调整模型的偏移。

bool UPlatformerPlayerMovementComp::RestoreCollisionHeightAfterSlide()
{
	ACharacter* CharacterOwner = Cast<ACharacter>(PawnOwner);
	if (!CharacterOwner)
	{
		return false;
	}

	ACharacter* DefCharacter = CharacterOwner->GetClass()->GetDefaultObject<ACharacter>();
	const float DefHalfHeight = DefCharacter->CapsuleComponent->GetUnscaledCapsuleHalfHeight();
	const float DefRadius = DefCharacter->CapsuleComponent->GetUnscaledCapsuleRadius();

	// Do not perform if collision is already at desired size.
	if (CharacterOwner->CapsuleComponent->GetUnscaledCapsuleHalfHeight() == DefHalfHeight)
	{
		return true;
	}	

	const float HeightAdjust = DefHalfHeight - CharacterOwner->CapsuleComponent->GetUnscaledCapsuleHalfHeight();
	const FVector NewLocation = CharacterOwner->GetActorLocation() + FVector(0.0f, 0.0f, HeightAdjust);

	// check if there is enough space for default capsule size
	FCollisionQueryParams TraceParams(TEXT("FinishSlide"), false, CharacterOwner);
	FCollisionResponseParams ResponseParam;
	InitCollisionParams(TraceParams, ResponseParam);
	const bool bBlocked = GetWorld()->OverlapTest(NewLocation, FQuat::Identity, UpdatedComponent->GetCollisionObjectType(), FCollisionShape::MakeCapsule(DefRadius, DefHalfHeight), TraceParams);
	if (bBlocked)
	{
		return false;
	}

	// restore capsule size and move up to adjusted location
	CharacterOwner->TeleportTo(NewLocation, CharacterOwner->GetActorRotation(), false, true);
	CharacterOwner->CapsuleComponent->SetCapsuleSize(DefRadius, DefHalfHeight);

	// restoring original PawnOwner mesh relative location
	if (bWantsSlideMeshRelativeLocationOffset)
	{
		CharacterOwner->Mesh->SetRelativeLocation(DefCharacter->Mesh->RelativeLocation);
	}

	return true;
}

在还原碰撞高度的时候,需要测试当前是不是能够恢复,比如头顶上有个障碍,是不能够恢复的。

const bool bBlocked = GetWorld()->OverlapTest(NewLocation, FQuat::Identity, UpdatedComponent->GetCollisionObjectType(), FCollisionShape::MakeCapsule(DefRadius, DefHalfHeight), TraceParams);

这一行,对要恢复碰撞的那个位置进行测试,看有没有东西覆盖,没有就把角色传送到此位置。

最后恢复角色模型的偏移。

9.碰到障碍和碰到抓取边缘的处理

void UPlatformerPlayerMovementComp::PauseMovementForObstacleHit()
{
	SavedSpeed = Velocity.Size() * ModSpeedObstacleHit;

	StopMovementImmediately();
	DisableMovement();
	TryToEndSlide();
}

void UPlatformerPlayerMovementComp::PauseMovementForLedgeGrab()
{
	SavedSpeed = Velocity.Size() * ModSpeedLedgeGrab;

	StopMovementImmediately();
	DisableMovement();
	TryToEndSlide();
}

PauseMovementForObstacleHit()处理当遇到障碍时的情况。

PauseMovementForLedgeGrab()处理当抓取障碍边缘时的情况。

都是保存当前的速度,然后停止移动,停止滑行。

10.RestoreMovement()恢复移动

void UPlatformerPlayerMovementComp::RestoreMovement()
{
	SetMovementMode(MOVE_Walking);

	if (SavedSpeed > 0)
	{
		Velocity = PawnOwner->GetActorRotation().Vector() * SavedSpeed;
	}
}

在当前角色面对的方向上赋予保存的速度值。

时间: 2024-08-29 17:57:26

Unreal Engine 4 C++ PlatformerGame自定义角色控制器源代码分析的相关文章

Unreal Engine 4 Smear Frame效果的实现与分析

转自:http://www.52vr.com/article-868-1.html 这篇文章介绍了类似守望先锋中的帧转移模糊(Smear Frame)效果. 该效果由Jan Kaluza实现,本博客的介绍已获得原作者同意. Github地址:传送门 效果 效果图如下,两个模型实际上都是球: Tessellation 在本质上,这个效果是使用PN Tessellation来对模型进行曲面细分,然后使用world displacement通道来进行顶点的偏移操作.Tessellation能够给模型带

Unreal Engine 4 蓝图之自定义事件

UE4的蓝图就跟C++等编程语言在概念上是非常类似的.在蓝图中你可以定义变量.函数.宏等等,高级点的,它还可以被继承.这还不算,我们还可以定义蓝图接口,规范子类的行为.基本上C++中可以做的,蓝图也可以做到,而且是所见即所得,拖拖拽拽,即时编译,立即生效. 一般的做法是,程序员在C++中做好功能模块,关卡设计师,用蓝图创建游戏的逻辑,关卡师不需要会编程.可以自己创建逻辑,至少在逻辑这一层不需要程序员来配合.这样的效率会高很多,而且也没有类似Lua这样的脚本语言参与,设计师的门槛要低很多了. 这里

UNREAL ENGINE 4.12 正式发布!下载地址

UNREAL ENGINE 4.12 正式发布! 下载地址:https://www.unrealengine.com/ Alexander Paschall 在 June 1, 2016 |功能新闻社区 Share on Facebook Share on Twitter Share on Google+ Share on LinkedIn 此版本内含虚幻引擎 4 的数百个更新,以及 GitHub 虚幻引擎开发者社区提交的 106 项改良!特此对虚幻引擎 4.12 版本的贡献者们表达诚挚谢意:

从Unreal Engine 3到Unreal Engine 4

Unreal Engine 4发布好长好长时间了,直到最近才有时间仔细去看一下. TimSweeney老大一句话"IF YOU LOVE SOMETHING, SET IT FREE",原来需要几十万授权金才能拿到的东西,就从$19,变成免费了,而且开源.作为一个国际顶尖的引擎,能走出这一步,对我等普通开发者真是福音啊.如此的牛X,再加上开源,相信Unreal Engine 4会火起来的,你看看最近E3上的产品展示也能感觉到.不过,Unreal的定位毕竟是"国际顶尖"

Unreal Engine 4 系列教程 Part 1:入门

.katex { display: block; text-align: center; white-space: nowrap; } .katex-display > .katex > .katex-html { display: block; } .katex-display > .katex > .katex-html > .tag { position: absolute; right: 0px; } .katex { font: 1.21em/1.2 KaTeX_M

Unreal Engine 4 动态切割模型实现

Unreal Engine 4 动态切割模型实现 <合金装备:复仇>里面,有一个很有趣的设定,游戏里大部分的场景和物件都可以用主角的刀动态切割. UE4中的ProceduralMeshComponent这个组件可以很容易的就实现这种功能,下面介绍下实现的方法. 准备模型 首先我们准备一个模型. 我做了一个简单的圆柱体,用来测试.注意需要切割的地方,为了保证细节,多放点顶点. 为了能够动态切割,我们要勾选这个模型的Allow CPUAccess选项. 新建蓝图 新建一个Actor蓝图,为它添加两

Unreal Engine 4 C++代码动态创建Constraint

在最新的Unreal Engine 4.4版本中,Blueprint内的PhysicsConstraint是有bug的,Blueprint不能编辑Constraint的两个Actor组件,唯一的方法是通过C++代码来实现.还有很多这样的问题,好在源代码都给你了,想怎么改随自己. 我想在ThirdPerson这个模板里实现角色荡秋千的功能,就像波斯猴子里面这种. 首先,给Character Blueprint添加一个UPhysicsConstraintComponent: UPROPERTY(Vi

游戏音频技术备忘 (四) Wwise Unreal Engine 集成代码浅析 (一)

在Engine\Plugins\Wwise\Source下为主要Wwise相关代码,AkAudio文件夹下为运行时相关代码,AudiokineticTools下为编辑器工具相关代码,AudiokineticTools.Build.cs为用于UnrealBuildTool的相应代码,与音频直接相关位于 AkAudio文件夹下. Unreal Engine提供若干宏来实现GC.序列化.网络等需求,譬如声明为UPROPERTY()的变量与UFUNCTION()的函数即可由引擎实现变量复制和远端调用 ,

游戏音频技术备忘 (三) 集成Wwise到Unreal Engine

当前受众较广的商业游戏引擎有 Unreal Engine.Unity.cocos2d-x等,在音频领域的第三方中间件则有Wwise.FMOD.Criware等,言多且烦,我们首先集成Wwise到 Unreal Engine 中去. 在https://github.com/EpicGames/UnrealEngine上可以抓取到Unreal Engine的源代码,前提条件是需要有一个 organization 加入EpicGames 的github账号,这就需要在https://www.unrea