UE 事件分发机制(二) day10

news2025/1/24 7:27:00

自定义事件分发机制

自建事件分发机制与结构

  • Unreal推荐的游戏逻辑开发流程
    在这里插入图片描述
  • 基于 Unreal推荐的游戏逻辑开发流程,一般我们的整体规划也就是这样
    在这里插入图片描述
  • 大致结构类图
    在这里插入图片描述

创建接口类与管理类以及所需函数

  • 新建一个Unreal接口类作为接口
    在这里插入图片描述
  • 然后创建一个蓝图函数库的基类
    在这里插入图片描述

EventInterface接口类

EventInterface.h

  • 复习一下BlueprintNativeEvent这个参数:会在C++中提供一个默认的实现,然后蓝图中去覆盖它改写它,在蓝图中实现这个函数时,如果调用一个父类的版本,它会先调用C++里面加了_Implementation这个函数,然后再去做蓝图其他的操作
// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "UObject/Interface.h"
#include "EventInterface.generated.h"

// This class does not need to be modified.
UINTERFACE(MinimalAPI)
class UEventInterface : public UInterface
{
	GENERATED_BODY()
};

/**
 * 
 */
class DISTRIBUTE_API IEventInterface
{
	GENERATED_BODY()

	// Add interface functions to this class. This is the class that will be inherited to implement this interface.
public:
	UFUNCTION(BlueprintNativeEvent,Category="Event Dispather Tool")
	void OnReceiveEvent(UObject* Data);
};

EventInterface.cpp

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


#include "EventInterface.h"

// Add default functionality here for any IEventInterface functions that are not pure virtual.

EventManager蓝图函数库的基类管理类

EventManager.h

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

#pragma once

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

/**
 * 
 */
UCLASS()
class DISTRIBUTE_API UEventManager : public UBlueprintFunctionLibrary
{
	GENERATED_BODY()
	
private:
	//监听者
	static TMap<FString, TArray<UObject*>> AllListeners;

public:

	UFUNCTION(BlueprintCallable, Category = "Event Dispather Tool")
	static void AddEventListener(FString EventName, UObject* Listener);

	UFUNCTION(BlueprintCallable, Category = "Event Dispather Tool")
	static void RemoveEventListener(FString EventName, UObject* Listener);

	UFUNCTION(BlueprintCallable, Category = "Event Dispather Tool")
	static FString  DispatchEvent(FString EventName, UObject* Data);

	UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Event Dispather Tool")
	static UObject* NameAsset(UClass* ClassType);
};

EventManager.cpp

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


#include "EventManager.h"

void UEventManager::AddEventListener(FString EventName, UObject* Listener)
{
}

void UEventManager::RemoveEventListener(FString EventName, UObject* Listener)
{
}

FString UEventManager::DispatchEvent(FString EventName, UObject* Data)
{
	return FString();
}

UObject* UEventManager::NameAsset(UClass* ClassType)
{
	return nullptr;
}

编写AddEventListener函数

  • 加入接口的头文件,判断添加进来的事件名字是否为空,监听指针是否为空,检测对象是否有效,是否实现了指定的接口类

  • Listener->IsValidLowLevel():检查对象是否有效

  • Listener->GetClass()->ImplementsInterface(UEventInterface::StaticClass()):检测接口是否实现指定接口,这里传入的UEventInterface就是接口类中的参与蓝图的类名
    在这里插入图片描述

    //初始化监听器
    TMap<FString, TArray<UObject*>> UEventManager::AllListeners;
    
    void UEventManager::AddEventListener(FString EventName, UObject* Listener)
    {
    	if (EventName == "" || Listener == nullptr || !Listener->IsValidLowLevel() ||
    		!Listener->GetClass()->ImplementsInterface(UEventInterface::StaticClass()))
    	{
    		return;
    	}
    }
    
  • 然后查找是否存在这个事件数组,如果为空说明目前没有这个事件,就重新添加一下,存在就直接添加事件进入到数组即可

  • AddEventListener函数完整代码逻辑

#include "EventManager.h"
#include "EventInterface.h"

//初始化监听器
TMap<FString, TArray<UObject*>> UEventManager::AllListeners;

