独立游戏《星尘异变》UE5 C++程序开发日志5——实现物流系统

avatar
作者
筋斗云
阅读量:2

目录

一、进出口清单

二、路径计算

 三、包裹

1.包裹的数据结构

 2.包裹在场景中的运动

四、道路

1.道路的数据结构

2.道路的建造

3.道路的销毁

4.某个有道路连接的建筑被删除


        作为一个工厂类模拟经营游戏,各个工厂之间的运输必不可少,本游戏采用的是按需进口的模式,工厂之间可以建立类似于传送带一样的直连道路,每个工厂根据自身当前缺少的所需物品,按照从近到远的顺序依次访问能够生产该物品的工厂,然后收到出口订单的工厂会发出包裹,沿着玩家建设的道路送达发出进口需求的工厂,玩家可以手动配置进出口清单,也就是工厂仓库中某类物品少于多少个就要进口,以及某类物品多于多少个才可以出口,效果如下:

一、进出口清单

         玩家可以编辑每一个建筑的进出口清单实现对进出口的调控,即库存少于多少进口,多于多少出口。清单是一个数组,包括物品的种类和数量,同时还有自动和手动计算的功能切换,在自动模式下,清单中的数值即为生产时实际需求的原料数量,在改为手动模式后,对应物品的数量等于上次手动设置过的数量,清单数组中的数据结构如下:

USTRUCT(BlueprintType) struct FImportStardust { 	FImportStardust(const FName& StardustId, const int Quantity) 		: StardustId(StardustId), 		  Quantity(Quantity) 	{ 	}  	GENERATED_BODY()  	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Import") 	FName StardustId{ "Empty" };  	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Import") 	int Quantity{ 0 };  	//是否手动更新数量 	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Import") 	bool IsAuto{true};  	//上一次手动设定的值 	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Import") 	int LastManualSet{0};  	FImportStardust()=default; };

        设置清单中某类星尘的数量:

