2010-04-08 3 views
2

У меня возникли трудности с архитектурным решением для моей игры C# XNA.C#: Наследование, переопределение и скрытие

Основная сущность в мире, такая как дерево, зомби или игрок, представлена ​​как объект GameObject. Каждый объект GameObject состоит по меньшей мере из GameObjectController, GameObjectModel и GameObjectView.

Этих трех достаточно для простых сущностей, таких как неодушевленные деревья или камни. Однако, поскольку я стараюсь сохранить функциональность как можно более уместную, наследование начинает чувствовать себя громоздким. Синтаксически я даже не уверен, как лучше всего выполнить мои цели.

Вот GameObjectController:

public class GameObjectController 
{ 
    protected GameObjectModel model; 

    protected GameObjectView view; 

    public GameObjectController(GameObjectManager gameObjectManager) 
    { 
     this.gameObjectManager = gameObjectManager; 
     model = new GameObjectModel(this); 
     view = new GameObjectView(this); 
    } 

    public GameObjectManager GameObjectManager 
    { 
     get 
     { 
      return gameObjectManager; 
     } 
    } 

    public virtual GameObjectView View 
    { 
     get 
     { 
      return view; 
     } 
    } 

    public virtual GameObjectModel Model 
    { 
     get 
     { 
      return model; 
     } 
    } 

    public virtual void Update(long tick) 
    { 
    } 
} 

Я хочу, чтобы указать, что каждый подкласс GameObjectController будет доступен по крайней мере GameObjectView и GameObjectModel. Если подклассы отлично используют эти классы, но, возможно, переопределяют более сложный метод Update(), я не хочу, чтобы они дублировали код для создания этих зависимостей. Таким образом, конструктор GameObjectController устанавливает эти объекты вверх.

Однако некоторые объекты действительно хотят переопределить модель и просмотреть. Это где приходит беда в

Некоторые объекты должны бороться, чтобы они CombatantGameObjects:.

public class CombatantGameObject : GameObjectController 
{ 
    protected new readonly CombatantGameModel model; 
    public new virtual CombatantGameModel Model 
    { 
     get { return model; } 
    } 

    protected readonly CombatEngine combatEngine; 

    public CombatantGameObject(GameObjectManager gameObjectManager, CombatEngine combatEngine) 
     : base(gameObjectManager) 
    { 
     model = new CombatantGameModel(this); 
     this.combatEngine = combatEngine; 
    } 

    public override void Update(long tick) 
    { 
     if (model.Health <= 0) 
     { 
      gameObjectManager.RemoveFromWorld(this); 
     } 
     base.Update(tick); 
    } 
} 

Еще довольно просто. Мое использование new, чтобы скрыть переменные экземпляра правильно? Обратите внимание, что здесь я назначаю CombatantObjectController.model, хотя GameObjectController.Model уже был установлен. И, комбатанты не нуждаются в каких-либо специальных функциях просмотра, поэтому они оставляют только GameObjectController.View.

Затем я спускаюсь к PlayerController, на котором обнаружена ошибка.

public class PlayerController : CombatantGameObject 
{ 
    private readonly IInputReader inputReader; 

    private new readonly PlayerModel model; 
    public new PlayerModel Model 
    { 
     get { return model; } 
    } 

    private float lastInventoryIndexAt; 
    private float lastThrowAt; 

    public PlayerController(GameObjectManager gameObjectManager, IInputReader inputReader, CombatEngine combatEngine) 
     : base(gameObjectManager, combatEngine) 
    { 
     this.inputReader = inputReader; 
     model = new PlayerModel(this); 
     Model.Health = Constants.PLAYER_HEALTH; 
    } 

    public override void Update(long tick) 
    { 
     if (Model.Health <= 0) 
     { 
      gameObjectManager.RemoveFromWorld(this); 
      for (int i = 0; i < 10; i++) 
      { 
       Debug.WriteLine("YOU DEAD SON!!!"); 
      } 
      return; 
     } 

     UpdateFromInput(tick); 
     // .... 
    } 
    } 

В первый раз, что эта линия выполнена, я получаю нулевое ссылочное исключение:

model.Body.ApplyImpulse(movementImpulse, model.Position); 

model.Position смотрит на model.Body, который является нулевым.

