Конфигурация многопользователских игр
Задача сделать игровой конфиг, который будет легко доступен другими разработчиками, геймдизайнерами и на клиенте и на сервере.
Есть множество требований к такому файлу настроек как и множество реализаций. Напомню я делаю онлайн игру с жестко типизированным Go на сервере и Cocos 2D js на клиенте. Один из вариантов - это делать конфиг файл отдельным XML или JSON файлом, который будет доступен с двух сторон и для двух сторон будет экспортироваться. Это хороший подход, но жесткая типизация Go будет требовать вместе с изменением конфига - изменения структуры, с помощью которой он будет читаться
JSON в Go
JSON в Go требует описания всей структуры JSON файла в виде структур. Так например выглядит JSON парсер для сцен из Cocos Creator:

type ContentSize struct {
	Type   string  `json:"__type__,omitempty"`
	Width  float64 `json:"width,omitempty"`
	Height float64 `json:"height,omitempty"`
}

type CoconNode struct {
	Id          int                 `json:"__id__"`
	ParamId     int    		`json:"ParamId,omitempty"`

}
type Position struct {
	X float64
	Y float64
}

type CoconObject struct {
	ContentSize ContentSize `json:"_contentSize"`
	Type        string      `json:"__type__"`
	Id          int         `json:"_id"`
	NodeId      int         `json:"__id__"`

	ParamId     int    	`json:"ParamId,omitempty"`
	Enemies     []int	`json:"enemies,omitempty"`
	Position    Position    `json:"_position"`
	Name        string      `json:"_name"`
	Node        CoconNode   `json:"node,omitempty"`
	Parent      CoconNode   `json:"_parent,omitempty"`
	Components  []CoconNode `json:"_components,omitempty"`
}


func (l *Level) loadLevel(level string) {
	file, err := os.Open(dirPath + level)
	if err != nil {
		fmt.Println("Error opening file:", err)
		return
	}

	defer file.Close()

	b, _ := ioutil.ReadAll(file)

	objects := []CoconObject{}

	json.Unmarshal(b, &objects)

	l.objects = objects
	l.createStatic()
}
Принцип простой, описываем типизированную структуру, пока файл соответствует структуре, мы получаем на выходе заполненный объект, со всеми типами, переменными и массивами. Но при небольших отклонениях от этой структуры Go вываливается с ошибкой

Поэтому я пришел к решению хранить конфиг в config.go файле, и при запуске сервера генерировать js файл, который будет загружаться на клиенте. Я исходил из предпосылки, что у каждого разработчика будет локальный экземпляр сервера и вносить изменения в config.go файл не составит труда.

Конфиг содержит вполне себе игровые переменные. Например Class у монстра - это функция фабрика, которая создает монстры определенного класса. Задача - избавиться от всех слоев загрузок, экспорта переменных и глубинных разделение кода
type ColGroups struct {
	Wall 		Bitmask
	Player 		Bitmask
	Bullet 		Bitmask
	Door 		Bitmask
	Monster 	Bitmask
}

type MonsterBehaviours struct {
	SeekerBehaviour *SeekerBehaviour
}

type Commands struct {
	CMD_USE string
}

type PhysConfig struct {
	PlayerSlowdown float64
	PlayerRadius   float64
	BulletRadius   float64
}

type ClientConfig struct {
	Host string
}

type Weapon struct {
	DefaultAmmo		int
	Ammo			int
	ID			int
	ProjectileSpeed int
	Dmg 			float64
	FireRate		time.Duration
}

type WeaponsConfig struct {
	Default Weapon
}

type Config struct {
	MonsterBehaviours 	MonsterBehaviours `json:"-"`
	Client 				ClientConfig
	Weapons 			WeaponsConfig
	ColGroups 			ColGroups
	Commands 			Commands
	Phys 				PhysConfig
	Player 				PlayerConfig
	Server 				ServerConfig 	`json:"-"`
	Monsters 			MonstersConfig
}

type ServerConfig struct {
	SpawnersUpdateTimer		time.Duration
	ResourcePath			string
	MaxRoomsPlayers 		int
	FrameDelay 				time.Duration
	ServerPhysDelta 		float64
	WriteWait 			time.Duration
	PongWait  			time.Duration
	PingPeriod 			time.Duration
	MaxMessageSize 			int64
}

