Buttons and Context Menu Actions for Extending Editors and Gameplay

You can easily extend the toolset functionality with your own methods defined in your entity and component C# classes, by showing them as actions in the right-click context menu shown in the Visual3D All-in-One Development Tool (toolset) when right clicking on an entity or component of your class type (in World Explorer, Object Editor, Asset Explorer, Scene Editor viewport, etc.) as well as have buttons shown in Object Editor when the component or entity is selected in the toolset. 

Similarly, you can have these methods exposed in the in-game (right click) context menus in addition to or instead of showing them in the editor context menus. can be shown by right clicking on any entity which has one or more context menu actions defined for them.  
Also, you can define buttons to be shown in Object Editor for methods you define in your entity and component C# classes.



Right clicking during gameplay can be performed by showing the mouse cursor, such as by double right clicking when controlling an avatar or pressing Escape key when controlling an avatar (if avatar changing is an enabled for a scene).
ContextMenuActionAttribute's defined on an entity class are what define such context menu actions for methods of that class, and derived attributes like [UserAction] can be used for actions that should only be shown for in-game context menus (not the toolset) while [DesignAction] can be used for actions that should only be shown in the toolset.  

You can also register your own icons to use for these context menus such as in a static contructor shown in this example and then use them via the ImageKey property for ContextMenuActionAttribute.

 

  [DataContract]

       [EntityType]

       [AssetSelector(AssetTypeName = "Entity", FilterType = typeof(EntityBase))]

       [ContextMenuAction(MenuItemName = "Activate Action", ImageKey = "Play",

Order = 2, GetDropDownItemsMethod = "CreateActionsMenu", Visibility = ContextMenuActionVisibility.Scene)]

       [ContextMenuAction(MenuItemName = "Deactivate Action", ImageKey = "Stop",

Order = 3, GetDropDownItemsMethod = "CreateActivatedActionsMenu", Visibility =

ContextMenuActionVisibility.Scene)]

       [ContextMenuAction(MenuItemName = "Add New Action", ActionMethod = "AddNewAction",

 ImageKey = "AIAction", Order = 4, ImageKey="ActiveBrush")]

       [ContextMenuAction(MenuItemName = "Add New Event Trigger", ActionMethod =

"AddNewEventTrigger", ImageKey = "Trigger", Order = 5)]

       [ContextMenuAction(MenuItemName = "Add New Ability", ActionMethod =

"AddNewAbility", ImageKey = "Ability", Order = 6)]

       public class EntityBase : ActivatedComponent, IComponentOwner,

IFixedAttributeProvider, IActivated, Assets.IDetachmentSite, IAssetItem,

