feat: initial commit with M1-M4 implementation
This commit is contained in:
187
docs/plans/2026-05-08-m1-engine.md
Normal file
187
docs/plans/2026-05-08-m1-engine.md
Normal file
@@ -0,0 +1,187 @@
|
||||
# План: M1 - Core Engine & Basic Rendering
|
||||
|
||||
**Цель:** Создать базовое окно игры, отрисовать хоккейную площадку и одну статичную шайбу, настроить основной игровой цикл.
|
||||
|
||||
**Архитектура:**
|
||||
- Использование Ebitengine для рендеринга.
|
||||
- Разделение на `World` (состояние) и `Renderer` (отрисовка).
|
||||
- Точка входа в `cmd/game/main.go`.
|
||||
|
||||
**Стек:** Go, Ebitengine.
|
||||
**Спека:** [docs/specs/2026-05-08-hockey-design.md](../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: Минимальный код для запуска окна**
|
||||
```go
|
||||
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**
|
||||
```go
|
||||
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**
|
||||
```go
|
||||
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: Функция отрисовки мира**
|
||||
```go
|
||||
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**
|
||||
```go
|
||||
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"`
|
||||
Reference in New Issue
Block a user