stiry improvements
This commit is contained in:
parent
d304ff7837
commit
1ba189697f
@ -1,10 +1,172 @@
|
|||||||
|
|
||||||
|
Делать
|
||||||
|
---
|
||||||
|
#### декодирование ввода в отдельном потоке
|
||||||
|
|
||||||
|
Этот пункт исключительно только ради удобства. По умолчанию в BLT ввод отдается в виде int-ов, мне гораздо удобнее
|
||||||
|
получать ввод в виде "<модификатор(ы)+><имя клавиши>", например "Ctrl+Space".
|
||||||
|
|
||||||
|
Для этого пришлось написать map[int]string и пеервести все BLT-токены для клавиатуры в строки, а также написать
|
||||||
|
простенький обработчик:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
|
||||||
|
func decodeInput(ctx util.ClientCtx) {
|
||||||
|
var exit = false
|
||||||
|
|
||||||
|
for !exit {
|
||||||
|
select {
|
||||||
|
case keycode := <-State.RawInput:
|
||||||
|
if keycode == blt.TK_NONE {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if keycode == blt.TK_CLOSE {
|
||||||
|
State.Exit <- struct{}{}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var pressed = ""
|
||||||
|
var isModifier, _ = util.IntInSlice(keycode, modifiers)
|
||||||
|
if !isModifier {
|
||||||
|
|
||||||
|
pressed = ui.Scancodemap[keycode]
|
||||||
|
|
||||||
|
if blt.Check(blt.TK_SHIFT) != 0 {
|
||||||
|
pressed = "Shift+" + pressed
|
||||||
|
}
|
||||||
|
if blt.Check(blt.TK_ALT) != 0 {
|
||||||
|
pressed = "Alt+" + pressed
|
||||||
|
}
|
||||||
|
if blt.Check(blt.TK_CONTROL) != 0 {
|
||||||
|
pressed = "Ctrl+" + pressed
|
||||||
|
}
|
||||||
|
|
||||||
|
//global hotkeys
|
||||||
|
switch pressed {
|
||||||
|
//case "Ctrl+q", "Escape":
|
||||||
|
case "Ctrl+q":
|
||||||
|
State.Exit <- struct{}{}
|
||||||
|
exit = true
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
if pressed != "" {
|
||||||
|
State.Input <- pressed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
и в main функции обустроить всё для ее работы:
|
||||||
|
```go
|
||||||
|
var State = gamestate.GameState{
|
||||||
|
Exit: make(chan struct{}, 1),
|
||||||
|
Input: make(chan string, 1),
|
||||||
|
RawInput: make(chan int, 1),
|
||||||
|
}
|
||||||
|
...
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
...
|
||||||
|
go decodeInput()
|
||||||
|
...
|
||||||
|
var exit = false
|
||||||
|
for !exit {
|
||||||
|
|
||||||
|
select {
|
||||||
|
case State.RawInput <- readKeyCode():
|
||||||
|
break
|
||||||
|
case pressed := <-State.Input:
|
||||||
|
screenMgr.CurrentScreen.HandleInput(pressed)
|
||||||
|
break
|
||||||
|
case <-State.Exit:
|
||||||
|
mainCtx.Logger().Warn().Msg("quitting NOW")
|
||||||
|
exit = true
|
||||||
|
break
|
||||||
|
...
|
||||||
|
}
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
func readKeyCode() int {
|
||||||
|
if !blt.HasInput() {
|
||||||
|
return blt.TK_NONE
|
||||||
|
}
|
||||||
|
return blt.Read()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Теперь как только что-то приезжает к канал State.Input - просто передаем это в обработчик ввода текущего экрана.
|
||||||
|
И пользуемся удобными обозначениями клавиш.
|
||||||
|
|
||||||
|
#### Реализация FPS и троттлинг
|
||||||
|
|
||||||
|
FPS в случае использования скажем SDL напрямую может уехать за несколько сотен. Это не лучшим способом скажется
|
||||||
|
на утилизации CPU.
|
||||||
|
|
||||||
|
Иногда нужен отдельный FPS для некоторых видов анимации (в моем случае есть слегка анимиррованные тайлы).
|
||||||
|
|
||||||
|
реализуется это достаточно просто:
|
||||||
|
|
||||||
|
```go
|
||||||
|
type TerrainRenderer {
|
||||||
|
...
|
||||||
|
animateTiles *time.Ticker //поле для тикера
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
декодирование ввода в отдельном потоке
|
func NewTerrainRenderer() *TerrainRenderer {
|
||||||
|
t := &TerrainRenderer{}
|
||||||
|
...
|
||||||
|
t.animateTiles = time.NewTicker(time.Second / 12)
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
...
|
||||||
|
var redraw = true
|
||||||
|
...
|
||||||
|
|
||||||
Реализация FPS и троттлинг
|
//Это запускается в main thread ДО main loop в отдельном потоке - go terrainRenderer.Listen()
|
||||||
|
func (tr *TerrainRenderer) Listen(state gamestate.GameState) {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-state.FovRecompute: //глобальное состояние говорит что видимость изменилась
|
||||||
|
fovRecompute = true
|
||||||
|
case <-state.Redraw: // или что просто надо совежить картинку
|
||||||
|
redraw = true
|
||||||
|
case <-tr.animateTiles.C: // а вот внутренний тикер говорит нам что пора играть следующий кадр анимации
|
||||||
|
redraw = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
...
|
||||||
|
//А это собственно отрисовка ландшафта, вызывается в main thread
|
||||||
|
func (tr *TerrainRender) Render() {
|
||||||
|
|
||||||
|
if redraw {
|
||||||
|
redraw = false
|
||||||
|
//рисуем ландшафт на карте
|
||||||
|
...
|
||||||
|
}
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
Отдельный FPS для некоторых видов анимации (в моем случае анимиррованные тайлы)
|
#### Каналы состояний и их Listеner-ы
|
||||||
|
|
||||||
Каналы состояний и их Listеner-ы
|
Ну вобщем-то из кода выше видно, как это работает. Для состояния движка ок, для состояния игры - кмк больше подойдет обычная или timed queue.
|
||||||
|
|
||||||
|
Не делать
|
||||||
|
==
|
||||||
|
|
||||||
|
- Замыкания (не зная точно, где и как вы их уничтожите). Просто наберите в гугле golang closure memleak
|
||||||
|
|
||||||
|
- reflect в main loop. Лишь **только** выкинув рефлкесию и больше ничего не делая - я снизил потребление CPU приложением
|
||||||
|
**вдвое**. Это удобная штука, не спорю, но пользоваться ей надо при загрузке ресурсов, при сохранении/загрузке состояния
|
||||||
|
приложения - т.е. при разовых операциях. Как оказалось, она _очень_ дорогая по CPU. Кто пользоуется ей в main loop, ORM
|
||||||
|
и прочих нагруженных местах - да будет предан анафеме.
|
||||||
|
|
@ -1,10 +1,45 @@
|
|||||||
Автоматизация сборки и тестирование
|
Автоматизация сборки и тестирование
|
||||||
===
|
===
|
||||||
|
|
||||||
- Почему это важно: мелочи сжирают кучу времени. Не позволяйте им это делать!
|
Мелочи сжирают кучу времени. Не позволяйте им это делать!
|
||||||
- настройка под Linux: все внешние либы собраны и включены в монорепо (дело вкуса)
|
|
||||||
- Go-специфичные вещи: glide, go mod
|
Почему это важно
|
||||||
|
---
|
||||||
|
Про сборку под разные ОС я даже уюеждать не буду - аудитория рогаликов мало того что крохотная, так еще и сильно
|
||||||
|
сегментирована по осям. Go почти бесплатно дает вам возможность сборки под все мажорные оси, пользуйтесь этим - и
|
||||||
|
потенциально в разы больше народа ознакомится с вашим творением.
|
||||||
|
|
||||||
|
Почему это очень важно
|
||||||
|
---
|
||||||
|
Запуск тестов и сборки руками отнимает вроде бы немного времени. Но это настолько частая операция (вы же собираете новые
|
||||||
|
версии после багфиксов, да?!), что даже жалкие 5 минут после 12 сборок в день превращаются в ЧАС потерянного времени. А
|
||||||
|
время вам никто, никогда, ни при каких обстоятельствах не вернет.
|
||||||
|
|
||||||
|
Подготовка
|
||||||
|
---
|
||||||
|
Дальше я буду рассказывать про настройку под Linux, поскольку это моя основаня рабочая ось. Но на самом деле настройка
|
||||||
|
под Windows/MinGW и Мак не так уж и сильно отличаются.
|
||||||
|
|
||||||
|
###### соберите внешние библиотеки под каждую целевую для сборки ось, одной версии, и положите их в репозиторий.
|
||||||
|
|
||||||
|
Каждый раз выкачивать скажем BLT от разработчика - ненадежно, менять версию библиотеки на новую и несовместимую вы точно не
|
||||||
|
будете, а готовая версия, уже собранная вас наверняка устраивает по фичам, правда ведь?
|
||||||
|
|
||||||
|
Вот и заморозьте версию и оставьте эталон там, где он не потеряется. Можно прям в системе контроля версий (Git).
|
||||||
|
|
||||||
|
###### Потратьте день на изучение вендоринга в Go
|
||||||
|
|
||||||
|
Прежде всего речь про go mod. Glide, godep и прочие - официально уже более не поддерживаются.
|
||||||
|
Только не просто пролистайте мануал, а прям создайте пакет с hello world, опубликуйте на гитхабе, попробуйте его в
|
||||||
|
другой проект импортировать, поймите зачем нужны теги и что такое семантическое версионирование.
|
||||||
|
|
||||||
|
Если вы профессионально программируете (или планируете это делать) на Go - этот потраченный день вернется вам отсуствием
|
||||||
|
в вашей жизни недель головной боли из-за того, что "сборка внезапно сломалась и не работает".
|
||||||
|
|
||||||
|
Как выжить при кросс-компиляции, краткий курс
|
||||||
|
---
|
||||||
- Кросскомпиляция, CGO для Mac и Linux. CGO_ENABLED=1, mingw, локальная видимость библиотек
|
- Кросскомпиляция, CGO для Mac и Linux. CGO_ENABLED=1, mingw, локальная видимость библиотек
|
||||||
|
|
||||||
- Makefile и нафига он нужен
|
- Makefile и нафига он нужен
|
||||||
- Таргеты: Убираем бардак за собой - distclean, build
|
- Таргеты: Убираем бардак за собой - distclean, build
|
||||||
- Автоматическое тестирование, testify. Не ленитесь писать тесты!
|
- Автоматическое тестирование, testify. Не ленитесь писать тесты!
|
||||||
|
Loading…
x
Reference in New Issue
Block a user