IEditableDesignObject

       {

 

 

#region "Context Menu Actions and other logic for Design"

 

              private IEnumerable<ContextMenuActionAttribute> CreateActionsMenu()

              {

                     var result = new List<ContextMenuActionAttribute>();

                     int commandNum = 0;

                     foreach (var item in Components)

                     {

                            var action = item as ActionBase;

                           if (action == null)

                                  continue;

 

                           var menuItem = new ContextMenuActionAttribute

                           {

                                  MenuItemName = action.NameID,

                                  Action = action.Activate,

                                  ImageKey = "Play",

                                  Order = commandNum++

                           };

                            result.Add(menuItem);

                     }

                     return result;

              }

 

              private IEnumerable<ContextMenuActionAttribute> CreateActivatedActionsMenu()

              {

                     var result = new List<ContextMenuActionAttribute>();

                     int commandNum = 0;

                     foreach (var item in Components)

                     {

                            var action = item as ActionBase;

                           if (action == null)

                                  continue;

 

                           if (!action.IsActive)

                                  continue;

                           var menuItem = new ContextMenuActionAttribute

                           {

                                  MenuItemName = action.NameID,

                                  Action = action.Deactivate,

                                  ImageKey = "Stop",

                                  Order = commandNum++

                           };

                           result.Add(menuItem);

                     }

                     return result;

              }

 

              ComponentContainerProxy<object> _proxyForActions;

 

              [PropertyCategory, Browsable]

              [ComponentList(ShowFolder = false, ShowComponentGroupFolders = true, AreItemNamesUnique = true, UserLevel = UserLevel.Normal)]

              public ComponentContainerProxy<object> Actions

              {

                     get

                     {

                           if (_proxyForActions == null && Components != null)

                                 _proxyForActions = new ComponentContainerProxy<object>(Components, obj => obj is ActionBase);

                           return _proxyForActions;

                     }

              }

 

              [Button]

              [FirstProperty]

              [Category("Actions"), DisplayName("Add New Action")]

              private bool EditorAddNewAction

              {

                     get { return true; }

                     set { AddNewAction(); }

              }

 

              [ReflectionUsed]

              private void AddNewAction()

              {

                     IAssetSelectorService assetSelector;

                     if (App.Services.HasService(out assetSelector))

                     {

                          var action = assetSelector.Show<ActionBase>(TypeConstraint.Create<ActionBase>(),

                                  "Select Action...", true, InstancingMode.Instanced);

                           if (action != null)

                           {

                                  AddAction(action);

 

                                  IObjectSelectionService selectionService;

                                  if (App.Services.HasService(out selectionService))

                                         selectionService.SelectObjects(SelectionContext.WorldExplorer, new object[] { action });

                                  BaseWorldApplication.NotifyComponentOwnerNeedsUpdating(this);

                           }

                     }

              }

 

              [Button]

              [FirstProperty]

              [Category("Actions"), DisplayName("Add New Event Trigger")]

              private bool EditorAddNewEventTrigger

              {

                     get { return true; }

                     set { AddNewEventTrigger(); }

              }

 

              [ReflectionUsed]

              privatevoid AddNewEventTrigger()

              {

                     IAssetSelectorService assetSelector;

                     if (App.Services.HasService(out assetSelector))

                     {

                           var evnt = assetSelector.Show<BaseEvent>(TypeConstraint.Create<BaseEvent>(),

                                  "Select Event Trigger...", true, InstancingMode.Instanced);

                           if (evnt != null)

                           {

                                  var trigger = new Trigger(evnt.Name + "Trigger", evnt);

                                  AddAction(trigger);

 

                                  IObjectSelectionService selectionService;

                                  if (App.Services.HasService(out selectionService))

                                         selectionService.SelectObjects(SelectionContext.WorldExplorer, new object[] { trigger });

                                  BaseWorldApplication.NotifyComponentOwnerNeedsUpdating(this);

                           }

                     }

              }

 

              ComponentContainerProxy<object> _proxyForAbilities;

 

              [PropertyCategory, Browsable]

              [ComponentList(ShowFolder = false, ShowComponentGroupFolders = true, AreItemNamesUnique = true, UserLevel = UserLevel.Advanced)]

              public ComponentContainerProxy<object> Abilities

              {

                     get

                     {

                            if (_proxyForAbilities == null && Components != null)

                                  _proxyForAbilities = new ComponentContainerProxy<object>(Components, obj => obj is Ability);

                           return _proxyForAbilities;

                     }

              }

 

              [Button]

              [FirstProperty]

              [Category("Abilities"), DisplayName("Add New Ability")]

              private bool EditorAddNewAbility

              {

                     get { return true; }

                     set { AddNewAbility(); }

              }

 

              [ReflectionUsed]

              private void AddNewAbility()

              {

                     IAssetSelectorService assetSelector;

                     if (App.Services.HasService(out assetSelector))

                     {

                           var ability = assetSelector.Show<Ability>(TypeConstraint.Create<Ability>(),

                                  "Select Ability...", true, InstancingMode.Instanced);

                           if (ability != null)

                           {

                                  Set(ability.ComponentTypeKey, ability);

 

                                  IObjectSelectionService selectionService;

                                  if (App.Services.HasService(out selectionService))

                                         selectionService.SelectObjects(SelectionContext.WorldExplorer, new object[] { ability });

                                  BaseWorldApplication.NotifyComponentOwnerNeedsUpdating(this);

                           }

                     }

              }

        static EntityBase()
        {
            ...
            AssetIconRegistry.AddIcon("ActiveBrush", Resources.LineColorHS);
            AssetIconRegistry.AddIcon("InactiveBrush", Resources.Paintbrush2);
        }

 

 



              #endregion

                            }

 



Below is another example showing how to extend the NewLizardman demo avatar class so that you can use context menu in-game or in the editor or click a button in Object Editor to add or remove the torch (fire) attachment from it.


using System;

using System.Collections.Generic;

using Visual3D.Demo.Actors;

using System.Runtime.Serialization;

using Visual3D.Design;

using Visual3D.Graphics3D.Animations;

using Microsoft.Xna.Framework;

using Visual3D.Physics;

using Visual3D.Scripting.Behaviors;

using Visual3D.Demo.SampleParticles;

using Visual3D.Scripting.Actions;

 

 

namespace Visual3D.Demo

{

       [DataContract]

       [DisplayName("Lizardman")]

       [ContextMenuAction(ActionMethod="AddFire", MenuItemName = "Add Torch")]  //shown in both toolset for developers and in-game context menu for players

       [DesignAction(ActionMethod="RemoveFire", MenuItemName = "Remove Torch")] //shown only in toolset context menu right click context menu in the toolset

       [UserAction(ActionMethod="RemoveFire", MenuItemName = "Drop Torch")] //shown only in in-game context menu for players

       public class NewLizardman : CharacterBase

       {

              protected override int RequiredVersion

              {

                     get { return base.RequiredVersion + 2; }

              }

 

              public NewLizardman()

              {

              }

 

              protected override void OnConstructed()

