Чтобы подтянуть свои знания, занялся разработкой коллекционной карточной игры. По задумке должно получиться что-то среднее между Heartstone и Gwint. Два игрока, у каждого есть своя колода и рука (карты в быстром доступе, так сказать), есть игровое поле, разделенное на четыре линии у каждого игрока. Каждая карта обладает своими характеристиками и специальными бонусами. Все, в-общем, стандартно. Разметив более менее баланс игры, я приступил к разработке и столкнулся с рядом проблем, где особняком стоит расчет бонусов на каждую карту. Например, если две карты с одинаковым бонусом лежат рядом, то у каждой карты увеличивается определенная характеристика на несколько пунктов.
Сейчас у меня есть класс Card с характеристиками карты, класс Field (игровое поле игрока). Проблема в том, что я не знаю как лучше рассчитывать бонусы. Пока идея такая: у класса Card добавить список бонусов, которые распространяются на эту карту и при любом действии пересчитывать этот список для всех карт.
Но что-то мне подсказывает, что можно сделать проще. Может, кто-то разрабатывал нечто похожее и поможет дельным советом? Ну и я всегда буду рад качественной литературе по программированию компьютерных игр.
Очередной джун с грандиозными амбициями создать "убийцу хартстоуна" споткнулся в самом начале. Ая-я-й. Вопрос вне тематики ресурса, а вам на геймдев.ру в компанию таких же великих творцов. Жалоба отправлена.
AtomKrieg: Да, я очередной джун, коих вы, судя по количеству желчи в вашем комментарии, повидали немало. Написали жалобу? Ваше право. Но заходить в тему и в своем комментарии тыкать моськой автора в его невежество вместо того, чтобы дать дельный совет или просто пройти мимо... Даже не знаю. Не с той ноги утром встали?
V Sh.: "Ну и я всегда буду рад качественной литературе по программированию компьютерных игр." Ещё совет - учитесь пользоваться поисковиком вместо писания возвышенных фраз. Гугл, Яндекс, у тостера тоже есть встроенный.
Выглядит как PubSub-задача. Допустим у нас есть карта "Король гоблинов" 5/5 таунт, класс - гоблины.
Когда мы кладем ее на стол она ( в смысле соответствующий объект ) отписывается от канала "для тех, кто лежит в руке" и подписывается на каналы "Лежим на столе", "Класс - гоблины", "Карты с таунтом" и тэ дэ.
И посылает в соответствующие каналы сообщение - "Я 5/5 гоблин пришел на стол на такую-то позицию".
Таким образом мы можем баффать гоблинов, уничтожать вражеские таунты или баффать соседей по столу без перебора всех карт. Ну и добавление новых эффектов тоже с меньшей болью происходит
public class Publisher : IPublisher
{
Dictionary> channels;
public Publisher()
{
channels = new Dictionary>();
}
public void Subscribe(ISubscriber subscriber, string channel)
{
if (channels.ContainsKey(channel))
{
bool exist = channels[channel].Any(item => item.IdSubscriber == subscriber.IdSubscriber);
if (!exist)
{
channels[channel].Add(subscriber);
}
else
{
throw new Exception(String.Format("Элемент {0} уже подписан на канал {1}!", subscriber.IdSubscriber, channel));
}
}
else
{
channels.Add(channel, new List() { subscriber });
}
}
public void Unsubscribe(ISubscriber subscriber, string channel)
{
if (channels.ContainsKey(channel))
{
bool exist = channels[channel].Any(item => item.IdSubscriber == subscriber.IdSubscriber);
if (exist)
{
channels[channel].Remove(subscriber);
}
else
{
throw new Exception(String.Format("Элемент {0} не подписан на канал {1}!", subscriber.IdSubscriber, channel));
}
}
else
{
throw new Exception(String.Format("Канал {0} не существует!", channel));
}
}
public void Publish(string channel)
{
if (channels.ContainsKey(channel))
{
foreach (var subscriber in channels[channel])
{
//Здесь нужно передавать делегат на обрабатывающую функцию.
subscriber.Update();
}
}
}
}
Я думаю, лучше обрабатывать с другой стороны - в игровом поле иметь список карт и список применённых бонусов. При использовании карты изменять характеристики как самой карты, так и игрового поля. И возможно, что придётся перерасчитывать каждую карту (зависит от применённой карты). Взяв карту и обработав её, игровое состояние сразу станет изменённым, и не нужно будет при каждой проверке перепроверять состояния каждой карты.
Предположим, что у нас есть две карты с особенностью "усилить все карты, кроме себя, в одном ряду". При выкладывании этих карт на игровое поле в одну линию, каждая карта будет усилена. Однако когда я перемещаю одну карту на другую линию, то бонус теряется. Причем теряется у обеих карт. Как обойти такое без пересчета каждой карты?
V Sh.: в общем случае - никак. Придётся перерасчитывать карты, причём, скорее всего, все карты вообще.
Да, можно придумать какой-то код, который обработает только текущие карты в этом конкретном эффекте. Но я сильно не рекомендую так делать. Если эффектов в игре всего десяток - пожалуйста. Но если их будет сотни, то делать неуниверсальный код делать нельзя - проект станет неуправляемым и "ломким", изменение кода в одном эффекте может сломать код других эффектов.
Был вопрос Как хранить данную структуру данных?, в нём я пытался описать, как я бы делал. И даже сделал :), но не до конца, времени доделать не хватило. Но проект был почти доделан, могу дать как есть, может, будет интересно.
https://github.com/lexxpavlov/TinyMages
Это не реальная игра, это демка, которую мне было интересно сделать. А в то время мне было интересно использовать WPF, MVVM, конвертеры, DelegateCommand-ы, а также интерфейсы - интересную реализацию интерфейсов в реальном проекте.
К сожалению, не успел доделать все типы эффектов, которые я планировал, в частности, специальные эффекты.
Если решать немножко в лоб. Предположим у вас не класс Card а иерархия классов ICard для каждого класса или типа карты(тут уже полно фантазии для реализации). И весь этот зоопарк классов реализует интерфейс IBonus с методом StartBonusEffect который на вход получает ссылку на окружение(класс доски, из которого можно вытащить свои карты или оппонента, жизни каждого игрока и прочее). А в реализации этого метода можете уже куражиться как хотите. Выиграть, проиграть, убить всех оппонентов, сделать своих имбами, и т.д. и т.п.