攀爬系统概述
运用场景
在许多动作类游戏中,攀爬系统作为动作系统的一部分为游戏提供了更多的玩法。如“跑酷系统”、“躲避系统”、“轻功系统”。攀爬为以往操作在 “XY” 平面的动作游戏拓展了 “Z” 轴。让游戏的高楼大厦不再是背景板,而是游玩的空间。
类型游戏
- 刺客信条系列
- 看门狗系列
- 天涯明月刀(武侠类游戏轻功)
- 古墓丽影系列
- 蜘蛛侠
- 波斯王子
- 虐杀原形
- 塞尔达传说荒野之息
- …
攀爬系统的实现
实现灵感
在游玩 《古剑奇谭3》 的 “古厝回廊” 关卡时,由于跳跃没有吸附的功能,使得在越过障碍时很容易掉入深坑。使得该关卡的游玩体验与流畅度大打折扣。自己太菜的缘故。
而这类关卡在游玩 刺客信条、虐杀原形 等游戏时并没有这方面的困惑。像是刺客信条中的古墓探索等“纯跑酷”、“机关解密”关卡其花费的时间更多的时在 解密 与 场景变换 上,对于失误操作也有“ 边缘吸附 ”、“ 地图返回 ”等措施的补救。
经过以上的思想碰撞,我产生了一个问题 “攀爬系统是如何实现的呢?” 经过一段时间的学习与资料查找,我实现了攀爬系统。下面请让我来分享自己实现的思路与步骤。
引擎
虚幻4
4.25
操作
C++混蓝图
成果
攀爬动画资源
虚幻商城的:Climbing Animation Set
bilibili知名UP主"驴肉墨鱼汤UNOFFICIAL"的搬运视频:50分钟创建攀爬系统
实现思路
- 使用ue4第三人称模板。
- 角色在腾空时通过碰撞检测判断是否可攀爬。
- 可以攀爬时吸附到碰撞点,角色移动模式设置为 “Fly”。
- 判断上下左右四个方向是否可达,若可达根据输入向相应的方向进行移动。
- 转角、边缘处通过Montage动画进行特殊处理。
- 跳跃系统通过碰撞检测与Montage实现。
- 蹬墙跳、脱离,改变朝向停止吸附,改变移动模式“Falling”。
动画蓝图及Montage
动画蓝图
状态机新增一个 “Hanging” 状态
在状态机中使用一个 “Blend Space” 进行动画混合。将向上爬,向下爬,向左爬,向右爬,静止动画通过 Vertical 、 Horizontal 混合在一起,之后可以通过角色进行控制。
动画蓝图每帧获取角色状态,并通过状态调用相应的事件。
当角色状态改变时调用相应的事件,播放相应的动画。
当Montage播放完毕时,使用AnimNotify进行定位。在动画蓝图中实现该事件,调用角色中的处理函数。
Montage
相较于状态机,该动画资源主要用于使用较灵活,触发事件较短的动画。
使用DefaultGroup.DefaultSlot,添加 “AnimNotify” 标签用于状态检测与触发。
DefaultGroup.DefaultSlot决定Default Slot插槽。
Character和碰撞检测
碰撞检测
创建一个SceneComponent的子类
用于实时的状态检测
TraceSceneComponent.h
#include "CoreMinimal.h"
#include "Components/SceneComponent.h"
#include "TraceSceneComponent.generated.h"
class UArrowComponent;
class AClimbAnimCharacter;
UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class CLIMBANIM_API UTraceSceneComponent : public USceneComponent
{
GENERATED_BODY()
public:
// Sets default values for this component's properties
UTraceSceneComponent();
protected:
// Called when the game starts
virtual void BeginPlay() override;
public:
// Called every frame
virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
public:
UPROPERTY(EditAnywhere,BlueprintReadWrite, Category = "Poisition")
UArrowComponent* ArrowForward;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Poisition")
UArrowComponent* ArrowBack;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Climbing")
UArrowComponent* ArrowClimbingUp;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Climbing")
UArrowComponent* ArrowClimbingDown;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Climbing")
UArrowComponent* ArrowClimbingRight;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Climbing")
UArrowComponent* ArrowClimbingLeft;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Jumping")
UArrowComponent* ArrowJumpUp;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Jumping")
UArrowComponent* ArrowJumpRight;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Jumping")
UArrowComponent* ArrowJumpLeft;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ArrowParamater")
float ArrowLength;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ArrowParamater")
float ArrowJumpLength;
bool IsHanging;
float num;
FVector hitLocation;
FVector hitNormal;
public:
UFUNCTION()
bool HitResult(FVector beginLocation, FVector endLocation, FHitResult& hitResult);
//对线性碰撞检测的封装
UFUNCTION()
bool IsInAir();
//判断是否在空中
UFUNCTION(BlueprintCallable)
FORCEINLINE bool GetIsHanging() {
return IsHanging; }
class AClimbAnimCharacter* GetOwnerActor();
UFUNCTION()
void ForwardTrace();
void UpTrace();
void DownTrace();
void LeftTrace();
void RightTrace();
void JumpUpTrace();
void JumpRightTrace();
void JumpLeftTrace();
void ClimbDownTrace();
void BuildArrow();
//箭头的初始化
virtual void BeginDestroy()override;
//析构函数用于调试
};
TraceSceneComponent.cpp
// Called every frame
void UTraceSceneComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
// ...
//GEngine->AddOnScreenDebugMessage(2, 0.1f, FColor::Black, FString::SanitizeFloat(DeltaTime)+FString(TEXT(" "))+FString::SanitizeFloat(num));
IsInAir();
if (GetOwnerActor()