UE4 动态创建寻路网格(二)

11 篇文章 0 订阅

目录

1. 依赖模块

2. 自定义 ANavMeshBoundsVolume

3. 动态绘制

4. 模块化

5.导航测试

1. 依赖模块

*.build.cs

        PublicDependencyModuleNames
                "NavigationSystem",
                "Landscape",

2. 自定义 ANavMeshBoundsVolume

#pragma once

#include "CoreMinimal.h"
//#include "GameFramework/Volume.h"
#include "NavMesh/NavMeshBoundsVolume.h"
#include "CustomNavMeshBoundsVolume.generated.h"

UCLASS(BlueprintType, Blueprintable)
class NAVTEST_API ACustomNavMeshBoundsVolume : public ANavMeshBoundsVolume, public INavRelevantInterface 
{
    GENERATED_BODY()

public:
    // Sets default values for this actor's properties
    ACustomNavMeshBoundsVolume();

    
protected:
    // Called when the game starts or when spawned
    virtual void BeginPlay() override;

public:
    // Called every frame
    virtual void Tick(float DeltaTime) override;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "RuntimeNavMesh")
    float Width;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "RuntimeNavMesh")
    float Height;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "RuntimeNavMesh")
    float Depth;

    // Function to update the Brush Shape
    UFUNCTION(BlueprintCallable, Category = "RuntimeNavMesh")
    void UpdateBrushShape();


    //~ Begin AActor Interface
    virtual void PostRegisterAllComponents() override;
    virtual void PostUnregisterAllComponents() override;
    //~ End AActor Interface
#if WITH_EDITOR
    //~ Begin UObject Interface
    virtual void PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) override;
    virtual void PostEditUndo() override;
    //~ End UObject Interface
#endif // WITH_EDITOR


    virtual void CalcAndCacheBounds() const;
    //~ Begin INavRelevantInterface Interface
    virtual FBox GetNavigationBounds() const override;
    virtual bool IsNavigationRelevant() const override;
    virtual void UpdateNavigationBounds() override;
    virtual UObject* GetNavigationParent() const override;
    //~ End INavRelevantInterface Interface

    //~ Begin Actor Interface
    virtual FBox GetComponentsBoundingBox(bool bNonColliding = false, bool bIncludeFromChildActors = false) const override;
    //~ End Actor Interface

private:
    void updateNavMesh();
protected:

	/** bounds for navigation octree */
	mutable  FBox Bounds;
    FBox BoundsPre;
    
	mutable uint32 bBoundsInitialized : 1;

};

.cpp

#include "CustomNavMeshBoundsVolume.h"
#include "PhysicsEngine/BoxElem.h"
#include "NavigationSystem.h"
#include "Components/BrushComponent.h"


ACustomNavMeshBoundsVolume::ACustomNavMeshBoundsVolume()
{
    Width = 1000.0f;
    Height = 1000.0f;
    Depth = 1000.0f;

    GetBrushComponent()->SetCollisionProfileName(UCollisionProfile::NoCollision_ProfileName);
    GetBrushComponent()->Mobility = EComponentMobility::Movable;

    BrushColor = FColor(200, 200, 200, 255);
    SupportedAgents.MarkInitialized();

    bColored = true;

    //bNavigationRelevant = true;
    PrimaryActorTick.bCanEverTick = true;
    BoundsPre.Init();
#if WITH_EDITOR
    updateNavMesh();
#endif
}

// Called when the game starts or when spawned
void ACustomNavMeshBoundsVolume::BeginPlay()
{
    Super::BeginPlay();

}

void ACustomNavMeshBoundsVolume::updateNavMesh()
{
    CalcAndCacheBounds();
    if (!(Bounds == BoundsPre))
    {
        BoundsPre = Bounds;
        UpdateBrushShape();
    }
}

// Called every frame
void ACustomNavMeshBoundsVolume::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);
    updateNavMesh();
}

// Function to update the Brush Shape
void ACustomNavMeshBoundsVolume::UpdateBrushShape()
{
    UpdateNavigationBounds();

    class UBrushComponent* CurrBrushComp = GetBrushComponent();
    if (CurrBrushComp)
    {
        // Clear the old Brush Component
        //GetBrushComponent()->ClearBrushComponent();
        //GetBrushComponent()->MarkRenderStateDirty();

        
        if (CurrBrushComp->BrushBodySetup)
        {
            CurrBrushComp->BrushBodySetup->AggGeom.BoxElems.Add(FKBoxElem(Depth, Width, Height));
        }
        else
        {
            if (GEngine)
            {
                //GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Red, FString::Printf(TEXT("CurrBrushComp->BrushBodySetup invalid!")));
                UE_LOG(LogTemp, Log, TEXT("CurrBrushComp->BrushBodySetup invalid!"));
            }
        }
            
        // Update navigation data
        FNavigationSystem::UpdateComponentData(*CurrBrushComp);

        UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(GetWorld());
        if (NavSys)
        {
            NavSys->OnNavigationBoundsUpdated(this);
            NavSys->UpdateActorInNavOctree(*this);
        }
    }
    else
    {
        if (GEngine)
        {
            //GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Red, FString::Printf(TEXT("CurrBrushComp invalid!")));
            UE_LOG(LogTemp, Log, TEXT("CurrBrushComp invalid!"));
        }
    }
}

void ACustomNavMeshBoundsVolume::PostRegisterAllComponents()
{
    Super::PostRegisterAllComponents();

    UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(GetWorld());
    if (NavSys && GetLocalRole() == ROLE_Authority)
    {
        NavSys->OnNavigationBoundsAdded(this);
    }
}

void ACustomNavMeshBoundsVolume::PostUnregisterAllComponents()
{
    Super::PostUnregisterAllComponents();

    UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(GetWorld());
    if (NavSys && GetLocalRole() == ROLE_Authority)
    {
        NavSys->OnNavigationBoundsRemoved(this);
    }
}

#if WITH_EDITOR

