Если вы управляете всеми классами, о которых идет речь, и если GameObjectController не определяет какие-либо поля, самым чистым подходом будет определение IGameObjectController (свойства и методы которого соответствуют свойствам GameObjectController) и ICombatantGameObjectContoller (который является производным от обоих IGameObjectController) и ICombatant). Каждый класс, который должен использоваться в ситуациях, когда требуются оба интерфейса, должен быть явно объявлен как реализующий ICombatantGameObjectController, даже если добавление этого объявления не потребует добавления дополнительного кода. Если это так, можно без труда использовать параметры, поля и переменные типа ICombatantGameObjectController.
Если вы не можете настроить свои классы и интерфейсы, как описано выше, подход, предложенный Джоном Скитом, в целом хорош, но с неприятным предостережением: для вызова универсальной функции, такой как ComputeAttackUpdate г-на Скита, компилятор имеет уметь определять единственный тип, который, как он знает, совместим с типом передаваемого объекта и со всеми ограничениями. Если есть потомки GameObjectController, которые реализуют ICombatant, но не являются производными от общего базового типа, который также реализует GameObjectController, может быть сложно хранить такие объекты в поле, а затем передавать их в общие подпрограммы. Есть способ, и если вам нужно, я могу объяснить это, но это немного сложно.