void UEventManager::AddEventListener(FString EventName, UObject* Listener)
{
	if (EventName == "" || Listener == nullptr || !Listener->IsValidLowLevel() ||
		!Listener->GetClass()->ImplementsInterface(UEventInterface::StaticClass()))
	{
		return;
	}

	//查找是否存在这个事件数组
	TArray<UObject*>* EventArr = UEventManager::AllListeners.Find(EventName);

	//如果为空说明目前没有这个事件,就重新添加一下
	if (EventArr == nullptr || EventArr->Num() == 0)
	{
		TArray<UObject*> NewEventArr = { Listener };
		UEventManager::AllListeners.Add(EventName, NewEventArr);
	}
	//存在就直接添加事件进入到数组即可
	else
	{
		EventArr->Add(Listener);
	}
}

编写其他函数

RemoveEventListener函数编写

  • 查询事件数组中是否有这个事件,如果有就删除这个事件
void UEventManager::RemoveEventListener(FString EventName, UObject* Listener)
{
	TArray<UObject*>* EventArr = UEventManager::AllListeners.Find(EventName);
	if (EventArr != nullptr && EventArr->Num() != 0)
	{
		EventArr->Remove(Listener);
	}
}

DispatchEvent函数编写

  • 分派事件,也是首先考虑事件是否存在,然后循环事件数组里事件,不存在的事件就直接剔除,存在的就进行派发事件通知,注意派发事件通知时也要进行安全检测
FString UEventManager::DispatchEvent(FString EventName, UObject* Data)
{
	TArray<UObject*>* EventArr = UEventManager::AllListeners.Find(EventName);
	//如果不存在此事件数组
	if (EventArr == nullptr || EventArr->Num() == 0)
	{
		return "'" + EventName + "'No Listener.";
	}
	//使用UE_LOG换行,方便查看
	FString ErrorInfo = "\n";

	for (int i = 0; i < EventArr->Num(); i++)
	{
		UObject* Obj = (*EventArr)[i];
		//安全判断一下事件是否存在
		if (Obj == nullptr || !Obj->IsValidLowLevel() || !Obj->GetClass()->ImplementsInterface(UEventInterface::StaticClass()))
		{
			//不存在直接剔除
			EventArr->RemoveAt(i);
			//剔除后要i--,因为TArray删除一个元素后会自动补齐,这样会导致我们当前循环出现bug
			i--;
		}
		//通过了安全检测就直接派遣事件通知
		else
		{
			UFunction* FUN = Obj->FindFunction("OnReceiveEvent");
			//安全检测这个FUN是否有效
			if (FUN == nullptr || !FUN->IsValidLowLevel())
			{
				//打印错误信息
				ErrorInfo += "'" + Obj->GetName() + "'No 'OnReceiveEvent' Function.\n";
			}
			else
			{
				//调用
				Obj->ProcessEvent(FUN, &Data);
			}
		}
	}
	return ErrorInfo;
}

NameAsset函数编写

  • 这个函数是方便我们在蓝图中去创建某一个新的指定类实例
  • 加入头文件 #include “Engine.h”,使用NewObject函数来创建一个新的指定对象
  • UObject* Obj = NewObject<UObject>(GetTransientPackage(), ClassType);
    • GetTransientPackage():返回一个临时包(主要用于对象池)的指针
    • ClassType:ClassType 是一个 UClass 指针,指定了新对象的类型。
UObject* UEventManager::NameAsset(UClass* ClassType)
{
	UObject* Obj = NewObject<UObject>(GetTransientPackage(), ClassType);
	return Obj;
}

EventManager.cpp

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


#include "EventManager.h"
#include "EventInterface.h"
#include "Engine.h"
//初始化监听器
TMap<FString, TArray<UObject*>> UEventManager::AllListeners;

void UEventManager::AddEventListener(FString EventName, UObject* Listener)
{
	if (EventName == "" || Listener == nullptr || !Listener->IsValidLowLevel() ||
		!Listener->GetClass()->ImplementsInterface(UEventInterface::StaticClass()))
	{
		return;
	}

	//查找是否存在这个事件数组
	TArray<UObject*>* EventArr = UEventManager::AllListeners.Find(EventName);
	
	//如果为空说明目前没有这个事件,就重新添加一下
	if (EventArr == nullptr || EventArr->Num() == 0)
	{
		TArray<UObject*> NewEventArr = { Listener };
		UEventManager::AllListeners.Add(EventName, NewEventArr);
	}
	//存在就直接添加事件进入到数组即可
	else
	{
		EventArr->Add(Listener);
	}
}