void ACustomNavMeshBoundsVolume::PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent)
{
    Super::PostEditChangeProperty(PropertyChangedEvent);

    UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(GetWorld());
    if (GIsEditor && NavSys)
    {
        const FName PropName = (PropertyChangedEvent.Property != nullptr) ? PropertyChangedEvent.Property->GetFName() : FName();
        const FName MemberName = (PropertyChangedEvent.MemberProperty != nullptr) ? PropertyChangedEvent.MemberProperty->GetFName() : FName();

        if (PropName == FName("Width")
            || PropName == FName("Height")
            || PropName == FName("Depth")
            || PropName == FName("ComponentToWorld")
            || PropName == GET_MEMBER_NAME_CHECKED(ABrush, BrushBuilder)
            || MemberName == GET_MEMBER_NAME_CHECKED(ANavMeshBoundsVolume, SupportedAgents)
            || MemberName == USceneComponent::GetRelativeLocationPropertyName()
            || MemberName == USceneComponent::GetRelativeRotationPropertyName()
            || MemberName == USceneComponent::GetRelativeScale3DPropertyName())
        {       
            CalcAndCacheBounds();
            BoundsPre = Bounds;
            NavSys->OnNavigationBoundsUpdated(this);
        }
    }
}

void ACustomNavMeshBoundsVolume::PostEditUndo()
{
    Super::PostEditUndo();
    UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(GetWorld());
    if (GIsEditor && NavSys)
    {
        NavSys->OnNavigationBoundsUpdated(this);
    }
}

#endif // WITH_EDITOR



void ACustomNavMeshBoundsVolume::CalcAndCacheBounds() const
{
    FVector vecScale(1.0, 1.0, 1.0);
    vecScale = GetActorScale();
    Bounds = FBox::BuildAABB(GetActorLocation(), FVector(Depth, Width, Height) / 2 * vecScale);
    
    
    //旋转========旋转轴有问题,待优化
    FQuat Quat = GetActorQuat();    
    FTransform RotTran;
    RotTran.SetRotation(Quat);
    Bounds = Bounds.TransformBy(RotTran);
}

//~ Begin INavRelevantInterface Interface
FBox ACustomNavMeshBoundsVolume::GetNavigationBounds() const
{
    //if (!bBoundsInitialized || !(Bounds == BoundsPre))
    {
        CalcAndCacheBounds();
        bBoundsInitialized = true;
    }

    return Bounds;
}
bool ACustomNavMeshBoundsVolume::IsNavigationRelevant() const
{
    return true;// bNavigationRelevant;
}
void ACustomNavMeshBoundsVolume::UpdateNavigationBounds()
{
    CalcAndCacheBounds();
    bBoundsInitialized = true;
}
UObject* ACustomNavMeshBoundsVolume::GetNavigationParent() const
{
    return GetOwner();
}
//~ End INavRelevantInterface Interface


FBox ACustomNavMeshBoundsVolume::GetComponentsBoundingBox(bool bNonColliding/* = false*/, bool bIncludeFromChildActors/* = false*/) const
{
    FBox Box(ForceInit);
    Box = Super::GetComponentsBoundingBox(bNonColliding, bIncludeFromChildActors);
    Box += GetNavigationBounds();
    return Box;
}

3. 动态绘制

 .h

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "NavMeshDrawMgrActor.generated.h"

class ULineBatchComponent;

UCLASS()
class NAVTEST_API ANavMeshDrawMgrActor : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	ANavMeshDrawMgrActor();

public:

	UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category=DebugDraw)
		bool bShowBox=false;
	/** Box Line Batchers.  */
	UPROPERTY(Transient)
		class ULineBatchComponent* BoxLineBatcher;
	UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = DebugDraw)
		bool bShowNavMesh = false;
	/** NavMesh Line Batchers.  */
	UPROPERTY(Transient)
		class ULineBatchComponent* NavMeshLineBatcher;
	/** NavMesh Edges Line Batchers.  */
	UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = DebugDraw)
		bool bShowNavMeshEdges = false;
	UPROPERTY(Transient)
		class ULineBatchComponent* NavMeshEdgesLineBatcher;
private:
	/** LineBatcher组件初始化 */
	void initLineBatcher();
	/** LineBatcher组件清理 */
	void clearLineBatcher();
	/** 绘制清理 */
	void FlushBoxDebugLines();
	void FlushNavMeshDebugLines();
	void FlushNavMeshEdgesDebugLines();
	/** 绘制Box */
	void DrawDebugBox(const UWorld* InWorld, FVector const& Center, FVector const& Extent, FColor const& Color, bool bPersistentLines = false, float LifeTime = -1.f, uint8 DepthPriority = 0, float Thickness = 0.f);
	/** 绘制Mesh */
	void DrawDebugMesh(const UWorld* InWorld, TArray<FVector> const& Verts, TArray<int32> const& Indices, FColor const& Color, bool bPersistent = false, float LifeTime = -1.f, uint8 DepthPriority = 0);
	/** 绘制Line */
	void DrawDebugLine(const UWorld* InWorld, FVector const& LineStart, FVector const& LineEnd, FColor const& Color, bool bPersistentLines = false, float LifeTime = -1.f, uint8 DepthPriority = 0, float Thickness = 0.f);
	/** 更新导航盒子 */
	void UpdateNavBoxDraw();
	/** 更新导航网格 */
	void UpdateNavMeshDraw();
	/** 更新导航网格边界 */
	void UpdateNavMeshEdgesDraw();
	/** 绘制导航网格Tiles */
	void DrawNavMeshTiles();
	/** 绘制导航网格Edges */
	void DrawNavMeshEdges();