Это функция, которая инициализирует GameObjects, прежде чем они будут развернуты в мире:

public void Initialize(GameObjectController controller, IDictionary<string, string> data, WorldState worldState) 
    { 
     controller.View.read(data); 
     controller.View.createSpriteAnimations(data, _assets); 

     controller.Model.read(data); 

     SetUpPhysics(controller, 
     worldState, 
     controller.Model.BoundingCircleRadius, 
     Single.Parse(data["x"]), 
     Single.Parse(data["y"]), bool.Parse(data["isBullet"])); 
    } 

Каждый объект передается как GameObjectController. Означает ли это, что если объект действительно равен PlayerController, то controller.Model будет ссылаться на базу GameObjectModel, а не на PlayerController's overriden PlayerObjectModel?

В ответ на РХ:

Это означает, что в настоящее время для PlayerModel р, p.Model не эквивалентно ((CombatantGameObject) р) .MODEL и также не эквивалентно ((GameObjectController) p) .Model.

Это именно то, чего я не хочу. Я хочу:

PlayerController p; 
p.Model == ((CombatantGameObject)p).Model 
p.Model == ((GameObjectController)p).Model 

Как это сделать? override?

+1

Ответ: больше зомби. В игре никогда не бывает слишком много зомби. – slugster

+0

Спасибо - я обновил свой ответ несколькими способами, которые вы можете продолжить. Переопределение не будет вашим лучшим выбором в этом случае, потому что вы действительно хотите вернуть другой _type_, что дает свойству другую подпись. –

ответ

2

Ключ лежит в вашем использовании «новой» ключевое слово здесь:

private new readonly PlayerModel model; 
public new PlayerModel Model 
{ 
    get { return model; } 
} 

и здесь:

protected new readonly CombatantGameModel model; 
public new virtual CombatantGameModel Model 
{ 
    get { return model; } 
} 

Что вы говорите: «Я знаю, что мой базовый класс уже определены эти , но я хочу определить разные те, у которых одноименное имя. "

Это означает, что в настоящее время для PlayerModel р, p.Model является не эквивалентен ((CombatantGameObject) р) .MODEL, а также не эквивалентен ((GameObjectController) р) .MODEL.

Вот несколько способов, которыми вы могли бы продолжить.

1) Не предлагайте сильно типизированные свойства ребенка. Я знаю, что это, вероятно, сначала звучит плохо, но на самом деле это очень мощная концепция. Если ваша базовая модель определяет соответствующие абстрактные/виртуальные методы, которые подходят для всех подклассов, вы можете определить свойство один раз в базовом классе и сделать это. Затем подклассы могут обеспечить их собственную реализацию.

Вот один из возможных способов реализации этого.

public class GameObjectController /* ... */ 
{ 
    /* ... */ 
    public GameObjectController() 
    { 
     Model = new GameObjectModel(this); 
    } 

    public GameObjectModel Model { get; protected set; } 
} 

public class CombatantGameObject : GameObjectController 
{ 
    /* ... */ 
    public CombatantGameObject() 
    { 
     Model = new CombatantModel(this); 
    } 
} 
/* ... */ 

2) Предлагать строго типизированные свойства при доступе через подклассы, но хранить поле один раз в базовом классе.

Это может работать, но это сложно сделать правильно. Это был не мой первый выбор.

public class GameObjectController /* ... */ 
{ 
    /* ... */ 
    public GameObjectController() 
    { 
     Model = new GameObjectModel(this); 
    } 

    public GameObjectModel Model { get; protected set; } 
} 

public class CombatantGameObject : GameObjectController 
{ 
    /* ... */ 
    public CombatantGameObject() 
    { 
     Model = new CombatantModel(this); 
    } 

    public new CombatantModel Model 
    { 
     get 
     { 
      return (CombatantModel)base.Model; 
     } 
     protected set 
     { 
      base.Model = value; 
     } 
    } 
} 
/* ... */ 

Кроме того, будьте осторожны относительно чрезмерной фиксации сложной объектной модели слишком рано. Иногда начинать простой и рефакторинг агрессивно, это лучший способ получить хорошую модель, которая отражает то, как ваш код действительно работает.

+0

Спасибо. Я обновил свой OP с ответом на это. –