void UEventManager::RemoveEventListener(FString EventName, UObject* Listener)
{
	TArray<UObject*>* EventArr = UEventManager::AllListeners.Find(EventName);
	if (EventArr != nullptr && EventArr->Num() != 0)
	{
		EventArr->Remove(Listener);
	}
}

FString UEventManager::DispatchEvent(FString EventName, UObject* Data)
{
	TArray<UObject*>* EventArr = UEventManager::AllListeners.Find(EventName);
	//如果不存在此事件数组
	if (EventArr == nullptr || EventArr->Num() == 0)
	{
		return "'" + EventName + "'No Listener.";
	}
	//使用UE_LOG换行,方便查看
	FString ErrorInfo = "\n";

	for (int i = 0; i < EventArr->Num(); i++)
	{
		UObject* Obj = (*EventArr)[i];
		//安全判断一下事件是否存在
		if (Obj == nullptr || !Obj->IsValidLowLevel() || !Obj->GetClass()->ImplementsInterface(UEventInterface::StaticClass()))
		{
			//不存在直接剔除
			EventArr->RemoveAt(i);
			//剔除后要i--,因为TArray删除一个元素后会自动补齐,这样会导致我们当前循环出现bug
			i--;
		}
		//通过了安全检测就直接派遣事件通知
		else
		{
			UFunction* FUN = Obj->FindFunction("OnReceiveEvent");
			//安全检测这个FUN是否有效
			if (FUN == nullptr || !FUN->IsValidLowLevel())
			{
				//打印错误信息
				ErrorInfo += "'" + Obj->GetName() + "'No 'OnReceiveEvent' Function.\n";
			}
			else
			{
				//调用
				Obj->ProcessEvent(FUN, &Data);
			}
		}
	}
	return ErrorInfo;
}

UObject* UEventManager::NameAsset(UClass* ClassType)
{
	UObject* Obj = NewObject<UObject>(GetTransientPackage(), ClassType);
	return Obj;
}

自定义事件分发广播事件

  • 因为我们的自定义事件系统里面发布数据只有一个参数,所以我们新建一个Object类专门来放数据,需要传递数据时就通过这个Object类
    在这里插入图片描述
    在这里插入图片描述
  • 然后创建一个Pawn类作为SenderEvent发起者,开始广播事件
// Called when the game starts or when spawned
void AMyBPAndCpp_Sender::BeginPlay()
{
	Super::BeginPlay();
	
	FTimerHandle TimerHandle;
	auto Lambda = []()
	{
		//开始广播事件
		UMyData* Data = Cast<UMyData>(UEventManager::NameAsset(UMyData::StaticClass()));
		Data->Param = FMath::RandRange(0, 100);
		//委派事件
		UEventManager::DispatchEvent("MyBPAndCpp_DispatchEvent", Data);
	};
	GetWorld()->GetTimerManager().SetTimer(TimerHandle, FTimerDelegate::CreateLambda(Lambda), 3.f, true);
	
}

MyBPAndCpp_Sender.h

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

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Pawn.h"
#include "MyBPAndCpp_Sender.generated.h"

UCLASS()
class DISTRIBUTE_API AMyBPAndCpp_Sender : public APawn
{
	GENERATED_BODY()

public:
	// Sets default values for this pawn's properties
	AMyBPAndCpp_Sender();

	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Properties")
	class UStaticMeshComponent* StaticMesh;
protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

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

	// Called to bind functionality to input
	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;

};

MyBPAndCpp_Sender.cpp

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


#include "MyBPAndCpp_Sender.h"
#include "Components/StaticMeshComponent.h"
#include "UObject/ConstructorHelpers.h"
#include "EventDistributeTool/EventManager.h"
#include "Public/TimerManager.h"
#include "MyData.h"

