180 lines
5.7 KiB
Markdown
180 lines
5.7 KiB
Markdown
|
||
Делать
|
||
---
|
||
#### декодирование ввода в отдельном потоке
|
||
|
||
Этот пункт исключительно только ради удобства. По умолчанию в 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
|
||
...
|
||
|
||
//Это запускается в 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
|
||
//рисуем ландшафт на карте
|
||
...
|
||
}
|
||
...
|
||
}
|
||
```
|
||
TODO: троттлинг
|
||
|
||
|
||
|
||
#### Каналы состояний и их Listеner-ы
|
||
|
||
Ну вобщем-то из кода выше видно, как это работает. Для состояния движка ок, для состояния игры - кмк больше подойдет обычная или timed queue.
|
||
|
||
Не делать
|
||
==
|
||
|
||
- Замыкания (не зная точно, где и как вы их уничтожите). Просто наберите в гугле golang closure memleak
|
||
|
||
- reflect в main loop. Лишь **только** выкинув рефлкесию и больше ничего не делая - я снизил потребление CPU приложением
|
||
**вдвое**. Это удобная штука, не спорю, но пользоваться ей надо при загрузке ресурсов, при сохранении/загрузке состояния
|
||
приложения - т.е. при разовых операциях. Как оказалось, она _очень_ дорогая по CPU. Кто пользуется ей в main loop, ORM
|
||
и прочих нагруженных местах - да будет предан анафеме.
|
||
|
||
|
||
Обратить внимание
|
||
===
|
||
При казалось бы передаче по значению слайсы - всё равно передаются по ссылке! (пример с комнатами и выходами)
|
||
|