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:
@@ -156,7 +156,7 @@ func (e *env) Draw(screen *ebiten.Image) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (e *env) renderCell(x, y, z int, 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 {
|
if err != nil {
|
||||||
return err
|
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
|
// TODO: iso.Scale(e.state.zoom, e.state.zoom) // apply current zoom factor
|
||||||
|
|
||||||
for _, img := range images {
|
for _, spr := range sprites {
|
||||||
if err := screen.DrawImage(img, &ebiten.DrawImageOptions{GeoM: iso}); err != nil {
|
iso.Translate(float64(spr.XOffset), float64(spr.YOffset))
|
||||||
|
if err := screen.DrawImage(spr.Image, &ebiten.DrawImageOptions{GeoM: iso}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
iso.Translate(float64(-spr.XOffset), float64(-spr.YOffset))
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@@ -164,10 +164,11 @@ func (e *env) drawRecord(record *menus.Record, screen *ebiten.Image, offset ebit
|
|||||||
x := float64(record.X)
|
x := float64(record.X)
|
||||||
y := float64(record.Y)
|
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 {
|
if len(record.SpriteId) == 3 {
|
||||||
x = x + float64(record.SpriteId[1])
|
// x = x + float64(record.SpriteId[1])
|
||||||
y = y + float64(record.SpriteId[0]*2) // FIXME: *2 works, no idea
|
// y = y + float64(record.SpriteId[0])
|
||||||
spriteId = record.SpriteId[2]
|
spriteId = record.SpriteId[2]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,15 +185,21 @@ func (e *env) drawRecord(record *menus.Record, screen *ebiten.Image, offset ebit
|
|||||||
y = 0.0
|
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
|
// FIXME: Need to handle multiple objects
|
||||||
obj := e.objects[0]
|
obj := e.objects[0]
|
||||||
sprite := obj.Sprites[spriteId]
|
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)
|
offset.Translate(x, y)
|
||||||
screen.DrawImage(sprite.Image, &ebiten.DrawImageOptions{GeoM: offset})
|
screen.DrawImage(sprite.Image, &ebiten.DrawImageOptions{GeoM: offset})
|
||||||
|
|
||||||
|
BIN
doc/formats/img/Options.mnu.png
Normal file
BIN
doc/formats/img/Options.mnu.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 281 KiB |
@@ -183,3 +183,21 @@ values:
|
|||||||
| 405,0,0,{8, 16} | ? |
|
| 405,0,0,{8, 16} | ? |
|
||||||
| 405,22,22,{2, 4, 5, 6, 7, 8, 9, 10, 13, 16} | ? |
|
| 405,22,22,{2, 4, 5, 6, 7, 8, 9, 10, 13, 16} | ? |
|
||||||
| 405,30,-1,5 | ? |
|
| 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:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
@@ -202,15 +202,13 @@ in the CENTER position. Interesting.
|
|||||||
|
|
||||||
| Offset | Purpose |
|
| Offset | Purpose |
|
||||||
| ------ | ------- |
|
| ------ | ------- |
|
||||||
| 0x0000 | ?
|
| 0x0000 | x,y offset (16 bits each) |
|
||||||
| 0x0004 | x,y size (16 bits each) |
|
| 0x0004 | x,y size (16 bits each) |
|
||||||
| 0x0008 | ? (blank in all cases so far)
|
| 0x0008 | ? (blank in all cases so far)
|
||||||
| 0x000c | Size of remaining pixeldata |
|
| 0x000c | Size of remaining pixeldata |
|
||||||
| 0x0010 | Padding? |
|
| 0x0010 | Padding? |
|
||||||
| 0x0014 | 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
|
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
|
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.
|
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
|
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
|
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
|
the cell the palette objects were being placed into. Menus have a `.obj` file
|
||||||
represent an x,y offset to draw to. Need to experiment more.
|
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.
|
Considering sprites with a 1,1 x,y.
|
||||||
|
|
||||||
|
@@ -4,8 +4,6 @@ import (
|
|||||||
"image"
|
"image"
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten"
|
|
||||||
|
|
||||||
"code.ur.gs/lupine/ordoor/internal/maps"
|
"code.ur.gs/lupine/ordoor/internal/maps"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -64,11 +62,11 @@ func (a *AssetStore) Map(name string) (*Map, error) {
|
|||||||
return m, nil
|
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
|
// 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)
|
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} {
|
for _, ref := range []maps.ObjRef{cell.Surface, cell.Right, cell.Left, cell.Center} {
|
||||||
if !ref.IsActive() {
|
if !ref.IsActive() {
|
||||||
@@ -80,13 +78,13 @@ func (m *Map) ImagesForCell(x, y, z int) ([]*ebiten.Image, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
img, err := obj.Image(ref.Sprite())
|
sprite, err := obj.Sprite(ref.Sprite())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
images = append(images, img)
|
sprites = append(sprites, sprite)
|
||||||
}
|
}
|
||||||
|
|
||||||
return images, nil
|
return sprites, nil
|
||||||
}
|
}
|
||||||
|
@@ -9,12 +9,23 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Object struct {
|
type Object struct {
|
||||||
assets *AssetStore
|
assets *AssetStore
|
||||||
images []*ebiten.Image
|
sprites []*Sprite
|
||||||
|
|
||||||
raw *data.Object
|
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) {
|
func (a *AssetStore) Object(name string) (*Object, error) {
|
||||||
name = canonical(name)
|
name = canonical(name)
|
||||||
|
|
||||||
@@ -35,9 +46,9 @@ func (a *AssetStore) Object(name string) (*Object, error) {
|
|||||||
raw.Name = name
|
raw.Name = name
|
||||||
|
|
||||||
obj := &Object{
|
obj := &Object{
|
||||||
assets: a,
|
assets: a,
|
||||||
images: make([]*ebiten.Image, int(raw.NumSprites)),
|
sprites: make([]*Sprite, int(raw.NumSprites)),
|
||||||
raw: raw,
|
raw: raw,
|
||||||
}
|
}
|
||||||
a.objs[name] = obj
|
a.objs[name] = obj
|
||||||
|
|
||||||
@@ -45,11 +56,11 @@ func (a *AssetStore) Object(name string) (*Object, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Filled lazily
|
// Filled lazily
|
||||||
func (o *Object) Image(idx int) (*ebiten.Image, error) {
|
func (o *Object) Sprite(idx int) (*Sprite, error) {
|
||||||
if img := o.images[idx]; img != nil {
|
if sprite := o.sprites[idx]; sprite != nil {
|
||||||
return img, 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 o.raw.Sprites[idx] == nil {
|
||||||
if err := o.raw.LoadSprite(idx); err != 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()
|
raw := o.raw.Sprites[idx]
|
||||||
img, err := ebiten.NewImageFromImage(stdImg, ebiten.FilterDefault)
|
img, err := ebiten.NewImageFromImage(raw.ToImage(), ebiten.FilterDefault)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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
|
||||||
}
|
}
|
||||||
|
@@ -11,8 +11,10 @@ import (
|
|||||||
// * Width & height now stored using int
|
// * Width & height now stored using int
|
||||||
// * Colour data is now 32-bit rather than using a palette
|
// * Colour data is now 32-bit rather than using a palette
|
||||||
type Sprite struct {
|
type Sprite struct {
|
||||||
Width int
|
XOffset int
|
||||||
Height int
|
YOffset int
|
||||||
|
Width int
|
||||||
|
Height int
|
||||||
|
|
||||||
Image *ebiten.Image
|
Image *ebiten.Image
|
||||||
}
|
}
|
||||||
@@ -39,14 +41,18 @@ func ConvertObject(rawObj *data.Object, name string) (*Object, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i, rawSpr := range rawObj.Sprites {
|
for i, rawSpr := range rawObj.Sprites {
|
||||||
w := int(rawSpr.Width)
|
|
||||||
h := int(rawSpr.Height)
|
|
||||||
ebitenImage, err := ebiten.NewImageFromImage(rawSpr.ToImage(), ebiten.FilterDefault)
|
ebitenImage, err := ebiten.NewImageFromImage(rawSpr.ToImage(), ebiten.FilterDefault)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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
|
return out, nil
|
||||||
|
@@ -14,7 +14,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type SpriteHeader struct {
|
type SpriteHeader struct {
|
||||||
Unknown0 uint32
|
XOffset uint16
|
||||||
|
YOffset uint16
|
||||||
Width uint16
|
Width uint16
|
||||||
Height uint16
|
Height uint16
|
||||||
Padding1 uint32 // I don't think this is used. Could be wrong.
|
Padding1 uint32 // I don't think this is used. Could be wrong.
|
||||||
|
@@ -15,6 +15,9 @@ import (
|
|||||||
var (
|
var (
|
||||||
screenScale = flag.Float64("screen-scale", 1.0, "Scale the window by this factor")
|
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`")
|
cpuprofile = flag.String("cpuprofile", "", "write cpu profile to `file`")
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -27,7 +30,7 @@ type Window struct {
|
|||||||
updateFn func() error
|
updateFn func() error
|
||||||
drawFn func(*ebiten.Image) error
|
drawFn func(*ebiten.Image) error
|
||||||
|
|
||||||
debug bool
|
debug bool
|
||||||
firstRun bool
|
firstRun bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,16 +106,16 @@ func (w *Window) Run(updateFn func() error, drawFn func(*ebiten.Image) error) er
|
|||||||
w.drawFn = drawFn
|
w.drawFn = drawFn
|
||||||
|
|
||||||
if *cpuprofile != "" {
|
if *cpuprofile != "" {
|
||||||
f, err := os.Create(*cpuprofile)
|
f, err := os.Create(*cpuprofile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("could not create CPU profile: ", err)
|
log.Fatal("could not create CPU profile: ", err)
|
||||||
}
|
}
|
||||||
defer f.Close() // error handling omitted for example
|
defer f.Close() // error handling omitted for example
|
||||||
if err := pprof.StartCPUProfile(f); err != nil {
|
if err := pprof.StartCPUProfile(f); err != nil {
|
||||||
log.Fatal("could not start CPU profile: ", err)
|
log.Fatal("could not start CPU profile: ", err)
|
||||||
}
|
}
|
||||||
defer pprof.StopCPUProfile()
|
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
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user