// Sets default values
AMyBPAndCpp_Sender::AMyBPAndCpp_Sender()
{
 	// Set this pawn to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;
	StaticMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("StaticMesh"));
	RootComponent = StaticMesh;

	static ConstructorHelpers::FObjectFinder<UStaticMesh> StaticMeshAsset(TEXT("StaticMesh'/Engine/BasicShapes/Sphere.Sphere'"));
	static ConstructorHelpers::FObjectFinder<UMaterialInterface> MaterialAsset(TEXT("Material'/Engine/BasicShapes/BasicShapeMaterial.BasicShapeMaterial'"));
	if (StaticMeshAsset.Succeeded() && MaterialAsset.Succeeded())
	{
		StaticMesh->SetStaticMesh(StaticMeshAsset.Object);
		StaticMesh->SetMaterial(0, MaterialAsset.Object);
	}
}

// Called when the game starts or when spawned
void AMyBPAndCpp_Sender::BeginPlay()
{
	Super::BeginPlay();
	
	FTimerHandle TimerHandle;
	auto Lambda = []()
	{
		//开始广播事件
		UMyData* Data = Cast<UMyData>(UEventManager::NameAsset(UMyData::StaticClass()));
		Data->Param = FMath::RandRange(0, 100);
		//委派事件
		UEventManager::DispatchEvent("MyBPAndCpp_DispatchEvent", Data);
	};
	GetWorld()->GetTimerManager().SetTimer(TimerHandle, FTimerDelegate::CreateLambda(Lambda), 3.f, true);
	
}

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

}

// Called to bind functionality to input
void AMyBPAndCpp_Sender::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);

}

自定义事件分发订阅事件

  • 新建一个Actor基类,然后派生三个子类来订阅事件测试,这三个子类需要继承EventInterface这个接口类,父类接口中方法有BlueprintNativeEvent反射参数,所以在这实现加后缀_Implementation
    在这里插入图片描述

Actor_Receive_R.h

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

#pragma once

#include "CoreMinimal.h"
#include "EventDistributeTool/EventInterface.h"
#include "BPAndCpp/Actor_Receive.h"
#include "Actor_Receive_R.generated.h"

/**
 * 
 */
UCLASS()
class DISTRIBUTE_API AActor_Receive_R : public AActor_Receive, public IEventInterface
{
	GENERATED_BODY()
	
protected:
	virtual void BeginPlay() override;
public:
	virtual void OnReceiveEvent_Implementation(UObject* Data) override;
};

Actor_Receive_R.cpp

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


#include "Actor_Receive_R.h"
#include "Engine/Engine.h"
#include "MyData.h"
#include "EventDistributeTool/EventManager.h"
void AActor_Receive_R::BeginPlay()
{
	Super::BeginPlay();
	//订阅事件
	UEventManager::AddEventListener("MyBPAndCpp_DispatchEvent", this);
}
void AActor_Receive_R::OnReceiveEvent_Implementation(UObject* Data)
{
	//打印到屏幕
	GEngine->AddOnScreenDebugMessage(INDEX_NONE, 10.f, FColor::Red, FString::Printf(TEXT("%i"), Cast<UMyData>(Data)->Param));
}
  • 运行结果
    在这里插入图片描述
  • 触发错误打印log的原因是因为,EventManager类中的都是静态方法,静态方法只有到下次编译之后才会被释放,所以在调试中就会这样触发log
  • 使用在视口中播放就不会触发log,在正式游戏中也不会触发,或者有正常的解绑事件也不会有问题
    在这里插入图片描述

自定义事件分发蓝图订阅与解绑事件

蓝图订阅事件

  • 蓝图订阅事件,创建一个Actor蓝图然后派生三个子蓝图,注意调用接口类中的事件时要添加接口后编译才能找到OnReceiveEvent,然后委派事件时的名字记得填写
    在这里插入图片描述
  • 运行结果
    在这里插入图片描述

解绑事件

  • 解绑调用RemoveEventListener函数即可
    在这里插入图片描述
  • C++中也是差不多调用RemoveEventListener函数即可
    在这里插入图片描述
  • 运行结果,只会播放一轮了
    在这里插入图片描述

当播放结束时自动移除事件

  • 这样也能解决C++中的触发错误打印log
  • 蓝图中结束后自动移除事件
    在这里插入图片描述
  • C++中是重写BeginDestroy函数,调用移除函数即可
    在这里插入图片描述
    在这里插入图片描述
  • 运行结果,这样就不会有log错误了
    在这里插入图片描述

