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
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.
89 lines
3.0 KiB
Go
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...)
|
|
}
|