              {

                     base.OnConstructed();

 

                     ModelName = "Lizardman.mesh";

 

                     Model.CameraFocusOffset = new Vector3(0, 2, 0); // Need to save model.

 

                     var aset = Get<AnimationSet>();

                     if (aset != null)

                     {

                           aset.AllowBlending = true;

 

                           float animSpeed = 0.7f;

                           float runSpeed = 7.0f;

 

                           aset.ConfigureAnimations(true,

                                 new Animation(AvatarCommands.Action1, "attack1", animSpeed, null, false),

                                  new Animation(AvatarCommands.MoveForward, "run", animSpeed, new Acceleration.Linear(Vector3.UnitZ, runSpeed, 0, 0.05f, true)),

                                  new Animation(AvatarCommands.MoveBackward, "run", -0.7f * animSpeed, new Acceleration.Linear(Vector3.UnitZ, -runSpeed, 0, 0.05f, true)),

                                  new Animation(AvatarCommands.MoveLeft, "run", animSpeed, new Acceleration.Linear(Vec3.UnitX, 0.5f * runSpeed, 0, 0.05f, true)),

                                  new Animation(AvatarCommands.MoveRight, "run", animSpeed, new Acceleration.Linear(Vec3.MinusUnitX, 0.5f * runSpeed, 0, 0.05f, true)),

                                  new Animation(AvatarCommands.Idle, "idle", 1.0f, null),

                                 new Animation(AvatarCommands.Jump, "stilljump", 1.0f, null, false, false)

                           );

                     }

 

                     ReplaceAction(Acceleration.CreateSpinYawFixed(Mobile.MotionName.Yaw, 2.0f, 0, 0.4f));

 

                     ReplaceAction(AvatarCommands.Action1, new Parallel(SuccessPolicy.One, new ActionBase[] {

                           new ActivationLimiter(1, new Delay(Units.msec(600), new WeaponAttack())),

                           new Animate(AvatarCommands.Action1)

                     }));

 

                     var aic = InputController as ActorInputController;

                     if (aic != null)

                           aic.DeactivateAction1IfMouseButtonReleased = false;

 

                     var inv = Get<Inventory>();

                     inv.MainWeapon = new ColdSteel()

                     {

                           //DamageDistance = 2,

                           Damage = 5

                     };

 

                     var combatant = Get<Combatant>();

                     combatant.AttackInterval = 1000;

 

                     var es = Get<ExplosionSupport>();

                     es.BodyMaterial = MaterialMakeupType.Flesh;

 

                     GetOrCreateDamagedTrigger();

              }

 

              public override void OnModelLoaded()

              {

                     base.OnModelLoaded();

 

                     var mobile = GetOrCreate<Mobile, HumanoidMobile>();

                     mobile.ForwardMotionDegreesCutoff = 20f;

                     mobile.MinScaleForForwardMotion = 0f;

                     Vector3 size = (MathUtil.GetSize(Spatial.WorldBoundingBox));

                     size.Y = 0;

                     float min = 0.2f * size.Length();

                     mobile.LockOnDistanceRange = new FloatRange(min * min, min * min * 5f);

              }

 

              // for deserializarion

              private void Damage(object ignoredArgs)

              {

              }

 

              ///<summary>Gets/Sets if this Lizardman has Fire at the end of his Spear.</summary>

              [DataMember]

              [Button, DisplayName("Has Torch"), Position(0), Category(PropertyCategoryName.Attachments)]

              public bool HasFire

              {

                     get { return (GetFire() != null); }

                     set

                     {

                           if (HasFire == value) return; // no change

                           if (value)

                                  AddFire();

                           else

                                  RemoveFire();

                     }

              }

 

              [Button, DisplayName("Add Torch"), Position(1), Category(PropertyCategoryName.Attachments)]

              public bool AddTorch { get { return false; } set { AddFire(); } }

             

             [Button, DisplayName("Remove Torch"), Position(2), Category(PropertyCategoryName.Attachments)]

              public bool RemoveTorch { get { return false; } set { AddFire(); } }

 

              const string MountPointName = "Stick";

             

              [ReflectionUsed]

              private Fire GetFire()

              {

                     if (Actor == null)

                           return null;

 

                     var attachment = Actor.GetAttachment(MountPointName);

                     if (attachment != null)

                           return attachment.AttachedObject as Fire;

                     return null;

              }

 

              [ReflectionUsed]

              //though ReflectionUsedAttribute is not required, its a good idea to use it to comment your method, especially if private, so its clear to others that it shouldn't be removed even if not used anywhere in the code

              private void RemoveFire()

              {

                     if (!IsInitialized)

                           return;

 

                     var fire = GetFire();

                     if (fire != null)

                     {

                           Actor.DetachObjectFromMountPoint(MountPointName);

                           fire.Dispose();

                           fire = null;

                     }

              }

 

              private void AddFire()

              {

                     RemoveFire();

 

                     if (!IsInitialized)

                           return;

 

                     Fire fire = new Fire(Name + " Fire");

                     fire.IsLightSource = true;

                     fire.PrepareForUse();

                     Actor.AttachObjectToMountPoint(MountPointName, fire);

              }

       }

}