Scenario viewpoint, Z index management, and arrow controls

This commit is contained in:
2020-04-20 00:16:21 +01:00
parent 1f4bfc771c
commit 9d0750d134
10 changed files with 194 additions and 63 deletions

View File

@@ -3,6 +3,7 @@ package main
import ( import (
"flag" "flag"
"log" "log"
"math"
"os" "os"
"github.com/hajimehoshi/ebiten" "github.com/hajimehoshi/ebiten"
@@ -51,16 +52,18 @@ func main() {
log.Fatal("Couldn't create window: %v", err) log.Fatal("Couldn't create window: %v", err)
} }
win.OnKeyUp(ebiten.KeyLeft, env.changeOrigin(-64, +0)) step := 32
win.OnKeyUp(ebiten.KeyRight, env.changeOrigin(+64, +0)) win.WhileKeyDown(ebiten.KeyLeft, env.changeOrigin(-step, +0))
win.OnKeyUp(ebiten.KeyUp, env.changeOrigin(+0, -64)) win.WhileKeyDown(ebiten.KeyRight, env.changeOrigin(+step, +0))
win.OnKeyUp(ebiten.KeyDown, env.changeOrigin(+0, +64)) win.WhileKeyDown(ebiten.KeyUp, env.changeOrigin(+0, -step))
win.WhileKeyDown(ebiten.KeyDown, env.changeOrigin(+0, +step))
for i := 0; i <= 6; i++ { for i := 0; i <= 6; i++ {
win.OnKeyUp(ebiten.Key1+ebiten.Key(i), env.setZIdx(i)) win.OnKeyUp(ebiten.Key1+ebiten.Key(i), env.setZIdx(i))
} }
win.OnMouseClick(env.showCellData) win.OnMouseClick(env.showCellData)
win.OnMouseWheel(env.changeZoom)
if err := win.Run(); err != nil { if err := win.Run(); err != nil {
log.Fatal(err) log.Fatal(err)
@@ -82,6 +85,10 @@ func (e *env) changeOrigin(byX, byY int) func() {
} }
} }
func (e *env) changeZoom(_, byY float64) {
e.scenario.Zoom *= math.Pow(1.2, byY)
}
func (e *env) setZIdx(to int) func() { func (e *env) setZIdx(to int) func() {
return func() { return func() {
e.scenario.ZIdx = to e.scenario.ZIdx = to

View File

@@ -1,6 +1,7 @@
package assetstore package assetstore
import ( import (
"fmt"
"image" "image"
"log" "log"
@@ -95,7 +96,7 @@ func (m *Map) SpritesForCell(x, y, z int) ([]*Sprite, error) {
obj, err := m.set.Object(ref.Index()) obj, err := m.set.Object(ref.Index())
if err != nil { if err != nil {
return nil, err return nil, fmt.Errorf("Failed to get object for %#+v: %v", ref, err)
} }
sprite, err := obj.Sprite(ref.Sprite()) sprite, err := obj.Sprite(ref.Sprite())

View File

@@ -97,6 +97,24 @@ func (f *Flow) Update(screenX, screenY int) error {
return f.exit return f.exit
} }
// Keybindings for map control
// FIXME: this needs a big rethink
if f.current != nil && f.scenario != nil && !f.current.IsInDialogue() {
step := 32
if ebiten.IsKeyPressed(ebiten.KeyLeft) {
f.scenario.Viewpoint.X -= step
}
if ebiten.IsKeyPressed(ebiten.KeyRight) {
f.scenario.Viewpoint.X += step
}
if ebiten.IsKeyPressed(ebiten.KeyUp) {
f.scenario.Viewpoint.Y -= step
}
if ebiten.IsKeyPressed(ebiten.KeyDown) {
f.scenario.Viewpoint.Y += step
}
}
if f.scenario != nil { if f.scenario != nil {
if err := f.scenario.Update(screenX, screenY); err != nil { if err := f.scenario.Update(screenX, screenY); err != nil {
return err return err
@@ -265,6 +283,14 @@ func (f *Flow) hideDialogue(driver driverName) func() {
return f.drivers[driver].HideDialogue return f.drivers[driver].HideDialogue
} }
func (f *Flow) withScenario(then func()) func() {
return func() {
if f.scenario != nil {
then()
}
}
}
func (f *Flow) reset() { func (f *Flow) reset() {
if f.exit != nil { if f.exit != nil {
return return

View File

@@ -6,9 +6,9 @@ package flow
func (f *Flow) linkMainGame() { func (f *Flow) linkMainGame() {
f.linkMainGameActionMenu() f.linkMainGameActionMenu()
f.linkMainGameInterfaceOptionsMenu() f.linkMainGameInterfaceOptionsMenu()
// 5: Holding menu // 5: Holding menu
// 6: View menu f.linkMainGameViewMenu()
// 7: General character menu // 7: General character menu
f.onClick(mainGame, "7.4", func() { // More button f.onClick(mainGame, "7.4", func() { // More button
f.setActiveNow(mainGame, "7", false) f.setActiveNow(mainGame, "7", false)
@@ -26,10 +26,10 @@ func (f *Flow) linkMainGame() {
// 11: Psyker spell dialogue // 11: Psyker spell dialogue
// 12: Inventory dialogue // 12: Inventory dialogue
f.onClick(mainGame, "12.21", f.hideDialogue(mainGame)) // Exit f.onClick(mainGame, "12.21", f.hideDialogue(mainGame)) // Exit
// 13: exchange menu // 13: exchange menu
// 14: Map // 14: Map
// FIXME: Map should be right-aligned
// 14.1: MAP_SPRITE // 14.1: MAP_SPRITE
// 14.2: Multiplier button (2x) // 14.2: Multiplier button (2x)
f.onClick(mainGame, "14.3", f.setActive(mainGame, "14", false)) f.onClick(mainGame, "14.3", f.setActive(mainGame, "14", false))
@@ -46,8 +46,6 @@ func (f *Flow) linkMainGame() {
// and the dead space is filled by the "interface wing" sprites in the // and the dead space is filled by the "interface wing" sprites in the
// background. Should we replicate this, or keep with the current scaling // background. Should we replicate this, or keep with the current scaling
// behaviour? Which is better? // behaviour? Which is better?
//
// FIXME: the menu bar should be at the bottom, not top, of the screen
f.exit = maybeErr(mainGame, f.setActiveNow(mainGame, "15", false)) // Interface wing left f.exit = maybeErr(mainGame, f.setActiveNow(mainGame, "15", false)) // Interface wing left
f.exit = maybeErr(mainGame, f.setActiveNow(mainGame, "16", false)) // Interface wing right f.exit = maybeErr(mainGame, f.setActiveNow(mainGame, "16", false)) // Interface wing right
// 17: Grenade dialogue // 17: Grenade dialogue
@@ -90,6 +88,9 @@ func (f *Flow) linkMainGameActionMenu() {
func (f *Flow) linkMainGameInterfaceOptionsMenu() { func (f *Flow) linkMainGameInterfaceOptionsMenu() {
// 4: Interface options menu // 4: Interface options menu
f.onClick(mainGame, "4.1", f.setReturningDriver(mainGame, options)) // Options button f.onClick(mainGame, "4.1", f.setReturningDriver(mainGame, options)) // Options button
// FIXME: map should be shown top-right, not top-left. We need to support 2x
// mode as well.
f.onClick(mainGame, "4.2", f.toggleActive(mainGame, "14")) // Map button f.onClick(mainGame, "4.2", f.toggleActive(mainGame, "14")) // Map button
// FIXME: mission objectives should be shown top-left, not centered // FIXME: mission objectives should be shown top-left, not centered
@@ -100,3 +101,56 @@ func (f *Flow) linkMainGameInterfaceOptionsMenu() {
// 4.6: Next enemy // 4.6: Next enemy
// 4.7: Total enemy text // 4.7: Total enemy text
} }
func (f *Flow) linkMainGameViewMenu() {
// FIXME: all these buttons should show current state as well as have an
// effect
f.onClick(mainGame, "6.1", f.withScenario(func() { // View 100%
f.scenario.Zoom = 1.0
}))
f.onClick(mainGame, "6.2", f.withScenario(func() { // View 50%
f.scenario.Zoom = 0.5
}))
f.onClick(mainGame, "6.3", f.withScenario(func() { // View 25%
f.scenario.Zoom = 0.25
}))
f.onClick(mainGame, "6.4", f.withScenario(func() { // Z index up
f.scenario.ChangeZIdx(+1)
}))
f.onClick(mainGame, "6.5", f.withScenario(func() { // Z index down
f.scenario.ChangeZIdx(-1)
}))
f.onClick(mainGame, "6.6", f.withScenario(func() { // Z index 1
f.scenario.ZIdx = 0
}))
f.onClick(mainGame, "6.7", f.withScenario(func() { // Z index 2
f.scenario.ZIdx = 1
}))
f.onClick(mainGame, "6.8", f.withScenario(func() { // Z index 3
f.scenario.ZIdx = 2
}))
f.onClick(mainGame, "6.9", f.withScenario(func() { // Z index 4
f.scenario.ZIdx = 3
}))
f.onClick(mainGame, "6.10", f.withScenario(func() { // Z index 5
f.scenario.ZIdx = 4
}))
f.onClick(mainGame, "6.11", f.withScenario(func() { // Z index 6
f.scenario.ZIdx = 5
}))
f.onClick(mainGame, "6.12", f.withScenario(func() { // Z index 7
f.scenario.ZIdx = 6
}))
}

View File

@@ -26,7 +26,7 @@ const (
CellSize = 16 // seems to be CellSize = 16 // seems to be
cellDataOffset = 0x110 // tentatively cellDataOffset = 0x110 // definitely
cellCount = MaxHeight * MaxLength * MaxWidth cellCount = MaxHeight * MaxLength * MaxWidth
) )

View File

@@ -22,16 +22,21 @@ type IsoPt struct {
func (s *Scenario) Update(screenX, screenY int) error { func (s *Scenario) Update(screenX, screenY int) error {
s.tick += 1 s.tick += 1
x, y := ebiten.CursorPosition() geo := s.geoForCam()
geo.Translate(cellWidthHalf, 0)
geo.Scale(s.Zoom, s.Zoom)
geo.Invert()
cX, cY := ebiten.CursorPosition()
x, y := geo.Apply(float64(cX), float64(cY))
screenPos := CartPt{ screenPos := CartPt{
X: float64(s.Viewpoint.X + x), X: x,
Y: float64(s.Viewpoint.Y + y), Y: y,
} }
s.selectedCell = screenPos.ToISO()
// TODO: zoom support will need a camera
// FIXME: adjust for Z level // FIXME: adjust for Z level
s.selectedCell = screenPos.ToISO()
return nil return nil
} }
@@ -41,17 +46,18 @@ func (s *Scenario) Draw(screen *ebiten.Image) error {
// http://www.java-gaming.org/index.php?topic=24922.0 // http://www.java-gaming.org/index.php?topic=24922.0
// https://stackoverflow.com/questions/892811/drawing-isometric-game-worlds // https://stackoverflow.com/questions/892811/drawing-isometric-game-worlds
// https://gamedev.stackexchange.com/questions/25896/how-do-i-find-which-isometric-tiles-are-inside-the-cameras-current-view // https://gamedev.stackexchange.com/questions/25896/how-do-i-find-which-isometric-tiles-are-inside-the-cameras-current-view
// FIXME: we don't cope with zoom very neatly here
sw, sh := screen.Size() sw, sh := screen.Size()
topLeft := CartPt{ topLeft := CartPt{
X: float64(s.Viewpoint.X - 2*cellWidth), // Ensure all visible cells are rendered X: float64(s.Viewpoint.X) - (2*cellWidth/s.Zoom), // Ensure all visible cells are rendered
Y: float64(s.Viewpoint.Y - 2*cellHeight), Y: float64(s.Viewpoint.Y) - (2*cellHeight/s.Zoom),
}.ToISO() }.ToISO()
bottomRight := CartPt{ bottomRight := CartPt{
X: float64(s.Viewpoint.X + sw + 2*cellHeight), X: float64(s.Viewpoint.X) + (float64(sw)/s.Zoom) + (2*cellHeight/s.Zoom),
Y: float64(s.Viewpoint.Y + sh + 5*cellHeight), // Z dimension requires it Y: float64(s.Viewpoint.Y) + (float64(sh)/s.Zoom) + (5*cellHeight/s.Zoom), // Z dimension requires it
}.ToISO() }.ToISO()
// X+Y is constant for all tiles in a column // X+Y is constant for all tiles in a column
@@ -101,29 +107,53 @@ func (s *Scenario) Draw(screen *ebiten.Image) error {
//log.Printf("%#+v", counter) //log.Printf("%#+v", counter)
// Finally, draw cursor chrome // Finally, draw cursor chrome
// FIXME: it looks like we might need to do this in normal painting order...
spr, err := s.specials.Sprite(0) spr, err := s.specials.Sprite(0)
if err != nil { if err != nil {
return err return err
} }
op := ebiten.DrawImageOptions{} op := ebiten.DrawImageOptions{}
op.GeoM = s.geoForCoords(int(s.selectedCell.X), int(s.selectedCell.Y), 0) geo := s.geoForCoords(int(s.selectedCell.X), int(s.selectedCell.Y), 0)
op.GeoM.Translate(-cellWidthHalf, -cellHeightHalf) op.GeoM = geo
op.GeoM.Translate(-209, -332)
op.GeoM.Translate(float64(spr.Rect.Min.X), float64(spr.Rect.Min.Y))
op.GeoM.Scale(s.Zoom, s.Zoom)
if err := screen.DrawImage(spr.Image, &op); err != nil { if err := screen.DrawImage(spr.Image, &op); err != nil {
return err return err
} }
sx, sy := op.GeoM.Apply(0, 0) x1, y1 := geo.Apply(0, 0)
ebitenutil.DebugPrintAt(screen, fmt.Sprintf("(%.0f,%.0f)", s.selectedCell.X, s.selectedCell.Y), int(sx), int(sy)) ebitenutil.DebugPrintAt(
screen,
fmt.Sprintf("(%d,%d)", int(s.selectedCell.X), int(s.selectedCell.Y)),
int(x1),
int(y1),
)
/*
// debug: draw a square around the selected cell
x2, y2 := geo.Apply(cellWidth, cellHeight)
ebitenutil.DrawLine(screen, x1, y1, x2, y1, colornames.Green) // top line
ebitenutil.DrawLine(screen, x1, y1, x1, y2, colornames.Green) // left line
ebitenutil.DrawLine(screen, x2, y1, x2, y2, colornames.Green) // right line
ebitenutil.DrawLine(screen, x1, y2, x2, y2, colornames.Green) // bottom line
*/
return nil return nil
} }
func (s *Scenario) geoForCoords(x, y, z int) ebiten.GeoM { func (s *Scenario) geoForCam() ebiten.GeoM {
geo := ebiten.GeoM{} geo := ebiten.GeoM{}
geo.Translate(-float64(s.Viewpoint.X), -float64(s.Viewpoint.Y)) geo.Translate(-float64(s.Viewpoint.X), -float64(s.Viewpoint.Y))
return geo
}
func (s *Scenario) geoForCoords(x, y, z int) ebiten.GeoM {
geo := s.geoForCam()
pix := IsoPt{X: float64(x), Y: float64(y)}.ToCart() pix := IsoPt{X: float64(x), Y: float64(y)}.ToCart()
geo.Translate(pix.X, pix.Y) geo.Translate(pix.X, pix.Y)
@@ -157,6 +187,9 @@ func (s *Scenario) renderCell(x, y, z int, screen *ebiten.Image, counter map[str
op.GeoM.Translate(float64(spr.Rect.Min.X), float64(spr.Rect.Min.Y)) op.GeoM.Translate(float64(spr.Rect.Min.X), float64(spr.Rect.Min.Y))
// Zoom has to come last
op.GeoM.Scale(s.Zoom, s.Zoom)
if err := screen.DrawImage(spr.Image, &op); err != nil { if err := screen.DrawImage(spr.Image, &op); err != nil {
return err return err
} }
@@ -166,11 +199,11 @@ func (s *Scenario) renderCell(x, y, z int, screen *ebiten.Image, counter map[str
} }
const ( const (
cellWidth = 128 cellWidth = 128.0
cellHeight = 64 cellHeight = 63.0
cellWidthHalf = cellWidth / 2 cellWidthHalf = cellWidth / 2.0
cellHeightHalf = cellHeight / 2 cellHeightHalf = cellHeight / 2.0
) )
func (p CartPt) ToISO() IsoPt { func (p CartPt) ToISO() IsoPt {
@@ -186,28 +219,3 @@ func (p IsoPt) ToCart() CartPt {
Y: (p.X + p.Y) * cellHeightHalf, Y: (p.X + p.Y) * cellHeightHalf,
} }
} }
/*
// Doesn't take the camera or Z level into account
func cellToPix(pt image.Point) image.Point {
return image.Pt(
(pt.X-pt.Y)*cellWidthHalf,
(pt.X+pt.Y)*cellHeightHalf,
)
}
// Doesn't take the camera or Z level into account
func pixToCell(pt image.Point) image.Point {
fX := pt.X
fY := pt.Y
return image.Pt(
// (pt.X / cellWidthHalf + pt.Y / cellHeightHalf) / 2,
// (pt.Y / cellHeightHalf - (pt.Y / cellWidthHalf)) / 2,
// int(fY/cellHeight+fX/(cellWidth*2)),
// int(fY/cellHeight-fX/(cellWidth*2)),
//int((fY / cellHeight) + (fX / cellWidth)),
//int((-fX / cellWidth) + (fY / cellHeight)),
)
}*/

View File

@@ -13,3 +13,16 @@ func (s *Scenario) CellAtCursor() (maps.Cell, CellPoint) {
cell := s.area.Cell(int(s.selectedCell.X), int(s.selectedCell.Y), 0) cell := s.area.Cell(int(s.selectedCell.X), int(s.selectedCell.Y), 0)
return cell, CellPoint{IsoPt: s.selectedCell, Z: 0} return cell, CellPoint{IsoPt: s.selectedCell, Z: 0}
} }
func (s *Scenario) ChangeZIdx(by int) {
newZ := s.ZIdx + by
if newZ < 0 {
newZ = 0
}
if newZ > 6 {
newZ = 6
}
s.ZIdx = newZ
}

View File

@@ -21,6 +21,7 @@ type Scenario struct {
// Or have a separater Drawer for the Scenario? // Or have a separater Drawer for the Scenario?
Viewpoint image.Point // Top-left of the screen Viewpoint image.Point // Top-left of the screen
ZIdx int // Currently-viewed Z index ZIdx int // Currently-viewed Z index
Zoom float64 // Zoom level to set
} }
func NewScenario(assets *assetstore.AssetStore, name string) (*Scenario, error) { func NewScenario(assets *assetstore.AssetStore, name string) (*Scenario, error) {
@@ -43,6 +44,7 @@ func NewScenario(assets *assetstore.AssetStore, name string) (*Scenario, error)
area: area, area: area,
specials: specials, specials: specials,
Viewpoint: image.Pt(0, 3000), // FIXME: haxxx Viewpoint: image.Pt(0, 3000), // FIXME: haxxx
Zoom: 1.0,
} }
return out, nil return out, nil

View File

@@ -14,6 +14,10 @@ func (d *Driver) Dialogues() []string {
return out return out
} }
func (d *Driver) IsInDialogue() bool {
return d.activeDialogue != nil
}
func (d *Driver) ShowDialogue(locator string) error { func (d *Driver) ShowDialogue(locator string) error {
for _, dialogue := range d.dialogues { for _, dialogue := range d.dialogues {
if dialogue.Locator == locator { if dialogue.Locator == locator {

View File

@@ -37,6 +37,8 @@ type Window struct {
MouseWheelHandler func(float64, float64) MouseWheelHandler func(float64, float64)
MouseClickHandler func() MouseClickHandler func()
WhileKeyDownHandlers map[ebiten.Key]func()
// Allow the "game" to be switched out at any time // Allow the "game" to be switched out at any time
game Game game Game
@@ -55,12 +57,15 @@ func NewWindow(game Game, title string, xRes int, yRes int) (*Window, error) {
return &Window{ return &Window{
Title: title, Title: title,
KeyUpHandlers: make(map[ebiten.Key]func()),
debug: true, debug: true,
firstRun: true, firstRun: true,
game: game, game: game,
xRes: xRes, xRes: xRes,
yRes: yRes, yRes: yRes,
WhileKeyDownHandlers: make(map[ebiten.Key]func()),
KeyUpHandlers: make(map[ebiten.Key]func()),
}, nil }, nil
} }
@@ -69,6 +74,10 @@ func (w *Window) OnKeyUp(key ebiten.Key, f func()) {
w.KeyUpHandlers[key] = f w.KeyUpHandlers[key] = f
} }
func (w *Window) WhileKeyDown(key ebiten.Key, f func()) {
w.WhileKeyDownHandlers[key] = f
}
func (w *Window) OnMouseWheel(f func(x, y float64)) { func (w *Window) OnMouseWheel(f func(x, y float64)) {
w.MouseWheelHandler = f w.MouseWheelHandler = f
} }
@@ -119,7 +128,8 @@ func (w *Window) Update(screen *ebiten.Image) (outErr error) {
return err return err
} }
// Process keys // Process keys.
// FIXME: : should this happen before or after update?
// TODO: efficient set operations // TODO: efficient set operations
for key, cb := range w.KeyUpHandlers { for key, cb := range w.KeyUpHandlers {
@@ -128,6 +138,12 @@ func (w *Window) Update(screen *ebiten.Image) (outErr error) {
} }
} }
for key, cb := range w.WhileKeyDownHandlers {
if ebiten.IsKeyPressed(key) {
cb()
}
}
if w.MouseWheelHandler != nil { if w.MouseWheelHandler != nil {
x, y := ebiten.Wheel() x, y := ebiten.Wheel()
if x != 0 || y != 0 { if x != 0 || y != 0 {