I have an EnemyData ScriptableObject that holds data about enemies. I'd like to have a field on EnemyData that references some logic about how this enemy behaves on its turn (in turn-based card game).
My current attempt at this is to structure that as a ScriptableObject too, basically like this:
public class EnemyData : ScriptableObject
{
public int health;
public EnemyAIBase enemyAI;
}
public abstract class EnemyAIBase : ScriptableObject
{
public abstract void PlayTurn(Enemy thisEnemy);
}
public class PirateShipAI : EnemyAIBase
{
public override void PlayTurn(Enemy thisEnemy)
{
thisEnemy.Heal();
AttackPlayer();
}
}
So as an example, I've got a "PirateShip" asset of type EnemyData, whose enemyAI field points to a "PirateShipAI" asset of type PirateShipAI.
But this feels wrong, every time I code up a new enemy's AI I have to also instantiate an asset just so it can be referenced by an EnemyData. I feel like EnemyAIBase shouldn't even be an SO, it's not like it has any variables that different assets will override. There will be a 1-to-1 mapping between EnemyData assets and custom AI for that enemy. So this SO is just a container for some logic, which doesn't feel right. But I don't know any other way to reference that logic in my EnemyData SO.
I guess I wish an SO could reference a C# class directly, but I don't think this is possible.
One option is that I could build an Editor that hides the EnemyAI asset as a sub-asset of the EnemyData asset, kinda like I did over here: Building an Editor for nested ScriptableObjects to compose abilities in a card game
But that feels really wrong here, because I don't intend to make any of this AI generic.
How can I attach behavior/logic to a ScriptableObject?
You can indeed simply make it not a ScriptableObject. To define different behaviors I would use a generic here:
public abstract class EnemyData<T> : ScriptableObject where T : EnemyAIBase
{
public int health;
public T enemyAI;
}
public abstract class EnemyAIBase
{
public abstract void PlayTurn(Enemy thisEnemy);
}
And then from these create your actual implementations
[CreateAssetMenu]
public class PirateShip : EnemyData<PirateShipAI>{ }
public class PirateShipAI : EnemyAIBase
{
public override void PlayTurn(Enemy thisEnemy)
{
thisEnemy.Heal();
AttackPlayer();
}
}
Related
TLDR: How can I have a script that inherits from a public abstract class have access to an often changing Enemy gameObject variable (so it can't be static) without passing it through several other scripts first?
In my game, I have a battle system where a different "Battle Event" gets loaded for each battle. Each "Battle Event" gets its own script, and each of those events inherits from the same BattleEvent parent (which is public abstract).
The code structure basically goes:
BattleSystem (main brain of battles which holds the Enemy
gameObject) ->
BattleEventsManager (handles both which BattleEvent to load, and which methods to run on that BattleEvent) ->
a random BattleEvent (BattleEventOne or BattleEventTwo etc)
public class BattleSystem : MonoBehaviour
{
BattleEventsManager battleEventsManager;
public Enemy currentEnemy;
// the Enemy data is passed when the battle starts
public void Start(Enemy enemyToLoad)
{
battleEventsManager = GetComponent<BattleEventsManager>();
currentEnemy = enemyToLoad;
}
public void BeginPlayerTurn()
{
battleEventsManager.SetupEvent(currentEnemy);
}
}
public class BattleEventsManager : MonoBehaviour
{
BattleEvent currentBattleEvent;
private void Awake()
{
// define this battleEvent
currentBattleEvent = GetComponent<BattleEventOne>();
}
public void SetupEvent(Enemy currentEnemy)
{
// start the battleEvent with its Setup function
currentBattleEvent.Setup(currentEnemy);
}
}
// inherits from `BattleEvent` parent class, shown below
public class BattleEventOne : BattleEvent
{
// override the method from the parent
public override void Setup(Enemy currentEnemy) {
// we can now use the data we need in `currentEnemy`
// all I wanted was to get access to `BattleSystem.currentEnemy`
// but i had to pass it down all the way here. Is there a better way?
}
}
// parent of all `BattleEvents`
public abstract class BattleEvent : MonoBehaviour
{
public abstract void Setup(Enemy currentEnemy);
} // end BattleEvent class
As you can see, the the currentEnemy variable needs to be passed down through 2 classes in order to get to where it needs to be: BattleEventOne.Setup().
Furthermore, I needed to add the Enemy currentEnemy param to the parent BattleEvent, which is problematic because not all BattleEvents will need this information.
I originally wanted to just call BattleSystem.currentEnemy from BattleEventOne (using a property or something), but because the BattleSystem is abstract/static, it can't access it. And because currentEnemy contains new data each battle, I can't make that a static variable.
So, how can I have BattleEventOne here access BattleSystem.currentEnemy without having to pass it down as I've done above?
(I still struggle a lot with passing information between scripts, so any help here is really appreciated!)
So I'm experimenting with DI and am trying to create a GameObject Generator.
That GameObject Generator generates GameObjects inside the scene based on some internal logic.
There is variation to what kinds of GameObjects are generated and the logic can vary.
I thought that I could create a Interface and be able to create a class per "unique logic" (i.e. for every generator behaviour i create a class and can switch between generating a lot of small objects and a couple of big objects without having to use if statements, but instead the power of polymorphism).
So I've got something like
GameObjectGenerator : Monobehaviour
IGeneratorType
SmallGenerator : Monobehaviour, IGeneratorType
BigGenerator : Monobehaviour, IGeneratorType
from a logical standpoint this seems to be making sense.
The issue arrises, when transitioning from those generators.
I want to have some sort of condition, where i call a method "TransitionGenerator" from IGeneratorType
that returns a new IGeneratorType. Logically this is working aswell.
However, i want to keep track of my generated Objects (in a list for instance), because they need to be Destroyed later.
When transitioning, the List of generated Objects need to be passed to the new IGeneratorType.
This is where I find myself struggling.
The classes that implement from IGeneratorType need to extend Monobehaviour aswell because i need to make calls to Instantiate and Destroy.
But because they extend from Monobehaviour, I can't seem to create a constructor.
After a bit of research i found a lot of people pointing to either Awake/Start or to creating a Init method.
The problem is, with Awake/Start i cant pass anything and with Init, i would need to put that into the interface aswell, which doesnt make a lot of to me sense from a design standpoint.
Example code:
public class GameObjectGenerator : Monobehaviour{
private IGeneratorType generator;
public void Start(){
generator = new SmallGenerator();
}
public void Update(){
generator.Generate();
if(somecondition){
generator = generator.Transition();
}
}
}
public interface IGeneratorType{
void Generate();
IGeneratorType Transition();
}
public class SmallGenerator : Monobehaviour, IGeneratorType{
private List<GameObject> generatedObjects;
public SmallGenerator(/*List<GameObject> previousObjects*/){
//generatedObjects = previousObjects;
}
public void Generate(){
//...
if(somespecificcond){
generatedObjects.Add(Instantiate(...));
}
if(somecondition){
Destroy(generatedObjects[0])
}
}
public IGeneratorType Transition(){
return new BigGenerator(/*generatedObjects*/);
}
}
public class BigGenerator : Monobehaviour, IGeneratorType{
private List<GameObject> generatedObjects;
public BigGenerator(/*List<GameObject> previousObjects*/){
//generatedObjects = previousObjects;
}
public void Generate(){
//...
if(somespecificothercond){
generatedObjects.Add(Instantiate(...));
}
if(somecondition){
Destroy(generatedObjects[0])
}
}
public IGeneratorType Transition(){
return new SmallGenerator(/*generatedObjects*/);
}
}
I just found the simplest workaround to this specific case:
public class BigGenerator : IGeneratorType{
private List<GameObject> generatedObjects;
public BigGenerator(/*List<GameObject> previousObjects*/){
//generatedObjects = previousObjects;
}
public void Generate(){
//...
if(somespecificothercond){
generatedObjects.Add(Object.Instantiate(...));
}
if(somecondition){
Object.Destroy(generatedObjects[0])
}
}
public IGeneratorType Transition(){
return new SmallGenerator(/*generatedObjects*/);
}
}
This works because Instantiate and Destroy are static methods from "Object", of which "GameObject" inherits.
However this doesn't solve the problem in case one really HAS TO inherit from monobehaviour
I want to create a class named Enemy, which should be used in a programmed rpg-themed-battlesystem. The problem is that I would want to create multiple monster types in the Enemy class, but then I would have to create a possibility for the battlesystem with every enemy class for example Enemy.Goblin or Enemy.Golem.
Question:
How could I achieve this by using only one parameter in the battlesystem function? I wanted to use
public static void InitiateBattle ( Player player, Enemy enemy )
but now I cannot use the Enemy.Goblin instance, because it cant implicitly convert Enemy.Goblin to Enemy. How could I most easily and with minimal code fix this?
You need to use inheritance.
public class Enemy
{
// put all properties and methods common to all here
}
public class Goblin: Enemy
{
// goblin specific stuff here
}
you will then be able to pass in a goblin as an enemy.
It sounds like you want to use inheritance?
public class Enemy {}
public class Goblin : Enemy {}
public class Golem : Enemy {}
You can then pass in an instance of Goblin or Golem to your method and the statement will be valid because the compiler will 'box' your object into an instance of the parent type.
Then, if you want to use a member from the Goblin or Golem subclasses, you would need to 'cast' the enemy parameter variable back into the appropriate type using as:
public static void InitiateBattle (Player player, Enemy enemy)
{
var golem = enemy as Golem;
var goblin = enemy as Goblin;
}
Make sure you check for null after the cast!
Bear in mind that C# does not allow multiple-inheritance; each class can inherit from only one parent.
I think it would be best to use interface.
public interface IEnemy
{
//e.g.
public void Attack();
}
public class Goblin : IEnemy
{
public void Attack()
{
throw new System.NotImplementedException();
}
}
public class Battle
{
public static void InitiateBattle(Player player, IEnemy enemy);
}
I'm working on a game for WP7 with XNA. Here is my structure:
public abstract class enemy
{}
Child elements:
public class genericEnemy : enemy{}
...
public class snake : enemy {}
etc...
In WP7, a lot of things have been moved around and/or removed (especially with Serialization) it seems. Despite much searching, I haven't been able to find a solution. I'm trying to duplicate the child elements.
For example: On loading a level, I pass an array of three different enemies into the loading phase. During loading, I need to duplicate each of those enemies so that 20 of each are flying around doing their own thing during gameplay.
All the solutions I've seen refer to things that are not present in the WP7 library.
There's no "library" way of doing this as far as I know. One solution would be:
1) Declare a Clone() method in enemy that returns a copy of that enemy.
abstract class Enemy {
public abstract Enemy Clone();
}
2) Implement it in every concrete type, so a Snake creates a new Snake, etc. Example:
class Snake : Enemy {
int speed;
public override void Enemy Clone() {
var clone = new Snake();
clone.speed = speed;
return clone;
}
}
3) Now any object of a concrete type knows how to clone itself, so if you have an array of Enemies, you can call Clone() on each and it will create the proper concrete type in the proper way.
Create an enemy factory that can create enemies from an id of some sorts. While loading your level, you can then call the factory when you need to create an enemy:
class EnemyFactory
{
Enemy CreateEnemy(int id)
{
if (id == 0)
return new Snake();
return new GenericEnemy();
}
}
void LoadLevel()
{
// bla bla
Level level = new Level();
int enemyId = LoadFromFile();
level.AddEnemy(EnemyFactory.CreateEnemy(enemyId));
}
This way you get rid of the nasty cloning code, and you can control all enemy instantiation in the factory class.
use an abstract method that calls a copy constructor:
public abstract class Enemy
{
private readonly int mEnemyData;
protected Enemy(Enemy pEnemy)
{
mEnemyData = pEnemy.mEnemyData;
}
public abstract Enemy Clone();
}
public sealed class GenericEnemy : Enemy
{
private readonly double mGenericEnemyData;
private GenericEnemy(GenericEnemy pGenericEnemy)
: base(pGenericEnemy)
{
mGenericEnemyData = pGenericEnemy.mGenericEnemyData;
}
public override Enemy Clone()
{
return new GenericEnemy(this);
}
}
If I have a class that is based off another class, how do I access the properties of the first class if it can have any name? I was thinking of using generics to access the properties, but the generics are "generic" for a reason...
For example:
public class AGameInXNA : Microsoft.Xna.Framework.Game
{
int ExampleGameProperty;
}
// ... another class ... //
public class ReferenceToAGameInXNA
{
Game gameInstance;
public void SetGameInstance(Game game)
{
gameInstance = game;
}
public void SetExampleGameProperty()
{
gameInstance.ExampleGameProperty = 21; // I don't know the name of
// AGameInXNA, so I want to
// access it using a generic
// class.
}
}
I know that that does not work, so how would I use generics in this case to access the AGameInXNA's properties in another class if I don't know AGameInXNA's name?
EDIT: I am trying to make it so that I can reuse this code later on. I want to be able to have a class that is unknown, such as public class unknownclassname that extends another class, such as Microsoft.Xna.Framework.Game, and be able to access the class unknownclassname without directly calling/implementing it in the library code.
I would recommend looking into XNA Services.
So for example, you would create a service which could be as simple as an
interface IExamplePropertyService
{
int ExampleProperty { get; set; }
}
public class AGameInXNA : Microsoft.Xna.Framework.Game, IExamplePropertyService
{
int ExampleGameProperty { get; set; }
void Initialize()
{
// Do other initialization
Services.Add( typeof(IExamplePropertyService), this );
}
}
public class ReferenceToAGameInXNA
{
IExamplePropertyService propertyService;
public void GetGameInstance(Game game)
{
propertyService = (IExamplePropertyService)game.GetService( typeof(IExamplePropertyService) );
}
public void SetExampleGameProperty()
{
propertyService.ExampleGameProperty = 21;
}
}
Implement it, and register it with the Game component, then in your ReferenceToAGameInXNA, you would query for this service and store it (rather than the Game) for use later.
As a bonus benefit, The IExamplePropertyService no longer even needs to be implemented by the Game class, it could be implemented by any GameComponent.
This makes for an easy way to seperate classes from having to know about the inner workings of other classes in the Game. So long as the services exist somewhere, your ReferenceToAGameInXNA can be used.
I don't think generics are what you are actually looking for here. In your second class, just change the type of all of the gameInstance to the type of the class you created for your game, in this case AGameInXNA. There should only be a need for one subclass of the Game type in each XNA game. That will allow you to access any public members of AGameInXNA from the Reference class.
If this isn't what you are after, please give a more detailed explanation of what you are trying to accomplish and I'll try to help you.
I don't know XNA, but if you want to have several classes that inherit from Game and have the same property on all of them, you could create an abstract class that inherits from Game and let the other classes inherit from that instead.
(Also, your GetGameInstance() is badly named, because it sets the field, it doesn't get it. And it's probably better as property anyway.)
public abstract class GameBase : Microsoft.Xna.Framework.Game
{
public int ExampleGameProperty { get; set; }
}
public class AGameInXNA : GameBase
{
// code specific to AGameInXNA
}
public class ReferenceToAGameInXNA
{
public GameBase GameInstance { get; set; }
public void SetExampleGameProperty()
{
GameInstance.ExampleGameProperty = 21;
}
}
If the other classed that have ExampleGameProperty shouldn't inherit from Game, you could create an interface instead. AGameInXNA would then inherit from Game directly and it would also implement the interface. And you would work with that interface in ReferenceToAGameInXNA.
using "Game gameInstance;" you can not acess ExmpleProp. You should use "AGameInXNA gameInstance;" too access ExampleProp.