Golang + Phaser3 = MMORPG - Klien dan Server

gambar

Pada artikel terakhir, kami membuat kekosongan dengan Anda, sehingga untuk berbicara, dasar di mana alam semesta kita akan dibuat, visualisasi menggunakan konsol mungkin terlihat baik, tetapi karakter teks membosankan dan tidak terlalu indah, dalam artikel ini kita akan fokus pada memvisualisasikan ubin kami menggunakan Phaser.js

Dalam artikel sebelumnya, proyek kami terlihat seperti ini:

gambar

Sekarang kita akan menggunakan alat lain untuk pengembangan web, saya harap Anda memiliki Node.js dan npm diinstal, jika tidak, segera instal. Jadi kami membuka terminal dan mulai:

$ npm install phaser@3.22.0

Setelah berhasil menyelesaikan tim, kita akan melihat yang berikut:

+ phaser@3.22.0
added 15 packages from 48 contributors and audited 20 packages in 4.38s

gambar

Begitu bagus, modul muncul, sekarang kita akan membuat direktori untuk klien kita.

gambar

Dalam Konten kita akan menyimpan sumber daya game, mis. sprite kami. Kami juga akan membuat dua file game.js dan MainScene.js, di direktori root (di mana file main.go berada) buat index.html
game.js - menyimpan pengaturan utama untuk game
MainScene.js - ini akan berisi kelas
index.html adegan permainan utama - halaman tempat adegan akan

segera sambungkan skrip kami ke index.html dan kami tidak akan kembali ke file ini lagi:

    <script src="node_modules/phaser/dist/phaser.js" type="module"></script>
    <script src="Client/game.js" type="module"></script>

Di MainScene.js kita akan membuat templat kelas kecil untuk adegan masa depan kita:

export {MainScene}
class MainScene extends Phaser.Scene{
constructor() {
    super({key: 'MainScene'})
}
preload() {

}
create() {

}
update() {

}
}

Di game.js tambahkan pengaturan khas sesuai keinginan Anda, inilah milik saya:

import {MainScene} from "./MainScene.js";
let config = {
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    disableContextMenu: true,
    background: 'black',
    physics: {
        default: 'arcade',
        arcadePhysics: {
            overlapBias: 1
        }
    },
    scene:[MainScene],
    pixelArt: true,
    roundPixels: true,
    antialias: true

}
let game = new Phaser.Game(config);

Sekarang kita memerlukan server HTTP, pada ini dilakukan dalam beberapa baris. Buka main.go dan buat server:

package main

import (
	"fmt"
	"html/template"
	"net/http"
)

func main() {
	//     
	http.HandleFunc("/", indexHandler)
	//      (,   .)
	http.Handle("/node_modules/phaser/dist/", http.StripPrefix("/node_modules/phaser/dist/", http.FileServer(http.Dir("./node_modules/phaser/dist/"))))
	http.Handle("/Client/", http.StripPrefix("/Client/", http.FileServer(http.Dir("./Client/"))))
	http.Handle("/Client/Content/", http.StripPrefix("/Client/Content/", http.FileServer(http.Dir("./Client/Content/"))))
	//  .   ,   8080
	err := http.ListenAndServe(":8080", nil)
	if err != nil {
		fmt.Println(err.Error())
	}
}
//   index.html,      
func indexHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Println("indexAction")
	t, _ := template.ParseFiles("index.html")
	err := t.Execute(w, "index")
	if err != nil {
		fmt.Println(err.Error())
	}
}

Yah, kami memiliki server web dan klien kami sendiri! Mari kita memulainya! Buka konsol:

$ go run main.go

Kami membuka browser dan mencoba untuk terhubung ke server kami, dalam kasus saya itu
localhost: 8080

gambar

Jika Anda melihat layar hitam, maka Anda melakukan semuanya dengan benar.

Jadi, mari kita buat penangan lain dimana kita akan menerima potongan kita dalam format json. Buat direktori terpisah dan sebut itu GameController, di sini kita akan memiliki semua penangan yang bekerja dengan data game, membuat file Map_Controller.go

Juga kita perlu ditingkatkan

Chunk.go
package Chunk

import (
	"exampleMMO/PerlinNoise"
	"fmt"
)


var TILE_SIZE = 16
var CHUNK_SIZE = 16 * 16
var PERLIN_SEED float32 = 160

type Chunk struct {
	ChunkID [2]int `json:"chunkID"`
	Map     map[Coordinate]Tile `json:"map"`
}

/*
  
*/
type Tile struct {
	Key string `json:"key"`
	X   int    `json:"x"`
	Y   int    `json:"y"`
}

/*
    
*/
type Coordinate struct {
	X int `json:"x"`
	Y int `json:"y"`
}



