uFrame
uFrame copied to clipboard
Cannot apply group to collection type in component
This was discussed in the gitter and replicated by another:
The below video shows me trying to select a group as the type for the collections within a component and it just sets it to int each time.
https://dl.dropboxusercontent.com/u/98595151/ShareX/EMBERSOFT1/16/05/2016-05-10_20-23-47.mp4
Why in the world would you want to add a group as property type.. and is it just on the group?
I assumed this was a valid use case, how else are you meant to represent a collation of components as a property? not tried anything else.
If this is not a valid use case then I would probably say the bug should be to remove groups from the available property type options.
As it was suggested in the gitter I provide some context ass to what is happening (to go with the video), here is the conv from yesterday which sparked it:
LP @grofit May 11:09:08
rule of thumb:
- Granular data goes in components
- Composed high level entities go into groups
- Logical reactions go in Systems
well let me give you an example from last night before I hit issue #4
so in the buff world you have buffs which may last a period, or be passive, they also may have tick based effects
mkuhr @mkuhr May 11 09:16
yeah dota
LP @grofit May 11 09:17
so an active buff would at least need the buff type data, then data on how long it lasts
for passives you could just set the duration to 0
however how do you express the periodic tick? as that may or may not be needed
and you may have passive buffs with ticks, or duration ones with ticks
I just took the simplest route and made components for Buff { name, type, value }, HasDuration { TimeActive, Duration }, HasPeriodicEffect { TickPeriod }
then I had a group which basically took all of them ActiveBuff { Buff, HasDuration, HasPeriodicEffect }
but for 90% of buffs you would probably not have a periodic effect
so it seems fairly needless to have that there
you COULD decide to have 2 groups
PeriodicBuff { Buff, HasDuration, HasPeriodicEffect } and Buff { Buff, HasDuration }
but then when you want to have something which is Buffable you then need to give it 2 collections, one for basic buffs and one for periodic ones, then you need 2 events for each logical event (BuffExpired etc)
so you could do this both ways, but I could not work out which was the better, so I just went with the simpler one and just allowed everything to have the data to support ticks etc, just leave it empty if not used
but that kinda goes against the ECS principal of only composing of what you need
So maybe I should be using a component to compose the other components into something that would be a collation of concerns, but I always thought it was a groups job to collate and represent entity concerns at a high level. So I am happy to be steered in a different direction if my understanding of groups is wrong.
Well with ECS groups already are a collection of components and are pretty accessible from a system.
This does not really help much, assuming you need to give the notion of a buffable
object so it is something that contains some a collection of active buffs, an active buff in this scenario being something which contains the component Buff, HasDuration, HasPeriodicEffect
. So currently I need to tell that collection that it will contain something that contains all those 3 components, a group seemed a good way to collate, but if that is only for system level collation, should I be making a new component which contains a property for each of those 3 sub components or is there some other approach we should be taking.
I've done it the reverse way where the Buff would have a Target property that points to the player. Here's my attempt at it with some partial code:
Player Module PlayerSystem BuffTypesSystem BuffEffectsSystem
public partial class BuffEffectsSystem {
protected override void OnBuffDurationCreaetd( BuffDuration data, BuffDuration group ) {
Observable.Timer(TimeSpan.FromSeconds(group.Duration)).Subscribe(_ => {
Destroy(group.gameObject);
}).DisposeWith(group);
}
protected override void OnLosesStrengthOverTimeGroupCreated( LosesStrengthOverTimeGroup data, LosesStrengthOverTimeGroup group ) {
float tickRate = 0.1f;
Observable.Interval(TimeSpan.FromSeconds(tickRate)).Subscribe(_ => {
float tickStrength = group.LosesStrengthOverTime.ReduceStrengthPerSecond * tickRate;
group.BuffStrength.Strength -= tickStrength;
if( group.BuffStrength.Strength <= 0 ) {
Destroy(group.LosesStrengthOverTime.gameObject);
}
}).DisposeWith(group);
}
}
public partial class BuffTypesSystem {
protected override void OnRegenHealthPerSecondTarget( RegenHealthPerSecondTarget data, RegenHealthPerSecondTarget group ) {
Health health = HealthManager.ForEntity(group.BuffTarget.Player.EntityId) as Health;
if( health == null )
return;
float tickRate = 0.1f;
Observable.Interval(TimeSpan.FromSeconds(tickRate)).Subscribe(_ => {
float tickStrength = group.BuffStrength.Strength * tickRate;
health.CurHealth = Mathf.Clamp(health.CurHealth + tickStrength, 0, health.MaxHealth);
}).DisposeWith(group);
}
}
Yeah someone else I discussed with was of the opinion that every buff type should be its own component and should just be applied to the player, however my query with that approach was that you may have your buffs in a database somewhere, so its easier to add new effects into the system, which is why the Buff
object in my original idea was more a data contract for the DB, but that being said the data would ideally contain the duration and periodic frequency if needed.
@mkuhr as you were interested in this any thoughts? I could do with just having a conv in the gitter on this subject if you have time today @Protonz
I second the approach by @Protonz. We've been doing this for a few games, and modularity always wins :)
This does not really help much, assuming you need to give the notion of a buffable object so it is something that contains some a collection of active buffs, [...]
I wouldn't go down that road. Each buff should be an own entity, and thinking more from a database-ish point of view, buffs should know their targets, rather than storing a collection of applied buffs of an entity. You might want to do the latter within system code for caching purposes, but that's way out of scope of this uFrame discussion.
[...] an active buff in this scenario being something which contains the component Buff, HasDuration, HasPeriodicEffect.
I don't even know whether I'd introduce a specific BuffComponent
, either. I'd rather add more versatile components such as a TargetComponent or DurationComponent. You might need some kind of BuffComponent for filtering (grouping) entities, but I can't seem to come up with any property that component would have.
Thus, the solution proposed by @Protonz seems reasonable to me.
However, I don't quite understand your last answer, @grofit - especially that part:
you may have your buffs in a database somewhere, so its easier to add new effects into the system, which is why the Buff object in my original idea was more a data contract for the DB, but that being said the data would ideally contain the duration and periodic frequency if needed.
Can you elaborate on that?
Hey,
Just to confirm this is just a scenario I picked out of the air as it is something I have done before for more data driven games and thought it would be a good scenario to see how to handle it with a more "in game focused" paradigm.
You are right that you would probably want to reference the target when you are actually actioning buff related events, however I would see that being expressed as a group relationship as a Buff just acts on the entity it is held on, so you could express this at a higher level by making a group that only contains { Buffable, HasStats }
etc
The last bit for me is one of the more important bits, so without waffling too much it is often beneficial wherever possible to push as much as is feasibly possible out of the "game" source code. In some games you wont have much data outside of the core game code, however in larger games with lots of members of varying skill sets you would often push certain configuration data outside of the source or game executable. So for example if you were making a fantasy game which had lots of dialogue, weapons with varying traits, spells with various effects and buffs, lots of quests etc you would not want to hard code each aspect of it into your system, you would want to express the data that encapsulates the object you want to use, that way you can let content creators add new spells, buffs, quests, items, weapons etc without having to touch a line of code or add new entities etc.
So if you were to go down the route of having every possible buff as its own entity it is near impossible for content creators to work outside the game and add/edit content quickly. So the point I was trying to push home on the last blurb was that often all your buff/item/quest information etc is stored in an external data source (be it a database, file system etc) so you want to express the required information in some kind of model but let the game use that data to drive behaviour. Also this means that the systems have the intelligence in this scenario not the components, as you should not need a PoisonBuff
component which is just expressing the notion of a poison effect, it could be done far more extensibly as a generic Buff
model which encompasses the data concerns and lets the system filter the buff type to specific handlers at the higher level, that way think of games like Mass Effect and Dragon Age where its 2 similar games from a functional perspective but they are both from same company but have different settings etc. You could share the same buff infrastructure between both projects this way as its just data, your systems would then know how to interpret it and just get on and route it to the right bit.
Also if anything this way is more modular as you are just expressing an interface as such for something else to plug into, if you were to skip this layer and go straight into explicit butf types are component level you would end up losing a lot of flexibility for tooling outside your game as well as locking your buff system into games that have the same notions as you.
Hope that adds some more context to my waffle.
Hey @grofit!
I totally see your point. We have been evangelizing this data-driven approach for many years now, and in every single project, we loved the "turning point", usually about halfway through the project, when the first components could be re-used and the designers finally were able to enjoy the full power of the whole component-based approach.
The point is: At some point in your code, you will actually have to code something. If you've got an Armor
property, then there's gonna be some code that deducts that value from the actual damage dealt. If you've got an Critical Strike Chance
property, there's gonna be some code that uses that value for checking whether you're dealing increased damage.
Also, for all of these properties, there will most likely already be components, such as an ArmorComponent
and a CritChanceComponent
. At that point, these are most likely attached to combat entities such as the player or his or her enemies.
Now, designers are free to create a new Buff entity with an ArmorComponent
, a TargetComponent
and a DurationComponent
, and there could be a game system that applies all buffs, that is, entities with targets, to their targets.
Yes, if you're adding a new property, say Critical Damage
, it needs new code to apply these buffs. The only approach I can imagine to avoid this is running all of there properties as key-value-pairs only, using string
keys for armor and crit chance, and building a generic buff system that updates the property tables according to applied buffs.
However, this might be much more error-prone, and in many cases, it is not entirely clear, how and in which order these buffs apply. Say you've got an item that increases Armor
by 120
(plain), and a buff that increases Armor
by 5%. Now, you definitely need to figure out how and in which order to apply these value changes, and that order might vary depending on the property we are talking about.
That said, if you're making any positive experience with any generic approach, I'd love to hear about it! Please share any solutions you might come up with, will be interesting for everyone else as well :)
You are right with most of the problems and issues with combat systems you describe, and yeah you 100% will need to write some code at some point, but for this scenario that code/concern would be handled in the consuming layer of this proposed module not within the module itself, as it purely handles the logistics of buffs, not the implementation of buffs..
I had a quick go at making a simple implementation of it (for my own sanity) and have got the basics together but alas #7 stops me from progressing currently. Like you say the main drive for this was to make it then share it so everyone else can see how it was done and/or build upon it etc. As since the start of ECS (before it was released) I really liked the idea of having a standard way of creating bitesized focused bits of functionality/behaviour for a game and then sharing it so people can easily in a sane and extensible way get some sort of game infrastructure/middleware for free from the ECS community then just build their custom stuff on top.
Anyway will let you know once I have some form of example to post.