bool ABP_Asters::SetElementInImportingStardust(const int& Index, const int& Amount) {     //检查索引是否合法 	if(Index<0||Index>=ImportingStardust.Num()) 	{ 		UE_LOG(LogTemp,Error,TEXT("SetElementInImportingStardust failed,invalid index:%d"),Index); 		return false; 	} 	ImportingStardust[Index].Quantity=Amount;     //维护上一次手动设置的值 	if(!ImportingStardust[Index].IsAuto) 	{ 		ImportingStardust[Index].LastManualSet=Amount; 	} 	return true; }

设置某类星尘的计算是否手动:

void ABP_Asters::SetIsAutoInImportingStardust(const int& Index, const bool& IsAuto) {     //检查索引是否合法 	if(Index<0||Index>=ImportingStardust.Num()) 	{ 		UE_LOG(LogTemp,Error,TEXT("SetIsAutoInImportingStardust failed,invalid index:%d"),Index); 		return; 	} 	ImportingStardust[Index].IsAuto=IsAuto; 	if(IsAuto) 	{ 		ImportingStardust[Index].LastManualSet=ImportingStardust[Index].Quantity; 		//计算某类星尘的需求量 ImportingStardust[Index].Quantity=CalCulateReactionConsumption(ImportingStardust[Index].StardustId); 	} 	else 	{ 		ImportingStardust[Index].Quantity=ImportingStardust[Index].LastManualSet; 	} }

二、路径计算

        我们的物流是由进口需求引导的,所以寻路也是由某一个建筑出发,依次遍历连通的最近的建筑来尝试从其进口需要的物品,路径为从出口天体到该天体的路径

TArray<FStardustBasic> ATradingSystemActor::TriggerImport(const int& SourceAsterIndex, const TArray<FStardustBasic> ImportingStardust) {//输入进口源天体的索引和需求的星尘,返回有哪些进口需求未被满足     //检查输入索引是否合法 	if(!DebugActor->AllAster.Find(SourceAsterIndex)) 	{ 		UE_LOG(LogTemp,Error,TEXT("TriggerImport failed,invalid index:%d"),SourceAsterIndex); 		return TArray<FStardustBasic>(); 	} 	std::unordered_map<std::string,int>StardustNeed; 	for(const auto& it:ImportingStardust) 	{ 		StardustNeed[TCHAR_TO_UTF8(*it.StardustId.ToString())]=it.Quantity; 	}     //建立一个dijkstra算法使用的节点结构,包含点的ID和到起点距离 	struct Node 	{ 		Node(const int& ID, const long long& DIstance) 			: ID(ID), 			  DIstance(DIstance) 		{ 		} 		Node(const Node& Other):ID(Other.ID),DIstance(Other.DIstance){} 		int ID; 		long long DIstance; 		 	};     //重载优先队列排序规则 	auto cmp{[](const TSharedPtr<Node>&a,const TSharedPtr<Node>& b){return a->DIstance>b->DIstance;}}; 	 //储存当前待遍历的点的优先队列,按到起点路径长度从小到大排序 std::priority_queue<TSharedPtr<Node>,std::vector<TSharedPtr<Node>>,decltype(cmp)>Queue(cmp);     //放入起点 	Queue.push(MakeShared<Node>(SourceAsterIndex, 0));     //起点到每一个点的最短距离 	std::map<int,long long>MinimumDistance;     //每个点是否被处理完毕 	std::map<int,bool>Done;     //储存最短路径中每个点的父节点 	std::map<int,int>Path; 	for(auto& it:DebugActor->AllAster) 	{         //初始化最短距离为极大值 		MinimumDistance[it.Key]=1e18; 		Done[it.Key]=false; 	} 	MinimumDistance[SourceAsterIndex]=0; 	while(!Queue.empty()) 	{ 		auto Current{Queue.top()}; 		Queue.pop(); 		if(Done[Current->ID]) 		{ 			continue; 		} 		if(Current->ID!=SourceAsterIndex) 		{ 			if(!DebugActor->AllAster.Find(Current->ID)) 			{ 				continue; 			}             //当前遍历到的天体 			auto FoundedAster{DebugActor->AllAster[Current->ID]}; 			TArray<FStardustBasic>PackgingStardust;             //遍历出口清单 			for(const auto&it:FoundedAster->GetExportingStardust()) 			{ 				std::string IDString{TCHAR_TO_UTF8(*it.StardustId.ToString())}; 				if(StardustNeed.find(IDString)==StardustNeed.end()||!StardustNeed[IDString]) 				{ 					continue; 				}                 //找到的天体可出口的星尘数量 				int Available{FoundedAster->OutputInventory->CheckStardust(it.StardustId)-it.Quantity};                 //实际出口的数量 				if(int Transfered{std::max(0,std::min(StardustNeed[IDString],Available))}) 				{                     //维护当前包裹中的星尘和天体仓库中的星尘 					PackgingStardust.Add(FStardustBasic(it.StardustId,Transfered)); 					FoundedAster->OutputInventory->RemoveStardust(it.StardustId,Transfered); 					StardustNeed[IDString]-=Transfered; 					if(!StardustNeed[IDString]) 					{ 						StardustNeed.erase(IDString); 					} 				} 			}             //该天体进行了出口 			if(!PackgingStardust.IsEmpty()) 			{ 				TArray<int>PassedAsters; 				int CurrentPosition{Current->ID};                 //记录该天体到进口需求发出天体的路径 				while (CurrentPosition!=SourceAsterIndex) 				{ 					CurrentPosition=Path[CurrentPosition]; 					PassedAsters.Add(CurrentPosition); 				} 				TArray<int>PassedAsters2;                 //使路径从后往前为包裹要走过的天体 				for(int i=PassedAsters.Num()-1;i>=0;i--) 				{ 					PassedAsters2.Add(PassedAsters[i]); 				}                 //令目标天体发送包裹 				SendPackage(FPackageInformation(Current->ID,PassedAsters2,PackgingStardust));                 //所有进口需求都被满足,提前终止 				if(StardustNeed.empty()) 				{ 					return TArray<FStardustBasic>(); 				} 			} 		}         //该天体处理完毕,防止被再次处理 		Done[Current->ID]=true;         //遍历该天体所有联通的天体 		for(const auto&it:AsterGraph[Current->ID]) 		{ 			if(Done[it->TerminalIndex]) 				continue;             //这条路是最短路 			if(MinimumDistance[it->TerminalIndex]>it->distance+Current->DIstance) 			{ 				Path[it->TerminalIndex]=Current->ID;                 //更新最短路径 				MinimumDistance[it->TerminalIndex]=it->distance+Current->DIstance; 				Queue.push(MakeShared<Node>(it->TerminalIndex,MinimumDistance[it->TerminalIndex])); 			} 		} 	}     //返回未满足的进口需求 	TArray<FStardustBasic> Result; 	if(!StardustNeed.empty()) 	{ 		for(const auto&it:StardustNeed) 		{ 			Result.Add(FStardustBasic(FName(UTF8_TO_TCHAR(it.first.c_str())),it.second)); 		} 	} 	return Result; }

重新寻路的逻辑与之类似,区别在于只是搜索确定的两点之间的最短路,不会发送包裹:

TArray<int> ATradingSystemActor::ReRoute(const int& Start, const int& end) { 	TArray<int>Result; 	struct Node 	{ 		Node(const int ID, const int DIstance) 			: ID(ID), 			  DIstance(DIstance) 		{ 		} 		int ID; 		long long DIstance; 	}; 	auto cmp{[](const TSharedPtr<Node>&a,const TSharedPtr<Node>& b){return a->DIstance>b->DIstance;}}; 	std::priority_queue<TSharedPtr<Node>,std::vector<TSharedPtr<Node>>,decltype(cmp)>Queue(cmp); 	Queue.push(MakeShared<Node>(Start,0)); 	std::unordered_map<int,long long>MinimumDistance; 	std::unordered_map<int,bool>Done; 	std::map<int,int>Path; 	for(auto& it:DebugActor->AllAster) 	{ 		MinimumDistance[it.Key]=1e18; 		Done[it.Key]=false; 	} 	MinimumDistance[0]=0; 	while(!Queue.empty()) 	{ 		auto Current{Queue.top()}; 		Queue.pop(); 		if(Done[Current->ID]) 		{ 			continue; 		} 		Done[Current->ID]=true; 		for(const auto&it:AsterGraph[Current->ID]) 		{             //找到终点立刻终止运算 			if(it->TerminalIndex==end) 			{ 				TArray<int>PassedAsters; 				int CurrentPosition{Current->ID}; 				while (CurrentPosition!=Start) 				{ 					CurrentPosition=Path[CurrentPosition]; 					PassedAsters.Add(CurrentPosition); 				} 				TArray<int>PassedAsters2; 				for(int i=PassedAsters.Num()-1;i>=0;i--) 				{ 					PassedAsters2.Add(PassedAsters[i]); 				} 				return PassedAsters2; 			} 			if(Done[it->TerminalIndex]) 				continue; 			if(MinimumDistance[it->TerminalIndex]>it->distance+Current->DIstance) 			{ 				Path[it->TerminalIndex]=Current->ID; 				MinimumDistance[it->TerminalIndex]=it->distance+Current->DIstance; 				Queue.push(MakeShared<Node>(it->TerminalIndex,MinimumDistance[it->TerminalIndex])); 			} 		} 	}     //没找到路径返回的是空数组 	return Result; }

 三、包裹

1.包裹的数据结构

        包裹的数据包裹发出该包裹的建筑的索引,计划要经过的所有建筑的索引,和携带的星尘

USTRUCT(BlueprintType) struct FPackageInformation { 	explicit  FPackageInformation(const int SourceAsterIndex, const TArray<int>& ExpectedPath,const TArray<FStardustBasic>&ExpectedStardusts) 		: SourceAsterIndex(SourceAsterIndex), 		  ExpectedPath(ExpectedPath),Stardusts(ExpectedStardusts) 	{ 	}  	FPackageInformation() = default; 	GENERATED_BODY()      //发出包裹的源天体 	UPROPERTY(VisibleAnywhere,BlueprintReadWrite,Category="Package") 	int SourceAsterIndex{0};      //计划的路径,从后到前依次为即将走过的天体索引 	UPROPERTY(VisibleAnywhere,BlueprintReadWrite,Category="Package") 	TArray<int> ExpectedPath;      //包裹携带的星尘 	UPROPERTY(VisibleAnywhere,BlueprintReadWrite,Category="Package") 	TArray<FStardustBasic>Stardusts; };

 2.包裹在场景中的运动

            每个包裹的路径是在其生成时就计算好的,数组中从后到前依次是其计划经过的建筑的索引,每到达一个建筑后将末尾的元素弹出,直到全部弹出即到达终点

bool APackageActor::AsterReached(const int& AsterIndex) {     //检查输入的天体索引是否真实存在 	if(!TradingSystem->DebugActor->AllAster.Find(AsterIndex)) 	{ 		UE_LOG(LogTemp,Error,TEXT("AsterReached failed,invalid index:%d"),AsterIndex); 		return false; 	}     //即将到达终点 	if(PackgeInfo.ExpectedPath.Num()==1) 	{         //送达包裹中的星尘 		for(auto&it:PackgeInfo.Stardusts) 		{ 			TradingSystem->DebugActor->AllAster[AsterIndex]->InputInventory->AddStardust(it.StardustId,it.Quantity); 			it.Quantity-=std::min(it.Quantity,TradingSystem->DebugActor->AllAster[AsterIndex]->InputInventory->CheckAddable(it.StardustId)); 		}         //更新库存UI 		TradingSystem->DebugActor->AllAster[AsterIndex]->MCUpdateEvent(); 		TArray<FStardustBasic>LostStardust;         //统计因终点库存已满而丢包的星尘 		for(const auto&it:PackgeInfo.Stardusts) 		{ 			if(it.Quantity) 			{ 				LostStardust.Add(FStardustBasic(it.StardustId,it.Quantity)); 				UE_LOG(LogTemp,Error,TEXT("%d %s can't put in target aster"),it.Quantity,*it.StardustId.ToString()); 			} 		} 		return true; 	}     //弹出路径中队尾的元素 	PackgeInfo.ExpectedPath.Pop();     //更新包裹的路径 	UpdatePathEvent(PackgeInfo.ExpectedPath); 	return false; } 

        我们使用时间轴和设置actor变换的方式来使包裹在场景中移动,也可以实现游戏暂停时停止移动和恢复移动

四、道路

1.道路的数据结构

        在本游戏中,玩家可以建造多种道路,每种道路有不同的传输速度,最大建造距离和消耗,首先是数据表格的数据结构,这里和DataTable的互动可以看开发日志2(独立游戏《星尘异变》UE5 C++程序开发日志2——实现一个存储物品数据的c++类-CSDN博客

USTRUCT(BlueprintType) struct FRoadDataTable:public FTableRowBase { 	FRoadDataTable() = default;  	FRoadDataTable(const FString& RoadName, ERoadType RoadType, int TransferSpeed, double MaximumLength) 		: RoadName(RoadName), 		  RoadType(RoadType), 		  TransferSpeed(TransferSpeed), 		  MaximumLength(MaximumLength) 	{ 		 	}  	GENERATED_USTRUCT_BODY()  	//道路名称 	UPROPERTY(EditAnywhere,BlueprintReadWrite,Category="RoadInfo") 	FString RoadName{"Empty"};  	//道路种类 	UPROPERTY(EditAnywhere,BlueprintReadWrite,Category="RoadInfo") 	ERoadType RoadType{ERoadType::Empty};  	//传输速度,单位距离/秒 	UPROPERTY(EditAnywhere,BlueprintReadWrite,Category="RoadInfo") 	int TransferSpeed{1};  	//最大长度 	UPROPERTY(EditAnywhere,BlueprintReadWrite,Category="RoadInfo") 	double MaximumLength{1};  	//道路建造消耗 	UPROPERTY(EditAnywhere,BlueprintReadWrite,Category="RoadInfo") 	TMap<FString,int>RoadConsumption; 	 };

        然后是每条建造出来的道路的数据结构,包括道路的起点和终点,用的是所连建筑物的全局索引,以及这条路建成的长度和表格数据。我们有一个数组维护着所有场上的建筑物的指针,通过这两个索引就可以访问到道路两端的建筑

USTRUCT(BlueprintType) struct FRoadInformation {     friend bool operator<(const FRoadInformation& Lhs, const FRoadInformation& RHS) 	{ 		return Lhs.distance > RHS.distance; 	}  	friend bool operator<=(const FRoadInformation& Lhs, const FRoadInformation& RHS) 	{ 		return !(RHS < Lhs); 	}  	friend bool operator>(const FRoadInformation& Lhs, const FRoadInformation& RHS) 	{ 		return RHS < Lhs; 	}  	friend bool operator>=(const FRoadInformation& Lhs, const FRoadInformation& RHS) 	{ 		return !(Lhs < RHS); 	} 	friend bool operator==(const FRoadInformation& Lhs, const FRoadInformation& RHS) 	{ 		return Lhs.TerminalIndex == RHS.TerminalIndex && Lhs.StartIndex==RHS.StartIndex; 	}  	friend bool operator!=(const FRoadInformation& Lhs, const FRoadInformation& RHS) 	{ 		return !(Lhs == RHS); 	}  	FRoadInformation() = default; 	 	explicit FRoadInformation(const int& StartIndex,const int& TerminalIndex,const FVector&StartLocation,const FVector&EndLocation,const FRoadDataTable& Road) 		:StartIndex(StartIndex), TerminalIndex(TerminalIndex),distance(StartLocation.Distance(StartLocation,EndLocation)),RoadInfo(Road){ 	} 	 	GENERATED_USTRUCT_BODY()  	UPROPERTY(EditAnywhere,BlueprintReadWrite,Category="Road") 	int StartIndex{0};//起点天体的索引 	 	UPROPERTY(EditAnywhere,BlueprintReadWrite,Category="Road") 	int TerminalIndex{0};//终点天体的索引  	UPROPERTY(EditAnywhere,BlueprintReadWrite,Category="Road") 	int distance{0};//两个天体之间的距离,取整  	//道路的数据 	UPROPERTY(EditAnywhere,BlueprintReadWrite,Category="Road") 	FRoadDataTable RoadInfo; 	 };

2.道路的建造

        我们用一个红黑树来储存每个建筑都分别链接了哪些建筑

	std::map<int,TArray<TSharedPtr<FRoadInformation>>> AsterGraph;//所有天体构成的图

        在建造道路时传入起点和终点索引,以及道路类型的名称,将建造的道路存入上面存图的容器中

bool ATradingSystemActor::RoadBuilt(const int& Aster1, const int& Aster2,const FString& RoadName) { 	if(!DebugActor->IsValidLowLevel()) 	{ 		UE_LOG(LogTemp,Error,TEXT("RoadBuild failed,invalid pointer:DebugActor")); 		return false; 	}     //这两个建筑之间已存在道路,不可重复建造 	if(AsterGraph[Aster1].FindByPredicate([Aster2](const TSharedPtr<FRoadInformation>& Road){return Road->TerminalIndex==Aster2;})) 	{ 		return false; 	}     //对应索引的天体不存在 	if(!DebugActor->AllAster.Find(Aster1)||!DebugActor->AllAster.Find(Aster2)) 	{ 		UE_LOG(LogTemp,Error,TEXT("RoadBuilt failed,invalid index :%d %d"),Aster1,Aster2); 		return false; 	}     //数据表中存储的道路信息 	auto RoadInfo{*Instance->RoadDataMap[TCHAR_TO_UTF8(*RoadName)]};     //存双向边 	AsterGraph[Aster1].Add(MakeShared<FRoadInformation>(Aster1,Aster2,DebugActor->AllAster[Aster1]->AsterPosition,DebugActor->AllAster[Aster2]->AsterPosition,RoadInfo)); 	AsterGraph[Aster2].Add(MakeShared<FRoadInformation>(Aster2,Aster1,DebugActor->AllAster[Aster2]->AsterPosition,DebugActor->AllAster[Aster1]->AsterPosition,RoadInfo)); 	return true; }

3.道路的销毁

        在销毁道路时,我们需要将存的图中的该道路删除,同时对于所有传输中的包裹,如果其原本的路径中包含这条道路,则重新计算路径,如果计算路径失败则将包裹送到下一个到达的建筑物处

void ATradingSystemActor::RoadDestructed(const int& Aster1, const int& Aster2) { 	if(!DebugActor->IsValidLowLevel()) 	{ 		UE_LOG(LogTemp,Error,TEXT("RoadDestructed failed,invalid pointer:DebugActor")); 		return; 	}     //两个方向都要删除 	AsterGraph[Aster1].RemoveAll([Aster2](const TSharedPtr<FRoadInformation>& Road){return Road->TerminalIndex==Aster2;}); 	AsterGraph[Aster2].RemoveAll([Aster1](const TSharedPtr<FRoadInformation>& Road){return Road->TerminalIndex==Aster1;});     //遍历所有在路上的包裹 	for(auto&it:TransferingPackage) 	{ 		auto Temp{it->GetPackageInfo()};         //遍历其计划经过的天体 		for(int i=Temp.ExpectedPath.Num()-1;i>=1;i--) 		{             //是否经过该条道路 			if(Temp.ExpectedPath[i]==Aster1&&Temp.ExpectedPath[i-1]==Aster2||Temp.ExpectedPath[i]==Aster2&&Temp.ExpectedPath[i-1]==Aster1) 			{                 //尝试重新计算路径 				auto TempArray{ReRoute(Temp.ExpectedPath[Temp.ExpectedPath.Num()-1],Temp.ExpectedPath[0])};                 //没有能到终点的道路了 				if(TempArray.IsEmpty()) 				{ 					UE_LOG(LogTemp,Error,TEXT("RerouteFailed"));                     //将终点改为下一个天体 					TArray<int>Result; 					Result.Add(Temp.ExpectedPath[Temp.ExpectedPath.Num()-1]); 					Temp.ExpectedPath=Result; 					it->SetPackageInfo(Temp); 					it->UpdatePathEvent(Temp.ExpectedPath); 					break; 				}                 //应用新的路径 				Temp.ExpectedPath=TempArray; 				it->SetPackageInfo(Temp); 				it->UpdatePathEvent(Temp.ExpectedPath); 				break; 			} 		} 	} }

4.某个有道路连接的建筑被删除

        在有道路连接的建筑被删除后,所有路径中包含该建筑的包裹要重新寻路,如果不能到达终点,同样送到下一个建筑为止

void ABP_Asters::AsterDestructed() { //这里展示的仅是该函数中关于物流系统的部分     //删除以该天体为起点的道路 	TradingSystem->AsterGraph.erase(AsterIndex); 	for(auto&it:TradingSystem->AsterGraph) 	{         //删除以该天体为终点的道路 		auto temp{AsterIndex}; 		it.second.RemoveAll([temp](const TSharedPtr<FRoadInformation>& Road){return Road->TerminalIndex==temp;}); 	} 	for(int i=0;i<TradingSystem->TransferingPackage.Num();i++) 	{ 		auto it{TradingSystem->TransferingPackage[i]}; 		if(!IsValid(it)) 		{ 			TradingSystem->TransferingPackage.RemoveAt(i); 			i--; 			continue; 		} 		auto Temp{it->GetPackageInfo()}; 		bool NeedReroute{false};         //计划路径中有该天体就需要重新寻路 		for(auto& it2:Temp.ExpectedPath) 		{ 			if(it2==AsterIndex) 			{ 				NeedReroute=true; 			} 		} 		if(NeedReroute) 		{                     //下一个目的地就是该天体,直接删除 			if(Temp.ExpectedPath.Num()==1) 			{ 				it->Destroy(); 				continue; 			}             //终点是该天体,那肯定找不到路了 			if(Temp.ExpectedPath[0]==AsterIndex) 			{ 				UE_LOG(LogTemp,Error,TEXT("Reroute failed")); 				TArray<int>Result; 				Result.Add(Temp.ExpectedPath[Temp.ExpectedPath.Num()-1]); 				Temp.ExpectedPath=Result; 				it->SetPackageInfo(Temp); 				it->UpdatePathEvent(Temp.ExpectedPath); 				continue; 			}             //尝试重新寻路 			auto TempArray{TradingSystem->ReRoute(Temp.ExpectedPath[Temp.ExpectedPath.Num()-1],Temp.ExpectedPath[0])};             //没找到合适的道路 			if(TempArray.IsEmpty()) 			{ 				UE_LOG(LogTemp,Error,TEXT("Reroute failed")); 				TArray<int>Result; 				Result.Add(Temp.ExpectedPath[Temp.ExpectedPath.Num()-1]); 				Temp.ExpectedPath=Result; 				it->SetPackageInfo(Temp); 				it->UpdatePathEvent(Temp.ExpectedPath); 				continue; 			}             //应用新的路径 			Temp.ExpectedPath=TempArray; 			it->SetPackageInfo(Temp); 			it->UpdatePathEvent(Temp.ExpectedPath); 		} 	}     //蓝图实现的事件,因为道路的指针存在蓝图里,所以交给蓝图来删除对象 	AsterDestructedEvent(this); }


 

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!