自定义事件之蓝图广播事件

  • 将MyBPAndCpp_Sender类转换为蓝图,首先调用一下父类的BeginPlay这样C++那边的事件才会生效
    在这里插入图片描述
  • 编写蓝图逻辑,发布广播
    在这里插入图片描述
  • 然后找个Actor蓝图接收事件,测试,订阅一下这个事件名即可
    在这里插入图片描述
  • 运行结果
    在这里插入图片描述

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

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

相关文章

适合大部分人可以做的兼职副业

大部分人每天八点上班&#xff0c;五点下班&#xff0c;日复一日地重复着枯燥的工作内容&#xff0c;关键是工资还不够花。那么&#xff0c;怎么才能增加日常收入、提高生活质量呢&#xff1f;副业兼职是个不错的赚钱选择。打工人除去日常工作&#xff0c;额外的空闲时间还是很…

宏基因组分析项目又双叒叕出新了!期盼已久的铁循环来了!!

铁是地球上几乎所有生命的微量营养素。它可以被铁氧化和铁还原微生物用作电子供体和电子受体&#xff0c;并用于多种生物过程&#xff0c;包括光合作用和呼吸作用。虽然铁是地壳中含量第四丰富的金属&#xff0c;但在有毒环境中&#xff0c;铁的生长往往受到限制&#xff0c;因…

惠威M200MKII音箱拆机

惠威M200MKII音箱是一对老音箱了&#xff0c;买了大概快有十多年了吧&#xff0c;几年前就已经有音量旋钮旋转的时候有噪音问题&#xff0c;一直用使用前快速左右旋一段时间&#xff0c;让电位器内部摩擦一下&#xff0c;就会好不少&#xff0c;但过些天又不行了。因为不是蓝牙…

基于springboot和vue的教务学生选课管理系统的设计与实现 (含源码+sql+视频导入教程)

&#x1f449;文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1 、功能描述 基于Springboot和vue的教务&#xff08;学生&#xff09;管理系统拥有三种角色&#xff1a;管理员、教师和学生 管理员&#xff1a;班级管理、课程管理、创建课程、管理员管理、教师管理…

SDN、SD-WAN、CDN、SDH分别是什么,有什么关联?

SDN代表“软件定义网络”&#xff0c;是一种网络架构&#xff0c;它将网络控制和数据转发分离。SDWAN代表“软件定义广域网”&#xff0c;是SDN的一种实现&#xff0c;在广域网中使用虚拟化技术来连接分支机构和数据中心。 CDN代表“内容分发网络”&#xff0c;是一种通过在全球…

Python内置函数与标准库函数的详细解读

一、内置函数与标准库函数的区分 Python 解释器自带的函数叫做内置函数&#xff0c;这些函数可以直接使用&#xff0c;不需要导入某个模块。 Python 解释器也是一个程序&#xff0c;它给用户提供了一些常用功能&#xff0c;并给它们起了独一无二的名字&#xff0c;这些常用功能…

算法通关村第一关—青铜挑战—用Java基本实现各种链表操作

文章目录 第一关—链表【青铜挑战】1.1 单链表的概念1.2 链表的相关概念1.3 创建链表 - Java实现1.4 链表的增删改查1.4.1 遍历单链表 - 求单链表长度1.4.2 链表插入 - 三种位置插入&#xff08;1&#xff09;在链表的表头插入&#xff08;2&#xff09;在链表的中间插入&#…

sqlserver调试clr程序集

右键项目属性&#xff0c;选择调试&#xff0c;勾选sqlserver调试选项&#xff1a; 选择附加到进程调试&#xff0c;勾选显示所有用户的进程&#xff0c;选择sqlserver.exe进程&#xff0c;打好程序集的断点&#xff0c;基于sqlserver触发clr程序集&#xff0c;即可中止到相关断…

个人开发者是否能借助开源获利?

前言 近几年国内外开源软件的成倍增长&#xff0c;以及开源软件在互联网领域的比重越来越高&#xff0c;开源软件扮演者越来越重要的角色&#xff0c;所以开源软件不仅推动了技术领域的不断创新和发展&#xff0c;也给个人开发者们提供了越来越多的合作机会和获利的机会。同时&…

二叉树(判断是否为单值二叉树)

