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

news2024/11/13 16:19:08

目录

一、进出口清单

二、路径计算

 三、包裹

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);
}


 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1935716.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java语言程序设计基础篇_编程练习题15.7(使用鼠标改变颜色)

15.7(使用鼠标改变颜色) 编写一个程序&#xff0c;显示一个圆的颜色&#xff0c;当按下鼠标键时颜色为黑色&#xff0c;释放鼠标时颜色为白色 代码展示&#xff1a;编程练习题15_7CircleColor.java package chapter_15;import javafx.application.Application; import javafx.…

STM32之八:IIC通信协议

目录 1. IIC协议简介 1.1 主从模式 1.2 2根通信线 2. IIC协议时序 2.1 起始条件和终止条件 2.2 发送一个字节 2.3 接收一个字节 2.4 应答信号 1. IIC协议简介 IIC协议是一个半双工、同步、一主多从、多主多从的串行通用数据总线。该通信模式需要2根线&#xff1a;SCL、…

数据监控电商平台价格心得分享

一、引言 在当今竞争激烈的电商环境中&#xff0c;价格是影响消费者购买决策的重要因素之一。对于电商从业者和商家来说&#xff0c;有效地监控电商平台的价格变动至关重要。通过数据监控&#xff0c;我们可以及时了解市场动态、调整策略&#xff0c;以保持竞争力并实现利润最大…

泰迪科技2024年高校(本科/职业院校)大数据实验室建设及大数据实训平台整体解决方案

高校大数据应用人才培养目标 大数据专业是面向信息技术行业&#xff0c;培养德智体美劳全面发展的大数据领域的高素质管理型专门人才&#xff0c;毕业生具备扎实的管理学、经济学、自然科学、技术应用、人文社科的基本理论, 系统深入的大数据管理专业知识和实践能力&#xff0c…

04 Git与远程仓库

第4章&#xff1a;Git与远程仓库 一、Gitee介绍及创建仓库 一&#xff09;获取远程仓库 ​ 使用在线的代码托管平台&#xff0c;如Gitee&#xff08;码云&#xff09;、GitHub等 ​ 自行搭建Git代码托管平台&#xff0c;如GitLab 二&#xff09;Gitee创建仓库 ​ gitee官…

四种垃圾收集算法详解(JVM)

一、标记清除 1、原理 从根集合节点进行扫描&#xff0c;标记出所有的存活对象&#xff0c;最后扫描整个内存空间并清除没有标记的对象&#xff08;即死亡对象) 标记后 &#xff08;黑色&#xff1a;可回收 | 灰色&#xff1a;存活对象 | 白色&#xff1a;未使用 &#xff0…

HarmonyOS鸿蒙- 跳转系统应用能力

