5.5 this 指针

问题:当在对象的外部访问该对象的公有成员时,必须指明是哪一个对象。但是当我们用对象的成员函数来访问本对象的成员时,在成员函数中只要给出成员名就可以实现对该对象成员的访问。再进一步可用同一个类创建很多个对象,但它们共用同一份成员函数的拷贝。既然是同一份拷贝,那么成员函数又怎么知道是取哪一个对象的成员数据呢?

其实,当调用一个成员函数时,系统自动产生一个隐藏的指针,这个指针称为this指针,始终指向产生这个调用的对象,并将该指针作为一个参数自动传递给该成员函数。这就是说,成员操作符总是要使用的,只是在对象内是隐式的,而在对象外是显式的。即在对象内省略了this指针。

实际上编译器是这样实现this指针的:
1.改变类成员函数的定义,用附加参数this指针来定义每个成员函数。如:
void Cgoods::RegisterGoods(Cgoods*this,char*nam,
int amount,float price){
    strcpy(this->Name,name);this->Amount=amount;
    this->price=price;
}

2.每个类成员函数的调用,加上一个附加的实参——被调用对象的地址。如:
    Car1.RegisterGoods ( String , number , pr );
改变为:
    RegisterGoods ( &Car1 , String , number , pr);

在上例中,this指针不必写成显式的,但是有时必须写成显式的,如在以后要学的某些类型的链表管理中,在需要返回当前调用的对象时(对复数类的赋值号重载中 ),等等。但必须指出静态成员函数没有this指针。因为普通成员函数虽然在物理上只有一份拷贝,但在逻辑上都认为一个对象有一份拷贝,所以有this指针,而静态成员函数在逻辑上也只有一份拷贝,不属于具体的对象,当然没有this指针。
<think>我们正在使用C++和蓝图混合编程在UE5.5中实现一个状态机。状态机通常由状态、转换条件和转换动作组成。在UE中,我们可以使用多种方法,例如使用UE内置的状态机组件(如GameplayStateTree)或者自定义实现。这里我们选择自定义实现一个简单的状态机,以便更好地控制。步骤:1.使用C++定义状态机的基类,包括状态、转换等基本结构。2.在蓝图中继承并扩展这些基类,实现具体的状态和转换逻辑。3.在C++中管理状态机的运行,包括状态转换、状态进入和退出等。我们将按照以下步骤进行:第一步:使用C++定义状态基类(State)和状态机基类(StateMachine)状态基类(State):-包含进入状态(Enter)、退出状态(Exit)、更新状态(Update)等虚函数。-可以包含状态名称等基本信息。状态机基类(StateMachine):-包含当前状态。-包含状态注册和状态切换的方法。-包含一个状态映射(TMap)来存储所有状态。第二步:在蓝图中创建具体的状态和转换-创建基于State类的蓝图,并在蓝图中实现具体的Enter、Exit、Update逻辑。-在状态机中注册这些状态。第三步:在C++中实现状态转换逻辑-状态转换可以基于条件(例如,在Update中检查条件),当条件满足时切换到下一个状态。第四步:在Actor或Component中使用状态机-将状态机作为Actor的一个组件或者作为一个成员变量,并在其中初始化状态机。下面我们开始代码部分:首先,创建状态基类(State):注意:在UE中,我们通常使用UCLASS宏来创建可以在蓝图中扩展的类。State.h```cpp#pragmaonce#include "CoreMinimal.h" #include"UObject/NoExportTypes.h" #include"State.generated.h"UCLASS(Blueprintable,Abstract)class STATEMACHINEEXAMPLE_API UState :public UObject{ GENERATED_BODY() public:UFUNCTION(BlueprintNativeEvent, Category= "State")void Enter(); virtual voidEnter_Implementation(){}UFUNCTION(BlueprintNativeEvent, Category= "State")void Exit(); virtual voidExit_Implementation(){}UFUNCTION(BlueprintNativeEvent, Category= "State")void Update(float DeltaTime);virtualvoid Update_Implementation(float DeltaTime){} };```然后,创建状态机基类(StateMachine):注意:状态机需要管理状态,并且需要能够切换状态。我们将其设计为一个UObject,以便在蓝图中使用。 StateMachine.h```cpp#pragma once#include"CoreMinimal.h"#include "UObject/NoExportTypes.h" #include"State.generated.h"UCLASS(Blueprintable)class STATEMACHINEEXAMPLE_API UStateMachine: publicUObject{GENERATED_BODY()public://设置初始状态UFUNCTION(BlueprintCallable, Category= "StateMachine") voidInitialize(UState*InitialState);//更新当前状态UFUNCTION(BlueprintCallable,Category ="StateMachine")void Update(float DeltaTime);//切换到新状态UFUNCTION(BlueprintCallable,Category ="StateMachine")void ChangeState(UState*NewState);//注册状态(可选,如果需要通过状态机动态管理状态)UFUNCTION(BlueprintCallable,Category ="StateMachine")void RegisterState(FStringStateName, UState*State);//通过状态名获取状态(如果注册过)UFUNCTION(BlueprintCallable, Category= "StateMachine")UState* GetState(FString StateName);private:UPROPERTY()UState*CurrentState= nullptr;//状态映射UPROPERTY() TMap<FString, UState*>States;}; ```StateMachine.cpp```cpp#include"StateMachine.h"void UStateMachine::Initialize(UState* InitialState){ if(InitialState){CurrentState= InitialState;CurrentState->Enter();} }void UStateMachine::Update(float DeltaTime){ if(CurrentState){CurrentState->Update(DeltaTime);}} voidUStateMachine::ChangeState(UState* NewState){ if(NewState &&NewState !=CurrentState){if (CurrentState){CurrentState->Exit();}CurrentState= NewState;CurrentState->Enter();} }void UStateMachine::RegisterState(FString StateName,UState* State) {if (State){States.Add(StateName,State);}} UState*UStateMachine::GetState(FStringStateName) {if (States.Contains(StateName)){returnStates[StateName];} returnnullptr;} ```第三步:在蓝图中使用1.创建几个具体的状态蓝图类,继承自UState,并实现Enter、Exit、Update函数。2.创建一个蓝图,例如一个Actor,它包含一个UStateMachine类型的变量。3.在Actor的BeginPlay中初始化状态机,并设置初始状态。例如,在Actor中:-在构造脚本中创建状态机对象(使用CreateDefaultSubobject?注意:UObject不能这样创建,因为不是ActorComponent。我们可以使用NewObject来创建,但要注意生命周期)-在BeginPlay中初始化状态机。由于UStateMachine和UState都是UObject,我们需要在Actor中持有它们的引用,并负责它们的生命周期。示例Actor(C++部分):```cpp//在Actor的头文件中UCLASS() classSTATEMACHINEEXAMPLE_APIAMyActor :public AActor{ GENERATED_BODY() public:AMyActor();UPROPERTY(BlueprintReadWrite)UStateMachine* StateMachine;protected:virtualvoid BeginPlay()override;virtualvoid Tick(float DeltaTime)override;}; ```在cpp中:```cppAMyActor::AMyActor() {PrimaryActorTick.bCanEverTick= true;StateMachine= NewObject<UStateMachine>(this);//创建状态机,并指定Outer为this,这样就会随着Actor一起销毁} voidAMyActor::BeginPlay() {Super::BeginPlay();//假设我们已经在蓝图中创建了初始状态(比如IdleState)的蓝图,并且在这里获取到它的实例//注意:在C++中创建蓝图类的实例比较复杂,通常我们会在蓝图中设置初始状态,所以这里我们留空,由蓝图来初始化//我们可以在蓝图中调用StateMachine的Initialize函数,传入一个状态对象(蓝图创建的状态对象) }void AMyActor::Tick(float DeltaTime){Super::Tick(DeltaTime); if(StateMachine){StateMachine->Update(DeltaTime);}} ```第四步:在蓝图中完成状态机的初始化1.创建几个状态蓝图(例如IdleState,MoveState,AttackState等)。2.在Actor蓝图中,我们可以在BeginPlay事件中:-创建这些状态的对象(使用“Construct Objectfrom Class”节点,选择我们创建的状态蓝图类)。-将这些状态注册到状态机(可选,如果需要通过名字切换状态)。-初始化状态机,设置初始状态。第五步:在状态蓝图中实现逻辑-例如,在IdleState的Update中,我们可以检查是否满足移动条件,如果满足则触发状态切换(通过状态机ChangeState函数)。注意:状态切换可以在状态内部触发,但需要持有状态机的引用。因此,我们可以在状态基类中添加一个状态机的引用,并在状态机设置当前状态时,将这个状态机的引用传递给状态。修改State基类:-在UState中添加一个StateMachine的指针,并提供一个设置状态机的函数(在状态机设置当前状态时调用)。修改State.h```cppUCLASS(Blueprintable, Abstract) classSTATEMACHINEEXAMPLE_APIUState: publicUObject{...public://设置状态机void SetStateMachine(UStateMachine*InStateMachine){ StateMachine =InStateMachine;} protected:UPROPERTY()UStateMachine* StateMachine =nullptr;...}; ```修改StateMachine的ChangeState函数:```cppvoidUStateMachine::ChangeState(UState* NewState){ if(NewState &&NewState!= CurrentState){if(CurrentState){CurrentState->Exit();}CurrentState =NewState;//设置新状态的状态机引用CurrentState->SetStateMachine(this);CurrentState->Enter();} }```同样,在Initialize函数中也要设置状态机的引用: ```cppvoid UStateMachine::Initialize(UState* InitialState){ if(InitialState){CurrentState= InitialState;CurrentState->SetStateMachine(this);CurrentState->Enter();} }```这样,在状态蓝图中,我们就可以通过StateMachine引用来切换状态了。示例:在IdleState的Update中,如果满足条件,切换到MoveState-在IdleState的蓝图中,我们可以这样:在Update事件中,检查某个条件(比如距离),如果满足,则调用StateMachine的ChangeState函数,传入MoveState对象(需要事先获取到MoveState对象)。但是,我们如何获取MoveState对象?一种方法是在初始化状态机时,将所有的状态对象都创建并注册,然后在需要时通过状态名获取。另一种方法是在Actor中持有这些状态对象的引用,然后传递给状态。我们通常采用第一种方法:在Actor的BeginPlay(蓝图)中,创建所有状态对象,注册到状态机,并设置初始状态。在Actor蓝图中:-创建状态对象(IdleStateObj, MoveStateObj等)-调用StateMachine的RegisterState函数,为每个状态注册一个名字(比如"Idle","Move")-然后调用StateMachine的Initialize,传入IdleStateObj在IdleState的蓝图中,当需要切换状态时:-调用StateMachine的GetState函数,传入"Move",获取到MoveStateObj,然后调用ChangeState(MoveStateObj)注意:我们也可以直接在Actor蓝图中持有状态对象的引用,然后在状态蓝图中通过状态机获取Actor,再获取状态对象。但这样耦合较高。使用状态机内部的状态映射更符合封装原则。最后,关于性能:状态机在游戏中的使用非常普遍,这种实现方式在中小型状态机中效率足够。对于非常复杂的系统,可以考虑使用更高效的结构,比如使用枚举代替对象等,但灵活性会降低。以上是一个基本的混合编程状态机实现。根据具体需求,还可以扩展,例如添加状态转换条件类、转换动作等。引用说明:[^1]:UE5C++(二)—游戏架构介绍。其中提到了GameMode可以设置默认的Pawn、HUD等类,但我们的状态机实现与GameMode无关,是独立的。[^2]: UE5.5 Niagara发射器更新属性。提到了CPU和GPU计算的选择,但我们的状态机是在逻辑线程(CPU)上运行的,适用于控制角色行为等。相关问题:1.在UE5.5中,状态机与行为树有什么区别?各自适用什么场景?2.如何优化状态机的性能,特别是在大量AI同时使用状态机时?3.状态机如何与UE5.5的GameplayAbilitySystem(GAS)结合使用?</think>### UE5.5 C++与蓝图混合实现状态机教程 在UE5.5中使用C++和蓝图混合实现状态机是高效的游戏开发方式,结合了C++的性能优势和蓝图的快速迭代能力。以下是完整实现步骤: --- ### 一、状态机架构设计 状态机核心组件: 1. **状态基类**(C++):定义状态通用接口 2. **状态机控制器**(C++):管理状态转换逻辑 3. **具体状态**(蓝图):实现各状态行为 4. **上下文对象**(蓝图/C++):存储共享数据 状态转换关系: ```mermaid graph LR A[状态A] -->|条件1| B[状态B] B -->|条件2| C[状态C] C -->|条件3| A ``` --- ### 二、C++核心实现 #### 1. 状态基类(StateBase.h) ```cpp #pragma once #include "CoreMinimal.h" #include "UObject/Interface.h" #include "StateBase.generated.h" UINTERFACE(MinimalAPI) class UStateBase : public UInterface { GENERATED_BODY() }; class STATEMACHINE_API IStateBase { GENERATED_BODY() public: UFUNCTION(BlueprintNativeEvent, Category = "State") void EnterState(AActor* Owner); UFUNCTION(BlueprintNativeEvent, Category = "State") void UpdateState(AActor* Owner, float DeltaTime); UFUNCTION(BlueprintNativeEvent, Category = "State") void ExitState(AActor* Owner); }; ``` #### 2. 状态机控制器(StateMachineComponent.h) ```cpp #pragma once #include "CoreMinimal.h" #include "Components/ActorComponent.h" #include "StateMachineComponent.generated.h" UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent)) class STATEMACHINE_API UStateMachineComponent : public UActorComponent { GENERATED_BODY() public: UStateMachineComponent(); UFUNCTION(BlueprintCallable, Category = "State Machine") void ChangeState(TSubclassOf<UObject> NewStateClass); UFUNCTION(BlueprintCallable, Category = "State Machine") void SetInitialState(TSubclassOf<UObject> InitialStateClass); virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; protected: UPROPERTY() TScriptInterface<IStateBase> CurrentState; }; ``` (StateMachineComponent.cpp) ```cpp #include "StateMachineComponent.h" #include "StateBase.h" void UStateMachineComponent::ChangeState(TSubclassOf<UObject> NewStateClass) { if (CurrentState && CurrentState.GetObject()) { IStateBase::Execute_ExitState(CurrentState.GetObject(), GetOwner()); } CurrentState = NewObject<UObject>(this, NewStateClass); if (CurrentState) { IStateBase::Execute_EnterState(CurrentState.GetObject(), GetOwner()); } } void UStateMachineComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) { Super::TickComponent(DeltaTime, TickType, ThisTickFunction); if (CurrentState) { IStateBase::Execute_UpdateState(CurrentState.GetObject(), GetOwner(), DeltaTime); } } ``` --- ### 三、蓝图实现步骤 #### 1. 创建具体状态蓝图 1. 新建**蓝图类** → 选择**Actor**作为父类 2. 添加**StateBase接口**(类设置 → 实现接口) 3. 实现三个核心函数: - `Enter State`:进入状态时的初始化 - `Update State`:每帧更新逻辑 - `Exit State`:退出状态时的清理 #### 2. 配置状态机组件 1. 在角色/敌人蓝图中添加**State Machine Component** 2. 在事件图表中初始化: ```mermaid graph TB BeginPlay --> InitState[调用 SetInitial State<br>传入初始状态类] ``` #### 3. 状态转换示例 ```mermaid graph LR IdleState -->|玩家进入视野| ChaseState ChaseState -->|玩家丢失| PatrolState PatrolState -->|发现玩家| ChaseState ``` --- ### 四、混合编程最佳实践 1. **性能关键逻辑**用C++实现(如距离计算、路径查找) 2. **行为表现逻辑**用蓝图实现(如动画切换、音效触发) 3. **数据传递**: - C++暴露变量给蓝图:`UPROPERTY(BlueprintReadWrite)` - 蓝图调用C++函数:`UFUNCTION(BlueprintCallable)` 4. **调试支持**: ```cpp GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Green, FString::Printf(TEXT("State Changed to: %s"), *NewStateClass->GetName())); ``` --- ### 五、状态机优化技巧 1. **分层状态机**:复杂角色使用嵌套状态机 2. **状态共享**:通过`GameInstance`存储全局状态 3. **异步状态切换**:使用`Latent`函数处理过渡动画 4. **与GameplayAbilitySystem集成**:将状态转换为`GameplayTags`[^1] --- ### 示例应用场景 1. 敌人AI行为: ```mermaid graph TD A[巡逻] -->|发现玩家| B[追击] B -->|距离<2米| C[攻击] C -->|玩家死亡| D[返回巡逻] ``` 2. 玩家角色状态: ```mermaid graph LR Grounded -->|跳跃| InAir InAir -->|落地| Grounded Grounded -->|蹲下| Crouching ``` [^1]: UE5 C++(二)— 游戏架构介绍中提到的状态管理概念 [^2]: UE5.5 Niagara发射器更新属性中提到的计算单元选择逻辑 --- ### 常见问题解决方案 1. **状态切换延迟**:确保在`Tick`中更新状态机 2. **蓝图状态不触发**:检查接口实现是否正确 3. **多状态冲突**:使用状态优先级系统 4. **网络同步**:在C++中使用`ReplicatedUsing`标记状态变量 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值