На русскоязычных ресурсах трудно найти подходящую информацию, возможно этот материал позволит вам понять некоторые основы для создания многопользовательских игр и не только. Я планирую сделать серию статей по созданию 2.5D MMORPG, то бишь в изометрии, наш мир будет разделен на процедурно-генерируемые чанки, состоящие из тайтлов. Сервер будет написан на языке Golang, который как мне кажется отлично для этого подходит, клиентская часть будет на JavaScript с использованием фреймворка — Phaser.js
Создаем генерацию мира
И так в этой статье мы напишем генератор чанков для ММО на Голанг, мы не будем пока рассматривать Phaser. Для процедурной генерации нам потребуется шумовая функция, мы будем использовать Шум Перлина, рекомендую ознакомиться с данной статьей и переписать код на Го или взять мой вариант.
package PerlinNoise
import (
"math"
"math/rand"
)
func Noise(x, y float32) float32 {
//Coordinate left and top vertex square
left := float32(math.Floor(float64(x)))
top := float32(math.Floor(float64(y)))
//Local coordinate
localPoinX := x - left
localPoiny := y - top
topLeft := getRandomVector(left, top)
topRight := getRandomVector(left+1, top)
bottomLeft := getRandomVector(left, top+1)
bottomRight := getRandomVector(left+1, top+1)
// Вектора от вершин до точки внутри
DtopLeft := []float32{localPoinX, localPoiny}
DtopRight := []float32{localPoinX - 1, localPoiny}
DbottomLeft := []float32{localPoinX, localPoiny - 1}
DbottomRight := []float32{localPoinX - 1, localPoiny - 1}
//Скалярное произведение
tx1 := dot(DtopLeft, topLeft)
tx2 := dot(DtopRight, topRight)
bx1 := dot(DbottomLeft, bottomLeft)
bx2 := dot(DbottomRight, bottomRight)
//параметры для нелинейности
pointX := curve(localPoinX)
pointY := curve(localPoiny)
//Интерполяция
tx := lerp(tx1, tx2, pointX)
bx := lerp(bx1, bx2, pointX)
tb := lerp(tx, bx, pointY)
return tb
}
func getRandomVector(x, y float32) []float32 {
rand.Seed(int64(x * y))
v := rand.Intn(3)
switch v {
case 0:
return []float32{-1, 0}
case 1:
return []float32{1, 0}
case 2:
return []float32{0, 1}
default:
return []float32{0, -1}
}
}
func dot(a []float32, b []float32) float32 {
return (a[0]*b[0] + b[1]*a[1])
}
func lerp(a, b, c float32) float32 {
return a*(1-c) + b*c
}
func curve(t float32) float32 {
return (t * t * t * (t*(t*6-15) + 10))
}
Давайте создадим небольшой проект, где протестируем работоспособность нашей функции, вот структура моего проекта:
Добавьте в файл main.go следующее:
func main() {
var a, b float32= 1330, 2500
v:= PerlinNoise.Noise(a/2500, b/2500)
fmt.Println(v)
}
Будьте внимательны с типами чисел, всегда указывайте типы явно, это избавит вас от проблем в будущем, вывод функции:
-0.23416707
И так у нас есть шумовая функция для генерации наших миров. Давайте приступим к созданию чанков. Создадим директорию Chunk и файл Chunk.go в ней и сразу определим основные константы:
var TILE_SIZE = 16
var CHANK_SIZE = TILE_SIZE * TILE_SIZE
var PERLIN_SEED float32 = 150
TILE_SIZE — это разрешение наших будущих чанков в пикселях
CHANK_SIZE — размер чанка, в данном случае 16*16
PERLIN_SEED — здесь вы можете поставить любое число, чем выше оно, тем более однороден шум, т.е. если вы хотите небольшие острова, то ставьте число меньше, если огромные материки то выше.
Далее создадим тип данных для координат:
type Coordinate struct {
X int
Y int
}
Данный тип нам очень пригодится в будущем, а сейчас создадим еще одну важную функцию, для определения уникальных координат нашего чанка в дальнейшем будем называть их ID:
func GetChunkID(x, y int) Coordinate {
tileX := float64(float64(x) / float64(TILE_SIZE))
tileY := float64(float64(y) / float64(TILE_SIZE))
var ChunkID Coordinate
if tileX < 0 {
ChunkID.X = int(math.Floor(tileX / float64(TILE_SIZE)))
} else {
ChunkID.X = int(math.Ceil(tileX / float64(TILE_SIZE)))
}
if tileY < 0 {
ChunkID.Y = int(math.Floor(tileY / float64(TILE_SIZE)))
} else {
ChunkID.Y = int(math.Ceil(tileY / float64(TILE_SIZE)))
}
if tileX == 0 {
ChunkID.X = 1
}
if tileY == 0 {
ChunkID.Y = 1
}
return ChunkID
}
Функция определения ID чанка достаточно простая, мы просто делим позицию на карте на размер тайла, а затем полученный результат делим еще раз на размер тайла с округлением в меньшую или большую сторону, в зависимости от ID чанка т.к. наш мир будет генерироваться бесконечно в любом направлении.
Далее добавим наш строительный блок для создания чанка, это тайл и сам чанк:
type Chunk struct {
ChunkID [2]int
Map map[Coordinate]Tile
}
type Tile struct {
Key string
X int
Y int
}
Чанк содержит карту тайлов. Тайлы хранят свои координаты и ключ(ключ это тип вашего тайтла: земля, вода, горы и т.д.)
Теперь перейдем к самому главному, функции создания нашего чанка, я взял свою рабочую функцию из проекта и немного переделал для этой статьи:
func NewChunk(idChunk Coordinate) Chunk {
// Помечаем чанк уникальным ИД
chunk := Chunk{ChunkID: [2]int{idChunk.X, idChunk.Y}}
// Максимальный координаты чанка
var chunkXMax, chunkYMax int
// Инициируем карту для тайлов
var chunkMap map[Coordinate]Tile
chunkMap = make(map[Coordinate]Tile)
// Определяем максимальные координаты для чанка
chunkXMax = idChunk.X * CHUNK_SIZE
chunkYMax = idChunk.Y * CHUNK_SIZE
switch {
// логика генерации чанка для отрицательных координат
case chunkXMax < 0 && chunkYMax < 0:
{
for x := chunkXMax + CHUNK_SIZE; x > chunkXMax; x -= TILE_SIZE {
for y := chunkYMax + CHUNK_SIZE; y > chunkYMax; y -= TILE_SIZE {
//Координаты для изображения тайла на клиенте
posX := float32(x - (TILE_SIZE / 2))
posY := float32(y + (TILE_SIZE / 2))
tile := Tile{}
tile.X = int(posX)
tile.Y = int(posY)
perlinValue := PerlinNoise.Noise(posX/PERLIN_SEED, posY/PERLIN_SEED)
switch {
case perlinValue < -0.01:
tile.Key = "~" //Вода
case perlinValue >= -0.01 && perlinValue <= 0.5:
tile.Key = "1" //Земля
case perlinValue > 0.5:
tile.Key = "^" // Горы
}
chunkMap[Coordinate{X: tile.X, Y: tile.Y}] = tile
}
}
}
//Для отрицательной X координаты
case chunkXMax < 0:
{
for x := chunkXMax + CHUNK_SIZE; x > chunkXMax; x -= TILE_SIZE {
for y := chunkYMax - CHUNK_SIZE; y < chunkYMax; y += TILE_SIZE {
posX := float32(x - (TILE_SIZE / 2))
posY := float32(y + (TILE_SIZE / 2))
tile := Tile{}
tile.X = int(posX)
tile.Y = int(posY)
perlinValue := PerlinNoise.Noise(posX/PERLIN_SEED, posY/PERLIN_SEED)
switch {
case perlinValue < -0.12:
tile.Key = "~"
case perlinValue >= -0.12 && perlinValue <= 0.5:
tile.Key = "1"
case perlinValue > 0.5:
tile.Key = "^"
}
chunkMap[Coordinate{X: tile.X, Y: tile.Y}] = tile
}
}
}
// отрицательной Y координаты
case chunkYMax < 0:
{
for x := chunkXMax - CHUNK_SIZE; x < chunkXMax; x += TILE_SIZE {
for y := chunkYMax + CHUNK_SIZE; y > chunkYMax; y -= TILE_SIZE {
posX := float32(x + (TILE_SIZE / 2))
posY := float32(y - (TILE_SIZE / 2))
tile := Tile{}
tile.X = int(posX)
tile.Y = int(posY)
perlinValue := PerlinNoise.Noise(posX/PERLIN_SEED, posY/PERLIN_SEED)
switch {
case perlinValue < -0.12:
tile.Key = "~"
case perlinValue >= -0.12 && perlinValue <= 0.5:
tile.Key = "1"
case perlinValue > 0.5:
tile.Key = "^"
}
chunkMap[Coordinate{X: tile.X, Y: tile.Y}] = tile
}
}
}
// Для положительных координат
default:
{
for x := chunkXMax - CHUNK_SIZE; x < chunkXMax; x += TILE_SIZE {
for y := chunkYMax - CHUNK_SIZE; y < chunkYMax; y += TILE_SIZE {
posX := float32(x + (TILE_SIZE / 2))
posY := float32(y + (TILE_SIZE / 2))
tile := Tile{}
tile.X = int(posX)
tile.Y = int(posY)
perlinValue := PerlinNoise.Noise(posX/PERLIN_SEED, posY/PERLIN_SEED)
switch {
case perlinValue < -0.12:
tile.Key = "~"
case perlinValue >= -0.12 && perlinValue <= 0.5:
tile.Key = "1"
case perlinValue > 0.5:
tile.Key = "^"
}
chunkMap[Coordinate{X: tile.X, Y: tile.Y}] = tile
}
}
}
}
chunk.Map = chunkMap
return chunk
}
И так, в данной функции или скорее конструкторе нашего чанка мы определяем максимальные координаты чанка, к которым мы будем последовательно двигаться, заполняя тайлы необходимой информацией. ChunkMax определяются тоже достаточно просто, для этого умножаем ID чанка на его размер(CHUNK_SIZE), т.е при ID{1;1} наши координаты chunkXMax и chunkYMax будут равны 256.
В posX/posY мы определяем координаты для вставки нашей графики:
posX := float32(x + (TILE_SIZE / 2))
posY := float32(y + (TILE_SIZE / 2))
Используем switch для выбора логики в зависимости от значения ID нашего чанка(Могут быть положительные и отрицательные значения). Ключ тайла будет определять шум перлина, например, если шум перлина ниже 0 это будет вода, выше — суша. Мы сделаем так:
case perlinValue < -0.12:
tile.Key = "~" // Вода
case perlinValue >= -0.12 && perlinValue <= 0.5:
tile.Key = "1" // Земля
case perlinValue > 0.5:
tile.Key = "^" // Горы
Посмотрим как работает наша функция, заменяем код в main следующим содержимым:
func main() {
coord := Chunk.Coordinate{Y: 1, X: 1}
chunk := Chunk.NewChunk(coord)
m := chunk.Map
out := os.Stdout
for y := 8; y < 16*16; y += 16 {
for x := 8; x < 16*16; x += 16 {
c := Chunk.Coordinate{X: x, Y: y}
out.Write([]byte(m[c].Key))
}
out.Write([]byte("n"))
}
}
Вывод:
11~~~11111111111
11~~~11111111111
11~~~~1111111111
11~~~~1111111111
11~~~~~111111111
11~~~~~~1111111~
11~~~~~~~~~11~~~
11~~~~~~~~~~~~~~
11~~~~~~~~~~~~~~
11~~~~~~~~~~~~~~
11~~~~~~~~~~~~~~
11~~~~~~~~~~~~~~
11~~~~~~~~~~~~~~
11~~~~~~~~~~~~~~
11~~~~~~~~~~1111
11~~~~~~~~~11111
Смотрится неплохо, вы можете поменять функцию вывода и поиграться с параметрами:
11111~~~~~~~~111111111111111111111111111111~~~~~~~11111111111111
11111~~~~~~~~111111111111111111111111111111~~~~~~~11111111111111
11111~~~~~~~~111111111111111111111111111111~~~~~~~11111111111111
11111~~~~~~~~111111111111111111111111111111~~~~~~~~1111111111111
11111~~~~~~~~11111111111111111111111111111~~~~~~~~~1111111111111
11111~~~~~~~~11111111111111111111111111111~~~~~~~~~1111111111111
11111~~~~~~~~~111111111111111111111111111~~~~~~~~~~~111111111111
11111~~~~~~~~~111111111111111111111111111~~~~~~~~~~~~11111111111
11111~~~~~~~~~~1111111111111111111111111~~~~~~~~~~~~~11111111111
11111~~~~~~~~~~~11111111111111111111111~~~~~~~~~~~~~~~1111111111
11111~~~~~~~~~~~1111111111111111111111~~~~~~~~~~~~~~~~~111111111
11111~~~~~~~~~~~~~11111111111111111111~~~~~~~~~~~~~~~~~111111111
11111~~~~~~~~~~~~~~111111111111111111~~~~~~~~~~~~~~~~~~~11111111
11111~~~~~~~~~~~~~~~~11111111111111~~~~~~~~~~~~~~~~~~~~~~1111111
11111~~~~~~~~~~~~~~~~~~11111111111~~~~~~~~~~~~~~~~~~~~~~~~111111
11111~~~~~~~~~~~~~~~~~~~~~111111~~~~~~~~~~~~~~~~~~~~~~~~~~~11111
11111~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~1111
11111~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~111
11111~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~11
11111~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~1
11111~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
11111~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
11111~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
11111~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
11111~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
11111~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
11111~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1111~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1111~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1111~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1111~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1111~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
11111~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
11111~~~~~~~~~~~~~~~~~~~~~~~~~~~1111111111~~~~~~~~~~~~~~~~~~~~~~
11111~~~~~~~~~~~~~~~~~~~~~~~~~111111111111111~~~~~~~~~~~~~~~~~~~
11111~~~~~~~~~~~~~~~~~~~~~~~1111111111111111111~~~~~~~~~~~~~~~~~
11111~~~~~~~~~~~~~~~~~~~~~~111111111111111111111~~~~~~~~~~~~~~~~
11111~~~~~~~~~~~~~~~~~~~~~11111111111111111111111~~~~~~~~~~~~~~~
11111~~~~~~~~~~~~~~~~~~~~1111111111111111111111111~~~~~~~~~~~~~~
11111~~~~~~~~~~~~~~~~~~~111111111111111111111111111~~~~~~~~~~~~~
11111~~~~~~~~~~~~~~~~~~11111111111111111111111111111~~~~~~~~~~~~
11111~~~~~~~~~~~~~~~~~~111111111111111111111111111111~~~~~~~~~~~
11111~~~~~~~~~~~~~~~~~1111111111111111111111111111111~~~~~~~~~~~
11111~~~~~~~~~~~~~~~~111111111111111111111111111111111~~~~~~~~~~
11111~~~~~~~~~~~~~~~~1111111111111111111111111111111111~~~~~~~~~
11111~~~~~~~~~~~~~~~~11111111111111111111111111111111111~~~~~~~~
11111~~~~~~~~~~~~~~~~11111111111111111111111111111111111~~~~~~~~
11111~~~~~~~~~~~~~~~~111111111111111111111111111111111111~~~~~~~
11111~~~~~~~~~~~~~~~~1111111111111111111111111111111111111~~~~~~
11111~~~~~~~~~~~~~~~~1111111111111111111111111111111111111~~~~~~
11111~~~~~~~~~~~~~~~~11111111111111111111111111111111111111~~~~~
11111~~~~~~~~~~~~~~~~11111111111111111111111111111111111111~~~~~
11111~~~~~~~~~~~~~~~~~11111111111111111111111111111111111111~~~~
11111~~~~~~~~~~~~~~~~~11111111111111111111111111111111111111~~~~
11111~~~~~~~~~~~~~~~~~~11111111111111111111111111111111111111~~~
1111~~~~~~~~~~~~~~~~~~~11111111111111111111111111111111111111~~~
1111~~~~~~~~~~~~~~~~~~~~1111111111111111111111111111111111111~~~
1111~~~~~~~~~~~~~~~~~~~~~111111111111111111111111111111111111~~~
1111~~~~~~~~~~~~~~~~~~~~~1111111111111111111111111111111111111~~
1111~~~~~~~~~~~~~~~~~~~~~~111111111111111111111111111111111111~~
1111~~~~~~~~~~~~~~~~~~~~~~~11111111111111111111111111111111111~~
1111~~~~~~~~~~~~~~~~~~~~~~~~1111111111111111111111111111111111~~
1111~~~~~~~~~~~~~~~~~~~~~~~~1111111111111111111111111111111111~~
1111~~~~~~~~~~~~~~~~~~~~~~~~~111111111111111111111111111111111~~
Код:
package Chunk
import (
"PerlinNoise"
"math"
)
var TILE_SIZE = 16
var CHUNK_SIZE = 32 * 32
var PERLIN_SEED float32 = 600
type Coordinate struct {
X int `json:"x"`
Y int `json:"y"`
}
type Chunk struct {
ChunkID [2]int
Map map[Coordinate]Tile
}
type Tile struct {
Key string
X int
Y int
}
func GetChunkID(x, y int) Coordinate {
tileX := float64(x)
tileY := float64(y)
var ChunkID Coordinate
if tileX < 0 {
ChunkID.X = int(math.Floor(tileX / float64(TILE_SIZE)))
} else {
ChunkID.X = int(math.Ceil(tileX / float64(TILE_SIZE)))
}
if tileY < 0 {
ChunkID.Y = int(math.Floor(tileY / float64(TILE_SIZE)))
} else {
ChunkID.Y = int(math.Ceil(tileY / float64(TILE_SIZE)))
}
if tileX == 0 {
ChunkID.X = 1
}
if tileY == 0 {
ChunkID.Y = 1
}
return ChunkID
}
func NewChunk(idChunk Coordinate) Chunk {
// Помечаем чанк уникальным ИД
chunk := Chunk{ChunkID: [2]int{idChunk.X, idChunk.Y}}
// Максимальный координаты чанка
var chunkXMax, chunkYMax int
// Инициируем карту для тайлов
var chunkMap map[Coordinate]Tile
chunkMap = make(map[Coordinate]Tile)
// Определяем максимальные координаты для чанка
chunkXMax = idChunk.X * CHUNK_SIZE
chunkYMax = idChunk.Y * CHUNK_SIZE
switch {
// логика генерации чанка для отрицательных координат
case chunkXMax < 0 && chunkYMax < 0:
{
for x := chunkXMax + CHUNK_SIZE; x > chunkXMax; x -= TILE_SIZE {
for y := chunkYMax + CHUNK_SIZE; y > chunkYMax; y -= TILE_SIZE {
//Координаты для изображения тайла на клиенте
posX := float32(x - (TILE_SIZE / 2))
posY := float32(y + (TILE_SIZE / 2))
tile := Tile{}
tile.X = int(posX)
tile.Y = int(posY)
perlinValue := PerlinNoise.Noise(posX/PERLIN_SEED, posY/PERLIN_SEED)
switch {
case perlinValue < -0.01:
tile.Key = "~" //Вода
case perlinValue >= -0.01 && perlinValue <= 0.5:
tile.Key = "1" //Земля
case perlinValue > 0.5:
tile.Key = "^" // Горы
}
chunkMap[Coordinate{X: tile.X, Y: tile.Y}] = tile
}
}
}
//Для отрицательной X координаты
case chunkXMax < 0:
{
for x := chunkXMax + CHUNK_SIZE; x > chunkXMax; x -= TILE_SIZE {
for y := chunkYMax - CHUNK_SIZE; y < chunkYMax; y += TILE_SIZE {
posX := float32(x - (TILE_SIZE / 2))
posY := float32(y + (TILE_SIZE / 2))
tile := Tile{}
tile.X = int(posX)
tile.Y = int(posY)
perlinValue := PerlinNoise.Noise(posX/PERLIN_SEED, posY/PERLIN_SEED)
switch {
case perlinValue < -0.12:
tile.Key = "~"
case perlinValue >= -0.12 && perlinValue <= 0.5:
tile.Key = "1"
case perlinValue > 0.5:
tile.Key = "^"
}
chunkMap[Coordinate{X: tile.X, Y: tile.Y}] = tile
}
}
}
// отрицательной Y координаты
case chunkYMax < 0:
{
for x := chunkXMax - CHUNK_SIZE; x < chunkXMax; x += TILE_SIZE {
for y := chunkYMax + CHUNK_SIZE; y > chunkYMax; y -= TILE_SIZE {
posX := float32(x + (TILE_SIZE / 2))
posY := float32(y - (TILE_SIZE / 2))
tile := Tile{}
tile.X = int(posX)
tile.Y = int(posY)
perlinValue := PerlinNoise.Noise(posX/PERLIN_SEED, posY/PERLIN_SEED)
switch {
case perlinValue < -0.12:
tile.Key = "~"
case perlinValue >= -0.12 && perlinValue <= 0.5:
tile.Key = "1"
case perlinValue > 0.5:
tile.Key = "^"
}
chunkMap[Coordinate{X: tile.X, Y: tile.Y}] = tile
}
}
}
// Для положительных координат
default:
{
for x := chunkXMax - CHUNK_SIZE; x < chunkXMax; x += TILE_SIZE {
for y := chunkYMax - CHUNK_SIZE; y < chunkYMax; y += TILE_SIZE {
posX := float32(x + (TILE_SIZE / 2))
posY := float32(y + (TILE_SIZE / 2))
tile := Tile{}
tile.X = int(posX)
tile.Y = int(posY)
perlinValue := PerlinNoise.Noise(posX/PERLIN_SEED, posY/PERLIN_SEED)
switch {
case perlinValue < -0.12:
tile.Key = "~"
case perlinValue >= -0.12 && perlinValue <= 0.5:
tile.Key = "1"
case perlinValue > 0.5:
tile.Key = "^"
}
chunkMap[Coordinate{X: tile.X, Y: tile.Y}] = tile
}
}
}
}
chunk.Map = chunkMap
return chunk
}
package PerlinNoise
import (
"math"
"math/rand"
)
func Noise(x, y float32) float32 {
//Coordinate left and top vertex square
left := float32(math.Floor(float64(x)))
top := float32(math.Floor(float64(y)))
//Local coordinate
localPoinX := x - left
localPoiny := y - top
topLeft := getRandomVector(left, top)
topRight := getRandomVector(left+1, top)
bottomLeft := getRandomVector(left, top+1)
bottomRight := getRandomVector(left+1, top+1)
// Вектора от вершин до точки внутри
DtopLeft := []float32{localPoinX, localPoiny}
DtopRight := []float32{localPoinX - 1, localPoiny}
DbottomLeft := []float32{localPoinX, localPoiny - 1}
DbottomRight := []float32{localPoinX - 1, localPoiny - 1}
//Скалярное произведение
tx1 := dot(DtopLeft, topLeft)
tx2 := dot(DtopRight, topRight)
bx1 := dot(DbottomLeft, bottomLeft)
bx2 := dot(DbottomRight, bottomRight)
//параметры для нелинейности
pointX := curve(localPoinX)
pointY := curve(localPoiny)
//Интерполяция
tx := lerp(tx1, tx2, pointX)
bx := lerp(bx1, bx2, pointX)
tb := lerp(tx, bx, pointY)
return tb
}
func getRandomVector(x, y float32) []float32 {
rand.Seed(int64(x * y))
v := rand.Intn(3)
switch v {
case 0:
return []float32{-1, 0}
case 1:
return []float32{1, 0}
case 2:
return []float32{0, 1}
default:
return []float32{0, -1}
}
}
func dot(a []float32, b []float32) float32 {
return (a[0]*b[0] + b[1]*a[1])
}
func lerp(a, b, c float32) float32 {
return a*(1-c) + b*c
}
func curve(t float32) float32 {
return (t * t * t * (t*(t*6-15) + 10))
}
package main
import (
"fmt"
"habr/Chunk"
"os"
)
func main() {
coord:= Chunk.GetChunkID(0,0)
fmt.Println(coord)
chunk := Chunk.NewChunk(coord)
m := chunk.Map
out := os.Stdout
for y := 8; y < 32*32; y += 16 {
for x := 8; x < 32*32; x += 16 {
c := Chunk.Coordinate{X: x, Y: y}
out.Write([]byte(m[c].Key))
}
out.Write([]byte("n"))
}
}
В следующей статье, мы рассмотрим работу с HTTP а если затронем то и WS подключением. Создадим Тип игровой карты, которую будем сериализировать в json формат для отрисовки на клиенте и вообще посмотрим как нам взаимодействовать с клиентом.