alchemyst/story/go_game_dos_and_donts.md
2020-09-25 00:45:45 +03:00

5.7 KiB
Raw Blame History

Делать

декодирование ввода в отдельном потоке

Этот пункт исключительно только ради удобства. По умолчанию в BLT ввод отдается в виде int-ов, мне гораздо удобнее получать ввод в виде "<модификатор(ы)+><имя клавиши>", например "Ctrl+Space".

Для этого пришлось написать map[int]string и пеервести все BLT-токены для клавиатуры в строки, а также написать простенький обработчик:

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 функции обустроить всё для ее работы:

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 для некоторых видов анимации (в моем случае есть слегка анимиррованные тайлы).

реализуется это достаточно просто:

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 и прочих нагруженных местах - да будет предан анафеме.

Обратить внимание

При казалось бы передаче по значению слайсы - всё равно передаются по ссылке! (пример с комнатами и выходами)