public:

	/** Draw Navigation Mesh in Runtime (Box) */
	UFUNCTION(BlueprintCallable, Category = "RuntimeNavMesh|Draw")
	bool RtNavMeshDrawBox(bool bInShowBox = true);
	/** Draw Navigation Mesh in Runtime (Mesh) */
	UFUNCTION(BlueprintCallable, Category = "RuntimeNavMesh|Draw")
	bool RtNavMeshDrawMesh(bool bInShowNavMesh = true);
	/** Draw Navigation Mesh in Runtime (Edges) */
	UFUNCTION(BlueprintCallable, Category = "RuntimeNavMesh|Draw")
	bool RtNavMeshDrawEdges(bool bInShowNavMeshEdges = true);

	/** Draw Navigation Mesh in Runtime */
	UFUNCTION(BlueprintCallable, Category = "RuntimeNavMesh|Draw")
		bool RtNavMeshDraw(bool bInShowBox = true, bool bInShowNavMesh = true);
	/** Draw Navigation Mesh in Runtime */
	UFUNCTION(BlueprintCallable, Category = "RuntimeNavMesh|Draw")
		bool RuntimeNavMeshDraw(bool bInShowBox = true, bool bInShowNavMesh = true, bool bInShowNavMeshEdges = true);

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;
	virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

};

.cpp

// Fill out your copyright notice in the Description page of Project Settings.


#include "NavMeshDrawMgrActor.h"
#include "NavMesh/NavMeshRenderingComponent.h"
#include "Kismet/GameplayStatics.h"
#include "NavMesh/RecastNavMesh.h"
#include "NavMesh/NavMeshBoundsVolume.h"
#include "EngineUtils.h"
#include "Components/LineBatchComponent.h"
#include "NavigationSystem.h"
#include <QERBoxCollision.h>
//#include "DetourNavMesh.h"

// Sets default values
ANavMeshDrawMgrActor::ANavMeshDrawMgrActor()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;

}

void ANavMeshDrawMgrActor::initLineBatcher()
{
	if (!BoxLineBatcher)
	{
		BoxLineBatcher = NewObject<ULineBatchComponent>();
		BoxLineBatcher->bCalculateAccurateBounds = false;
	}
	if (BoxLineBatcher && !BoxLineBatcher->IsRegistered())
	{
		BoxLineBatcher->RegisterComponentWithWorld(GetWorld());
	}

	if (!NavMeshLineBatcher)
	{
		NavMeshLineBatcher = NewObject<ULineBatchComponent>();
		NavMeshLineBatcher->bCalculateAccurateBounds = false;
	}
	if (NavMeshLineBatcher && !NavMeshLineBatcher->IsRegistered())
	{
		NavMeshLineBatcher->RegisterComponentWithWorld(GetWorld());
	}

	if (!NavMeshEdgesLineBatcher)
	{
		NavMeshEdgesLineBatcher = NewObject<ULineBatchComponent>();
		NavMeshEdgesLineBatcher->bCalculateAccurateBounds = false;
	}
	if (NavMeshEdgesLineBatcher && !NavMeshEdgesLineBatcher->IsRegistered())
	{
		NavMeshEdgesLineBatcher->RegisterComponentWithWorld(GetWorld());
	}
}

void ANavMeshDrawMgrActor::clearLineBatcher()
{
	if (BoxLineBatcher && BoxLineBatcher->IsRegistered())
	{
		BoxLineBatcher->UnregisterComponent();
	}

	if (NavMeshLineBatcher && NavMeshLineBatcher->IsRegistered())
	{
		NavMeshLineBatcher->UnregisterComponent();
	}

	if (NavMeshEdgesLineBatcher && NavMeshEdgesLineBatcher->IsRegistered())
	{
		NavMeshEdgesLineBatcher->UnregisterComponent();
	}
}

void ANavMeshDrawMgrActor::FlushBoxDebugLines()
{
	if (BoxLineBatcher)
	{
		BoxLineBatcher->Flush();
	}
}

void ANavMeshDrawMgrActor::FlushNavMeshDebugLines()
{
	if (NavMeshLineBatcher)
	{
		NavMeshLineBatcher->Flush();
	}
}

void ANavMeshDrawMgrActor::FlushNavMeshEdgesDebugLines()
{
	if (NavMeshEdgesLineBatcher)
	{
		NavMeshEdgesLineBatcher->Flush();
	}
}

void ANavMeshDrawMgrActor::DrawDebugBox(const UWorld* InWorld, FVector const& Center, FVector const& Box, FColor const& Color, bool bPersistentLines /*= false*/, float LifeTime /*= -1.f*/, uint8 DepthPriority /*= 0*/, float Thickness /*= 0.f*/)
{
	// no debug line drawing on dedicated server
	if (GEngine->GetNetMode(InWorld) != NM_DedicatedServer)
	{
		// this means foreground lines can't be persistent 
		if (ULineBatchComponent* const LineBatcher = BoxLineBatcher)
		{
			float LineLifeTime = bPersistentLines ? -1.0f : ((LifeTime > 0.f) ? LifeTime : LineBatcher->DefaultLifeTime);

			LineBatcher->DrawLine(Center + FVector(Box.X, Box.Y, Box.Z), Center + FVector(Box.X, -Box.Y, Box.Z), Color, DepthPriority, Thickness, LineLifeTime);
			LineBatcher->DrawLine(Center + FVector(Box.X, -Box.Y, Box.Z), Center + FVector(-Box.X, -Box.Y, Box.Z), Color, DepthPriority, Thickness, LineLifeTime);
			LineBatcher->DrawLine(Center + FVector(-Box.X, -Box.Y, Box.Z), Center + FVector(-Box.X, Box.Y, Box.Z), Color, DepthPriority, Thickness, LineLifeTime);
			LineBatcher->DrawLine(Center + FVector(-Box.X, Box.Y, Box.Z), Center + FVector(Box.X, Box.Y, Box.Z), Color, DepthPriority, Thickness, LineLifeTime);

			LineBatcher->DrawLine(Center + FVector(Box.X, Box.Y, -Box.Z), Center + FVector(Box.X, -Box.Y, -Box.Z), Color, DepthPriority, Thickness, LineLifeTime);
			LineBatcher->DrawLine(Center + FVector(Box.X, -Box.Y, -Box.Z), Center + FVector(-Box.X, -Box.Y, -Box.Z), Color, DepthPriority, Thickness, LineLifeTime);
			LineBatcher->DrawLine(Center + FVector(-Box.X, -Box.Y, -Box.Z), Center + FVector(-Box.X, Box.Y, -Box.Z), Color, DepthPriority, Thickness, LineLifeTime);
			LineBatcher->DrawLine(Center + FVector(-Box.X, Box.Y, -Box.Z), Center + FVector(Box.X, Box.Y, -Box.Z), Color, DepthPriority, Thickness, LineLifeTime);

			LineBatcher->DrawLine(Center + FVector(Box.X, Box.Y, Box.Z), Center + FVector(Box.X, Box.Y, -Box.Z), Color, DepthPriority, Thickness, LineLifeTime);
			LineBatcher->DrawLine(Center + FVector(Box.X, -Box.Y, Box.Z), Center + FVector(Box.X, -Box.Y, -Box.Z), Color, DepthPriority, Thickness, LineLifeTime);
			LineBatcher->DrawLine(Center + FVector(-Box.X, -Box.Y, Box.Z), Center + FVector(-Box.X, -Box.Y, -Box.Z), Color, DepthPriority, Thickness, LineLifeTime);
			LineBatcher->DrawLine(Center + FVector(-Box.X, Box.Y, Box.Z), Center + FVector(-Box.X, Box.Y, -Box.Z), Color, DepthPriority, Thickness, LineLifeTime);
		}
	}
	else
	{
		//UE_DRAW_SERVER_DEBUG_ON_EACH_CLIENT(&DrawDebugBox, Center, Box, Color, bPersistentLines, LifeTime, DepthPriority, Thickness);
	}
}

