Помогите с дизайном программы
В настоящее время я создаю простую консольную игру, в которой игрок может перемещаться между различными комнатами, собирать и использовать предметы и есть пищу. В текущем состоянии игры вот и все.
Мне нужна помощь с:
Создание хорошего класса "Event" для моей игры. Я хотел бы, чтобы все работало так, чтобы каждый предмет и комната могли быть связаны с событием.
Поскольку я новичок в этом, я был бы признателен за любые материалы для чтения, связанные с такого рода процедурами, или предложения о том, как было бы лучше настроить мой класс (ы) с учетом пунктов ниже, или просто как атаковать такого рода. проблемы (то есть проблемы с решением, как настроить классы).
Я хочу иметь возможность создавать различные виды событий, например:
Выведите текст, а затем задайте игроку вопрос. Если игрок дает правильный ответ, сделайте что-нибудь.
Выведите некоторый текст, затем удалите предмет из инвентаря игрока, а затем переместите игрока в другую комнату.
Чего я пытаюсь избежать:
Весь смысл игры в том, чтобы практиковать хороший дизайн класса, поэтому такие вещи, как ответственный дизайн, сплоченность и связь, являются важными аспектами. Поэтому я хочу, чтобы он был максимально простым, многоразовым и независимым.
Необходимость жестко кодировать каждое событие, чтобы элемент или комната просто вызывали определенный метод в классе Event.
Что я думаю на данный момент:
Создание нескольких подклассов, чтобы я мог, например, создавать новые события (и связывать их), используя: itemObject.setEvent(new Event(new Question("introText", "outroText", "correctAnswer")));
Пожалуйста, дайте мне знать, если нужна дополнительная информация! Спасибо!
4 ответа
Книги о программировании игр великолепны... хотя и немного дорогостоящи.
Вероятно, вы можете найти одну или две соответствующие статьи в списке статей GameDev, и, вероятно, на их форумах спрятаны некоторые хорошие материалы.
Что касается класса Event, вам почти наверняка нужен базовый класс с некоторым количеством подклассов (MoveEvent, InventoryEvent и т. Д.). И если вы действительно хотите избежать жесткого кодирования, вы должны определить свою игру в файлах данных. Комнаты, предметы и события... плюс все остальное, что вы добавите позже. Формат, который вы используете для определения вещей, полностью зависит от вас. Я рекомендую кое-что, что даст вам практический опыт, так как это учебное упражнение: например, анализатор XML, который вы не использовали. Не самый эффективный формат для разбора, но это консольная игра, так что это не такая проблема.
Одно из предложений, которое я имею для подкласса Event, - это то, что может связывать другие события вместе. Интерфейс не меняется, и он позволяет вам делать такие вещи, как "добавить это в инвентарь игрока, забрать это и переместить их сюда".
Одна вещь, которую я хотел бы рассмотреть, - это отделение синтаксического анализа входного текста и генерации выходного текста от базовой модели предметной области. Ваш вопрос, кажется, смешивает их вместе, тогда как я бы рассматривал текстовую часть проблемы как уровень представления.
Например, ваш домен может состоять из следующих интерфейсов:
public enum Action { HIT, TALK_TO, EAT, USE };
public interface GameEntity {
/**
* Performs the specified action on this GameEntity, passing in zero or
* more other GameEntity instances as parameters.
*/
void applyAction(Action action, GameEntity... params);
}
public interface Item extends GameEntity { }
public interface Person extends GameEntity { }
Кроме того, вы можете определить класс TextParser, который будет обрабатывать входную строку и делать результирующие вызовы на уровне домена, либо возвращать ответ по умолчанию: "Я не понимаю". Например, входная фраза "использовать ключ на двери" приведет к вызову метода:
key.applyAction(Action.USE, door);
Говоря о событиях, на ум приходит картина наблюдателя. Вы можете думать, что ваша игра будет представлена внутри нескольких конечных автоматов (вы также можете посмотреть это в Википедии, я не могу добавить 2-ю ссылку самостоятельно, потому что она новичок в SO). Каждый переход с одного этапа на другой - это событие, которое может быть сообщено зарегистрированным оберсерверам этого события.
Пример:
USE KEY ON LOCK: запускает удаление ключа и открытие двери; имплицитно "каскадный" переход к следующему событию без дополнительного взаимодействия с пользователем (открытие двери): запуск комнаты с дополнительным выходом и т. д.
Недавно я реализовал нечто похожее на то, что вы описываете. Я начал с создания класса EventManager с методами "Опубликовать" и "Подписаться". Когда система запускается, она сканирует все загруженные сборки (библиотеки) на предмет классов, которые реализуют интерфейс IEventListener:
/// <summary>
/// A class that intends to listen for specific events to be published by an
/// event manager.
/// <see cref="EventManager"/>
/// </summary>
public interface IEventListener
{
/// <summary>
/// Subscribes to the event types that this listener wishes to listen for.
/// </summary>
/// <param name="eventManager"></param>
void RegisterSubscriptions(EventManager eventManager);
}
... и вызывает метод RegisterSubscription для каждого из них. (Существует только один экземпляр EventManager, который используется везде; я использую внедрение зависимостей, чтобы связать его как одноэлементный экземпляр.)
RegisterSubscription просто вызовет метод Subscribe для каждого типа события и обработчика, которые данный класс предназначен для обработки. (Я использую.NET, поэтому с событиями и обработчиками немного проще работать, но вы можете сделать то же самое в Java, используя анонимные подклассы.)
Типичное событие может быть чем-то вроде RoomEntered, а аргументы, связанные с этим событием, могут включать идентификатор комнаты и, возможно, некоторые другие свойства комнаты. Движок игры публикует это событие, когда пользователь входит в комнату, предоставляя соответствующие аргументы события для этой комнаты, и класс EventManager отвечает за вызов каждого обработчика, подписавшегося на этот тип события. Одним из обработчиков этого события может быть "HandleEnterRoomWithMonsters", который проверяет, есть ли в комнате монстры, и выполняет соответствующее действие.
Имеет ли это смысл в целом? У вас есть вопросы по поводу этого подхода?