Download

What base class is the most suitable for non-physical object like Ability?

Sorry if my question is stupid, but I’m not very familiar with architecture stuff and I need some help :slight_smile:
I have my Pawn object that can attack in 2 different styles - 2 techniques, which heavily modifed during the game so I decide to make own classes for them in order to simplify the calls.
Like call once Technique->Setup(NewData) whenever there is some change or upgrade to attack style and call simple Technique->Strike() whenever I want to attack.
I never used “Object” as base class for my classes, is there some caveats I should be aware of?

Not sure if this is correct approach, but it does work. In my game I just subclass my ability from the AActor class, this is nice because replication is already set up and easy to use from the AActor class, also if you did need a visual representation for some reason, it would be easy to set up here as you could subclass it in blueprints and add particles, meshes, etc… to it.

Hope this helps!
Connor

It’s hard to tell without knowing what kind of data you have in mind.

Do you actually need engine memory management and reflection? Do you need inheritance? Do you want to make a blueprint to tweak this data?

In increasing order of complexity, I would use a simple struct, a USTRUCT, a UDataAsset containing a list of USTRUCTs or an outright UObject. In all cases, I would make this FTechnique/UTechnique a data container used by the pawn, rather than the other way around:


UMyPawn::PostInitializeComponents()
{
    for( FTechnique& Technique : TechniqueList )
    {
        Technique.Setup( SomeData );
    }
}

Then:


MyPawn->Strike( Technique );

p.s.: Please don’t make it an AActor, that simply makes no sense.

I implemented a similar system, and made each ability an actor so that it could be easily replicated across the network. Check this out for some ideas

Depends it’s very open ended question.

First question. Do you want to have multiplayer or not ?
If not, then UObject will serve your needs just as well.
If you do, then easiest way to start is to use AActor.

Though you can also use UObject if you need replication, but will require quite a lot of setup, and IMO it’s not worth the effort.

Considerations, such as memory, overhead, are not really important for gameplay code at this stage. You should focus on getting it working right, and then think about what can be optimized (if needed).