题目&#xff08;力扣&#xff09;&#xff1a; 判断二叉树上每个节点的值是否相同&#xff0c;就需要让root节点分别与左节点和右节点分别比较是否相同。 注意&#xff1a;root等于空时&#xff0c;直接可以返回true&#xff1b; 首先&#xff0c;先判断他的特殊情况&#x…

序列号管理

序列号管理&#xff0c;将从以下方面进行学习和阐述 WHY 为什么需要序列号&#xff0c;有什么作用 HOW sap如何进行管理序列号 WHEN 什么情况下适合进行序列号管理 1、 什么是序列号 首先简单介绍一个序列号是什么东西&#xff0c;我们使用的手机、电脑或者大家…

HarmonyOS4.0开发应用(一)【工具安装】

工具安装 地址:https://developer.harmonyos.com/cn/develop/deveco-studio#download 我是windows&#xff0c;所以安装的windows 解压后双击该文件进行安装 安装完成后可选择是否导入开发工具的设置 这里选择不导入进入到工具内 点击Next进行安装 都没问题最终就可以开…

操作系统原理-作业一-进程同步

1.某理发店可同时供 10 人理发&#xff0c;当店中顾客少于 10 人时&#xff0c;则店外的顾客可立即进入&#xff0c;否则需在外面等待。请定义所需信号量并写出信号量各种取值( 大于 0 、等于 0 、小于0)分别代表的含义&#xff0c;并用 P 、 V 操作编程实现完成多个顾…

最高可挽回 20% 损失!东京大学利用 AI 及无人机,预测农作物最佳采收日期

内容一览&#xff1a;如果能在短期内确定并预测田间所有作物的生长状况&#xff0c;就可以设定最佳采收日期&#xff0c;减少非标准尺寸作物的数量&#xff0c;并最大限度地减少收入损失。对此&#xff0c;来自东京大学和千叶大学的研究人员&#xff0c;给出了 AI 无人机解决方…

华为鸿蒙:安卓,拜拜了您呢!

9 月底&#xff0c;华为举办了今年的秋季全场景新品发布会&#xff0c;接近尾声的时候&#xff0c;华为终端 BG CEO 余承东突然宣布&#xff0c;鸿蒙 HarmonyOS NEXT 即将发布&#xff0c;鸿蒙原生应用全面启动。 不同于之前 HarmonyOS 基于 AOSP&#xff08;Android 开放源代…

Ebullient 硬件篇

一. 简介 哈喽&#xff0c;大家好&#xff0c;好久没有给大家分享新项目了&#xff0c;但之前分享了许多项目都没有认认真真的做完过&#xff0c;做到了一半&#xff0c;由于某些原因就放弃了&#xff0c;给自己的一种感觉是做了很多东西&#xff0c;但是能拿出来讲的缺没有几…

Verilator 用法

Verilating … 威尔逊-斯奈德版权所有 2003-2023。 … SPDX 许可证标识符&#xff1a; 仅限 LGPL-3.0 或 Artistic-2.0 验证 Verilator 可通过五种主要方式使用&#xff1a; 使用 --cc 或 :vlopt:-sc 选项&#xff0c;Verilator 将分别把设计翻译成 C 或 SystemC 代码。 将设计…

Redis 入门和环境搭建

认识Redis Redis是一种NoSQL数据库&#xff0c;以键值对形式存储数据&#xff0c;支持多种数据结构&#xff0c;包括字符串、哈希、列表、集合、有序集合等&#xff0c;使其适用于多种应用场景。由于所有数据都存储在内存中&#xff0c;Redis的读写性能非常高。同时&#xff0…

Xiamen I Fitness Platform

厦门I健身平台程 https://ijs.sports.xm.gov.cn/mgh5/#/ 1&#xff09;公众号 2&#xff09;主页 3&#xff09;【个人中心】【我的保险】就是要买一份保险&#xff0c;10元的那种&#xff0c;不然去场地出意外咋办 4&#xff09;我的保险状态&#xff1a;未购买&#xff0c;…

Web实现悬浮球-可点击拖拽禁止区域

这次要实现的是这种效果&#xff0c;能够在页面上推拽和点击的&#xff0c;拖拽的话&#xff0c;就跟随鼠标移动&#xff0c;点击的话&#xff0c;就触发新的行为&#xff0c;当然也有指定某些区域不能拖拽&#xff0c;接下来就一起来看看有什么难点吧~ 需要监听的鼠标事件 既…