void ANavMeshDrawMgrActor::DrawDebugMesh(const UWorld* InWorld, TArray<FVector> const& Verts, TArray<int32> const& Indices, FColor const& Color, bool bPersistent /*= false*/, float LifeTime /*= -1.f*/, uint8 DepthPriority /*= 0*/)
{
	// no debug line drawing on dedicated server
	if (GEngine->GetNetMode(InWorld) != NM_DedicatedServer)
	{
		if (ULineBatchComponent* const LineBatcher = NavMeshLineBatcher)
		{
			float const ActualLifetime = bPersistent ? -1.0f : ((LifeTime > 0.f) ? LifeTime : LineBatcher->DefaultLifeTime);
			LineBatcher->DrawMesh(Verts, Indices, Color, DepthPriority, ActualLifetime);
		}
	}
	else
	{
		//UE_DRAW_SERVER_DEBUG_ON_EACH_CLIENT(DrawDebugMesh, Verts, Indices, Color, bPersistent, LifeTime, DepthPriority);
	}
}

void ANavMeshDrawMgrActor::DrawDebugLine(const UWorld* InWorld, FVector const& LineStart, FVector const& LineEnd, FColor const& Color, bool bPersistentLines, float LifeTime, uint8 DepthPriority, float Thickness)
{
	if (GEngine->GetNetMode(InWorld) != NM_DedicatedServer)
	{
		// this means foreground lines can't be persistent 
		if (ULineBatchComponent* const LineBatcher = NavMeshEdgesLineBatcher)
		{
			float const LineLifeTime = bPersistentLines ? -1.0f : ((LifeTime > 0.f) ? LifeTime : LineBatcher->DefaultLifeTime);
			LineBatcher->DrawLine(LineStart, LineEnd, Color, DepthPriority, Thickness, LineLifeTime);
		}
	}
	else
	{
		//UE_DRAW_SERVER_DEBUG_ON_EACH_CLIENT(DrawDebugLine, LineStart, LineEnd, AdjustColorForServer(Color), bPersistentLines, LifeTime, DepthPriority, Thickness);
	}
}

void ANavMeshDrawMgrActor::UpdateNavBoxDraw()
{
	this->FlushBoxDebugLines();

	UWorld* CurrWorld = GetWorld();
	if (CurrWorld)
	{
		if (bShowBox)
		{
			const EActorIteratorFlags Flags = EActorIteratorFlags::SkipPendingKill;
			for (TActorIterator<ANavMeshBoundsVolume> ItNavMeshBV(CurrWorld, ANavMeshBoundsVolume::StaticClass(), Flags); ItNavMeshBV; ++ItNavMeshBV)
			{
				ANavMeshBoundsVolume* pTempNMBV = *ItNavMeshBV;
				if (pTempNMBV)
				{
					const FVector Center = pTempNMBV->GetComponentsBoundingBox().GetCenter();
					const FVector Extent = pTempNMBV->GetComponentsBoundingBox().GetSize() / 2.f;

					this->DrawDebugBox(CurrWorld, Center, Extent, FColor(0, 255, 0), true, -1.f, 0, 10.f);
				}
			}
		}
	}
}

/** 更新导航网格 */
void ANavMeshDrawMgrActor::UpdateNavMeshDraw()
{
	this->FlushNavMeshDebugLines();
	if (bShowNavMesh)
	{
		DrawNavMeshTiles();
	}
}

/** 更新导航网格边界 */
void ANavMeshDrawMgrActor::UpdateNavMeshEdgesDraw()
{
	this->FlushNavMeshEdgesDebugLines();
	if (bShowNavMeshEdges)
	{
		DrawNavMeshEdges();
	}
}

/** Draw Navigation Mesh in Runtime (Box) */
bool ANavMeshDrawMgrActor::RtNavMeshDrawBox(bool bInShowBox/* = true*/)
{
	bShowBox = bInShowBox;
	if (!bInShowBox)
	{
		this->FlushBoxDebugLines();
	}
	return true;
}
/** Draw Navigation Mesh in Runtime (Mesh) */
bool ANavMeshDrawMgrActor::RtNavMeshDrawMesh(bool bInShowNavMesh/* = true*/)
{
	bShowNavMesh = bInShowNavMesh;
	if (!bInShowNavMesh)
	{
		this->FlushNavMeshDebugLines();
	}
	return true;
}
/** Draw Navigation Mesh in Runtime (Edges) */
bool ANavMeshDrawMgrActor::RtNavMeshDrawEdges(bool bInShowNavMeshEdges/* = true*/)
{
	bShowNavMeshEdges = bInShowNavMeshEdges;
	if (!bInShowNavMeshEdges)
	{
		this->FlushNavMeshEdgesDebugLines();
	}
	return true;
}