Also for multiplayer do not disable things like relevancy or revolve to replicate everything to fix stuff (;. Work within constrains imposed by engine, They are here for a reason, especially if you are new to this.

If you are interested how I did it, you can check code in my rep (link in sig). It’s bit rotten at this point, but you should find core concepts useful. I have refactors some of this code but not yet commited to GitHub, to not create even more mess than there is now.

The general principle AActor, Interfaces and ActorComponenets are your friends in this struggle.

Inheriting AActor just for replication is like killing a fruit fly with a rocket launcher. It’s also sloppy coding and probably not necessary in this case. I doubt OP even needs an object to begin with.

Replication of attack data can be done through the pawn performing the attack and solving the problem that way will probably result in clearer and stabler code.

I’m sure guys who are coding Unreal Tournament or GameplayAbilities module doesn’t know about it, so you should tell them (;.

Using AActor for replication is common practice. Unless you are creating open world, persistent MMORPG.

Besides. The usecase is not defined enough. Because I still don’t know what author really want to achieve.

I suppose they do?


class GAMEPLAYABILITIES_API UGameplayAbility : public UObject

I will describe what I currently have and what I want to achieve and then will read other messages, thank you guys, by the way!

What behaviour I want to achieve:
Character have a set of basic abilities at his disposal. Each ability is heavily modified during gameplay, like basic attack and bomb in Binding of Isaac. For example, player start with “Fire Bolt” and during gameplay he convert it into giant Fireball which creates 9 Fire bolts on explosion and those fire bolts set ground on fire after landing.
There are some parts that utilize every ability, like power, distance, speed, size and etc. But some abilities requires unique ways of implementation, for example - projectile attack and beam attack.

What I have now:
Player Pawn that store Technique struct which modified during gameplay. Include power, distance, speed, size etc and Enum::AbilityType
Whenever pawn want to attack - Strike(PawnTechnique) is called.
Basically Strike read struct and then use Switch based on Enum::AbilityType and in result of strike spawn Projectile/Beam/Explosion/Whatever type of Actor I want, that replicated properly and damage what should be damaged.
But after I realize that I will have like ~8 different base types of abilities I thought maybe it is a good idea - to switch to something more flexible than switch with structs

About networking:
I doubt I want this part to be replicated, because result of this function makes call to server and then it spawns appropriate damaging actor.

Okay, that makes sense. I was thinking there would be runtime modifications to the abilities, whereas its mostly runtime choice of predefined abilities.

ShooterGame’s weapon system might be a useful inspiration. There really are two main weapon archetypes: hitscan and projectile weapons. The asset details are handled in derived blueprints – a rocket launcher blueprint can derive from projectile, as would a bow blueprint, whereas a minigun or laser would be hitscan.

A similar approach sounds good for these abilities. Create a native base class for each ability archetype that handles all the behaviour. Then create blueprints to actually define each ability of each archetype. i.e.: for projectile abilities, add a uproperty for your projectile actor (or an array, if it fires multiple projectiles), for power, distance, size, etc. You end up with a series of data-only blueprints that clearly define each ability. Then on each pawn, store a list of ability blueprints according to what abilities they can use.

Does that sound reasonable?

I got curious: Why are you placing abilities on pawn instead of controller? :smiley:

I don’t know what kind of game are you doing, but this thing of Skill Progression is a bit alike the one I’m doing, so, I’ll tell you my skill setup, maybe this can give you ideas…

First, I did UEnums to describe possible “generic” skill setups, E.G. EEffectType(Buff, Mellee, Summon, Projectile…), ETargetType(Me, Ally, Foe, Area…), on your case I would consider also make an Enum to hold SkillSize/Power(Small, Medium, Normal, Big, Giant, OP…), SkillRadius, Damage and so on…

After I did a FStruct (LARGE) holding all these things, plus the cosmetic things (name, icon, etc), and a float called SkillLevel (on your case you can just use the SkillPower Enum).

Another FStruct (SMALL) just holds an SkillCode (int32) and on this case the Enum (this second Struct will be saved if you want to keep skill evolution, used on the CastFunction and also replicated).

Did a DataAsset that holds a TArray from LARGE FStructs, that would be great to create new skills and balance the game after DataAssets can be modified on editor and have a search system.

The SMALL FStruct holds the datasset array index from the desired skill on the SkillCode Property, so if your Fireball is the seccond skill on the DataAsset, the skillcode to it will be 1 (0 indexed).

So, on input you can just send a CastSkill(int32 Code, ESkillPower Intensity) to your “pawn?” and BOOM. :smiley:

Hope this helps.

I would do it differently. You are trying to hardcode to many thing, and it will bite you back later.
Create base class Ability which will derive from actor.
Then create interface, like IInputAction.
Input action have function responsible for input (InputPressed, InputReleased). Make your ability implement this interface. Also other objects which need input can implement it.

Then when you need to use any object by pressing input you do:



...
IInputAction* input = Cast<IInputAction>(MyObject);
input->InputPressed();


You can also check if input is null just to make sure.

Assuming you need different casting type, you should implement state machine for it (check Unreal Tournament code).

Now we get to interesting part. Don’t think about Projectile, Beam, Instant hit, AOE in terms of ability type. They are not different abilities. They are different tasks, performed by ability.

And there are several ways to approach it. Create library of static functions, for spawning projectiles, and then just call it inside ability.
For other types, it just matter of different traces Beam can be continuous line trace or box trace depending if you need to affect area or just single line.

Another approach is to create task objects, which can be added to ability and they will handle any behaviour. Remember. Composition over inheritance. You will run yourself into corner when try to use to much inheritance.

Modifying abilities at runtime.
Again.
Create special object like UAbilityModifier, or UAbilityUpgrade, or something.

This object should bind delegate, which will check if ability is being used or if it has been equipped, depends at which stage you want to modify it.

We can assume that base ability have some common properties for all abilities. So you can modify them directly by calling function:



void ModifyAbility(AAbility* AbilityIn)
{
//mod your ability
}


If you need to modify specific ability, you can cast it before.
Also you should expose it to blueprint, since it wil be easier to quickly setup here.

Then you just call Delegate.Broadcast(this) on ability. (note that you can place delegate in separate component, which might store modifier objects, and call events). You bind events inside modifier objects (.AddDynamic()).

In anycase what you describe is perfect candidate for Component/Entity based approach, where compose bigger things out of smaller objects, and then you can easily swap, those objects at runtime if needed, or modify them.

With your current approach, you will quickly loose track of what are you doing, how it is supposed to interact with rest of the system and hardcode many thing, where modifying of simgle element can break everything else.

@inside

Well, I’ll not deny, got sort of confuse with your abstraction level (LOL) certainly a lot greater than mine.
Looks like what you suggested is a “SkillCastsHerselfUsingPlayer” thing (all self-contained and executed on SkillClass, a delegate to sync with the caster).
On the other hand I “guess” that my approach is more to “PlayerCastsSkill” thing.

Your comment got me curious about 2 things:

  1. If each skill is just a bunch of variables inside an FStruct Array index, (and acessed on demand) how could modify one element from it break everything? oO I got scared!
  2. You said my system is a candidate to Component/Entity approach, comparing with your suggestion should this be worst or better to Expansions/DLCs?

Thank you by your feedback. :smiley:

  1. The point is that order of TArray (I assume you use TArray) is by no means guaranteed for one thing. I don’t know specifics of your implementations, but I guess you will have problems with adding new funcionality as well.
    What if you need ability which is Channeled, does AoE dmage, is targetable (targeting circle or something), have cooldown etc ?
    If you use inheritance, polymorphism approach, how do you decide what you put higher in hierarchy Casting types or targeting types ? The answer is, you don’t. Both have the same priority.
    If you are using enums, if, switch, to select what object should do, you should consider using componenets, and polymprhism on componenets. Software is smart enough to choose right overridden function from object.
    you can have UAbilityState object for different casting types (UAbilityCooldownState, UAbilityCastingState, UAbilityInstantCastState, etc), all derived from UAbilityState, and all of them override several base functions.

  2. Componenet system is always better, it is much easier to expand. Each Component/Task/Entity (whatever you call it), is self containing class (object), which should not depend on other objects. It’s very easy to expand as all you need to do is to create new object which contains new needed functionality. You won’t break any functionality accidentally, because all specific functionality is isolated to single objects.
    There are many approaches to creating it none is particularly more or less valid. I decided upfront that all ability related Entities will depend on Ability class or ability related interfaces, for communication. It makes coding easier, and the usually issues from this approach like less code abstraction or higher coupling do not really apply to game development.

I done it using:



UCLASS(DefaultToInstanced, EditInLineNew, Within = ARAbility)


“Within” gives your access to GetOuterYourClassName() pointer which is very handy, for creating stuff you know will work only with particular family of classes.