I am trying to create a rpg and I used generics for the data type. Here is the code about the question.
public abstract class Entity<E> : MonoBehaviour where E : EntityData
{
public E entityData;
public virtual void SpawnEntity()
{
entityData.CreateMutableData.Invoke();
}
public void CreateData(ImmutableEntityData data)
{
entityData.Create(data);
}
}
public class Slime<E> : Entity<SlimeData>
{
private void Awake()
{
SpawnEntity();
entityData.jumpSpeed = 5;
}
}
public class Player : Entity<PlayerData>
{
private void Awake()
{
SpawnEntity();
}
}
As you can see, I use the generics to not type cast while trying to get value from the data. For example, slime data has jump speed field and I can get the value without doing (entityData as SlimeData).jumpSpeed. Is it a good approach or do you have any suggestion about that?
I think you made a typo in your code box #2 with Slime<E> you probably meant just Slime. If not, well, thatâs not a smart thing to do, mixing generics with concrete types.
Other than that, generics are great, but Iâm not sure in the context of what youâre doing. Typically you want them for generic stuff, pun not intended. High-level infrastructure doesnât count as generic stuff, itâs highly domain-specific, even if you do believe youâre making things more flexible.
If you really want flexibility, working with interfaces is the way to go. You can even make them blank, so they really work like tags basically. Though Iâm not necessarily recommending blank interfaces.
Yeah, I forgot to delete the E on the Slime, it wasnât at there normally, i was trying something at that moment. By the way, can you help me that how can I implement interfaces? For example I have these codes,
public class HealthController
{
[SerializeField]
private int currentHealth;
public int maxHealth;
public UnityEvent doAfterZeroHealth;
public HealthController(int maxHealth)
{
this.maxHealth = maxHealth;
currentHealth = maxHealth;
}
public int Health { get { return currentHealth; } private
set
{
if(currentHealth == 0)
{
doAfterZeroHealth.Invoke();
}
}
}
public void ChangeHealth(int changeAmount)
{
if(currentHealth < maxHealth)
{
if (changeAmount > 0) IncreaseHealth(changeAmount);
else DecreaseHealth(changeAmount);
}
}
private void IncreaseHealth(int inceraseAmount)
{
if (currentHealth + inceraseAmount < maxHealth)
{
currentHealth += inceraseAmount;
}
else
currentHealth = maxHealth;
}
private void DecreaseHealth(int decreaseAmount)
{
if (currentHealth - decreaseAmount > 0)
{
currentHealth -= decreaseAmount;
}
else
{
currentHealth = 0;
}
}
}
public class PlayerData : EntityData
{
public HealthController healthController;
public override void Create(ImmutableEntityData data)
{
ImmutablePlayerData playerData = data as ImmutablePlayerData;
healthController = new HealthController(playerData.baseMaxHealth);
}
}
public class Player : Entity<PlayerData>
{
private void Awake()
{
SpawnEntity();
}
}
Now, if I create an interface called âIDamagableâ, which class should I implement to? I think it is health controller but if i do that, i wouldnât use interfaces for tag like âobject is IDamagableâ since the interface is on the health controller. Note that, I will use composition over inheritence, inheritence is only for the common things that all object will have.
Inheritance commonly makes sense only if you go one step deep at most, for some systemic work. Rarely you might encounter two steps. Anything beyond that is looking for trouble in Unity.
(Obviously this is a generalization. Iâm not saying it doesnât exist, but it can be avoided.)
Anything that is damagable. Thatâs the basic principle, thatâs why we use adjectives for the interface names.
In fact, that HealthController is not IDamagable. Can you damage a health controller? Nope.
Then I will implement that to the mono behaivour one, it seems more make sense, I asked that because I have data classes for every mono behaivour class. Then should data classes have methods or methods should be in the mono behaivour classes? I am sorry that i asked a lot question but i try to understand.
What are data classes? Do you mean ScriptableObjects? If you donât, you ought to.
ScriptableObjects are data-only MonoBehaviour-likes, that serialize just the same, but do not live on the loop, and can be sprinkled with some code.
If youâre coming from vanilla OOP, Unity is slightly skewed in every sense, but thatâs actually not a bad thing.
I use SO for the base data which is immutable and create a mutable data that are what I mentioned above by saying data from them. Mutable data are stored in plain class and i am attaching it to the mono behaivour one. What i asked above is should I keep the methods on the mutable data class or mono-b class or also base immutable data?
Iâm guessing by this you mean youâre feeding the relevant data from SOs to your MBs. Itâs ok to have some means of mutable data sets for the runtime. And the methods should go wherever they belong.
If the methods are controlling the MBâs instance data, they belong to that MB. If you have some data-specific methods, keep them with data. Other than that Iâm not sure if I understand the question.
I mean if youâre asking that in the sense of some general architecture, that doesnât exist.
Architectures can differ a lot. It largely depends on the type of your game and the way youâve designed your core and surrounding systems. A turn-based game will be developed differently than FPS. The differences can be huge actually, from where the data is located, to how centralized the code is, what managers will govern the state, and also how you intend to cross-message across domains.
A small project can have nearly every top-level behavior in a single class. Then you distribute specific and repeating behaviors in a spine model.
A huge game with many concerns is probably using a multi-layered infrastructure and has dedicated departments for the UI, gameplay, savegame, animation, rendering, pathfinding, scene management, event systems, console logging, web request, who knows what else. All of this needs to talk to some central management, so to organize all of this, the concerns of the actual gameplay state and model become tiny in comparison.
So I believe the best answer is to simply go after whatever system you find the most friendly to work with. On one hand you want everything accessible, just there, ready to be used, on the other, you want to avoid exactly that, because that leads to a swamp, and good amount of isolation and modular and independent solutions will take you very far, because it is inevitable you will refactor or insert many things during the development.
Only from experience youâll be able to find the sweet spot. And itâs never perfect. Donât waste too much time early on, because the real achievement is to get past the point of no return, somewhere in-between the early- and the early-to-mid stages of development, that part is always messy as youâre consolidating standards and still learning what the hell it is that youâre making.
Understand it thanks, i created an IDamagable interface and attached to the player hitbox MB
public class PlayerHitbox : MonoBehaviour, IDamagable
{
private Player player;
private void Awake()
{
player = GetComponentInParent<Player>();
}
public void TakeDamage(int damage)
{
player.entityData.healthController.ChangeHealth(-damage);
}
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.CompareTag("trash"))
TakeDamage(5);
}
}
attached this class to the child of the player object, health controller is the health component of the player data as it is obvious, and created IMovable interface and attached them to the player object itself
public class Player : Entity<PlayerData>, IMovable
{
public void Move(float speed)
{
}
private void Awake()
{
SpawnEntity();
}
private void Update()
{
print(entityData.healthController.Health);
}
}
As you said, architecture can change but i am trying to understand the composition and interface logic. Thanks for your helping, it is more clear now.
If you donât mind, I have 2 questions.
1- Letâs say I have 2 interface which are IFlyable and IKnocbackable and Slime class. Some of slimes can be knocbacklable and flyable, some of them are knockbackable and canât fly and some of them are canât knocbackable and flyable, some of them are canât fly and knockbackable. Should I create 4 slime class for different situations?
2- Player canât fly at the beginning and after wearing an item, it gains fly ability, should i implement IFlyable to the player since it will gain flying ability if wears that item. Thanks in advance.
Thatâs a funny case and the answer is no. That would compromise the whole point.
Obviously if you have some slimes that are knocbackable and flyable, then the slimes are knocbackable and flyable.
Itâs some instances of these slimes that seem to have forgotten how to do it.
So make sure you can inform such instances appropriately.
You can introduce âspeciesâ or âtemplatesâ where some traits get inhibited automatically.
Inhibitors may also be separate components, see answer below.
No, thatâs the case for a composition. You add a Flyer component to the player, which itself is IFlyable.
Make sure you discover what is doable by composition alone. Components arenât there for nothing.
Hm, then should I add the components as a list instead of by one by? I have an idea about that:
public class SlimeData : EntityData
{
// public HealthController healthController;
// public FlyController flyController;
public List<BaseComponent> components;
public int jumpSpeed;
public override void Create(ImmutableEntityData data)
{
components.Add(new HealthController(123));
components.Add(new FlyController());
/* ImmutableBlueSlimeData slimeData = data as ImmutableBlueSlimeData;
healthController = new HealthController(slimeData.baseMaxHealth);
flyController = new FlyController();*/
}
}
If I donât have to create classes for every situation, I guess i need list of components to do that. If I create a slime that canât be knockbackable but can fly, then i will just do âcomponents.Add(flycomponent)â. I was confused that you said that you shouldnât add IDamagable to the health component, but if it has a health component, it should be damagable, isnt it? it confuses me some. Then shouldnt I add the IDamagable to the health component like you said that IFlyable should be added to the fly component? If my questions are weird, sorry. I am just trying to learn.
Well it certainly can sound confusing when youâre overthinking it like that.
First of all, these are all just useful mnemonics, there is no universal ontology or absolute semantics in oop that can guide every single design people can think of.
In my mind, no, health receiver can receive and process damage, but it itself is not damagable. What is damagable is what logically should be damagable â a character or object in your game. Health component doesnât sound like it lives inside your game world, it is quite apparently an artificial construct, an implementation detail, an emergent artifact from how you designed this system. Donât get lost in such tangents.
Decide the domain in which youâre inventing categories for yourself, you canât just cross between different domains and expect the categories to align naturally. Also talking about this extensively doesnât lead you anywhere.
Instead go make a system, find out why itâs bad. Literally every system is bad somehow, but the more experience you have, the more easier it becomes to locate where and how exactly it hurts your progress, limits your options, whatever.
Try to keep your ultimate goal in front of you. Is it to find the meaning of life? Probably not. It is so that some player can smack a goblin with a +2 sword of furies? Much more manageable.
Yeah, as you said, I am overthinking I was using inheritence over composition and very rarely using interfaces, thatâs why i am struggling in that situation. Thanks for your suggestions, actually i did some player enemy entity system in unity before but it was bad because i used inheritence.
Note that the particular form of struggling you are doing is truly just overthinking it.
All of this stuff can be done any number of possible ways, all of them valid in some context.
You need to go DO it and by doing it you will begin to learn the pros and cons of each way.
While you may THINK you are learning by asking 57 questions here, you actually arenât learning.
Yes, weâre answering your questions, but the deeper into the interface / composition / inheritance weeds we get, the less you are actually retaining, and the less this will actually help you make a game.
Learning is the modification of behavior as the result of experience, not as the result of just reading words.
Go get that experience. Come back often. Check in. Show what youâre doing and what your pain points are. âI did it this way but now itâs painful when I add a new Slime that also can be poisonedâŚâ etc. But DO the work first because often what you contemplate as âthis will be painfulâ will actually be quite easy.