Touch Screen Controls for Play Mode!
The game can now be played using only a touch screen! The left mouse click (Button1) can now move and control the player character. * A box in the very middle of the screen is the "Use" button and a deadzone for directional inputs. * Anywhere outside the middle and to the left registers a Left button, to the right a Right button, above the top of the middle is a Jump button, and below the bottom of the middle is a down input (for antigravity mode). * Tight platforming is possible: above and below the middle box, the left/right split is tight in the middle of the window. You can get tight jumps if jumping or go below if you don't want to jump. The left/right deadzone is only over the space of the Use button. If the player is idle for a while with no controller inputs, some hints will fade in about the touch controls. Note: the ScrollboxOffset to track the player character is changed to 60,60 from 60,100 so the camera will track tighter to the player and so the player will mostly be over the Use button on touch controls as long as he's away from a level boundary.
This commit is contained in:
parent
1f83300cec
commit
489a43ea8c
|
@ -1,6 +1,10 @@
|
||||||
package balance
|
package balance
|
||||||
|
|
||||||
import "git.kirsle.net/go/render"
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.kirsle.net/go/render"
|
||||||
|
)
|
||||||
|
|
||||||
// Numbers.
|
// Numbers.
|
||||||
var (
|
var (
|
||||||
|
@ -15,7 +19,7 @@ var (
|
||||||
// Window scrolling behavior in Play Mode.
|
// Window scrolling behavior in Play Mode.
|
||||||
ScrollboxOffset = render.Point{ // from center of screen
|
ScrollboxOffset = render.Point{ // from center of screen
|
||||||
X: 60,
|
X: 60,
|
||||||
Y: 100,
|
Y: 60,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Player speeds
|
// Player speeds
|
||||||
|
@ -67,6 +71,11 @@ var (
|
||||||
|
|
||||||
// File formats: save new levels and doodads gzip compressed
|
// File formats: save new levels and doodads gzip compressed
|
||||||
CompressDrawings = true
|
CompressDrawings = true
|
||||||
|
|
||||||
|
// Play Mode Touchscreen controls.
|
||||||
|
PlayModeIdleTimeout = 2200 * time.Millisecond
|
||||||
|
PlayModeAlphaStep = 8 // 0-255 alpha, steps per tick for fade in
|
||||||
|
PlayModeAlphaMax = 220
|
||||||
)
|
)
|
||||||
|
|
||||||
// Edit Mode Values
|
// Edit Mode Values
|
||||||
|
|
|
@ -45,6 +45,16 @@ var (
|
||||||
// Shadow: render.RGBA(200, 80, 0, 255),
|
// Shadow: render.RGBA(200, 80, 0, 255),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Play Mode Touch UI Hints Font
|
||||||
|
TouchHintsFont = render.Text{
|
||||||
|
FontFilename: "DejaVuSans.ttf",
|
||||||
|
Size: 14,
|
||||||
|
Color: render.SkyBlue,
|
||||||
|
Shadow: render.SkyBlue.Darken(128),
|
||||||
|
Padding: 8,
|
||||||
|
PadY: 12,
|
||||||
|
}
|
||||||
|
|
||||||
// Window and panel styles.
|
// Window and panel styles.
|
||||||
TitleConfig = ui.Config{
|
TitleConfig = ui.Config{
|
||||||
Background: render.MustHexColor("#FF9900"),
|
Background: render.MustHexColor("#FF9900"),
|
||||||
|
|
|
@ -2,6 +2,7 @@ package doodle
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
"git.kirsle.net/apps/doodle/pkg/balance"
|
"git.kirsle.net/apps/doodle/pkg/balance"
|
||||||
"git.kirsle.net/apps/doodle/pkg/collision"
|
"git.kirsle.net/apps/doodle/pkg/collision"
|
||||||
|
@ -57,6 +58,12 @@ type PlayScene struct {
|
||||||
invenFrame *ui.Frame
|
invenFrame *ui.Frame
|
||||||
invenItems []string // item list
|
invenItems []string // item list
|
||||||
invenDoodads map[string]*uix.Canvas
|
invenDoodads map[string]*uix.Canvas
|
||||||
|
|
||||||
|
// Touchscreen controls state.
|
||||||
|
isTouching bool
|
||||||
|
playerIsIdle bool // LoopTouchable watches for inactivity on input controls.
|
||||||
|
idleLastStart time.Time
|
||||||
|
idleHelpAlpha int // fade in UI hints
|
||||||
}
|
}
|
||||||
|
|
||||||
// Name of the scene.
|
// Name of the scene.
|
||||||
|
@ -407,6 +414,9 @@ func (s *PlayScene) Loop(d *Doodle, ev *event.State) error {
|
||||||
log.Error("PlayScene.Loop: scripting.Loop: %s", err)
|
log.Error("PlayScene.Loop: scripting.Loop: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Touch regions.
|
||||||
|
s.LoopTouchable(ev)
|
||||||
|
|
||||||
s.movePlayer(ev)
|
s.movePlayer(ev)
|
||||||
if err := s.drawing.Loop(ev); err != nil {
|
if err := s.drawing.Loop(ev); err != nil {
|
||||||
log.Error("Drawing loop error: %s", err.Error())
|
log.Error("Drawing loop error: %s", err.Error())
|
||||||
|
@ -460,6 +470,9 @@ func (s *PlayScene) Draw(d *Doodle) error {
|
||||||
})
|
})
|
||||||
s.editButton.Present(d.Engine, s.editButton.Point())
|
s.editButton.Present(d.Engine, s.editButton.Point())
|
||||||
|
|
||||||
|
// Visualize the touch regions?
|
||||||
|
s.DrawTouchable()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
205
pkg/play_scene_touch.go
Normal file
205
pkg/play_scene_touch.go
Normal file
|
@ -0,0 +1,205 @@
|
||||||
|
package doodle
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.kirsle.net/apps/doodle/pkg/balance"
|
||||||
|
"git.kirsle.net/apps/doodle/pkg/log"
|
||||||
|
"git.kirsle.net/go/render"
|
||||||
|
"git.kirsle.net/go/render/event"
|
||||||
|
"git.kirsle.net/go/ui"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Touchscreen Control functionality used in the Play Scene.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// LoopTouchable is called as part of PlayScene.Loop while the simulation is running.
|
||||||
|
//
|
||||||
|
// It looks for touch events on proportional regions of the window and emulates key
|
||||||
|
// input bindings to move the character, jump, etc.
|
||||||
|
//
|
||||||
|
// TODO: this function manipulates the event.State to set Up, Down, Left, Right and
|
||||||
|
// Space keys and may need love for reconfigurable keybinds later.
|
||||||
|
func (s *PlayScene) LoopTouchable(ev *event.State) {
|
||||||
|
var (
|
||||||
|
middle = s.touchGetMiddleBox()
|
||||||
|
cursor = render.NewPoint(ev.CursorX, ev.CursorY)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Detect if the player is idle.
|
||||||
|
// Idle means that they are not holding any directional or otherwise input key.
|
||||||
|
// Keyboard inputs and touch events from this function will set these keys.
|
||||||
|
// See if it stays unset long enough to consider idle.
|
||||||
|
if !ev.Up && !ev.Down && !ev.Right && !ev.Left && !ev.Space {
|
||||||
|
if s.idleLastStart.IsZero() {
|
||||||
|
s.idleLastStart = time.Now()
|
||||||
|
} else if time.Since(s.idleLastStart) > balance.PlayModeIdleTimeout {
|
||||||
|
if !s.playerIsIdle {
|
||||||
|
log.Debug("LoopTouchable: No keys pressed in a while, idle UI start")
|
||||||
|
}
|
||||||
|
s.playerIsIdle = true
|
||||||
|
|
||||||
|
// Fade in the hint UI by stepping up the alpha value.
|
||||||
|
if s.idleHelpAlpha < balance.PlayModeAlphaMax {
|
||||||
|
s.idleHelpAlpha += balance.PlayModeAlphaStep
|
||||||
|
}
|
||||||
|
|
||||||
|
// cap it from overflow
|
||||||
|
if s.idleHelpAlpha > balance.PlayModeAlphaMax {
|
||||||
|
s.idleHelpAlpha = balance.PlayModeAlphaMax
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
s.idleLastStart = time.Time{}
|
||||||
|
s.playerIsIdle = false
|
||||||
|
s.idleHelpAlpha = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Click (touch) event?
|
||||||
|
if ev.Button1 {
|
||||||
|
// Clicked left or right of middle = move left or right.
|
||||||
|
// By default the middle box is a dead zone, but if player
|
||||||
|
// is already moving laterally allow for quick precision.
|
||||||
|
if ev.Left || ev.Right {
|
||||||
|
if cursor.X < s.d.width/2 {
|
||||||
|
ev.Left = true
|
||||||
|
ev.Right = false
|
||||||
|
} else if cursor.X > s.d.width/2 {
|
||||||
|
ev.Right = true
|
||||||
|
ev.Left = false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if cursor.X < middle.X {
|
||||||
|
ev.Left = true
|
||||||
|
ev.Right = false
|
||||||
|
} else if cursor.X > middle.X+middle.W {
|
||||||
|
ev.Left = false
|
||||||
|
ev.Right = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clicked above middle = jump.
|
||||||
|
ev.Up = cursor.Y < middle.Y
|
||||||
|
|
||||||
|
// Clicked below middle = down (antigravity)
|
||||||
|
ev.Down = cursor.Y > middle.Y+middle.H
|
||||||
|
|
||||||
|
// Clicked on the middle box = Use.
|
||||||
|
if cursor.X >= middle.X && cursor.X <= middle.X+middle.W &&
|
||||||
|
cursor.Y >= middle.Y && cursor.Y <= middle.Y+middle.H {
|
||||||
|
ev.Space = true
|
||||||
|
|
||||||
|
// Also cancel any lateral movement.
|
||||||
|
ev.Left = false
|
||||||
|
ev.Right = false
|
||||||
|
}
|
||||||
|
|
||||||
|
s.isTouching = true
|
||||||
|
} else {
|
||||||
|
if s.isTouching {
|
||||||
|
ev.Left = false
|
||||||
|
ev.Right = false
|
||||||
|
ev.Up = false
|
||||||
|
ev.Down = false
|
||||||
|
ev.Space = false
|
||||||
|
s.isTouching = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DrawTouchable draws any UI elements if needed for the touch UI.
|
||||||
|
func (s *PlayScene) DrawTouchable() {
|
||||||
|
var (
|
||||||
|
middle = s.touchGetMiddleBox()
|
||||||
|
background = render.RGBA(200, 200, 200, uint8(s.idleHelpAlpha))
|
||||||
|
font = balance.TouchHintsFont
|
||||||
|
)
|
||||||
|
font.Color.Alpha = uint8(s.idleHelpAlpha)
|
||||||
|
font.Shadow.Alpha = uint8(s.idleHelpAlpha)
|
||||||
|
|
||||||
|
// If the player is idle for a while, start showing them a hint UI about
|
||||||
|
// the touch screen controls.
|
||||||
|
if s.playerIsIdle {
|
||||||
|
// Draw the "Use" button over the middle box.
|
||||||
|
useBtn := ui.NewLabel(ui.Label{
|
||||||
|
Text: "Touch here\nto 'use'\nobjects",
|
||||||
|
Font: font,
|
||||||
|
})
|
||||||
|
useBtn.SetBackground(background)
|
||||||
|
useBtn.Resize(middle)
|
||||||
|
useBtn.Compute(s.d.Engine)
|
||||||
|
useBtn.Present(s.d.Engine, middle.Point())
|
||||||
|
|
||||||
|
// Move Left and Move Right hints.
|
||||||
|
moveLeft := ui.NewLabel(ui.Label{
|
||||||
|
Text: "Touch here to\nmove left",
|
||||||
|
Font: font,
|
||||||
|
})
|
||||||
|
moveLeft.SetBackground(background)
|
||||||
|
moveLeft.Compute(s.d.Engine)
|
||||||
|
moveLeft.Present(s.d.Engine, render.Point{
|
||||||
|
X: (middle.X / 2) - (moveLeft.Size().W / 2),
|
||||||
|
Y: (s.d.height / 2) - (moveLeft.Size().H / 2),
|
||||||
|
})
|
||||||
|
|
||||||
|
// Move Left and Move Right hints.
|
||||||
|
moveRight := ui.NewLabel(ui.Label{
|
||||||
|
Text: "Touch here to\nmove right",
|
||||||
|
Font: font,
|
||||||
|
})
|
||||||
|
moveRight.SetBackground(background)
|
||||||
|
moveRight.Compute(s.d.Engine)
|
||||||
|
moveRight.Present(s.d.Engine, render.Point{
|
||||||
|
X: (middle.X+middle.W+s.d.width)/2 - (moveRight.Size().W / 2),
|
||||||
|
Y: (s.d.height / 2) - (moveRight.Size().H / 2),
|
||||||
|
})
|
||||||
|
|
||||||
|
// Jump hints.
|
||||||
|
moveUp := ui.NewLabel(ui.Label{
|
||||||
|
Text: "Touch anywhere above the middle of\nthe screen to jump up in the air",
|
||||||
|
Font: font,
|
||||||
|
})
|
||||||
|
moveUp.SetBackground(background)
|
||||||
|
moveUp.Compute(s.d.Engine)
|
||||||
|
moveUp.Present(s.d.Engine, render.Point{
|
||||||
|
X: (s.d.width / 2) - (moveUp.Size().W / 2),
|
||||||
|
Y: (middle.Y / 2) - (moveUp.Size().H / 2),
|
||||||
|
})
|
||||||
|
|
||||||
|
// Keybind hints.
|
||||||
|
keyHints := ui.NewLabel(ui.Label{
|
||||||
|
Text: "Keyboard controls:\n" +
|
||||||
|
"WASD or arrow keys for movement\n" +
|
||||||
|
"Space key to 'use' objects.",
|
||||||
|
Font: font,
|
||||||
|
})
|
||||||
|
keyHints.SetBackground(background)
|
||||||
|
keyHints.Compute(s.d.Engine)
|
||||||
|
keyHints.Present(s.d.Engine, render.Point{
|
||||||
|
X: (s.d.width / 2) - (keyHints.Size().W / 2),
|
||||||
|
Y: (middle.Y+middle.H+s.d.height)/2 - (keyHints.Size().H / 2),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the middle box of the screen and return it.
|
||||||
|
// X,Y are screen positions and W,H is the box size.
|
||||||
|
func (s *PlayScene) touchGetMiddleBox() render.Rect {
|
||||||
|
// Carve up the screen segments.
|
||||||
|
var (
|
||||||
|
// The application window dimensions.
|
||||||
|
width = s.d.width
|
||||||
|
height = s.d.height
|
||||||
|
|
||||||
|
// The middle box.
|
||||||
|
middleMinSize = 96 // minimum dimensions
|
||||||
|
middle = render.Rect{
|
||||||
|
X: (width / 2) - (middleMinSize / 2),
|
||||||
|
Y: (height / 2) - (middleMinSize / 2),
|
||||||
|
W: middleMinSize,
|
||||||
|
H: middleMinSize,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return middle
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user