Respect sprite X and Y offsets

This makes menus display more correctly, and also fixes trees and other
objects on the main map, although it messes up bounds clipping (sigh).
This commit is contained in:
2020-03-21 00:56:35 +00:00
parent eb5c4430e8
commit 7a8e9dbd97
10 changed files with 109 additions and 55 deletions

View File

@@ -156,7 +156,7 @@ func (e *env) Draw(screen *ebiten.Image) error {
}
func (e *env) renderCell(x, y, z int, screen *ebiten.Image) error {
images, err := e.area.ImagesForCell(x, y, z)
sprites, err := e.area.SpritesForCell(x, y, z)
if err != nil {
return err
}
@@ -177,10 +177,12 @@ func (e *env) renderCell(x, y, z int, screen *ebiten.Image) error {
// TODO: iso.Scale(e.state.zoom, e.state.zoom) // apply current zoom factor
for _, img := range images {
if err := screen.DrawImage(img, &ebiten.DrawImageOptions{GeoM: iso}); err != nil {
for _, spr := range sprites {
iso.Translate(float64(spr.XOffset), float64(spr.YOffset))
if err := screen.DrawImage(spr.Image, &ebiten.DrawImageOptions{GeoM: iso}); err != nil {
return err
}
iso.Translate(float64(-spr.XOffset), float64(-spr.YOffset))
}
return nil

View File

@@ -164,10 +164,11 @@ func (e *env) drawRecord(record *menus.Record, screen *ebiten.Image, offset ebit
x := float64(record.X)
y := float64(record.Y)
// Theory: we either give spriteid, or y,x,spriteId
// Maybe: we either give spriteid, or y,x,spriteId ? Unsure, doesn't seem
// to be needed for now
if len(record.SpriteId) == 3 {
x = x + float64(record.SpriteId[1])
y = y + float64(record.SpriteId[0]*2) // FIXME: *2 works, no idea
// x = x + float64(record.SpriteId[1])
// y = y + float64(record.SpriteId[0])
spriteId = record.SpriteId[2]
}
@@ -184,15 +185,21 @@ func (e *env) drawRecord(record *menus.Record, screen *ebiten.Image, offset ebit
y = 0.0
}
log.Printf(
"Drawing id=%v type=%v spriteid=%v x=%v y=%v desc=%q parent=%p",
record.Id, record.Type, spriteId, record.X, record.Y, record.Desc, record.Parent,
)
// FIXME: Need to handle multiple objects
obj := e.objects[0]
sprite := obj.Sprites[spriteId]
x = x + float64(sprite.XOffset)
y = y + float64(sprite.YOffset)
// Account for scaling
x, y = offset.Apply(x, y)
log.Printf(
"Drawing id=%v type=%v spriteid=%v x=%v(+%v) y=%v(%+v) desc=%q parent=%p",
record.Id, record.Type, spriteId, record.X, record.Y, sprite.XOffset, sprite.YOffset, record.Desc, record.Parent,
)
offset.Translate(x, y)
screen.DrawImage(sprite.Image, &ebiten.DrawImageOptions{GeoM: offset})

Binary file not shown.

After

Width:  |  Height:  |  Size: 281 KiB

View File

@@ -183,3 +183,21 @@ values:
| 405,0,0,{8, 16} | ? |
| 405,22,22,{2, 4, 5, 6, 7, 8, 9, 10, 13, 16} | ? |
| 405,30,-1,5 | ? |
## Positioning
The X-CORD and Y-CORD values would seem to be related, to this, but they are
universally set to 0 or -1.
Far more important are the XOffset and YOffset values for each sprite in the
associated .obj files. Taking these into account is enough to draw `Options.mnu`
successfully, for instance:
![](img/Options.mnu.png)
## Animation
This seems to be done by choosing a different sprite to draw every N ticks. They
are laid out sequentially, but I don't yet know how to animate them. It's likely
to be the same approach as used for other obj files.

View File

@@ -202,15 +202,13 @@ in the CENTER position. Interesting.
| Offset | Purpose |
| ------ | ------- |
| 0x0000 | ?
| 0x0000 | x,y offset (16 bits each) |
| 0x0004 | x,y size (16 bits each) |
| 0x0008 | ? (blank in all cases so far)
| 0x000c | Size of remaining pixeldata |
| 0x0010 | Padding? |
| 0x0014 | Padding? |
We still don't know what the first 32 bits are all about.
The volume represented by a cell is a little odd. We see three faces of a fake
3D volume of size 64x64x32(ish). This is presented in an isomorphic fashion, so
the height is 32px at the leftmost and rightmost extents and 96 in the centre.
@@ -222,8 +220,9 @@ for instance.
Loading a constructed `palette.obj` containing 1x63 pixels, with 0x0000 for the
first 32 bits, caused WH40K_TD.exe to render the pixels far to the left and above
the cell the palette objects were being placed into. This suggests they
represent an x,y offset to draw to. Need to experiment more.
the cell the palette objects were being placed into. Menus have a `.obj` file
associated with them too, and the elements for `Main.mnu` draw in the correct
places if we treat it as such.
Considering sprites with a 1,1 x,y.

View File

@@ -4,8 +4,6 @@ import (
"image"
"log"
"github.com/hajimehoshi/ebiten"
"code.ur.gs/lupine/ordoor/internal/maps"
)
@@ -64,11 +62,11 @@ func (a *AssetStore) Map(name string) (*Map, error) {
return m, nil
}
// ImagesForCell returns the sprites needed to correctly render this cell.
// SpritesForCell returns the sprites needed to correctly render this cell.
// They should be rendered from first to last to get the correct ordering
func (m *Map) ImagesForCell(x, y, z int) ([]*ebiten.Image, error) {
func (m *Map) SpritesForCell(x, y, z int) ([]*Sprite, error) {
cell := m.raw.At(x, y, z)
images := make([]*ebiten.Image, 0, 4)
sprites := make([]*Sprite, 0, 4)
for _, ref := range []maps.ObjRef{cell.Surface, cell.Right, cell.Left, cell.Center} {
if !ref.IsActive() {
@@ -80,13 +78,13 @@ func (m *Map) ImagesForCell(x, y, z int) ([]*ebiten.Image, error) {
return nil, err
}
img, err := obj.Image(ref.Sprite())
sprite, err := obj.Sprite(ref.Sprite())
if err != nil {
return nil, err
}
images = append(images, img)
sprites = append(sprites, sprite)
}
return images, nil
return sprites, nil
}

View File

@@ -9,12 +9,23 @@ import (
)
type Object struct {
assets *AssetStore
images []*ebiten.Image
assets *AssetStore
sprites []*Sprite
raw *data.Object
}
type Sprite struct {
obj *Object
XOffset int
YOffset int
Width int
Height int
Image *ebiten.Image
}
func (a *AssetStore) Object(name string) (*Object, error) {
name = canonical(name)
@@ -35,9 +46,9 @@ func (a *AssetStore) Object(name string) (*Object, error) {
raw.Name = name
obj := &Object{
assets: a,
images: make([]*ebiten.Image, int(raw.NumSprites)),
raw: raw,
assets: a,
sprites: make([]*Sprite, int(raw.NumSprites)),
raw: raw,
}
a.objs[name] = obj
@@ -45,11 +56,11 @@ func (a *AssetStore) Object(name string) (*Object, error) {
}
// Filled lazily
func (o *Object) Image(idx int) (*ebiten.Image, error) {
if img := o.images[idx]; img != nil {
return img, nil
func (o *Object) Sprite(idx int) (*Sprite, error) {
if sprite := o.sprites[idx]; sprite != nil {
return sprite, nil
}
log.Printf("Loading sprite %v %v", o.raw.Name, idx)
log.Printf("Loading sprite %v:%v", o.raw.Name, idx)
if o.raw.Sprites[idx] == nil {
if err := o.raw.LoadSprite(idx); err != nil {
@@ -57,13 +68,22 @@ func (o *Object) Image(idx int) (*ebiten.Image, error) {
}
}
stdImg := o.raw.Sprites[idx].ToImage()
img, err := ebiten.NewImageFromImage(stdImg, ebiten.FilterDefault)
raw := o.raw.Sprites[idx]
img, err := ebiten.NewImageFromImage(raw.ToImage(), ebiten.FilterDefault)
if err != nil {
return nil, err
}
o.images[idx] = img
sprite := &Sprite{
obj: o,
Width: int(raw.Width),
Height: int(raw.Width),
XOffset: int(raw.XOffset),
YOffset: int(raw.YOffset),
Image: img,
}
return img, nil
o.sprites[idx] = sprite
return sprite, nil
}

View File

@@ -11,8 +11,10 @@ import (
// * Width & height now stored using int
// * Colour data is now 32-bit rather than using a palette
type Sprite struct {
Width int
Height int
XOffset int
YOffset int
Width int
Height int
Image *ebiten.Image
}
@@ -39,14 +41,18 @@ func ConvertObject(rawObj *data.Object, name string) (*Object, error) {
}
for i, rawSpr := range rawObj.Sprites {
w := int(rawSpr.Width)
h := int(rawSpr.Height)
ebitenImage, err := ebiten.NewImageFromImage(rawSpr.ToImage(), ebiten.FilterDefault)
if err != nil {
return nil, err
}
out.Sprites[i] = &Sprite{Width: w, Height: h, Image: ebitenImage}
out.Sprites[i] = &Sprite{
XOffset: int(rawSpr.XOffset),
YOffset: int(rawSpr.YOffset),
Width: int(rawSpr.Width),
Height: int(rawSpr.Height),
Image: ebitenImage,
}
}
return out, nil

View File

@@ -14,7 +14,8 @@ import (
)
type SpriteHeader struct {
Unknown0 uint32
XOffset uint16
YOffset uint16
Width uint16
Height uint16
Padding1 uint32 // I don't think this is used. Could be wrong.

View File

@@ -15,6 +15,9 @@ import (
var (
screenScale = flag.Float64("screen-scale", 1.0, "Scale the window by this factor")
winX = flag.Int("win-x", 1280, "Pre-scaled window X dimension")
winY = flag.Int("win-y", 1024, "Pre-scaled window Y dimension")
cpuprofile = flag.String("cpuprofile", "", "write cpu profile to `file`")
)
@@ -27,7 +30,7 @@ type Window struct {
updateFn func() error
drawFn func(*ebiten.Image) error
debug bool
debug bool
firstRun bool
}
@@ -103,16 +106,16 @@ func (w *Window) Run(updateFn func() error, drawFn func(*ebiten.Image) error) er
w.drawFn = drawFn
if *cpuprofile != "" {
f, err := os.Create(*cpuprofile)
if err != nil {
log.Fatal("could not create CPU profile: ", err)
}
defer f.Close() // error handling omitted for example
if err := pprof.StartCPUProfile(f); err != nil {
log.Fatal("could not start CPU profile: ", err)
}
defer pprof.StopCPUProfile()
}
f, err := os.Create(*cpuprofile)
if err != nil {
log.Fatal("could not create CPU profile: ", err)
}
defer f.Close() // error handling omitted for example
if err := pprof.StartCPUProfile(f); err != nil {
log.Fatal("could not start CPU profile: ", err)
}
defer pprof.StopCPUProfile()
}
return ebiten.Run(w.run, 640, 480, 1, w.Title) // Native game resolution: 640x480
return ebiten.Run(w.run, *winX, *winY, 1, w.Title) // Native game resolution: 640x480
}