bool ANavMeshDrawMgrActor::RtNavMeshDraw(bool bInShowBox /*= true*/, bool bInShowNavMesh /*= true*/)
{
	UWorld* CurrWorld = GetWorld();
	if (CurrWorld)
	{
#if 0 //WITH_EDITOR
		ARecastNavMesh* DefRecastNavMesh = Cast<ARecastNavMesh>(UGameplayStatics::GetActorOfClass(CurrWorld, ARecastNavMesh::StaticClass()));
		if (DefRecastNavMesh)
		{
			DefRecastNavMesh->bDrawFilledPolys = bInDraw;
			DefRecastNavMesh->bDrawNavMeshEdges = bInDraw;
			//DefRecastNavMesh->UpdateDrawing();
			UNavMeshRenderingComponent* NavMeshRenderComp = Cast<UNavMeshRenderingComponent>(DefRecastNavMesh->RenderingComp);
			if (NavMeshRenderComp != nullptr && NavMeshRenderComp->GetVisibleFlag() && (NavMeshRenderComp->IsForcingUpdate() || UNavMeshRenderingComponent::IsNavigationShowFlagSet(CurrWorld)))
			{
				DefRecastNavMesh->RenderingComp->MarkRenderStateDirty();
			}
			//return true;
		}
#endif

		RtNavMeshDrawBox(bInShowBox);
		RtNavMeshDrawMesh(bInShowNavMesh);

		return true;
	}

	return false;
}

bool ANavMeshDrawMgrActor::RuntimeNavMeshDraw(bool bInShowBox /*= true*/, bool bInShowNavMesh /*= true*/, bool bInShowNavMeshEdges /*= true*/)
{
	RtNavMeshDrawBox(bInShowBox);
	RtNavMeshDrawMesh(bInShowNavMesh);
	RtNavMeshDrawEdges(bInShowNavMeshEdges);

	return true;
}

// Called when the game starts or when spawned
void ANavMeshDrawMgrActor::BeginPlay()
{
	Super::BeginPlay();

	initLineBatcher();
}

// Called every frame
void ANavMeshDrawMgrActor::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	if (bShowBox)
	{
		UpdateNavBoxDraw();
	}

	if (bShowNavMesh)
	{
		UpdateNavMeshDraw();
	}
	if (bShowNavMeshEdges)
	{
		UpdateNavMeshEdgesDraw();
	}
}

void ANavMeshDrawMgrActor::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
	FlushBoxDebugLines();
	FlushNavMeshDebugLines();
	FlushNavMeshEdgesDebugLines();
	clearLineBatcher();
}

#if WITH_RECAST
int32 ANavMeshDrawMgrActor::GetDetailFlags(const ARecastNavMesh* NavMesh)
{
	return (NavMesh == nullptr) ? 0 : 0 |
		(NavMesh->bDrawTriangleEdges ? (1 << static_cast<int32>(ENavMeshDetailFlags::TriangleEdges)) : 0) |
		(NavMesh->bDrawPolyEdges ? (1 << static_cast<int32>(ENavMeshDetailFlags::PolyEdges)) : 0) |
		(NavMesh->bDrawFilledPolys ? (1 << static_cast<int32>(ENavMeshDetailFlags::FilledPolys)) : 0) |
		(NavMesh->bDrawNavMeshEdges ? (1 << static_cast<int32>(ENavMeshDetailFlags::BoundaryEdges)) : 0) |
		(NavMesh->bDrawTileBounds ? (1 << static_cast<int32>(ENavMeshDetailFlags::TileBounds)) : 0) |
		(NavMesh->bDrawPathCollidingGeometry ? (1 << static_cast<int32>(ENavMeshDetailFlags::PathCollidingGeometry)) : 0) |
		(NavMesh->bDrawTileLabels ? (1 << static_cast<int32>(ENavMeshDetailFlags::TileLabels)) : 0) |
		(NavMesh->bDrawPolygonLabels ? (1 << static_cast<int32>(ENavMeshDetailFlags::PolygonLabels)) : 0) |
		(NavMesh->bDrawDefaultPolygonCost ? (1 << static_cast<int32>(ENavMeshDetailFlags::PolygonCost)) : 0) |
		(NavMesh->bDrawPolygonFlags ? (1 << static_cast<int32>(ENavMeshDetailFlags::PolygonFlags)) : 0) |
		(NavMesh->bDrawLabelsOnPathNodes ? (1 << static_cast<int32>(ENavMeshDetailFlags::PathLabels)) : 0) |
		(NavMesh->bDrawNavLinks ? (1 << static_cast<int32>(ENavMeshDetailFlags::NavLinks)) : 0) |
		(NavMesh->bDrawFailedNavLinks ? (1 << static_cast<int32>(ENavMeshDetailFlags::FailedNavLinks)) : 0) |
		(NavMesh->bDrawClusters ? (1 << static_cast<int32>(ENavMeshDetailFlags::Clusters)) : 0) |
		(NavMesh->bDrawOctree ? (1 << static_cast<int32>(ENavMeshDetailFlags::NavOctree)) : 0) |
		(NavMesh->bDrawOctreeDetails ? (1 << static_cast<int32>(ENavMeshDetailFlags::NavOctreeDetails)) : 0) |
		(NavMesh->bDrawMarkedForbiddenPolys ? (1 << static_cast<int32>(ENavMeshDetailFlags::MarkForbiddenPolys)) : 0);
}
#endif // WITH_RECAST