一、通过弹窗点击设置跳转系统应用能力 1、 自定义弹窗效果图 2、 自定义弹窗代码 import { common, Want } from kit.AbilityKit; import { BusinessError } from kit.BasicServicesKit;export function alertDialog() {AlertDialog.show({title: ,message: 当前功能依赖定位…

算法力扣刷题记录 五十一【654.最大二叉树】

前言 二叉树篇&#xff0c;继续。 记录 五十一【654.最大二叉树】 一、题目阅读 给定一个不重复的整数数组 nums 。 最大二叉树 可以用下面的算法从 nums 递归地构建: 创建一个根节点&#xff0c;其值为 nums 中的最大值。递归地在最大值 左边 的 子数组前缀上 构建左子树。…

【Linux】安装PHP扩展-Swoole

说明 本文档是在centos7.6的环境下&#xff0c;安装PHP7.4之后&#xff0c;安装对应的PHP扩展Swoole。 一、swoole简述 Swoole 是一个为 PHP 设计的高性能的异步并行网络通信引擎&#xff0c;它以扩展&#xff08;extension&#xff09;的形式存在&#xff0c;极大地提升了 …

Linux--YUM仓库部署及NFS共享存储

目录 一、YUM仓库服务 1.1 YUM介绍 1.2 yum 常用的命令 1.3 YUM 源的提供方式 1.3.1 配置本地 yum 源仓库 1.3.2 配置 ftp 源 1.3.3 配置http服务源 二、NFS 共享存储 2.1 NFS基本概述 2.2 为什么使用 NFS 共享存储 2.3 NFS 应用场景 2.4 NFS 实现原理 2.5 NFS文件…

【python学习】爬虫中常使用的urllib和requests库的的背景、定义、特点、功能、代码示例以及两者的区别

引言 urllib是Python标准库中的一个模块&#xff0c;它提供了一系列用于操作URL的功能 requests是一个Python第三方库&#xff0c;由Kenneth Reitz创建&#xff0c;用于简化HTTP客户端的编程 一、urllib的定义 urllib可以操作url&#xff0c;主要分为以下几个子模块&#xff1…

Nginx详解(超级详细)

目录 Nginx简介 1. 为什么使用Nginx 2. 安装Nginx Nginx的核心功能 1. Nginx反向代理功能 2. Nginx的负载均衡 3 Nginx动静分离 Nginx简介 Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件&#xff08;IMAP/POP3&#xff09;代理服务器&#xff0c;在BSD-like 协…

深入Redis集群部署:从安装配置到测试验证的完整指南

&#x1f3e1;作者主页&#xff1a;点击&#xff01; &#x1f427;Linux基础知识(初学)&#xff1a;点击&#xff01; &#x1f427;Linux高级管理防护和群集专栏&#xff1a;点击&#xff01; &#x1f510;Linux中firewalld防火墙&#xff1a;点击&#xff01; ⏰️创作…

FastAPI 学习之路(六十)打造系统的日志输出

我们要搭建日志系统&#xff0c;可以使用loguru&#xff0c;很不错的一个开源日志系统 pip install loguru 我们在common创建log.py&#xff0c;使用方式也很简单 import os import timefrom loguru import logger# 日志的路径 log_path os.path.join(os.getcwd(), "log…

信息安全工程师题

物理隔离技术要求两台物理机物理上并不直连&#xff0c;只能进行间接的信息交换。所以防火墙不能实现网络的物理隔离Web应用防火墙可以防止SQL注入、xss攻击、恶意文件上传、远程命令执行、文件包含、恶意扫描拦截等&#xff1b;可以发现并拦截恶意的Web代码&#xff1b;可防止…

树形背包问题

一些题目给定了树形结构&#xff0c;在这个树形结构中选取一定数量的点或边&#xff08;也可能是其他属性&#xff09;&#xff0c;使得某种与点权或者边权相关的花费最大或者最小。解决这类问题&#xff0c;一般要考虑使用树上背包。 树上背包&#xff0c;顾名思义&#xff0c…

JDK垃圾回收机制和垃圾回收算法

查看java相关信息 java -XX:PrintCommandLineFlags -version UseParallelGC 即 Parallel Scavenge Parallel Old,再查看详细信息 内存分配策略 1. 对象优先在 Eden 分配 大多数情况下&#xff0c;对象在新生代 Eden 区分配&#xff0c;当 Eden 区空间不够时&#xff0c;发…

STM32被拔网线 LWIP的TCP无法重连解决方案

目录 一、问题描述 二、项目构成 三、问题解决 1.问题代码 2.解决思路 3.核心代码&#xff1a; 四、完整代码 1.监测网口插入拔出任务 2.TCP任务 3.创建tcp任务 4.删除tcp任务 五、总结 一、问题描述 最近遇到一个问题&#xff0c;就是我的stm32设备作为tcp客户端…

c# listview控件调整标题显示顺序

右键点击listview,选择编辑列 修改DisplayIndex listview在成员位置点击上下箭头移动后&#xff0c;实际显示不会改变&#xff0c;因为DisplayIndex没有改变

Python 工程师对 3D 高斯溅射的介绍(第 1 部分)

从 Python 工程师的角度理解和编写 Gaussian Splatting 欢迎来到雲闪世界。2023 年初&#xff0c;来自法国蔚蓝海岸大学和马克斯普朗克信息研究所的作者发表了一篇题为“用于实时场渲染的 3D 高斯溅射”的论文。 该论文展示了实时神经渲染的重大进步&#xff0c;超越了 NeRF 等…