Files
sanspie a6ebf841d0
ci-platforms / darwin amd64 smoke (push) Skipped
ci-platforms / windows amd64 smoke (push) Skipped
ci-platforms / darwin arm64 smoke (push) Skipped
ci / checks (push) Successful in 9m10s
ci / build (amd64, linux, ubuntu-24.04) (push) Successful in 12m25s
ci / release (push) Failing after 4m59s
Stage 11b8: agents draw their art (D363)
asset.pixels: pixel art as a character grid and a palette; frames of one size become an atlas with a sprite per frame and a flipbook clip; a preview image in the answer.
asset.svg: an SVG subset drawn by an in-tree rasterizer (format/svg) for tiles, UI and icons.
art module: project.aego art { palette pixelsPerUnit }, the default palette and scale for drawings; Project Health HLT009 for pixel art outside the palette.
The agent prompts say to draw instead of building sprites from rectangles.
Agent runs open the asset index shared without a warning; prefab.create on a scene open in the editor goes through the prefab service, so unsaved scenes work.
scene_run with shot names xvfb when there is no display; local make ci lists untracked files outside .gitignore.
2026-10-26 19:36:15 +03:00

89 lines
3.0 KiB
Go

package animedit
import (
"aego/anim"
"aego/core/curve"
"aego/core/ids"
"aego/engine/i18n"
"aego/format/aetext"
"aego/scene/mutate"
"aego/schema"
)
// NewClipText is a new empty clip: one second, looping.
func NewClipText() []byte {
return aetext.Write(anim.Encode(&anim.Clip{Length: 1, Loop: anim.LoopRepeat}))
}
// NewControllerText is a new controller with one Idle state, laid out.
func NewControllerText() ([]byte, error) {
root, err := aetext.Parse([]byte("controller version=1 {\n entry=\"Idle\"\n state \"Idle\" {}\n}\n"), "new.aectrl")
if err != nil {
return nil, err
}
g, _, err := anim.DecodeGraph(root)
if err != nil {
return nil, err
}
g.Layout()
return aetext.Write(anim.EncodeGraph(g)), nil
}
// FlipbookFPS is the frame rate of a flipbook made from sprites.
const FlipbookFPS = anim.FlipbookFPS
// FlipbookOps key the sprite of the clip's node with one frame per sprite, as step keys.
func FlipbookOps(frames []ids.AssetID) []mutate.Op {
extra := &aetext.Node{Head: "values", Attrs: []aetext.Field{{Name: "interp", Value: aetext.Ident(curve.InterpStep.String())}}}
ops := make([]mutate.Op, len(frames))
for i, id := range frames {
op := mutate.AnimKeySet("", anim.SpriteProp, timeText(float32(i)/FlipbookFPS), aetext.AssetRef(id.String()))
op.Comp, op.Values = schema.KindAssetRef.String(), extra
ops[i] = op
}
return ops
}
// NewFlipbookText is a looping clip that shows the sprites one after another.
func NewFlipbookText(frames []ids.AssetID) []byte {
return anim.FlipbookText(frames)
}
// FlipbookInto adds the flipbook to the open clip, lengthening it when the frames run past its end.
func (s *Service) FlipbookInto(frames []ids.AssetID) error {
if s.clip == nil {
return errNoClip
}
ops := FlipbookOps(frames)
if end := float32(len(frames)) / FlipbookFPS; end > s.clip.Length {
ops = append(ops, mutate.AnimClipSet("length", aetext.Float32(end)))
}
return s.EditClip(i18n.Tr("editor.history.flipbook", "Flipbook"), ops...)
}
// KeySprites keys sprite track i with the frames from time at on, one per FlipbookFPS step, lengthening the clip when they run past its end.
func (s *Service) KeySprites(i int, at float32, frames []ids.AssetID) error {
if s.clip == nil {
return errNoClip
}
if i < 0 || i >= len(s.clip.Tracks) || len(frames) == 0 {
return nil
}
tr := &s.clip.Tracks[i]
extra := &aetext.Node{Head: "values", Attrs: []aetext.Field{{Name: "interp", Value: aetext.Ident(curve.InterpStep.String())}}}
ops := make([]mutate.Op, 0, len(frames)+1)
for k, id := range frames {
op := mutate.AnimKeySet(tr.Target, tr.Property, timeText(at+float32(k)/FlipbookFPS), aetext.AssetRef(id.String()))
op.Comp, op.Values = schema.KindAssetRef.String(), extra
ops = append(ops, op)
}
if end := at + float32(len(frames)-1)/FlipbookFPS; end > s.clip.Length {
ops = append(ops, mutate.AnimClipSet("length", aetext.Float32(end)))
}
label := i18n.Tr("editor.animedit.key_sprite", "Key Sprite")
if len(frames) > 1 {
label = i18n.Tr("editor.animedit.key_sprites", "Key Sprites")
}
return s.EditClip(label, ops...)
}