Files
football/docs/plans/2026-05-08-m1-engine.md
2026-05-12 10:54:09 +03:00

5.0 KiB
Raw Blame History

План: M1 - Core Engine & Basic Rendering

Цель: Создать базовое окно игры, отрисовать хоккейную площадку и одну статичную шайбу, настроить основной игровой цикл.

Архитектура:

  • Использование Ebitengine для рендеринга.
  • Разделение на World (состояние) и Renderer (отрисовка).
  • Точка входа в cmd/game/main.go.

Стек: Go, Ebitengine. Спека: docs/specs/2026-05-08-hockey-design.md


Файловая структура

  • cmd/game/main.go — точка входа, инициализация Ebitengine.
  • internal/game/world.go — структура World, хранящая состояние игры.
  • internal/render/renderer.go — логика отрисовки объектов на экране.
  • internal/entities/puck.go — определение структуры Puck.

Задачи

Задача 1: Инициализация проекта и окна

Файлы:

  • Создать: cmd/game/main.go

  • Шаг 1: Минимальный код для запуска окна

package main

import (
	"log"
	"github.com/hajimehoshi/ebiten/v2"
)

type Game struct{}

func (g *Game) Update() error { return nil }
func (g *Game) Draw(screen *ebiten.Image) { screen.Fill(ebiten.ColorWhite) }
func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) { return 800, 600 }

func main() {
	ebiten.SetWindowSize(800, 600)
	ebiten.SetWindowTitle("Hockey Sim")
	if err := ebiten.RunGame(&Game{}); err != nil {
		log.Fatal(err)
	}
}
  • Шаг 2: Запуск и проверка go run cmd/game/main.go Ожидание: Открывается белое окно 800x600.

  • Шаг 3: Коммит git add cmd/game/main.go git commit -m "feat(engine): initialize ebitengine window"

Задача 2: Определение сущности Шайбы

Файлы:

  • Создать: internal/entities/puck.go

  • Шаг 1: Структура Puck

package entities

type Puck struct {
	X, Y float64
}

func NewPuck(x, y float64) *Puck {
	return &Puck{X: x, Y: y}
}
  • Шаг 2: Коммит git add internal/entities/puck.go git commit -m "feat(entities): add puck entity"

Задача 3: Создание игрового мира

Файлы:

  • Создать: internal/game/world.go

  • Шаг 1: Структура World

package game

import "football/internal/entities"

type World struct {
	Puck *entities.Puck
}

func NewWorld() *World {
	return &World{
		Puck: entities.NewPuck(400, 300),
	}
}
  • Шаг 2: Коммит git add internal/game/world.go git commit -m "feat(game): add world state"

Задача 4: Реализация базового рендерера

Файлы:

  • Создать: internal/render/renderer.go

  • Шаг 1: Функция отрисовки мира

package render

import (
	"football/internal/game"
	"github.com/hajimehoshi/ebiten/v2"
	"image/color"
)

type Renderer struct{}

func (r *Renderer) DrawWorld(screen *ebiten.Image, world *game.World) {
	// Отрисовка льда (белый фон)
	screen.Fill(color.RGBA{240, 240, 240, 255})
	
	// Отрисовка шайбы (черный квадрат для начала)
	// В Ebitengine для простых фигур используем vector или маленькие изображения.
	// Для M1 достаточно закрасить область.
}

Примечание: Для полноценного круга потребуется vector package, но для M1 начнем с простого заполнения.

  • Шаг 2: Коммит git add internal/render/renderer.go git commit -m "feat(render): add basic world renderer"

Задача 5: Интеграция в Game Loop

Файлы:

  • Изменить: cmd/game/main.go

  • Шаг 1: Подключение World и Renderer

package main

import (
	"football/internal/game"
	"football/internal/render"
	"github.com/hajimehoshi/ebiten/v2"
	"log"
)

type Game struct {
	world    *game.World
	renderer *render.Renderer
}

func (g *Game) Update() error { return nil }
func (g *Game) Draw(screen *ebiten.Image) {
	g.renderer.DrawWorld(screen, g.world)
}
func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) { return 800, 600 }

func main() {
	ebiten.SetWindowSize(800, 600)
	ebiten.SetWindowTitle("Hockey Sim")
	gameInstance := &Game{
		world:    game.NewWorld(),
		renderer: &render.Renderer{},
	}
	if err := ebiten.RunGame(gameInstance); err != nil {
		log.Fatal(err)
	}
}
  • Шаг 2: Запуск и проверка go run cmd/game/main.go Ожидание: Окно с серым фоном.

  • Шаг 3: Коммит git add cmd/game/main.go git commit -m "feat(engine): integrate world and renderer into loop"