write simple version engine and try write asset packer
This commit is contained in:
parent
f990b270f9
commit
72b8118c60
52
cmd/test/main.go
Normal file
52
cmd/test/main.go
Normal file
@ -0,0 +1,52 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"git.influ.su/artmares/art3de/pkg/engine"
|
||||
"git.influ.su/artmares/art3de/pkg/engine/renderer"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Создаем движок
|
||||
eng := engine.NewEngine("My Game", 1280, 1024)
|
||||
|
||||
// Создаем игру
|
||||
game := NewGame()
|
||||
|
||||
// Запускаем
|
||||
if err := eng.Run(game); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
}
|
||||
|
||||
type Game struct {
|
||||
renderer *renderer.Renderer
|
||||
width, height int
|
||||
}
|
||||
|
||||
func NewGame() *Game {
|
||||
return &Game{}
|
||||
}
|
||||
|
||||
func (g *Game) Init() error {
|
||||
println("Game initialized!")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *Game) Update(deltaTime float32) {
|
||||
// Обновление логики игры
|
||||
}
|
||||
|
||||
func (g *Game) Draw(r *renderer.Renderer) {
|
||||
// Отрисовка игровых объектов
|
||||
// Пока просто очищаем экран
|
||||
}
|
||||
|
||||
func (g *Game) OnResize(width, height int) {
|
||||
g.width = width
|
||||
g.height = height
|
||||
if g.renderer != nil {
|
||||
g.renderer.SetViewport(0, 0, int32(width), int32(height))
|
||||
}
|
||||
}
|
||||
5
go.mod
5
go.mod
@ -1,3 +1,8 @@
|
||||
module git.influ.su/artmares/art3de
|
||||
|
||||
go 1.25.6
|
||||
|
||||
require (
|
||||
github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 // indirect
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20250301202403-da16c1255728 // indirect
|
||||
)
|
||||
|
||||
4
go.sum
Normal file
4
go.sum
Normal file
@ -0,0 +1,4 @@
|
||||
github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 h1:5BVwOaUSBTlVZowGO6VZGw2H/zl9nrd3eCZfYV+NfQA=
|
||||
github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20250301202403-da16c1255728 h1:RkGhqHxEVAvPM0/R+8g7XRwQnHatO0KAuVcwHo8q9W8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20250301202403-da16c1255728/go.mod h1:SyRD8YfuKk+ZXlDqYiqe1qMSqjNgtHzBTG810KUagMc=
|
||||
1
pkg/assets/hybrid_format.go
Normal file
1
pkg/assets/hybrid_format.go
Normal file
@ -0,0 +1 @@
|
||||
package assets
|
||||
213
pkg/assets/res_format.go
Normal file
213
pkg/assets/res_format.go
Normal file
@ -0,0 +1,213 @@
|
||||
package assets
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
// RESHeader зоголовок оригинального файла
|
||||
type RESHeader struct {
|
||||
Magic [4]byte // 0x3C, 0xE2, 0x9C, 0x01
|
||||
FilesCount uint32
|
||||
FileTableOffset uint32
|
||||
NamesSize uint32
|
||||
}
|
||||
|
||||
// RESFileRecord запись файла в оригинальном RES
|
||||
type RESFileRecord struct {
|
||||
NextIndex int32
|
||||
FileSize uint32
|
||||
FileOffset uint32
|
||||
LastChange uint32
|
||||
NameLen uint16
|
||||
NameOffset uint32
|
||||
}
|
||||
|
||||
// RESFile информация о файле в RES
|
||||
type RESFile struct {
|
||||
Name string
|
||||
Size uint32
|
||||
Offset uint32
|
||||
LastChange time.Time
|
||||
NameLen uint16 // Добавим для корректной работы
|
||||
}
|
||||
|
||||
// RESArchive представляет оригинальный RES архив
|
||||
type RESArchive struct {
|
||||
File *os.File
|
||||
Header RESHeader
|
||||
Files []RESFile
|
||||
isOpen bool
|
||||
}
|
||||
|
||||
// OpenRES открывает оригинальный RES файл
|
||||
func OpenRES(path string) (*RESArchive, error) {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open RES file: %w", err)
|
||||
}
|
||||
|
||||
archive := &RESArchive{
|
||||
File: file,
|
||||
isOpen: true,
|
||||
}
|
||||
|
||||
// Читаем заголовок
|
||||
if err = binary.Read(file, binary.LittleEndian, &archive.Header); err != nil {
|
||||
_ = file.Close()
|
||||
return nil, fmt.Errorf("failed to read RES header: %w", err)
|
||||
}
|
||||
|
||||
// Проверяем магиеские байты
|
||||
if archive.Header.Magic != [4]byte{0x3C, 0xE2, 0x9C, 0x01} {
|
||||
_ = file.Close()
|
||||
return nil, fmt.Errorf("invalid RES file (wrong magic)")
|
||||
}
|
||||
|
||||
// Читаем таблицу файлов
|
||||
if err = archive.readFileTable(); err != nil {
|
||||
_ = file.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return archive, nil
|
||||
}
|
||||
|
||||
// readFileTable читает таблицу файлов RES
|
||||
func (ra *RESArchive) readFileTable() error {
|
||||
// Преходим к таблице файлов
|
||||
if _, err := ra.File.Seek(int64(ra.Header.FileTableOffset), io.SeekStart); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Сначала читаем все записи файлов
|
||||
records := make([]RESFileRecord, ra.Header.FilesCount)
|
||||
for i := uint32(0); i < ra.Header.FilesCount; i++ {
|
||||
var record RESFileRecord
|
||||
if err := binary.Read(ra.File, binary.LittleEndian, &record); err != nil {
|
||||
return fmt.Errorf("failed to read file record %d: %w", i, err)
|
||||
}
|
||||
records[i] = record
|
||||
}
|
||||
|
||||
// Вычисляем смещение таблицы имен
|
||||
nameTableOffset := ra.Header.FileTableOffset + 22*ra.Header.FilesCount
|
||||
ra.Files = make([]RESFile, ra.Header.FilesCount)
|
||||
|
||||
// Читаем имена файлов и создаем записи
|
||||
for i, record := range records {
|
||||
// Переходим к имени файла
|
||||
nameOffset := nameTableOffset + record.NameOffset
|
||||
if _, err := ra.File.Seek(int64(nameOffset), io.SeekStart); err != nil {
|
||||
return fmt.Errorf("failed to seek to name offset: %w", err)
|
||||
}
|
||||
|
||||
// Читаем имя в CP1251
|
||||
nameBytes := make([]byte, record.NameLen)
|
||||
if _, err := io.ReadFull(ra.File, nameBytes); err != nil {
|
||||
return fmt.Errorf("failed to read name: %w", err)
|
||||
}
|
||||
|
||||
// Конвертируем из CP1251 в UTF-8
|
||||
name := decodeCP1251(nameBytes)
|
||||
|
||||
// Создаем запись файла
|
||||
ra.Files[i] = RESFile{
|
||||
Name: name,
|
||||
Size: record.FileSize,
|
||||
Offset: record.FileOffset,
|
||||
LastChange: time.Unix(int64(record.LastChange), 0),
|
||||
NameLen: record.NameLen,
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetFile получает файл из архива
|
||||
func (ra *RESArchive) GetFile(name string) ([]byte, error) {
|
||||
for _, file := range ra.Files {
|
||||
if file.Name == name {
|
||||
return ra.readFileData(file.Offset, file.Size)
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("file not found: %s", name)
|
||||
}
|
||||
|
||||
// GetFileByIndex получает файл по индексу
|
||||
func (ra *RESArchive) GetFileByIndex(index int) ([]byte, error) {
|
||||
if index < 0 || index >= len(ra.Files) {
|
||||
return nil, fmt.Errorf("invalid file index: %d", index)
|
||||
}
|
||||
|
||||
file := ra.Files[index]
|
||||
return ra.readFileData(file.Offset, file.Size)
|
||||
}
|
||||
|
||||
// readFileData читает данные файла
|
||||
func (ra *RESArchive) readFileData(offset, size uint32) ([]byte, error) {
|
||||
if _, err := ra.File.Seek(int64(offset), io.SeekStart); err != nil {
|
||||
return nil, fmt.Errorf("failed to seek to file data: %w", err)
|
||||
}
|
||||
|
||||
data := make([]byte, size)
|
||||
if _, err := io.ReadFull(ra.File, data); err != nil {
|
||||
return nil, fmt.Errorf("failed to read file data: %w", err)
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// ListFiles возвращает список файлов
|
||||
func (ra *RESArchive) ListFiles() []string {
|
||||
names := make([]string, len(ra.Files))
|
||||
for i, file := range ra.Files {
|
||||
names[i] = file.Name
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
// GetFileInfo возвращает информацию о файле
|
||||
func (ra *RESArchive) GetFileInfo(name string) (*RESFile, error) {
|
||||
for _, file := range ra.Files {
|
||||
if file.Name == name {
|
||||
return &file, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("file not found: %s", name)
|
||||
}
|
||||
|
||||
// ExtractAll извлекает все файлы
|
||||
func (ra *RESArchive) ExtractAll(outputDir string) error {
|
||||
for _, file := range ra.Files {
|
||||
data, err := ra.GetFile(file.Name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to extract %s: %w", file.Name, err)
|
||||
}
|
||||
|
||||
outputPath := filepath.Join(outputDir, file.Name)
|
||||
if err = os.MkdirAll(filepath.Dir(outputPath), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = os.WriteFile(outputPath, data, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close закрывает архив
|
||||
func (ra *RESArchive) Close() error {
|
||||
if ra.isOpen {
|
||||
ra.isOpen = false
|
||||
return ra.File.Close()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -1,11 +1,15 @@
|
||||
package engine
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"time"
|
||||
|
||||
"git.influ.su/artmares/art3de/pkg/engine/renderer"
|
||||
)
|
||||
|
||||
type Engine struct {
|
||||
isRunning bool
|
||||
window *Window
|
||||
renderer *Renderer
|
||||
renderer *renderer.Renderer
|
||||
lastTime time.Time
|
||||
game Game
|
||||
}
|
||||
@ -24,7 +28,7 @@ func (e *Engine) Run(game Game) error {
|
||||
}
|
||||
defer e.window.Destroy()
|
||||
|
||||
e.renderer = NewRenderer()
|
||||
e.renderer = renderer.NewRenderer()
|
||||
if err := e.renderer.Init(); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -54,6 +58,12 @@ func (e *Engine) Run(game Game) error {
|
||||
e.game.Draw(e.renderer)
|
||||
|
||||
// Отображение буфера
|
||||
e.SwapBuffers()
|
||||
e.window.SwapBuffers()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Engine) Stop() {
|
||||
e.isRunning = false
|
||||
}
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
package engine
|
||||
|
||||
import "git.influ.su/artmares/art3de/pkg/engine/renderer"
|
||||
|
||||
type Game interface {
|
||||
Init() error
|
||||
Update(deltaTime float32)
|
||||
Draw(renderer *Renderer)
|
||||
Draw(renderer *renderer.Renderer)
|
||||
OnResize(width, height int)
|
||||
}
|
||||
|
||||
45
pkg/engine/renderer/renderer.go
Normal file
45
pkg/engine/renderer/renderer.go
Normal file
@ -0,0 +1,45 @@
|
||||
package renderer
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/go-gl/gl/v4.1-core/gl"
|
||||
)
|
||||
|
||||
type Renderer struct {
|
||||
clearColor [4]float32
|
||||
}
|
||||
|
||||
func NewRenderer() *Renderer {
|
||||
return &Renderer{
|
||||
clearColor: [4]float32{0.1, 0.1, 0.1, 0.1},
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Renderer) Init() error {
|
||||
if err := gl.Init(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gl.Enable(gl.DEPTH_TEST)
|
||||
gl.Enable(gl.BLEND)
|
||||
gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
|
||||
|
||||
version := gl.GoStr(gl.GetString(gl.VERSION))
|
||||
log.Println("OpenGL version:", version)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Renderer) Clear() {
|
||||
gl.ClearColor(r.clearColor[0], r.clearColor[1], r.clearColor[2], r.clearColor[3])
|
||||
gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
|
||||
}
|
||||
|
||||
func (r *Renderer) SetClearColor(rgba [4]float32) {
|
||||
r.clearColor = rgba
|
||||
}
|
||||
|
||||
func (r *Renderer) SetViewport(x, y, width, height int32) {
|
||||
gl.Viewport(x, y, width, height)
|
||||
}
|
||||
65
pkg/engine/window.go
Normal file
65
pkg/engine/window.go
Normal file
@ -0,0 +1,65 @@
|
||||
package engine
|
||||
|
||||
import "github.com/go-gl/glfw/v3.3/glfw"
|
||||
|
||||
type Window struct {
|
||||
window *glfw.Window
|
||||
title string
|
||||
width, height int
|
||||
}
|
||||
|
||||
func NewWindow(title string, width, height int) *Window {
|
||||
return &Window{
|
||||
title: title,
|
||||
width: width,
|
||||
height: height,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Window) Init() error {
|
||||
if err := glfw.Init(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
glfw.WindowHint(glfw.ContextVersionMajor, 4)
|
||||
glfw.WindowHint(glfw.ContextVersionMinor, 1)
|
||||
glfw.WindowHint(glfw.OpenGLProfile, glfw.OpenGLCoreProfile)
|
||||
glfw.WindowHint(glfw.OpenGLForwardCompatible, glfw.True)
|
||||
glfw.WindowHint(glfw.Resizable, glfw.True)
|
||||
|
||||
window, err := glfw.CreateWindow(w.width, w.height, w.title, nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w.window = window
|
||||
w.window.MakeContextCurrent()
|
||||
w.window.SetFramebufferSizeCallback(w.framebufferSizeCallback)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Window) framebufferSizeCallback(window *glfw.Window, width, height int) {
|
||||
w.width, w.height = width, height
|
||||
// Здесь можно добавить коллбэк для игры
|
||||
}
|
||||
|
||||
func (w *Window) ShouldClose() bool {
|
||||
return w.window.ShouldClose()
|
||||
}
|
||||
|
||||
func (w *Window) PollEvents() {
|
||||
glfw.PollEvents()
|
||||
}
|
||||
|
||||
func (w *Window) SwapBuffers() {
|
||||
w.window.SwapBuffers()
|
||||
}
|
||||
|
||||
func (w *Window) Destroy() {
|
||||
glfw.Terminate()
|
||||
}
|
||||
|
||||
func (w *Window) GetSize() (int, int) {
|
||||
return w.width, w.height
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user