write simple version engine and try write asset packer

This commit is contained in:
ArtMares 2026-02-10 01:36:55 +03:00
parent f990b270f9
commit 72b8118c60
9 changed files with 402 additions and 5 deletions

52
cmd/test/main.go Normal file
View 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
View File

@ -1,3 +1,8 @@
module git.influ.su/artmares/art3de module git.influ.su/artmares/art3de
go 1.25.6 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
View 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=

View File

@ -0,0 +1 @@
package assets

213
pkg/assets/res_format.go Normal file
View 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
}

View File

@ -1,11 +1,15 @@
package engine package engine
import "time" import (
"time"
"git.influ.su/artmares/art3de/pkg/engine/renderer"
)
type Engine struct { type Engine struct {
isRunning bool isRunning bool
window *Window window *Window
renderer *Renderer renderer *renderer.Renderer
lastTime time.Time lastTime time.Time
game Game game Game
} }
@ -24,7 +28,7 @@ func (e *Engine) Run(game Game) error {
} }
defer e.window.Destroy() defer e.window.Destroy()
e.renderer = NewRenderer() e.renderer = renderer.NewRenderer()
if err := e.renderer.Init(); err != nil { if err := e.renderer.Init(); err != nil {
return err return err
} }
@ -54,6 +58,12 @@ func (e *Engine) Run(game Game) error {
e.game.Draw(e.renderer) e.game.Draw(e.renderer)
// Отображение буфера // Отображение буфера
e.SwapBuffers() e.window.SwapBuffers()
} }
return nil
}
func (e *Engine) Stop() {
e.isRunning = false
} }

View File

@ -1,8 +1,10 @@
package engine package engine
import "git.influ.su/artmares/art3de/pkg/engine/renderer"
type Game interface { type Game interface {
Init() error Init() error
Update(deltaTime float32) Update(deltaTime float32)
Draw(renderer *Renderer) Draw(renderer *renderer.Renderer)
OnResize(width, height int) OnResize(width, height int)
} }

View 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
View 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
}