mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2025-01-26 02:42:02 +01:00
Add a new Touch example (#1547)
This commit is contained in:
parent
7696d51839
commit
e1e213cc10
255
examples/touch/main.go
Normal file
255
examples/touch/main.go
Normal file
@ -0,0 +1,255 @@
|
||||
// Copyright 2021 The Ebiten Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// +build example
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"image"
|
||||
_ "image/jpeg"
|
||||
"log"
|
||||
"math"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
|
||||
"github.com/hajimehoshi/ebiten/v2/examples/resources/images"
|
||||
"github.com/hajimehoshi/ebiten/v2/inpututil"
|
||||
)
|
||||
|
||||
// distance between points a and b.
|
||||
func distance(xa, ya, xb, yb int) float64 {
|
||||
x := math.Abs(float64(xa - xb))
|
||||
y := math.Abs(float64(ya - yb))
|
||||
return math.Sqrt(x*x + y*y)
|
||||
}
|
||||
|
||||
const (
|
||||
screenWidth = 640
|
||||
screenHeight = 480
|
||||
)
|
||||
|
||||
var (
|
||||
gophersImage *ebiten.Image
|
||||
)
|
||||
|
||||
type touch struct {
|
||||
originX, originY int
|
||||
currX, currY int
|
||||
duration int
|
||||
wasPinch, isPan bool
|
||||
}
|
||||
|
||||
type pinch struct {
|
||||
id1, id2 ebiten.TouchID
|
||||
originH float64
|
||||
prevH float64
|
||||
}
|
||||
|
||||
type pan struct {
|
||||
id ebiten.TouchID
|
||||
|
||||
prevX, prevY int
|
||||
originX, originY int
|
||||
}
|
||||
|
||||
type tap struct {
|
||||
X, Y int
|
||||
}
|
||||
|
||||
type Game struct {
|
||||
x, y float64
|
||||
zoom float64
|
||||
|
||||
touches map[ebiten.TouchID]*touch
|
||||
pinch *pinch
|
||||
pan *pan
|
||||
taps []tap
|
||||
}
|
||||
|
||||
func (g *Game) Update() error {
|
||||
// Clear the previous frame's taps.
|
||||
g.taps = g.taps[:0]
|
||||
|
||||
// What touches have just ended?
|
||||
for id, t := range g.touches {
|
||||
if inpututil.IsTouchJustReleased(id) {
|
||||
if g.pinch != nil && (id == g.pinch.id1 || id == g.pinch.id2) {
|
||||
g.pinch = nil
|
||||
}
|
||||
if g.pan != nil && id == g.pan.id {
|
||||
g.pan = nil
|
||||
}
|
||||
|
||||
// If this one has not been touched long (30 frames can be assumed
|
||||
// to be 500ms), or moved far, then it's a tap.
|
||||
diff := distance(t.originX, t.originY, t.currX, t.currY)
|
||||
if !t.wasPinch && !t.isPan && (t.duration <= 30 || diff < 2) {
|
||||
g.taps = append(g.taps, tap{
|
||||
X: t.currX,
|
||||
Y: t.currY,
|
||||
})
|
||||
}
|
||||
|
||||
delete(g.touches, id)
|
||||
}
|
||||
}
|
||||
|
||||
// What touches are new in this frame?
|
||||
for _, id := range inpututil.JustPressedTouchIDs() {
|
||||
x, y := ebiten.TouchPosition(id)
|
||||
g.touches[ebiten.TouchID(id)] = &touch{
|
||||
originX: x, originY: y,
|
||||
currX: x, currY: y,
|
||||
}
|
||||
}
|
||||
|
||||
// Update the current position and durations of any touches that have
|
||||
// neither begun nor ended in this frame.
|
||||
for _, id := range ebiten.TouchIDs() {
|
||||
t := g.touches[id]
|
||||
t.duration = inpututil.TouchPressDuration(id)
|
||||
t.currX, t.currY = ebiten.TouchPosition(id)
|
||||
}
|
||||
|
||||
// Interpret the raw touch data that's been collected into g.touches into
|
||||
// gestures like two-finger pinch or single-finger pan.
|
||||
switch len(g.touches) {
|
||||
case 2:
|
||||
// Potentially the user is making a pinch gesture with two fingers.
|
||||
// If the diff between their origins is different to the diff between
|
||||
// their currents and if these two are not already a pinch, then this is
|
||||
// a new pinch!
|
||||
id1, id2 := ebiten.TouchIDs()[0], ebiten.TouchIDs()[1]
|
||||
t1, t2 := g.touches[id1], g.touches[id2]
|
||||
originDiff := distance(t1.originX, t1.originY, t2.originX, t2.originY)
|
||||
currDiff := distance(t1.currX, t1.currY, t2.currX, t2.currY)
|
||||
if g.pinch == nil && g.pan == nil && math.Abs(originDiff-currDiff) > 3 {
|
||||
t1.wasPinch = true
|
||||
t2.wasPinch = true
|
||||
g.pinch = &pinch{
|
||||
id1: id1,
|
||||
id2: id2,
|
||||
originH: originDiff,
|
||||
prevH: originDiff,
|
||||
}
|
||||
}
|
||||
case 1:
|
||||
// Potentially this is a new pan.
|
||||
id := ebiten.TouchIDs()[0]
|
||||
t := g.touches[id]
|
||||
if !t.wasPinch && g.pan == nil && g.pinch == nil {
|
||||
diff := math.Abs(distance(t.originX, t.originY, t.currX, t.currY))
|
||||
if diff > 1 {
|
||||
t.isPan = true
|
||||
g.pan = &pan{
|
||||
id: id,
|
||||
originX: t.originX,
|
||||
originY: t.originY,
|
||||
prevX: t.originX,
|
||||
prevY: t.originY,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Copy any active pinch gesture's movement to the Game's zoom.
|
||||
if g.pinch != nil {
|
||||
x1, y1 := ebiten.TouchPosition(g.pinch.id1)
|
||||
x2, y2 := ebiten.TouchPosition(g.pinch.id2)
|
||||
curr := distance(x1, y1, x2, y2)
|
||||
delta := curr - g.pinch.prevH
|
||||
g.pinch.prevH = curr
|
||||
|
||||
g.zoom += (delta / 100) * g.zoom
|
||||
if g.zoom < 0.25 {
|
||||
g.zoom = 0.25
|
||||
} else if g.zoom > 10 {
|
||||
g.zoom = 10
|
||||
}
|
||||
}
|
||||
|
||||
// Copy any active pan gesture's movement to the Game's x and y pan values.
|
||||
if g.pan != nil {
|
||||
currX, currY := ebiten.TouchPosition(g.pan.id)
|
||||
deltaX, deltaY := currX-g.pan.prevX, currY-g.pan.prevY
|
||||
|
||||
g.pan.prevX, g.pan.prevY = currX, currY
|
||||
|
||||
g.x += float64(deltaX)
|
||||
g.y += float64(deltaY)
|
||||
}
|
||||
|
||||
// If the user has tapped, then reset the Game's pan and zoom.
|
||||
if len(g.taps) > 0 {
|
||||
g.x = screenWidth / 2
|
||||
g.y = screenHeight / 2
|
||||
g.zoom = 1.0
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *Game) Draw(screen *ebiten.Image) {
|
||||
op := &ebiten.DrawImageOptions{}
|
||||
|
||||
// Apply zoom.
|
||||
op.GeoM.Scale(g.zoom, g.zoom)
|
||||
|
||||
// Apply pan.
|
||||
op.GeoM.Translate(g.x, g.y)
|
||||
|
||||
// Center the image (corrected by the current zoom).
|
||||
w, h := gophersImage.Size()
|
||||
op.GeoM.Translate(float64(-w)/2*g.zoom, float64(-h)/2*g.zoom)
|
||||
|
||||
screen.DrawImage(gophersImage, op)
|
||||
|
||||
ebitenutil.DebugPrint(screen, "Use a two finger pinch to zoom, swipe with one finger to pan, or tap to reset the view")
|
||||
}
|
||||
|
||||
func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
|
||||
return screenWidth, screenHeight
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Decode image from a byte slice instead of a file so that
|
||||
// this example works in any working directory.
|
||||
// If you want to use a file, there are some options:
|
||||
// 1) Use os.Open and pass the file to the image decoder.
|
||||
// This is a very regular way, but doesn't work on browsers.
|
||||
// 2) Use ebitenutil.OpenFile and pass the file to the image decoder.
|
||||
// This works even on browsers.
|
||||
// 3) Use ebitenutil.NewImageFromFile to create an ebiten.Image directly from a file.
|
||||
// This also works on browsers.
|
||||
img, _, err := image.Decode(bytes.NewReader(images.Gophers_jpg))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
gophersImage = ebiten.NewImageFromImage(img)
|
||||
|
||||
g := &Game{
|
||||
x: screenWidth / 2,
|
||||
y: screenHeight / 2,
|
||||
zoom: 1.0,
|
||||
|
||||
touches: map[ebiten.TouchID]*touch{},
|
||||
}
|
||||
|
||||
ebiten.SetWindowSize(screenWidth, screenHeight)
|
||||
ebiten.SetWindowTitle("Touch (Ebiten Demo)")
|
||||
if err := ebiten.RunGame(g); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user