Scenario viewpoint, Z index management, and arrow controls
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package assetstore
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"log"
|
||||
|
||||
@@ -95,7 +96,7 @@ func (m *Map) SpritesForCell(x, y, z int) ([]*Sprite, error) {
|
||||
|
||||
obj, err := m.set.Object(ref.Index())
|
||||
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())
|
||||
|
@@ -97,6 +97,24 @@ func (f *Flow) Update(screenX, screenY int) error {
|
||||
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 err := f.scenario.Update(screenX, screenY); err != nil {
|
||||
return err
|
||||
@@ -265,6 +283,14 @@ func (f *Flow) hideDialogue(driver driverName) func() {
|
||||
return f.drivers[driver].HideDialogue
|
||||
}
|
||||
|
||||
func (f *Flow) withScenario(then func()) func() {
|
||||
return func() {
|
||||
if f.scenario != nil {
|
||||
then()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Flow) reset() {
|
||||
if f.exit != nil {
|
||||
return
|
||||
|
@@ -6,9 +6,9 @@ package flow
|
||||
func (f *Flow) linkMainGame() {
|
||||
f.linkMainGameActionMenu()
|
||||
f.linkMainGameInterfaceOptionsMenu()
|
||||
|
||||
// 5: Holding menu
|
||||
// 6: View menu
|
||||
f.linkMainGameViewMenu()
|
||||
|
||||
// 7: General character menu
|
||||
f.onClick(mainGame, "7.4", func() { // More button
|
||||
f.setActiveNow(mainGame, "7", false)
|
||||
@@ -26,10 +26,10 @@ func (f *Flow) linkMainGame() {
|
||||
// 11: Psyker spell dialogue
|
||||
// 12: Inventory dialogue
|
||||
f.onClick(mainGame, "12.21", f.hideDialogue(mainGame)) // Exit
|
||||
|
||||
// 13: exchange menu
|
||||
|
||||
// 14: Map
|
||||
// FIXME: Map should be right-aligned
|
||||
// 14.1: MAP_SPRITE
|
||||
// 14.2: Multiplier button (2x)
|
||||
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
|
||||
// background. Should we replicate this, or keep with the current scaling
|
||||
// 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, "16", false)) // Interface wing right
|
||||
// 17: Grenade dialogue
|
||||
@@ -90,7 +88,10 @@ func (f *Flow) linkMainGameActionMenu() {
|
||||
func (f *Flow) linkMainGameInterfaceOptionsMenu() {
|
||||
// 4: Interface options menu
|
||||
f.onClick(mainGame, "4.1", f.setReturningDriver(mainGame, options)) // Options button
|
||||
f.onClick(mainGame, "4.2", f.toggleActive(mainGame, "14")) // Map 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
|
||||
|
||||
// FIXME: mission objectives should be shown top-left, not centered
|
||||
f.onClick(mainGame, "4.3", f.toggleActive(mainGame, "18")) // Mission objectives
|
||||
@@ -100,3 +101,56 @@ func (f *Flow) linkMainGameInterfaceOptionsMenu() {
|
||||
// 4.6: Next enemy
|
||||
// 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
|
||||
}))
|
||||
|
||||
}
|
||||
|
@@ -26,7 +26,7 @@ const (
|
||||
|
||||
CellSize = 16 // seems to be
|
||||
|
||||
cellDataOffset = 0x110 // tentatively
|
||||
cellDataOffset = 0x110 // definitely
|
||||
cellCount = MaxHeight * MaxLength * MaxWidth
|
||||
)
|
||||
|
||||
|
@@ -22,16 +22,21 @@ type IsoPt struct {
|
||||
func (s *Scenario) Update(screenX, screenY int) error {
|
||||
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{
|
||||
X: float64(s.Viewpoint.X + x),
|
||||
Y: float64(s.Viewpoint.Y + y),
|
||||
X: x,
|
||||
Y: y,
|
||||
}
|
||||
|
||||
s.selectedCell = screenPos.ToISO()
|
||||
|
||||
// TODO: zoom support will need a camera
|
||||
// FIXME: adjust for Z level
|
||||
s.selectedCell = screenPos.ToISO()
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -41,17 +46,18 @@ func (s *Scenario) Draw(screen *ebiten.Image) error {
|
||||
// http://www.java-gaming.org/index.php?topic=24922.0
|
||||
// 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
|
||||
// FIXME: we don't cope with zoom very neatly here
|
||||
|
||||
sw, sh := screen.Size()
|
||||
|
||||
topLeft := CartPt{
|
||||
X: float64(s.Viewpoint.X - 2*cellWidth), // Ensure all visible cells are rendered
|
||||
Y: float64(s.Viewpoint.Y - 2*cellHeight),
|
||||
X: float64(s.Viewpoint.X) - (2*cellWidth/s.Zoom), // Ensure all visible cells are rendered
|
||||
Y: float64(s.Viewpoint.Y) - (2*cellHeight/s.Zoom),
|
||||
}.ToISO()
|
||||
|
||||
bottomRight := CartPt{
|
||||
X: float64(s.Viewpoint.X + sw + 2*cellHeight),
|
||||
Y: float64(s.Viewpoint.Y + sh + 5*cellHeight), // Z dimension requires it
|
||||
X: float64(s.Viewpoint.X) + (float64(sw)/s.Zoom) + (2*cellHeight/s.Zoom),
|
||||
Y: float64(s.Viewpoint.Y) + (float64(sh)/s.Zoom) + (5*cellHeight/s.Zoom), // Z dimension requires it
|
||||
}.ToISO()
|
||||
|
||||
// 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)
|
||||
|
||||
// Finally, draw cursor chrome
|
||||
// FIXME: it looks like we might need to do this in normal painting order...
|
||||
spr, err := s.specials.Sprite(0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
op := ebiten.DrawImageOptions{}
|
||||
op.GeoM = s.geoForCoords(int(s.selectedCell.X), int(s.selectedCell.Y), 0)
|
||||
op.GeoM.Translate(-cellWidthHalf, -cellHeightHalf)
|
||||
geo := s.geoForCoords(int(s.selectedCell.X), int(s.selectedCell.Y), 0)
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
||||
x1, y1 := geo.Apply(0, 0)
|
||||
ebitenutil.DebugPrintAt(
|
||||
screen,
|
||||
fmt.Sprintf("(%d,%d)", int(s.selectedCell.X), int(s.selectedCell.Y)),
|
||||
int(x1),
|
||||
int(y1),
|
||||
)
|
||||
|
||||
sx, sy := op.GeoM.Apply(0, 0)
|
||||
ebitenutil.DebugPrintAt(screen, fmt.Sprintf("(%.0f,%.0f)", s.selectedCell.X, s.selectedCell.Y), int(sx), int(sy))
|
||||
/*
|
||||
// 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
|
||||
}
|
||||
|
||||
func (s *Scenario) geoForCoords(x, y, z int) ebiten.GeoM {
|
||||
func (s *Scenario) geoForCam() ebiten.GeoM {
|
||||
geo := ebiten.GeoM{}
|
||||
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()
|
||||
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))
|
||||
|
||||
// Zoom has to come last
|
||||
op.GeoM.Scale(s.Zoom, s.Zoom)
|
||||
|
||||
if err := screen.DrawImage(spr.Image, &op); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -166,11 +199,11 @@ func (s *Scenario) renderCell(x, y, z int, screen *ebiten.Image, counter map[str
|
||||
}
|
||||
|
||||
const (
|
||||
cellWidth = 128
|
||||
cellHeight = 64
|
||||
cellWidth = 128.0
|
||||
cellHeight = 63.0
|
||||
|
||||
cellWidthHalf = cellWidth / 2
|
||||
cellHeightHalf = cellHeight / 2
|
||||
cellWidthHalf = cellWidth / 2.0
|
||||
cellHeightHalf = cellHeight / 2.0
|
||||
)
|
||||
|
||||
func (p CartPt) ToISO() IsoPt {
|
||||
@@ -186,28 +219,3 @@ func (p IsoPt) ToCart() CartPt {
|
||||
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)),
|
||||
|
||||
|
||||
)
|
||||
}*/
|
||||
|
@@ -13,3 +13,16 @@ func (s *Scenario) CellAtCursor() (maps.Cell, CellPoint) {
|
||||
cell := s.area.Cell(int(s.selectedCell.X), int(s.selectedCell.Y), 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
|
||||
}
|
||||
|
@@ -21,6 +21,7 @@ type Scenario struct {
|
||||
// Or have a separater Drawer for the Scenario?
|
||||
Viewpoint image.Point // Top-left of the screen
|
||||
ZIdx int // Currently-viewed Z index
|
||||
Zoom float64 // Zoom level to set
|
||||
}
|
||||
|
||||
func NewScenario(assets *assetstore.AssetStore, name string) (*Scenario, error) {
|
||||
@@ -43,6 +44,7 @@ func NewScenario(assets *assetstore.AssetStore, name string) (*Scenario, error)
|
||||
area: area,
|
||||
specials: specials,
|
||||
Viewpoint: image.Pt(0, 3000), // FIXME: haxxx
|
||||
Zoom: 1.0,
|
||||
}
|
||||
|
||||
return out, nil
|
||||
|
@@ -14,6 +14,10 @@ func (d *Driver) Dialogues() []string {
|
||||
return out
|
||||
}
|
||||
|
||||
func (d *Driver) IsInDialogue() bool {
|
||||
return d.activeDialogue != nil
|
||||
}
|
||||
|
||||
func (d *Driver) ShowDialogue(locator string) error {
|
||||
for _, dialogue := range d.dialogues {
|
||||
if dialogue.Locator == locator {
|
||||
|
@@ -37,6 +37,8 @@ type Window struct {
|
||||
MouseWheelHandler func(float64, float64)
|
||||
MouseClickHandler func()
|
||||
|
||||
WhileKeyDownHandlers map[ebiten.Key]func()
|
||||
|
||||
// Allow the "game" to be switched out at any time
|
||||
game Game
|
||||
|
||||
@@ -54,13 +56,16 @@ func NewWindow(game Game, title string, xRes int, yRes int) (*Window, error) {
|
||||
ebiten.SetRunnableInBackground(true)
|
||||
|
||||
return &Window{
|
||||
Title: title,
|
||||
Title: title,
|
||||
debug: true,
|
||||
firstRun: true,
|
||||
game: game,
|
||||
xRes: xRes,
|
||||
yRes: yRes,
|
||||
|
||||
WhileKeyDownHandlers: make(map[ebiten.Key]func()),
|
||||
|
||||
KeyUpHandlers: make(map[ebiten.Key]func()),
|
||||
debug: true,
|
||||
firstRun: true,
|
||||
game: game,
|
||||
xRes: xRes,
|
||||
yRes: yRes,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -69,6 +74,10 @@ func (w *Window) OnKeyUp(key ebiten.Key, f func()) {
|
||||
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)) {
|
||||
w.MouseWheelHandler = f
|
||||
}
|
||||
@@ -119,7 +128,8 @@ func (w *Window) Update(screen *ebiten.Image) (outErr error) {
|
||||
return err
|
||||
}
|
||||
|
||||
// Process keys
|
||||
// Process keys.
|
||||
// FIXME: : should this happen before or after update?
|
||||
// TODO: efficient set operations
|
||||
|
||||
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 {
|
||||
x, y := ebiten.Wheel()
|
||||
if x != 0 || y != 0 {
|
||||
|
Reference in New Issue
Block a user