type MobCreateFunc func(*MobConfig, *Config, int) (Component)

type MobConfig struct {
	PrefabFile				string
	Name					string
	Size					float64
	HPBase					float64
	HPRange					float64
	MeleeDamage				float64
	WalkSpeed				float64
	Class					MobCreateFunc `json:"-"`
}

type MonstersConfig []MobConfig

type PlayerConfig struct {
	Speed float64
}
Не все настройки хочется отдавать клиенту. Для этого в Go есть json тег json:"-". Записи, помеченные таким тегом не попадут в JSON

Давайте заполним конфиг
var config Config = Config{
	MonsterBehaviours: MonsterBehaviours{
		SeekerBehaviour: &SeekerBehaviour{},
	},

	Monsters: MonstersConfig{
		MobConfig{
			PrefabFile:	"monsters/monster1.prefab",
			Name:		"Melee Monster",
			Size:		30,
			HPBase:        	3,
			HPRange:        1,
			MeleeDamage:    1,
			WalkSpeed:      80,

			//Mapping method get a class
			Class:	func (a *MobConfig, c *Config, id int) Component {

				var b Behaviour = &SeekerBehaviour{
					LookRadius:		400.,
					SwitchFreq:		time.Second*5,
				}

				return &Mob{Dmg: 1,
					HitFreq: time.Second*1,
					HP: a.HPBase,
					WalkSpeed: a.WalkSpeed,
					UniqueTypeID: id,
					Behavior: b}
			},
		},

		MobConfig{
			PrefabFile:	"monsters/monster1.prefab",
			Name:		"Range Monster",
			Size:		30,
			HPBase:        	3,
			HPRange:        1,
			MeleeDamage:    1,
			WalkSpeed:      100,
			Class:	func (a *MobConfig, c *Config, id int) Component {
				return &Mob{HP: a.HPBase, 
					WalkSpeed: a.WalkSpeed, 
					UniqueTypeID: id}
			},
		},
	},


	Client: ClientConfig{
		Host: "localhost:5050",
	},

	Player: PlayerConfig {
		Speed: 20,
	},

	Weapons: WeaponsConfig {
		Default: Weapon{
			ID: 				0,
			FireRate:			time.Millisecond*300,
			DefaultAmmo:			100,
			ProjectileSpeed: 		300,
			Dmg: 				1,
		},
	},

	//SERVER SETTINGS
	Server: ServerConfig{
		SpawnersUpdateTimer:	4*time.Second,
		ResourcePath:		"/front/cocos/assets/resources/",
		MaxRoomsPlayers:	20,
		WriteWait: 		60 * time.Second,
		PongWait: 		60 * time.Second,
		PingPeriod: 		(60 * time.Second * 9) / 10,
		MaxMessageSize: 	1024,
		ServerPhysDelta: 	1/ 60.,
		FrameDelay: 		time.Second / 60 ,
	},

	//PLAYER COMMANDS
	Commands: Commands{
		CMD_USE: 		"USE",
	},

	Phys: PhysConfig{
		PlayerSlowdown: 	0.6,
		PlayerRadius: 		30,
		BulletRadius: 		20,
	},

	//COLLISION GROUPS
	ColGroups: ColGroups{
		Wall: 				1 << 0,
		Player: 			1 << 1,
		Bullet: 			1 << 2,
		Door: 				1 << 3,
		Monster:			1 << 4,
	},
}
Окей, мы описали настройки физики, монстров, сервера, клиента, графики и прочее. Теперь момент, где начинается магия. Давайте динамически создавать config.js:
func saveConfig() {
	ba, _ := json.Marshal(config)

	body := "module.exports = " + string(ba)
	err := ioutil.WriteFile(dirPath + "/front/cocos/assets/scripts/config.js", 
[]byte(body), 0644)
	println(err)
}
Вот и все волшебство, мы генерируем валидный js файл. Не смотря на то, что он получается нечитабельный - без отступов, любое IDE создает удобные подсказки по его структуре. В итоге мы получили одни и те же константы на сервере и клиенте в удобном для разработки виде
Моя почта ddearcj@gmail.com
Made on
Tilda