void ANavMeshDrawMgrActor::DrawNavMeshTiles()
{
	UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(GetWorld());

	if (NavSys)
	{
#if WITH_RECAST
		for (auto NavigationData : NavSys->NavDataSet)
		{
			ARecastNavMesh* RecastNavMesh = Cast<ARecastNavMesh>(NavigationData);
			if (RecastNavMesh)
			{
				const FNavDataConfig& NavConfig = RecastNavMesh->GetConfig();
				TArray<FColor> NavMeshColors;
				NavMeshColors.AddDefaulted(RECAST_MAX_AREAS);

				for (uint8 Idx = 0; Idx < RECAST_MAX_AREAS; Idx++)
				{
					NavMeshColors[Idx] = RecastNavMesh->GetAreaIDColor(Idx);
				}
				NavMeshColors[RECAST_DEFAULT_AREA] = NavConfig.Color.DWColor() > 0 ? NavConfig.Color : FColor(140, 255, 0, 164);
				FVector NavMeshDrawOffset = FVector::ZeroVector;
				NavMeshDrawOffset.Z = RecastNavMesh->DrawOffset;

				//方式2:代理方式
				FNavMeshSceneProxyData OutProxyData;
				//const int32 DetailFlags = OutProxyData.GetDetailFlags(RecastNavMesh);// UE4.27
				const int32 DetailFlags = GetDetailFlags(RecastNavMesh);
				TArray<int32> EmptyTileSet;
				OutProxyData.GatherData(RecastNavMesh, DetailFlags, EmptyTileSet);
				for (FNavMeshSceneProxyData::FDebugMeshData& ItDMeshData : OutProxyData.MeshBuilders)
				{
					TArray<FVector> ArrayVecTempVerties;
					int nVertNum = ItDMeshData.Vertices.Num();
					ArrayVecTempVerties.AddDefaulted(nVertNum);
					for (int nItVert = 0; nItVert < nVertNum; ++nItVert)
					{
						//ArrayVecTempVerties[nItVert] = ItDMeshData.Vertices[nItVert].Position;// UE4.27
						ArrayVecTempVerties[nItVert].X = ItDMeshData.Vertices[nItVert].Position.X;
						ArrayVecTempVerties[nItVert].Y = ItDMeshData.Vertices[nItVert].Position.Y;
						ArrayVecTempVerties[nItVert].Z = ItDMeshData.Vertices[nItVert].Position.Z;
					}
					TArray<int32> ArrayTempIndex;
					int nIdxNum = ItDMeshData.Indices.Num();
					ArrayTempIndex.AddDefaulted(nIdxNum);
					for (int nItIdx = 0; nItIdx < nIdxNum; ++nItIdx)
					{
						ArrayTempIndex[nItIdx] = ItDMeshData.Indices[nItIdx];
					}
					this->DrawDebugMesh(GetWorld(), ArrayVecTempVerties, ArrayTempIndex, ItDMeshData.ClusterColor, true, -1.f, 2);

				}

#if 0
				//方式1:GetDebugGeometry(FRecastDebugGeometry & OutGeometry, int32 TileIndex = INDEX_NONE)
				FRecastDebugGeometry OutGeometry;
				OutGeometry.bGatherNavMeshEdges = false;
				OutGeometry.bGatherPolyEdges = true;// false;
				OutGeometry.bMarkForbiddenPolys = false;
				RecastNavMesh->GetDebugGeometry(OutGeometry);
				//this->DrawDebugMesh(GetWorld(), OutGeometry.MeshVerts, OutGeometry.BuiltMeshIndices, FColor(0, 200, 0), true, -1.f, 0);
				//Offset
				TArray<FVector> VecMeshVertsWithOffset;
				int nVertNum = OutGeometry.MeshVerts.Num();
				VecMeshVertsWithOffset.AddDefaulted(nVertNum);
				for (int32 VertIdx = 0; VertIdx < nVertNum; ++VertIdx)
				{
					VecMeshVertsWithOffset[VertIdx] = OutGeometry.MeshVerts[VertIdx] + NavMeshDrawOffset;
				}
				for (int32 AreaType = 0; AreaType < RECAST_MAX_AREAS; ++AreaType)
				{
					if (NavMeshColors[AreaType].B>0  || NavMeshColors[AreaType].R> NavMeshColors[AreaType].G)
					{
						if (GEngine)
						{							
							GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Red, FString::Printf(TEXT("Blue Area %d: Color-%s"), AreaType, *NavMeshColors[AreaType].ToString()));
							UE_LOG(LogTemp, Log, TEXT("CurrBrushComp invalid!"));
						}
					}
					else
					{
						this->DrawDebugMesh(GetWorld(), VecMeshVertsWithOffset, OutGeometry.AreaIndices[AreaType], NavMeshColors[AreaType], true, -1.f, 0);
						break;
					}
				}
			}
		}
#endif
	}
}

void ANavMeshDrawMgrActor::DrawNavMeshEdges()
{
	UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(GetWorld());

	if (NavSys)
	{
#if WITH_RECAST
		for (auto NavigationData : NavSys->NavDataSet)
		{
			ARecastNavMesh* RecastNavMesh = Cast<ARecastNavMesh>(NavigationData);
			if (RecastNavMesh)
			{
				const FNavDataConfig& NavConfig = RecastNavMesh->GetConfig();
				TArray<FColor> NavMeshColors;
				NavMeshColors.AddDefaulted(RECAST_MAX_AREAS);

				for (uint8 Idx = 0; Idx < RECAST_MAX_AREAS; Idx++)
				{
					NavMeshColors[Idx] = RecastNavMesh->GetAreaIDColor(Idx);
				}
				NavMeshColors[RECAST_DEFAULT_AREA] = NavConfig.Color.DWColor() > 0 ? NavConfig.Color : FColor(140, 255, 0, 164);
				FVector NavMeshDrawOffset = FVector::ZeroVector;
				NavMeshDrawOffset.Z = RecastNavMesh->DrawOffset;

				//GetDebugGeometry(FRecastDebugGeometry & OutGeometry, int32 TileIndex = INDEX_NONE)
				FRecastDebugGeometry OutGeometry;
				OutGeometry.bGatherNavMeshEdges = true;
				OutGeometry.bGatherPolyEdges = false;
				OutGeometry.bMarkForbiddenPolys = false;
				RecastNavMesh->GetDebugGeometry(OutGeometry);

				//NavMeshEdges
				const uint32 Col = NavMeshColors[RECAST_DEFAULT_AREA].DWColor();
				const FColor EdgesColor = FColor(((Col >> 1) & 0x007f7f7f) | (Col & 0xff000000));
				const TArray<FVector>& NavMeshEdgeVerts = OutGeometry.NavMeshEdges;
				for (int32 Idx = 0; Idx < NavMeshEdgeVerts.Num(); Idx += 2)
				{
					this->DrawDebugLine(GetWorld(), NavMeshEdgeVerts[Idx] + NavMeshDrawOffset, NavMeshEdgeVerts[Idx + 1] + NavMeshDrawOffset, /*EdgesColor*/ FColor(0, 0, 255, 255), true, -1.f, 4);
				}
			}
		}
#endif
	}
}

