This time I have problem with virtual fields.
I have core class for my game objects. This class contains a field with Model class object. Model's object contains values such as position etc.
Now - while drawing I need to read position of each object from it's model. The problem starts when instead of default model class I'm using derived. Example:
abstract class GenericGameObject { public DefaultGameObjectModel Model = new DefaultGameObjectModel(); }
class Missile : GenericGameObject { public new MissileModel Model = new MissileModel(); }
class DefaultGameObjectModel { public Vector2 Position = new Vector2(){X=0}; }
class MissileModel : DefaultGameObjectModel { }
Missile m = new Missile();
m.Model.Position.X = 10;
// NOT OK! ((GenericGameObject)m).Model.Position.X == 0
I tried to make Model defined as virtual property instead of field, but this fails because
derived properties have to be of same type as their base. Casting is futile because there will be many other model types. What can I do if I want to read a value from derived class, not from base?
I asked this question already but the answer didn't brought any solution. Explaination:
to use interface IGameObjectModel
Concept is good, but I have to enforce fields. Interfaces can't define fields so I have to define property. But then I can't do IGameObjectModel.Position.X=10 because Position is not a field.
to make GenericGameObject a generic type such as GenericGameObject and Missile a type derived from GenericGameObject
I couldn't then cast a missile to GenericGameObject and generally store those object on same list. Of course I could make main base type which those two could inherit from, but then I wouldn't have access to Model field.
to make model a property instead of field.
It is impossible to change property type in derived class.
Whad can I do?
In this case your best approach would be to assign the value of your parent field to be an instance of your derived class, then either cast it back to your derived class or hold on to a reference of your derived class (probably better).
Or you could go down this road, which I like the best...
abstract class GenericGameObject
{
public DefaultGameObjectModel Model
{
get { return ModelInternal; }
}
protected abstract DefaultGameObjectModel ModelInternal { get; }
}
class Missile : GenericGameObject
{
private MissileModel model = new MissileModel();
public override DefaultGameObjectModel ModelInternal
{
get { return model; }
}
public new MissileModel Model
{
get { return model; }
set { model = value; }
}
}
class DefaultGameObjectModel { public Vector2 Position = new Vector2(){X=0}; }
class MissileModel : DefaultGameObjectModel { }
Missile m = new Missile();
m.Model.Position.X = 10;
This solution gives you access to your base model instance from the context of the base class, while giving you access to your concrete model instance from the inherited class.
There's no such thing as 'virtual fields'. Only properties and methods can be virtual.
In your Missle class, you appear to be using the new keyword as a modifier to hide the inherited member named Model.
When you hide an inherited member this way, you don't get polymorphic behavior. This is bad because the code in your base class (if it references the Model field) may not work as you expect.
Best bet: Use a property. Cast or generalize (move members to base class) as necessary.
If you used an interface, I believe you'd still be able to call:
IGameObjectModel.Position.X = 10;
As long as the object type you used for Position has a read/write property called X. Your interface would look something like:
public interface IGameObjectModel
{
Vector2 Position
{
get;
// only add set if you need to set the Position object outside of your class
// set;
}
// ...other properties
}
You said that if you used an interface with a property that you "can't do IGameObjectModel.Position.X=10". I assume this is because Vector2 is a struct and therefore has value-type semantics. If this is correct, you should simply assign the Position property to a new Vector2 calculated from the original value. For example:
Missile m = new Missile();
m.Model.Position = new Vector2()
{
X = m.Model.Position.X + 10,
Y = m.Model.Position.Y
};
Did you try using generics? Using generics you can separate your game object model from your game object. You can then instantiate your game object with any game object model. The game object can communicate with the game object model thru standard interfaces.
interface IGameObjectModel {
void Shoot();
:
}
class GameObject<TModel> where TModel:IGameObjectModel {
public TModel Model;
public GameObject(TModel model) {
Model = model;
}
public void Shoot() {
Model.Shoot();
}
:
}
class MissleModel : IGameObjectModel {
public void Shoot() {
:
}
}
With the above, you can then instantiate your game object with the missle model :-
MissleModel model = new MissleModel();
GameObject<MissleModel> obj =
new GameObject<MissleModel>(model);
Related
I am not sure if this is possible with C#, but is it possible to store information in an attribute that is related to the instance of a class?
So, I have the following class with the field Initialized, as seen here:
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class GameObjectAttribute : Attribute {
internal bool Initialized = false;
}
I then use this to add the attribute to the class:
[GameObject]
public class Player {
}
Now, in this class, is it possible to modify the data in the attribute for each individual instance of the class like in this pseudo code:
internal class Core {
async void Tick() {
while (isRunning) {
foreach (var gameObject in gameObjects) {
// Get attribute information
var refToAttribute = gameObject.... // Somehow get information
if (!refToAttribute.Initialized) {
// Do some stuff
refToAttribute.Initialized = true;
}
}
await Task.Delay(1);
}
}
}
Edit
When coming from a TypeScript world, I can return a new instance of a class when the class is created:
export function GameObject() {
return function (constructor: T) {
return class extends constructor {
initialized = false;
}
}
}
#GameObject
export class Player {
}
so now, in my loop I have access to instance, however Player does not have access.
Now, in this class, is it possible to modify the data in the attribute for each individual instance of the class
No, attributes are attached to the class, not to instances of said class. There is only a single instance of the attribute and that instance is attached to the class Player. You can access the attribute from the player instance, but only by looking at the attributes that are attached to the type. So it is really not anything that you could use to provide additional information for any particular player instance.
What you can do in a statically typed language is to wrap the value. Something like this would achieve what you are trying to do:
public class PlayerGameObject
{
public bool IsInitialized { get; set; }
public Player Player { get; set; }
}
// …
foreach (var gameObject in gameObjects)
{
if (!gameObject.IsInitialized)
{
var player = gameObject.Player;
// do some stuff
gameObject.IsInitialized = true;
}
}
is it possible to store information in an attribute that is related to the instance of a class?
No. Attributes are part of the definition of a class, not instances of it. It's impossible for the same reason that a method can't be public for one instance of a class but private for another instance of the same class.
Information about an instance of a class can only be stored in its fields and properties.
I suggest you use a base class instead.
//Your framework
abstract class GameObject
{
internal bool Initialized { get; set; } = false;
}
//Inside program that uses the framework
class Player : GameObject
{
}
Now the Player class has a property than only your code can access.
//Your framework
void Initialize(GameObject obj)
{
if (!obj.Initialized)
{
//Do something
obj.Initialized = true;
}
}
//Inside program that uses the framework
var player = new Player();
if (player.Initialized) //Compile-time error
So im trying to use compToGet string that have been passed through the parameter into slot.GetComponent().level++;
upgradeFoundation() will be called on button click.
and there is actually quite a lot of buttons with similar functionality (like: upgradeTurret(), upgradeTurret2(), etc)
thats why im trying to change the value of compToget string base on which button you click and use that new string to get component under the name of that new string but it seems it doesn't work that way and I dont know how it would work any other way, any help would be much appreciate.
public void upgradeFoundation()
{
float upgFoundationCost = slotGroup.transform.Find(slotName).gameObject.GetComponent<Slot>().upgFoundationCost;
Upgrade(upgFoundationCost, "Foundation");
}
public void Upgrade(float upgCost, string compToGet)
{
GameObject slot = slotGroup.transform.Find(slotName).gameObject;
if (inGameUIManagerScript.money >= upgCost)
{
Type compToGetType = Type.GetType(compToGet); //im not sure how to convert a string into a type
slot.GetComponent<compToGetType>().level++; //this is the error line saying im treating a var like a type
}
}
Thank you in advance.
Exactly the same issue as in your previous question => You can not use the generic! Instead use GetComponent(compToGetType);
However I removed the duplicate since you still would need to cast to your actual type which is anything but trivial!
=> Again I can only recommend: Don't use strings!
Rather have a common Base class or interface like e.g.
public abstract class BaseComponent : MonoBehaviour
{
private int level;
// public read-only access
public int Level => level;
public virtual void Upgrade()
{
level++;
}
// Other properties and methods all your components have in common
// Also get into "virtual" and "abstract" members!
}
and inherit your stuff from it like
public class Foundation : BaseComponent
{
// Additional stuff specific to the foundation
// overrides for the virtual and abstract members
}
public class Turret : BaseComponent
{
// Additional stuff specific to the turret
// overrides for the virtual and abstract members
}
//Maybe this would even inherit from Turret instead?
public class Turret2 : BaseComponent
{
// Additional stuff specific to the turret2
// overrides for the virtual and abstract members
}
and finally use that common base instead:
public void UpgradeComponent()
{
slot.GetComponent<BaseComponent>().Upgrade();
}
When you create new class and mark it as [System.Serializable] your inspector will create and show its default object for property of new class' type in your MonoBehaviour component.
When creating custom PropertyDrawer though you need to create this default object on your own and put its reference into SerializedProperty.objectReferenceValue (as far as I understand).
But this field is of type UnityEngine.Object and my new class cant be assigned there. How to overcome it? Inheriting your class from UnityEngine.Object doesnt help as SerializedProperty.objectReferenceValue is still null, even after assigning in there the newly created object (which is actually of the same type – UnityEngine.Object).
I hope I understood your question correctly, taken from the Unity documentation:
using UnityEngine;
public enum IngredientUnit { Spoon, Cup, Bowl, Piece }
// Custom serializable class
[Serializable]
public class Ingredient
{
public string name;
public int amount = 1;
public IngredientUnit unit;
}
public class Recipe : MonoBehaviour
{
public Ingredient potionResult;
public Ingredient[] potionIngredients;
}
[CustomPropertyDrawer(typeof(Ingredient))]
public class IngredientDrawerUIE : PropertyDrawer
{
public override VisualElement CreatePropertyGUI(SerializedProperty property)
{
// Create property container element.
var container = new VisualElement();
// Create property fields.
var amountField = new PropertyField(property.FindPropertyRelative("amount"));
var unitField = new PropertyField(property.FindPropertyRelative("unit"));
var nameField = new PropertyField(property.FindPropertyRelative("name"), "Fancy Name");
// Add fields to the container.
container.Add(amountField);
container.Add(unitField);
container.Add(nameField);
return container;
}
}
So when you view a GameObject with the Recipe component on it, Unity's inspector will show something like this:
So you do not need to inherit from anything, simply mark the class you want to create a property drawer as Serializable, and create a property drawer class for it (Make sure to place it in the Editor folder, or create a assembly definition file which targets the editor only if you are working with assembly definition files).
I am making a chess game and I have an abstract class named cls_ChessPieces and a class for every chess piece (cls_Pawn, cls_Knight, ...).
class cls_ChessPieces
{
}
class cls_Pawn : cls_ChessPieces
{
public bool CheckLegalMove()
{
// this is the method I want to call
}
}
I made this abstract class because I want to store every chess piece in a dictionary with the location on the chess board as Key (string).
private Dictionary<string, cls_ChessPieces> _dicPieces;
_dicPieces.Add((Column + Row), _pawn);
After making a move I want to know the type of the piece that has moved.
_chessLogic.DicPieces.TryGetValue(key, out piece1);
When I put a breakpoint and inspect the piece1 object, I can see which type it has (cls_Pawn). But this type is only given to that object at runtime. This makes it impossible to call the CheckLegalMove() method.
This is the way I solve this issue, but I don't think this is the proper way of doing it. It takes a lot of code do to this for every chess piece class and every time that I want to pass the object I need to do this again.
if (piece1 is cls_Pawn)
{
cls_Pawn pawn= (cls_Pawn)piece1;
pawn.CheckLegalMove();
}
Is there an easy way to get the cls_pawn class out of the dictionary an call the CheckLegalMove() method? I tried to get the type :
Type t = piece1.GetType();
But I don't know if it is possible to cast with this type
(t)piece1;
Thanks!
Edit:
CheckLegalMove() is maybe not a good example
public bool Promote()
{
// this is a method only this child class should have
}
I guess every type of chess item needs to check moves, so declare method in base class:
public abstract class cls_ChessPieces
{
public abstract bool CheckLegalMove();
}
class cls_Pawn : cls_ChessPieces
{
public override bool CheckLegalMove()
{
// this is the method I want to call
}
}
you will be able to call CheckLegalMove() for every class, derived from cls_Pawn:
bool ok = piece1.CheckLegalMove();
For this answer I suppose you don't want to have an abstract CheckLegalMove method in the cls_ChessPieces base class. If this is not a problem don't choose this approach.
Approach 1 (not recommended)
You could get the type of the object with
var type = piece1.GetType();
and then inspect per reflection if the type defines the method CheckLegalMove() by doing
var type = piece1.GetType();
var checkLegalMoveMethod = type.GetMethod("CheckLegalMove");
if(checkLegalMoveMethod != null)
{
var result = (bool)checkLegalMoveMethod.Invoke(piece1);
}
This works but only as long as your CheckLegalMove method doesn't contain any parameters.
Approach 2 (the recommended way)
One thing you could also do is introduce another base class (again only do this if you don't want to have a CheckLegalMove method directly inside cls_ChessPieces) <SubBaseClassName> which is defined as the following:
class <SubBaseClassName> : cls_ChessPieces
{
public abstract bool CheckLegalMove();
}
The your cls_Pawn would inherit from <SubBaseClassName> and you could just check if piece1 is of type <SubBaseClassName> and if it is call the CheckLegalMove method on it.
I have an abstract base class and two derived classes. The base class contains 6 properties which all can be maintained on a form.
The two derived classed both have 1 extra property. Those two properties can also be maintained on the same form.
In my form I have now code like this:
btnSomething.visible = (myObject is DerivedA);
pnlPanel.visible = !(myObject is DerivedA);
if(myObject is DerivedA)
myBindingSource.DataSource = myObject as DerivedA
mySecondBindingSource = myObject;
I am not very happy with this approach, it smells. So my question is, what is a neat/good way to make this more OO? Because it is possibly that in the future DerivedC comes in...
I think this approach breaks the OCP principle (and probably other principles)
You can use polymorphism and inheritance here:
Define an interface
interface ICommonFeatures
{
bool ContainsFoo {get;}
//yak-yak
}
Then your derived classes implement it
class DerivedA: ICommonFeatures
{
bool ContainsFoo {get {return true;}}
//so-and-so
}
class DerivedB: ICommonFeatures
{
bool ContainsFoo {get {return false;}}
//this-and-that
}
And when you use it, you deal only with the interface
ICommonFeatures foo = new DerivedB();
btnSomething.visible = foo.ContainsFoo;
pnlPanel.visible = foo.Prop2;
myBindingSource.DataSource = foo.CurrentDataSource
A crazy idea would be make the UI extensible.
You could make a form implement a base form.
Then in the derived form class you would only insert the missing controls and behavior for the its model class.
In the derived model class or library you could have some sort binding to the correct form.
A good approach for this would be follow some MVP principles.
Hope it helps you somehow..
I would declare an abstract boolean method/property for each control that need to behave according to the underlying type.
For instance
// to handle pnlPanel.visible = !(myObject is DerivedA);
abstract bool SupportsPanel{get;}
As for your binding sources, I would also provide some virtual BindingSource and SecondBindingSource properties.
Maybe something like (purely an example)
public abstract class BaseClass
{
// All your exising class declaration here
public virtual object BindingSource
{
get
{
// By default, a BaseClass is not valid as a binding source
return null;
}
}
public virtual object SecondBindingSource
{
get
{
// By default, a BaseClass is a valid Second Binding source
return this;
}
}
}
public class DerivedA : BaseClass
{
// All your exising class implementation here
public override object BindingSource
{
get
{
// For DerivedA, the data sourse is itself.
// other classes might have their own implementations.
return this;
}
}
// No need to override SecondBindingSource as the BaseClass one works as expected.
}
So, your code could stop caring about the object type and look like:
myBindingSource.DataSource = myObject.BindingSource;
mySecondBindingSource = myObject.SecondBindingSource;
Hope this helps.