Java: доступ к ресурсам и Закон Деметры
Обзор
В моей (Android) Java-игре у меня есть класс под названием resources. Как следует из названия, этот класс содержит ресурсы для игры. Здесь созданы все мои объекты OpenGL (Sprites)
Он выглядит примерно следующим образом (очевидно, это упрощенная версия по сравнению с тем, что появляется в реальном проекте):
public class Resources {
Hero hero;
Enemy enemy;
MenuButtons mainMenuButtons;
Background background;
Scene mainMenu;
public void createObjects(){
hero = new Hero();
enemy = new Enemy();
mainMenuButtons = new MenuButtons();
background = new Background();
mainMenu = new Scene(this);
}
}
Итак, в моей сцене mainMenu мне нужно получить доступ к моим объектам, чтобы мы могли увидеть что-то вроде этого:
public class mainMenu implements Scene {
Resources resources;
public mainMenu(Resources resources){
this.resources = resources;
}
@Override
public void render(){
resources.background.draw();
resources.hero.draw();
resources.enemy.draw();
mainMenuButtons.draw();
}
@Override
public void updateLogic(){
resources.hero.move();
resources.enemy.move();
resources.mainMenubuttons.animate();
}
}
Теперь этот метод является всего лишь одним из способов получить доступ к объектам в ресурсах и их методам. Но разве это действительно нарушает "сильный" закон Деметры? Если нет, почему бы и нет? Если да, то каков наилучший способ получить доступ к этим объектам таким образом, чтобы это не нарушало LOD?
Accessors?
Один из вариантов (который я исключал из TBH - см. ниже) помещает методы доступа в класс ресурсов. Чтобы я мог сделать что-то вроде:
resources.drawBackround();
У меня много объектов, и мне нужен аксессор для каждого метода/переменной каждого объекта. Не очень практично, мне кажется, что я пишу тонну дополнительного кода и, что самое важное, он делает класс ресурсов смехотворно длинным, когда он заполняется этими аксессуарами. Поэтому я не пойду по этому пути.
Передача объектов в конструктор сцены
Конечно, я также могу сделать что-то вроде этого:
hero = new Hero();
enemy = new Enemy();
mainMenuButtons = new MenuButtons();
background = new Background();
mainMenu = new Scene(hero, enemy, mainMenuButtons, background);
Поэтому я могу просто сделать это:
background.draw(); //etc....
Это работает для простой сцены (например, систем меню, для которых не требуется много объектов), но для основной игры она может быстро стать беспорядком, поскольку мне придется передавать ссылки на некоторые 30+ объектов в конструктор, который на самом деле не совсем прав...
Поэтому я был бы очень признателен, если бы кто-нибудь мог указать лучший способ продолжить и почему.
Ответы
Ответ 1
Поэтому я был бы очень признателен, если бы кто-нибудь мог указать лучший способ продолжить и почему.
Лучший способ, на мой взгляд, состоит в том, чтобы сохранить класс ресурсов, сделать все объекты частными, чтобы не нарушать закон и писать аксессоры (но не для каждого объекта, как вы уже исключены).
У меня много объектов, и мне нужен аксессор для каждого метода/переменной каждого объекта. Не очень практично, похоже, я пишу тонну дополнительного кода и, что самое важное, он делает класс ресурсов смехотворно длинным, когда он заполняется этими аксессуарами. Поэтому я не пойду по этому пути.
Я предполагаю, что многие объекты одного класса. Таким образом, вам не нужно создавать аксессуар для каждого объекта, что бы действительно взорвало класс.
В игре у вас обычно есть герой, один или несколько врагов и много спрайтов.
public class Resources {
private Hero hero;
private Enemy enemy;
private MenuButtons mainMenuButtons;
private Background background;
private Scene mainMenu;
public void createObjects(){
hero = new Hero();
enemy = new Enemy();
mainMenuButtons = new MenuButtons();
background = new Background();
mainMenu = new Scene(this);
}
public Hero getBackground() {
return background;
}
public Hero getHero() {
return hero;
}
public List<Enemy> getEnemies() {
ArrayList<Enemy> list = new ArrayList<Enemy>();
list.add(enemy);
list.add(next_enemy);
return list;
}
public List<Sprite> getSprites() {
ArrayList<Sprite> list = new ArrayList<Sprite>();
list.addAll(enemy.getActiveSprites());
return list;
}
}
Вместо getHero() и getEnemy() вы также можете сделать метод getActor(), если Hero и Enemy являются производными от того же класса.
Метод getSprites() является всего лишь примером того, как он может выглядеть.
Если это решение не сработает для вас, у меня есть другое предложение.
Сделайте класс Resources полезным.
public class ResourceManager {
private Hero hero;
private Enemy enemy;
private MenuButtons mainMenuButtons;
private Background background;
private Scene mainMenu;
public void createObjects(){
hero = new Hero();
enemy = new Enemy();
mainMenuButtons = new MenuButtons();
background = new Background();
mainMenu = new Scene(this);
}
public void render(Scene scene) {
this.background.draw();
if (scene != mainMenu) {
this.hero.draw();
this.enemy.draw();
}
this.mainMenuButtons.draw();
}
public void updateLogic(Scene scene){
this.hero.move();
this.enemy.move();
this.mainMenubuttons.animate();
}
}
mainMenu затем вызывает логические методы непосредственно в классе RescourceManager.
public class mainMenu implements Scene {
ResourceManager resourceManager;
public mainMenu(ResourceManager resourceManager){
this.resourceManager = resourceManager;
}
@Override
public void render(){
resourceManager.render(this);
}
@Override
public void updateLogic(){
resourceManager.updateLogic(this);
}
}
Надеюсь, мои предложения помогли вам немного разобраться, как продолжить свой проект.
Ответ 2
Вы можете использовать инъекцию зависимостей и исключить свой класс ресурсов. Затем вы можете вызывать функции на своих полях и не будут нарушать Закон Деметры.
Вот пример использования инсталляции конструктора:
public class MainMenu implements Scene {
Background background;
Hero hero;
Enemy enemy;
MenuButtons buttons
public mainMenu(Background background, Hero hero, Enemy enemy, MenuButtons buttons){
this.background = background;
this.hero = hero;
this.enemy = enemy;
this.buttons = buttons;
}
@Override
public void render(){
this.background.draw();
this.hero.draw();
this.enemy.draw();
this.mainMenuButtons.draw();
}
@Override
public void updateLogic(){
this.hero.move();
this.enemy.move();
this.mainMenubuttons.animate();
}
}
При введении зависимостей вы передаете экземпляры в конструкторы и функции вместо того, чтобы "вводить" их в свой класс. Вам нужно управлять своими экземплярами где-то, и есть много библиотек, которые сделают это для вас. Dagger является популярным для Android: http://square.github.io/dagger/
Ответ 3
Идея передачи списка не является плохим первым шагом, но этого недостаточно. Разработчики игр имеют (несколько противоречивую) концепцию структуры, называемой "графом сцены", которая помогает им отслеживать свои ресурсы (среди прочего). https://en.wikipedia.org/?title=Scene_graph
Это довольно сложная концепция, но рано или поздно вам нужно будет узнать об этом. Там есть много хороших советов по gamedev.stackexchange.com, поэтому я предлагаю вам заглянуть туда.
Вот хороший видеоролик YouTube по этому вопросу. https://www.youtube.com/watch?v=ktz9AlMSEoA
Ответ 4
Вы можете создать класс Drawer
, который обрабатывает чертеж всех объектов. Объектам сцены просто нужно передать Drawer
объекты, которые я предполагаю, Drawable
.
public class Drawer {
public void drawObjects(Drawable... objects) {
for(Drawable drawable : objects) {
drawable.draw();
}
}
}
Затем используется Scene
для рисования этих объектов.
public class mainMenu implements Scene {
Resources resources;
Drawer drawer;
...
public void render() {
drawer.drawObjects(resources.background,
resources.hero,
resources.enemy,
resources.mainMenuButtons);
}
...
}
Аналогичная стратегия, использующая Updater
, может применяться для других методов. Если ваш метод updateLogic()
делает так же просто, как он выглядит, вы можете сделать то же самое, сделав все эти объекты наследуемыми от интерфейса Updateable
.
public interface Updateable {
void update();
}
Hero
и Enemy
update()
методы могли просто вызвать их методы move()
, а MenuButtons
update()
могли делегировать animate()
и т.д.
Очевидно, что если вам нравится, вы можете использовать какую-то коллекцию вместо varargs для параметра Drawer
drawObjects()
. Мне просто нравится приятная беглость, которую вы можете сделать с помощью varargs, так как вам не нужно создавать коллекцию.
Для других советов по поддержанию кода в соответствии с Законом Деметры, ознакомьтесь с этой статьей: Закон Деметры и Как работать с ним
Ответ 5
Измените это:
public void render(){
resources.background.draw();
resources.hero.draw();
resources.enemy.draw();
mainMenuButtons.draw();
}
@Override
public void updateLogic(){
resources.hero.move();
resources.enemy.move();
resources.mainMenubuttons.animate();
}
При этом:
public void render(){
resources.render();
}
@Override
public void updateLogic(){
resources.update();
}
ResourceManager не должен знать, что внутри ресурсов. Это может быть один враг или десять, ему не нужен ResourceManager.
И так в "Ресурсе" вы можете сделать:
public void update(){
hero.update();// Cause hero may, or may not move, he makes the choice
enemy.update();//...
mainMenubuttons.update();//.
}
public void render(){
...
}
Больше, чем это! вы можете изменить реализацию "Ресурс" с помощью интерфейса, и вы будете программировать для интерфейсов, а не для реализаций, что классно! Таким образом, вы можете иметь "Ресурсы" для внутриигровой игры, а другой - для меню, которое будет использоваться одинаково: только при выполнении во время выполнения конкретных ресурсов вы будете в меню или в игре!
Во всяком случае, не всегда необходимо заполнить Demeter.
Ответ 6
Мне нравится концепция ResourceManager.
Но ResourceManager должен быть ответственен за загрузку ресурсов, кэширование и освобождение.
Рендеринг определенно является методом объекта рендеринга.
Таким образом, метод Scence-render может делегировать ему рендеринг после
создания экземпляра Renderer и подачи его с помощью Drawables, поскольку Renderer не выполняет рендеринг ресурсов, а визуализируемых объектов.
Скажи:
class MainMenu implements Scene {
Renderer sceneRenderer = new Renderer();
AnimatedRenderer animatedRenderer = new AnimatedRenderer();
ResourceManager resourceManager = ResourceManager.getInstance();
List<Resource> resources;
List<Drawable> renderedObjects;
GameObjectController gameObjectController;
void initializeScene() {
resources = resourceManager.getResources();
renderedObjects = resourcesAsRenderables();
sceneRenderer.setDrawables(renderedObjects);
}
List<Drawable> resourcesAsRenderables() {
// if resources are not directly renderable, do decoration etc
// and return a List of Drawable
}
@Override
public void render(){
sceneRenderer.render();
}
@Override
public void updateLogic(){
moveGameObjects();
doAnimations();
}
protected void moveGameObjects() {
gameObjectController.moveAllObjects(this, resources);
}
protected void doAnimations() {
animatedRenderer.render(resources);
}
class ResourceManager {
private static ResourceManager instance = null;
List<Resource> resources;
public ResourceManager getInstance() {
if(instance == null) {
instance = new ResourceManager();
instance.loadResources();
}
return instance;
}
private void loadResources() {
resources = new LinkedList<Resource>();
resources.add(new Hero());
....
}
public List<Resource> getResources() {
return resources;
}
}
Это четко разделяет логику и обязанности для задач, выполняемых в течение жизненного цикла сцены. Менеджер ресурсов, который отвечает за извлечение ресурсов и, поскольку они могут занять длительное время загрузки, делает такие вещи, как кеширование или освобождение в ситуациях с низкой памятью, скрывая детали от клиента. Средство визуализации, которое отвечает за отображение объектов и контроллера, который отвечает за перемещение объектов. Сам контроллер может реализовать обработчики для событий клавиатуры, но это не то, что должно быть прозрачным для сцены. Средство рендеринга может менять фоны или выходить, масштабировать или устанавливать световые эффекты, но сцена вызывает только метод визуализации. Анимационный рендеринг отвечает за запуск, рендеринг и остановку анимаций.
Ответ 7
Как видно, ваши ресурсы не нужно воссоздавать, вместо этого они используют некоторые ресурсы, которые не могут быть перезагружены (возможно, изображения).
Вы должны поделиться объектом изображений в классе ресурсов и создать свои объекты в классе Scene, в конструкторе объектов, которые вы можете получить предварительно загруженный ресурс.