Announcement

Collapse
No announcement yet.

Can an AActor react on inEditor Select ?

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

    Can an AActor react on inEditor Select ?

    Hello,

    i want to react on the selection and deselection of one of my actors. Is there an Event or somthing similar i get when my actor is selected in an Editor level ?

    Thanks !
    RealVis Studios

    Marketplace: Orbit Camera System
    | Culling Material Effect

    #2
    So, I went down the rabbit hole of UE4 code and found a couple of events in Selection.h: FOnSelectionChanged SelectionChangedEvent says it is called when a selection in the editor has changed and FOnSelectionChanged SelectObjectEvent is called when an object has been selected (generally an actor). Sounds like you want the second one. Maybe try to subscribe to that event and see if that gets you what you need. Hope this helps.

    Comment


      #3
      Ed.Kaminski Thanks i will have a look how and where to get the selectionn event from. Maybe someone knows how to subscribe on FOnSlectionChanged ?

      I also found AActor::IsSelectedInEditor() , but there for i would need to tick in editor ....
      RealVis Studios

      Marketplace: Orbit Camera System
      | Culling Material Effect

      Comment


        #4
        USelection::SelectionChangedEvent

        That's a global static delegate you can subscribe to.

        Comment


          #5
          PrHangs -- if you're unfamiliar with the whole event/delegate thing (and subscribing or even firing your own), here's a quick breakdown of how I usually go through the process. If there's a better way, I invite TheJamsh to give better information -- I may leave something out or there may be an updated way of doing it that I'm unaware of, etc.. Instead of using USelection::SelectionChangedEvent as the example, I'm going to use the example of rolling your own event, subscribing to it, and firing it. That way, if you find it useful, you could use it elsewhere in your projects.

          Also, the other thing I wanna point out is the terminology is a little weird for me -- as in the example above USelectionChangedEvent has "Event" in the name, but TheJamsh refers to it as a delegate. That has always confused me -- all of the documentation and forum posts about events/delegates seem to use these terms interchangeably -- again, I look to people like TheJamsh to give a better explanation, but this is how I look at it:

          An event is something that fires off. A delegate is something that reacts to an event firing off -- basically, a function that gets called due to the event firing.

          So, all that being said, let's set up our (oversimplified for explanation purposes) scenario.


          //BEGIN -- AN EXPLANATION OF EVENTS/DELEGATES BY SOMEONE WHO ONLY KNOWS KINDA WHAT HE'S TALKING ABOUT

          Let's pretend you have a class called Tank. And a class called Person. Tank can fire its gun. When it fires its gun, we want all of the Person actors to play their "cover your ears" animation. So, our event would be the Tank firing, the delegate would be a function that makes the "cover your ears" animation play on all the Person actors. Two separate classes - we want one to react to the action of the other.

          Now, at the top of the Tank.h file (outside of the class definition), we declare the following:

          Code:
          DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnTankFired);
          This is us declaring the event that will be broadcast to all delegates subscribed to it. Yes, there are different declarations for this sort of thing -- like DECLARE_DELEGATE -- (which I'm not 100% clear on all the differences), but this is the way I have been doing it, and it's served me just fine. I invite others to give explanations of the other ways of doing it and what the other declaration types mean/do, as I'm always looking to learn and do better than I currently am.

          Now, this declaration is important because besides declaring the "thing" that is going to broadcast the firing of the event to all subscribers, we're also declaring the type of function that is allowed to subscribe -- in this case a function that takes no parameters. If we wanted to declare an event that needs to pass a parameter, say a float value, we would need to declare it like this:

          Code:
          DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnTankFired, float, BoomVolume);
          A quick breakdown of this declaration: FOnTankFired is the thing that will broadcast the event, float is the type of the parameter that will be passed upon broadcast, and BoomVolume is whatever name you want to give the parameter (I'm not entirely sure why it's needed, though, as I never use this name anywhere except for in this declaration). And, yes, there are declarations for more than one parameter:

          Code:
          DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnTankFired, float, BoomVolume, int32, TurretKickback);
          And ThreeParams, and FourParams, and so on. Same formula for all params that follow the name of the thing (FOnTankFired) broadcasting the event -- type, name, type, name. But for our purposes, we're sticking with the original with no params.

          Alright, now that we have FOnTankFired declared, we need to add it as a property of our Tank. So, inside the Tank class definition, we give it a property of this type:

          Code:
          UPROPERTY()
          FOnTankFired OnTankFired;
          Additionally, we want to have a function to call when we want the tank to fire its gun, so maybe:

          Code:
          UFUNCTION()
          void FireGun();
          Now, that these two things have been declared in the Tank.h file, if we switch over to the .cpp file for the class, we can define the FireGun() function like this:

          Code:
          void ATank::FireGun()
          {
               //All the code for firing the gun goes here.
          
              OnTankFired.Broadcast();  //This is us firing the event and letting all subscribers know that it's fired so they may react
          }
          If we were using the event that takes a single float parameter, the Broadcast call would look like this (say, the BoomVolume value we want to broadcast is 5):

          Code:
          OnTankFired.Broadcast(5.0f);
          Ok, the Tank is all set up. Next, let's look at the Person class. For the Person class to react to the Tank's event, there are a couple of things we need to do. First off, we'll need a function that we'll want to execute whenever the OnTankFired event fires. So, inside the Person.h class definition, we declare:

          Code:
          UFUNCTION
          void CoverEars();
          If OnTankFired was declared using the single float parameter (OneParam), the function would need to accommodate that parameter:

          Code:
          UFUNCTION
          void CoverEars(float Volume);
          If you try subscribe a function whose signature doesn't match the params of the DECLARE_DYNAMIC_MULTICAST_DELEGATE declaration, it won't work...in fact, if I remember correctly, it may flat out not compile. So, be sure your parameters match. And the definition for CoverEars() will simply hold the code for playing the Person's cover ears animation:

          Code:
          void APerson::CoverEars()
          {
             //Code for telling the actor to play the cover ears animation goes here
          }
          So, now the question becomes "How does the Person subscribe to the Tank's event?" Well, first off, the Person needs access to the Tank. Secondly, the Person needs to set up the subscription to the Tank's event in the correct place -- for me, this has always been the actor's BeginPlay() function (since I know at that point both the Tank and the Person should be constructed and placed in the world).

          Now, I'm not going to go through the means of the Person having access to the Tank. You could put in code to find all of the Tanks in your world and have the Person subscribe to each and every tank that is found (though this would mean that the Person would cover its ears any time a Tank anywhere in the world would fire -- even if the Tank was waaaaay far away, which would be silly). Again, the scenario I'm presenting here is oversimplified and is purely for explaining events/delegates, not explaining how to get actors access to one another. So, let's simply assume that Person has a valid pointer to a Tank actor -- we'll call it:

          Code:
          ATank* TankICareAbout;
          To subscribe to this tank's OnTankFired event, in Person's BeginPlay() function, I would do the following:

          Code:
          TankICareAbout->OnTankFired.AddDynamic(this, &APerson::CoverEars);
          A quick explanation of the parameters being passed to AddDynamic() -- "this" is a pointer to the object that is subscribing (in our case, the Person) to the OnTankFired event. If the Person had, say, a component attached to it and we had a pointer property that referenced the component called USomeComponentType MyComponent*, and we wanted the component to be the thing subscribing to the event directly, we could do something like:

          Code:
          TankICareAbout->OnTankFired.AddDynamic(MyComponent, &USomeComponentType::NameOfFunctionToExecuteWhenTankEventFires);
          And, if you haven't guessed by now, the second parameter is a reference to the function (delegate) that should be executed when TankICareAbout's OnTankFired event fires. So, back to our original subscription:

          TankICareAbout->OnTankFired.AddDynamic(this, &APerson::CoverEars);

          Now, anytime TankICareAbout fires its OnTankFired.Broadcast() call, Person will execute its CoverEars() function.

          //END -- AN EXPLANATION OF EVENTS/DELEGATES BY SOMEONE WHO ONLY KNOWS KINDA WHAT HE'S TALKING ABOUT

          And that's about it. I hope you (or anyone else who finds this) finds it useful. And, again, I invite people who know better than I about UE4 to chime in and help me learn more -- not only about what I've laid out here, but alternative ways/methods/etc. on how all this stuff works.

          Thanks for bearing with me through the long-*** post.

          Now, PrHangs -- with all of the above, see if you can subscribe to USelection::SelectionChangedEvent. I do not know if AddDynamic is what it's expecting for subscription or not, as I do not recall if it was declared as a DECLARE_MULTICAST_DELEGATE or not. If not, maybe there's some other function it's looking for for subscribing. TheJamsh?
          Last edited by Ed.Kaminski; 05-17-2020, 12:02 AM.

          Comment


            #6
            Ed.Kaminski Thanks for the Detailed overview, but i am good. But I have to admit that Delegates can get a little confusing. I will add some thoughts of mine how to approach this, and post the solution as soon as i have time to implement it.

            To answer your question about the Multicast:
            Code:
            TMulticastDelegate_OneParam< void , UObject * > FOnSelectionChanged
            So Binding of the Delegate has to be done at editor time, my best guess would be:
            And only when in editor. And of course also unbind it at some point.

            Best
            Dominic
            RealVis Studios

            Marketplace: Orbit Camera System
            | Culling Material Effect

            Comment


              #7
              Now I feel dumb. I completely forgot that your issue was editor time and not runtime. Whoops.

              Comment


                #8
                So this is how you bind your delegate on SelectionChangedEvent:
                Code:
                USelection::SelectionChangedEvent.AddUObject(this, &AMyAcgor::MyFunction);
                MyFunction(UObject* InObject)
                But of course this will now trigger every time the selection is changed. So i need to find a cheap and clever way to only react if MyActor was deselected.

                There is also IsSelectedInEditor() which you can override, but this will be called even if you move the actor.
                Last edited by RealVis-Studios; 06-10-2020, 01:34 PM.
                RealVis Studios

                Marketplace: Orbit Camera System
                | Culling Material Effect

                Comment


                  #9
                  So what i ended up doing:

                  Code:
                  .h
                  
                  #if WITH_EDITOR
                  
                  UFUNCTION()
                  void OnSelectionChanged(UObject* InObject);
                  #endif
                  
                  protected:
                  
                  #if WITH_EDITOR
                  
                  FDelegateHandle OnSelectionChangedDelegateHandle;
                  
                  virtual void PostActorCreated() override;
                  virtual void BeginDestroy() override;
                  
                  bool bWasSelected = false;
                  
                  #endif

                  Code:
                  .cpp
                  
                  #if WITH_EDITOR
                  
                  void AMyActor::PostActorCreated()
                  {
                  Super::PostActorCreated();
                  
                  //Bind To SelectionChangedEvent if delegate is not yet bound
                  if (!OnSelectionChangedDelegateHandle.IsValid())
                  {
                  OnSelectionChangedDelegateHandle = USelection::SelectionChangedEvent.AddUObject(this, &AMyActor::OnSelectionChanged);
                  }
                  }
                  
                  void AMyActor::BeginDestroy()
                  {
                  // If Delegate is bound remove it
                  if (OnSelectionChangedDelegateHandle.IsValid())
                  {
                  USelection::SelectionChangedEvent.Remove(OnSelectionChangedDelegateHandle);
                  OnSelectionChangedDelegateHandle.Reset();
                  }
                  
                  Super::BeginDestroy();
                  }
                  
                  void AMyActor::OnSelectionChanged(UObject* InObject)
                  {
                  // Check if Actor is selected and store selection
                  if (IsSelected())
                  {
                  bWasSelected = true;
                  }
                  // If the actor is not selected but was selected before
                  else if (bWasSelected)
                  {
                  //UE_LOG(OrbitCamera, Warning, TEXT("DE-SELECTED"));
                  bWasSelected = false;
                  
                  //Do your deselection logic here
                  
                  }
                  }
                  #endif
                  RealVis Studios

                  Marketplace: Orbit Camera System
                  | Culling Material Effect

                  Comment

                  Working...
                  X