/*
    ,      
 [1,1]
*/
func NewChunk(idChunk Coordinate) Chunk {
	fmt.Println("New Chank", idChunk)
	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 = "Water"
					case perlinValue >= -0.01 && perlinValue < 0:
						tile.Key = "Sand"
					case perlinValue >= 0 && perlinValue <= 0.5:
						tile.Key = "Ground"
					case perlinValue > 0.5:
						tile.Key = "Mount"
					}
					chunkMap[Coordinate{X: tile.X, Y: tile.Y}] = tile

				}
			}
		}
	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 = "Water"
					case perlinValue >= -0.12 && perlinValue <= 0.5:
						tile.Key = "Ground"
					case perlinValue > 0.5:
						tile.Key = "Mount"
					}
					chunkMap[Coordinate{X: tile.X, Y: tile.Y}] = tile
				}
			}
		}
	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 = "Water"
					case perlinValue >= -0.12 && perlinValue <= 0.5:
						tile.Key = "Ground"
					case perlinValue > 0.5:
						tile.Key = "Mount"
					}
					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 = "Water"
					case perlinValue >= -0.12 && perlinValue <= 0.5:
						tile.Key = "Ground"
					case perlinValue > 0.5:
						tile.Key = "Mount"
					}
					chunkMap[Coordinate{X: tile.X, Y: tile.Y}] = tile

				}
			}
		}

	}
	chunk.Map = chunkMap
	return chunk
}


Kami baru saja menambahkan kunci json ke struktur kami dan sedikit memperbaiki kreasi. Kami
kembali ke Map_Controller,

package GameController

import (
	"encoding/json"
	"exampleMMO/Chunk"
	"fmt"
	"net/http"
)

func Map_Handler(w http.ResponseWriter, r *http.Request) {
			c:= Chunk.NewChunk(Chunk.Coordinate{1,1})
			 js, e :=json.Marshal(c)
			 if e!= nil {
			 	fmt.Println(e.Error())
			 }
			 fmt.Println(string(js))
}

dan tambahkan baris ke main.go

	http.HandleFunc("/map", GameController.Map_Handler)

Mari kita coba untuk memulai server dan pergi ke localhost: 8080 / map.

Output di terminal:

New Chank {1 1}
json: unsupported type: map[Chunk.Coordinate]Chunk.Tile

Ya, kami lupa bahwa di Golang, ketika diserialisasi, kunci peta harus berupa string. Untuk serialisasi, Go memeriksa apakah jenisnya cocok dengan antarmuka TextMarshaler, dan memanggil metode MarshalText (), kita hanya perlu membuat metode MarshalText () untuk tipe Koordinat kita. Kita
kembali ke Chunk.go dan menambahkan kode berikut:

func (t Coordinate) MarshalText() ([]byte, error) {

	return []byte("[" + strconv.Itoa(t.X) + "," + strconv.Itoa(t.Y) + "]"), nil
}

Anda dapat menulis implementasi Anda, yang paling penting adalah metode ini mengembalikan string unik. Kami akan menggunakan kunci ini untuk mengelola potongan pada klien, mari sekarang periksa cara kerja pengontrol kami, mulai server lagi dan lihat output ke konsol.

gambar

Ya, semuanya baik-baik saja, mari sekarang buat kesimpulan ke aliran, tambahkan dua baris di akhir pengontrol kami:


	w.Header().Set("Content-Type", "application/json")
	w.Write(js)

Untuk sekarang, selesaikan dengan Golang dan kembali ke klien. kita akan membutuhkan tiga judul, walaupun sebenarnya kita memiliki 4 judul, tetapi untuk saat ini kita memiliki tiga, atau bahkan dua.







Tambahkan ubin kami ke direktori Konten dan mulai bekerja dengan MainScene.js, untuk hasil pertama kami memerlukan beberapa fungsi:

class MainScene extends Phaser.Scene{
constructor() {
    super({key: 'MainScene'})

}
preload() {
    //     
    this.load.image("Ground", "Client/Content/sprGrass.png")
    this.load.image("Water", "Client/Content/sprWater1.png")
    this.load.image("Sand", "Client/Content/sprGrass.png")


}
create() {
    this.getGameMap()
}
update() {

}
//   
async getGameMap() {
    let res = await fetch("/map")
    let result = await res.json()    
    this.drawChunk(result.map)

}
//      
drawChunk(map) {
    for (let chunkKey in map) {
        this.add.image(map[chunkKey].x,map[chunkKey].y, map[chunkKey].key)
    }
}

}

Server mengembalikan bongkahan kami kepada kami dalam bentuk objek json, Anda dapat melihat strukturnya di konsol peramban:

gambar

Dan Phaser merendernya di peramban:

gambar

Kami memeriksa pekerjaan paling sederhana antara server dan klien, pada artikel berikutnya kami akan segera menggambar 9 bongkahan dan menavigasi ke dunia. Lihat semua kode dan sumber daya dari artikel di sini .

All Articles