Здравствуйте, уважаемые коллеги.
Перерыл достаточно немало информации и не найдя ответа на свой вопрос, я, возможно, затрону уже обсуждаемую тему, но момент совсем не очевидный.
Как я понял, логика работы любой игры на Monkey/Mojo - это обновление экрана внутри OnRender каждые N мс (а GUI-приложения на Monkey работают подобным же образом, бешено перерисовывая одни и те же контролы стопсят раз в секунду?). Поправьте, если это не так. Есть OnRender, он вызывается самим классом App (вручную вызвать его из своих методов нельзя). Минимальная задержка для обновления экрана - SetUpdateRate(1) - 1 раз в секунду. Для многих игр, очень регулярно обновляющих содержимое экрана, такая логика вполне хороша. Но как быть со спокойными играми типа карточных, где нужно вывести всю графику и спокойно ждать реакции пользователя, перерисовывая некоторые элементы на экране частями, а не лихорадочно обновлять экран каждые 60 или больше раз в секунду. Это кажется излишне нагружающим процессор (на десктопе) и садящим заряд батареи (на планшете/смартфоне).
Я было подумал, что можно использовать SetUpdateRate(0), устанавливая внутри своего метода SetUpdateRate(60) когда пора перерисовать экран, и снова возвращать SetUpdateRate(0) внутри OnRender. Но вот что пишут про SetUpdateRate(0):
http://socoder.net/index2.php?topic=4047&seenpost=40229
SetUpdateRate(0) will run OnUpdate and OnRender as fast as possible.
Т.е. аргумент 0 вовсе не значит "обновлять только по необходимости", но "обновлять с максимальной частотой".
Логика моей программы примерно такая (не событийная):
Нарисовать что-то одно на части экрана (появилось на экране)
Подождать секунду
Нарисовать что-то другое на части экрана (появилось на экране)
Опросить управление, отреагировать на управление, перерисовав что-то на части экрана
(предполагается наличие буфера, в котором производятся графические операции над частями экрана)
А не:
Method OnRender ' С крайне высоким фпс! ;)
Пришла пора срочно перерисовать весь экран, будьте любезны его заново сформировать
End Method
Как мне быть? Воспользоваться средним фпс, например, SetUpdateRate(15), переведя всё-таки логику работы на событийность (предвижу трудности)? Воспользоваться многозадачностью (например, с помощью Diddy), руля логикой в отдельном потоке, но каким-то образом отдавая OnRender'у команды на перерисовку графики? У кого были подобные трудности и нашлось ли решение? Примеры кода будут очень кстати. Спасибо.
Ответил: devolonter
Перейти к сообщениюИзвиняюсь за долгий ответ. По поводу Вашего примера - какую версию Monkey вы используете (номер версии)? Т.к. это похоже на баг, круг должен двигаться самостоятельно и во всех таргетах. Именно это у меня и происходит.
@Zorko как определить - пора перерисовывать экран по требованию ОС (скажем, курсор мышки проехал по экрану и нужно восстановить изображение)
На это мы повлиять уже не можем. Дело в том, что вне зависимости прервали вы рендер или нет, базовые функции отрисовки все равно выполняются, т.е. например, на таргетах с двойной буферизацией переключение буферов происходит в любом случае каждый тик.
@Zorko you might want to consider timestep-independent code. For this demo we assume fixed 60fps
Здесь, скорее всего, имеется в виду SetUpdateRate(0), но не тот, который получился у Вас, а тот который должен быть на самом деле.
@Zorko Вот игра JackHammer , типично тому, что я хочу сделать. Т.е. думаете, она перерисовывает одно и то же с постоянным фпс?
Изучил исходник. В игре установлен UpdateRate=60:
bb_app_SetUpdateRate(60);
В OnRender не нашел условие выхода и ренедра, т.е. игра в идеальных условиях перерисовывается 60 раз в секунду.
@Zorko Извиняюсь за некоторое упорство. Возможно, это связано с опытом работы с библиотекой SDL
В SDL мы имеем дело с низкоуровневым API, в mojo мы имеем дело с оберткой, со своими архитектурными ограничениями. Точнее ограничения заложены в код таргетов в угоду mojo. То что Вы хотите сделать, реализовать можно, но только путем хака кода таргетов и добавления дополнительных методов в класс App.
@Zorko Это интересно, хотелось бы примерчик.
Хотел сделать пример с BeginRender и EndRender, но в итоге пример вырос до, возможно, подходящего Вам решения. Постарался в комментариях все объяснить
Import mojo
Class MyApp Extends App
Field buffer:Image
Field pixels:Int[]
Field dirty:Int
Method OnCreate:Int()
SetUpdateRate(15)
'буфер для рендера
buffer = CreateImage(DeviceWidth(), DeviceHeight())
'кэш для пикселей
pixels = New Int[DeviceWidth()*DeviceHeight()]
Return 0
End Method
Method OnUpdate:Int()
If (MouseHit())
BeginRender() 'После вызова этой функции можем использовать функции рендера в любом месте
'рисуем
Cls(0, 0, 0)
SetColor(255, 255, 255)
DrawRect(MouseX(), MouseY(), 50, 50)
'записываем измененный регион в массив пикселей
ReadPixels(pixels, MouseX(), MouseY(), 50, 50)
EndRender() 'завершаем рендер
'записываем полученные пиксели в буфер
buffer.WritePixels(pixels, MouseX(), MouseY(), 50, 50)
dirty = 2 'хак, для таргетов с двойной буферизацией нам нужно нарисовать изображение два раза, чтобы оно попало в оба буфера, иначе получим мерцание
End If
End Method
Method OnRender:Int()
'Рисуем буфер, только в случае если он был изменен
If (Not dirty) Return
DrawImage(buffer, 0, 0)
dirty -= 1
Return 0
End Method
End Class
Function Main:Int()
New MyApp
Return 0
End Function