4. 模块化

.h

// Copyright Epic Games, Inc. All Rights Reserved.

#pragma once

#include "CoreMinimal.h"
#include "Modules/ModuleManager.h"

class FNavTestModule : public IModuleInterface
{
public:

	/** IModuleInterface implementation */
	virtual void StartupModule() override;
	virtual void ShutdownModule() override;
};

.cpp

// Copyright Epic Games, Inc. All Rights Reserved.

#include "NavTest.h"
#include "Modules/ModuleManager.h"

#define LOCTEXT_NAMESPACE "FNavTestModule"

void FNavTestModule::StartupModule()
{
	// This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module
}

void FNavTestModule::ShutdownModule()
{
	// This function may be called during shutdown to clean up your module.  For modules that support dynamic reloading,
	// we call this function before unloading the module.
}

#undef LOCTEXT_NAMESPACE
	
IMPLEMENT_MODULE(FNavTestModule, NavTest)
 

5.导航测试

 .h

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "NavBlueprintFunctionLibrary.generated.h"

class UNavigationPath;
/**
 * 
 */
UCLASS()
class NAVTEST_API UNavBlueprintFunctionLibrary : public UBlueprintFunctionLibrary
{
	GENERATED_BODY()
	
public:

	/** 获取指定名称的World; */
	/*@Param	InStrWorldName		World Name
	 *@Return	UWorld*				World Pointer(if World Name is Empty return the first vaild world.)	
	*/
	static UWorld* GetWorldByName(FString InStrWorldName=TEXT(""));

	// Function to find the Nearest Point of Navigation
	//@param InStartPoint		Start Point
	//@param InEndPoint			End Point(Preferably a navigable point)
	//@param fNavigationOffset	Navigation offset
	UFUNCTION(BlueprintCallable, Category = "RuntimeNavMesh|BPL")
		static FVector FindNearestNavPoint(const FVector& InStartPoint, const FVector& InEndPoint, float fNavigationOffset=100.0, FString InStrWorldName=TEXT(""));

	// Get the landscape Height of the special Location
	//@param InVecLoc				Location 
	//@param InStrWorldName			World Name
	//@return float					Height Value
	UFUNCTION(BlueprintCallable)
		static float GetLandscapeHeight(const FVector& InVecLoc, FString InStrWorldName = TEXT(""));
	// Get the landscape Max/Min Height of the special World(or First World)
	//@param OutfMinHeight			(Out) Minimum Height
	//@param OutfMaxHeight			(Out) Maximum Height
	//@return bool					True-Success;false-Fail.
	UFUNCTION(BlueprintCallable)
		static bool GetLandscapeMinMaxHeight(float& OutfMinHeight, float& OutfMaxHeight, FString InStrWorldName = TEXT(""), float fExtendScale=1.f);
private:

	static UWorld* GetFirstWorld();
	/** 查找导航路径 */
	static UNavigationPath* findNavigationPath(const FVector& InStartPoint, const FVector& InEndPoint, FString InStrWorldName=TEXT(""));
	
};

.cpp

// Fill out your copyright notice in the Description page of Project Settings.


#include "NavBlueprintFunctionLibrary.h"
#include "Engine/Engine.h"
#include "NavigationPath.h"
#include "NavigationSystem.h"
#include "NavigationData.h"
#include "NavFilters/NavigationQueryFilter.h"
#include "Landscape.h"
#include "EngineUtils.h"
#include "LandscapeInfo.h"
#include "LandscapeHeightfieldCollisionComponent.h"
#include "GeneratedCodeHelpers.h"



UWorld* UNavBlueprintFunctionLibrary::GetFirstWorld()
{
	//Get first valid world
	for (const FWorldContext& Context : GEngine->GetWorldContexts())
	{
#if WITH_EDITOR
		if (GIsEditor)
		{
			if (Context.WorldType == EWorldType::Editor)
			{
				return Context.World();
			}
		}
		else
		{
			if (Context.World() != nullptr)
			{
				return Context.World();
			}
		}
#else
		if (Context.World() != nullptr)
		{
			return Context.World();
		}
#endif
	}

	return nullptr;
}

UWorld* UNavBlueprintFunctionLibrary::GetWorldByName(FString InStrWorldName/*=TEXT("")*/)
{
	if (InStrWorldName.IsEmpty())
	{
		return GetFirstWorld();
	}

	for (const FWorldContext& Context : GEngine->GetWorldContexts())
	{
#if WITH_EDITOR
		if (GIsEditor)
		{
			if (Context.WorldType == EWorldType::Editor)
			{
				if (Context.World()->GetName() == InStrWorldName)
				{
					return Context.World();
				}
			}
		}
		else
		{
			if (Context.World() != nullptr)
			{
				if (Context.World()->GetName() == InStrWorldName)
				{
					return Context.World();
				}
			}
		}
#else
		if (Context.World() != nullptr)
		{
			if (Context.World()->GetName() == InStrWorldName)
			{
				return Context.World();
			}
		}
#endif
	}

	return nullptr;
}

