Хабр Курсы для всех
РЕКЛАМА
Практикум, Хекслет, SkyPro, авторские курсы — собрали всех и попросили скидки. Осталось выбрать!
public void Subscription(Text text) { this.Get(MONEY).SubscribeWithState(text, (x, t) => t.text = x.ToString()); }
class FlowerGivesMoneyView : CommandViewHandler<FlowerGivesMoney> {
@inject FlowersContainer flowersContainer;
@inject MoneyView moneyView;
async On (FlowerGivesMoney command) {
await flowersContainer.getFlower( command.flowerId ).animateGivingMoney();
await moneyView.animateMoneyValue( command.newMoneyValue );
}
}class FlowerGivesMoneyView : CommandViewHandler<FlowerGivesMoney> {
@inject FlowersContainer flowersContainer;
async On (FlowerGivesMoney command) {
await flowersContainer.getFlower( command.flowerId ).animateGivingMoney();
}
}
class GiveMoneyView : CommandViewHandler<GiveMoney> {
@inject MoneyView moneyView;
async On (GiveMoney command) {
await moneyView.animateMoneyValue( command.newMoneyValue );
}
}public class OpenMisterBox : Command {
public BoxItemModel item;
public int slot;
// Эта часть команды выполняется и на сервере тоже, и проверяется.
public override void Applay(GameState state) {
state.inventory[item.revardKey] += item.revardCount;
}
// Эта часть команды выполняется только на клиенте.
public override void Applay(GameState state) {
var cause = state.NewPersistent<WaitForCommand>();
cause.key = item.key;
cause.value = item.value;
state.ui.delayedInventoryVisualization.Add(cause);
state.ui.mysteryBoxScreen.animations.Add(new Animation() {cause = item, slot = slot}));
}
}
public class MysteryBoxView : View {
/* ... */
public override void ConnectModel(MysteryBoxScreenModel model, List<Control> c) {
model.Get(c, MysteryBoxScreenModel.ANIMATIONS)
.Control(c,
onAdd = item => animationFactory(item, OnComleteOrAbort => {
AsincQueue(new RemoveAnimation() {cause = item.cause, animation = item}) }),
onRemove = null
)
}
}
public class InventoryView : View<InventoryItem> {
public Text text;
public override void ConnectModel(InventoryItem model, List<Control> c) {
model.GameState.ui.Get(c, UIModel.DELAYED_INVENTORY_VISUALIZATION).
.Where(c, item => item.key == model.key)
.Expression(c, onChange = (IList<InventoryItem> list) => {
int sum = 0;
for (int i = 0; i < list.Count; i++)
sum += list[i].value;
return sum;
}, onAdd = null, onRemove = null ) // Чисто ради показать сигнатуру метода
.Join (c, model.GameState.Get(GameState.INVENTORY).ItemByKey(model.key))
.Expression(c, (delay, count) => count - delay)
.SetText(c, text);
// Здесь я написал полный код, но в реальности это операция типовая, поэтому для неё, конечно же, существует функция обёртка, которая дёргается в проекте во всех случаях, выглядит её вызов вот так:
model.inventory.CreateVisibleInventoryItemCount(c, model.key).SetText(c, text);
}
}
public class RemoveDelayedInventoryVisualization : Command {
public DelayCauseModel cause;
public override void Applay(GameState state) {
state.ui.delayedInventoryVisualization.Remove(cause);
state.DestroyPersistent(cause);
}
}
public class RemoveAnimation : RemoveDelayedInventoryVisualization {
public Animation animation
public override void Applay(GameState state) {
base.Applay(state);
state.ui.mysteryBoxScreen.animations.Remove(animation);
}
}Смысл этого как раз в том, чтобы не хранить внутри View никакой скрытой информации, которой не было бы видно на отладочном геймстейте. В том числе и в виде запущенных корутин.
Не очень понял Ваш подход…
Поля моделей обновляются сразу, вьюшка же может приберечь историю этих изменений
Как ведет себя второй вариант — у нас появляется две команды и вторая команда ждет первую? (которая нужна лишь для анимации
и не изменяет модель, чтобы потом запустить вторую с начислением денег?) Потом каждая команда запускает свой запрос, или это только клиентская штука?
Клиент и сервер синхронны, все чисто
[
{ command: 'attack', source: 42, target: 35 },
{ command: 'deal_damage', target: 35, value: 2 },
{ command: 'activate_ability', source: 35 },
{ command: 'give_money', targetPlayer: 1, newValue: 20 }
]По опыту разработки социалок и реал тайма не встречал, чтобы сервер присылал игровые команды
открытие сундучков например, но и там клиент обычно получает данные, не команды
В таком решении вы мешаете чисто клиентскую декоративную логику — анимацию — с серверной бизнес-логикой — зачислением денег
А другой человек реализует функциональность пропуска катсцен — отменой команды
Или же игрок босса добил, а анимацию решил не смотреть и перезагрузил страничку, скажемМодель уже изменена. Как только был нанесен последний удар — в тот же момент босу нанеслись повреждения, он зачислен мертвым, игроку упали деньги, матч назван завершенным. Просто вьюшка на это реагирует не реактивно. Она получила список команд (DealDamane, Death, GiveMoney, EndGame) и начинает по очереди их анимировать. Сначала первую, когда закончит — вторую и так далее.
отдельные события «обновить деньги на сервере» и «показать обновление денег клиенту» мне нравится больше
public class PlayerModel : Model {
public ReactiveProperty<int> money = new ReactiveProperty<int>();
public ReactiveProperty<InventoryModel> inventory = new ReactiveProperty<InventoryModel>();
/* Using */
public void SomeTestChanges() {
money.Value = 10;
inventory.Value.capacity.Value++;
}
public void Subscription(Text text) {
money.SubscribeWithState(text, (x, t) => t.text = x.ToString());
}
}model.Get(c, MysteryBoxScreenModel.ANIMATIONS)model.GameState.ui
Архитектурные решения для мобильной игры. Часть 1: Model