FVector UNavBlueprintFunctionLibrary::FindNearestNavPoint(const FVector& InStartPoint, const FVector& InEndPoint, float fNavigationOffset/*=100.0*/, FString InStrWorldName/*=TEXT("")*/)
{
	UE_LOG(LogTemp, Warning, TEXT("Start:%s; End:%s; Length=%f"), *InStartPoint.ToString(), *InEndPoint.ToString(), (InStartPoint - InEndPoint).Size());

	if ((InStartPoint - InEndPoint).Size() <= fNavigationOffset)
	{
		return InEndPoint;
	}

	UWorld* pTheWorld = GetWorldByName(InStrWorldName);
	if (!pTheWorld)
	{
		return InEndPoint;
	}
	UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(pTheWorld);
	if (NavSys)
	{
		FNavLocation OutNavLocation;
		ANavigationData* UseNavData = NavSys->GetDefaultNavDataInstance(FNavigationSystem::DontCreate);
		if (UseNavData)
		{
			bool bResult = false;
			FVector QueryExtent = FVector::ZeroVector;
			bResult = NavSys->ProjectPointToNavigation(InEndPoint, OutNavLocation, QueryExtent, nullptr);
			//终点导航不可达
			if (!bResult)
			{
				return InEndPoint;
			}
		}
	}

	UNavigationPath* pNavPath = findNavigationPath(InStartPoint, InEndPoint);
	if (pNavPath)
	{
		if (pNavPath->GetPathLength() <= 0.1)
		{
			FVector HalfStartPoint = (InEndPoint + InStartPoint) / 2;
			FVector VecNavigationableP = FindNearestNavPoint(HalfStartPoint, InEndPoint, fNavigationOffset);
			if ((VecNavigationableP - HalfStartPoint).Size() <= fNavigationOffset)
			{
				return VecNavigationableP;
			}
			else
			{
				return FindNearestNavPoint(HalfStartPoint, VecNavigationableP, fNavigationOffset);
			}
		}

		return InStartPoint;
	}

	return InEndPoint;
}

UNavigationPath* UNavBlueprintFunctionLibrary::findNavigationPath(const FVector& InStartPoint, const FVector& InEndPoint, FString InStrWorldName)
{
	UNavigationPath* ResultPath = NULL;

	UWorld* pTheWorld = GetWorldByName(InStrWorldName);
	if (pTheWorld)
	{
		UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(pTheWorld);
		if (NavSys)
		{
			ResultPath = NewObject<UNavigationPath>(NavSys);
			const ANavigationData* NavigationData = NULL;
			// just use default
			NavigationData = NavSys->GetDefaultNavDataInstance();
			check(NavigationData);
			TSubclassOf<UNavigationQueryFilter> FilterClass;
			const FPathFindingQuery Query(nullptr, *NavigationData, InStartPoint, InEndPoint, UNavigationQueryFilter::GetQueryFilter(*NavigationData, nullptr, FilterClass));
			const FPathFindingResult Result = NavSys->FindPathSync(Query, EPathFindingMode::Regular);
			if (Result.IsSuccessful() && ResultPath)
			{
				ResultPath->SetPath(Result.Path);
			}
		}
	}
	return ResultPath;
}


float UNavBlueprintFunctionLibrary::GetLandscapeHeight(const FVector& InVecLoc, FString InStrWorldName)
{
	UWorld* pTheWorld = GetWorldByName(InStrWorldName);
	if (pTheWorld)
	{
		// 获取Landscape的Actor
		ALandscape* Landscape = nullptr;
		const EActorIteratorFlags Flags = EActorIteratorFlags::SkipPendingKill;
		for (TActorIterator<ALandscape> ItLs(pTheWorld, ALandscape::StaticClass(), Flags); ItLs; ++ItLs)
		{
			Landscape = *ItLs;

			if (Landscape)
			{
				TOptional<float> fOpHeight = Landscape->GetHeightAtLocation(InVecLoc);
				if (fOpHeight.IsSet())
				{
					return fOpHeight.GetValue();
				}
			}
		}
	}

	return 0.0f;
}

bool UNavBlueprintFunctionLibrary::GetLandscapeMinMaxHeight(float& OutfMinHeight, float& OutfMaxHeight, FString InStrWorldName, float fExtendScale)
{
	UWorld* pTheWorld = GetWorldByName(InStrWorldName);
	if (pTheWorld)
	{
		float MinHeight = TNumericLimits<float>::Max();
		float MaxHeight = TNumericLimits<float>::Min();
		// 获取Landscape的Actor
		ALandscape* Landscape = nullptr;
		const EActorIteratorFlags Flags = EActorIteratorFlags::SkipPendingKill;
		for (TActorIterator<ALandscape> ItLs(pTheWorld, ALandscape::StaticClass(), Flags); ItLs; ++ItLs)
		{
			Landscape = *ItLs;

			if (Landscape)
			{				
				#if WITH_CHAOS
				int32 nSizeX = 0;
				int32 nSizeY = 0;
				TArray<float> ArrayValues;
				Landscape->GetHeightValues(nSizeX, nSizeY, ArrayValues);
				for (float ItHeight : ArrayValues)
				{
					if (ItHeight < MinHeight)
					{
						MinHeight = ItHeight;
					}
					if (ItHeight > MaxHeight)
					{
						MaxHeight = ItHeight;
					}
				}
				#else
				
				FVector VecBase = Landscape->GetActorLocation();
				FVector VecScale3D = Landscape->GetActorScale3D();
				FVector Origin; FVector BoxExtent;
				Landscape->GetActorBounds(false, Origin, BoxExtent);
				FVector VecResolution = (BoxExtent - Origin) * 2;
				float fInterval = (float)Landscape->ComponentSizeQuads;
				for (int nItSampleX = 0; nItSampleX < Landscape->ComponentSizeQuads; ++nItSampleX)
				{
					for (int nItSampleY = 0; nItSampleY < Landscape->ComponentSizeQuads; ++nItSampleY)
					{
						FVector VecAdder = VecResolution * FVector(nItSampleX / fInterval, nItSampleY/ fInterval, 0.f) * fExtendScale;
						TOptional<float> Height = Landscape->GetHeightAtLocation(VecBase + VecAdder);
						if (Height.IsSet())
						{
							if (Height.GetValue() < MinHeight)
							{
								MinHeight = Height.GetValue();
							}
							if (Height.GetValue() > MaxHeight)
							{
								MaxHeight = Height.GetValue();
							}
						}
					}
				}
				#endif				
			}
		}

		OutfMinHeight = MinHeight;
		OutfMaxHeight = MaxHeight;

		return true;
	}

	return false;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值