ebiten/examples/drag/main.go

285 lines
6.4 KiB
Go
Raw Normal View History

2018-04-30 17:50:53 +02:00
// Copyright 2018 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.
package main
import (
"bytes"
"image"
"image/color"
_ "image/png"
"log"
"math/rand"
2018-05-09 20:19:07 +02:00
"time"
2018-04-30 17:50:53 +02:00
2020-10-03 19:35:13 +02:00
"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"
2018-04-30 17:50:53 +02:00
)
2018-05-09 20:19:07 +02:00
func init() {
rand.Seed(time.Now().UnixNano())
}
2018-04-30 17:50:53 +02:00
const (
screenWidth = 640
screenHeight = 480
2018-04-30 17:50:53 +02:00
)
// Sprite represents an image.
type Sprite struct {
image *ebiten.Image
alphaImage *image.Alpha
x int
y int
2023-10-27 15:55:55 +02:00
dragged bool
2018-04-30 17:50:53 +02:00
}
// In returns true if (x, y) is in the sprite, and false otherwise.
func (s *Sprite) In(x, y int) bool {
// Check the actual color (alpha) value at the specified position
// so that the result of In becomes natural to users.
2018-05-01 11:07:52 +02:00
//
// Use alphaImage (*image.Alpha) instead of image (*ebiten.Image) here.
// It is because (*ebiten.Image).At is very slow as this reads pixels from GPU,
// and should be avoided whenever possible.
return s.alphaImage.At(x-s.x, y-s.y).(color.Alpha).A > 0
2018-04-30 17:50:53 +02:00
}
2023-10-27 15:55:55 +02:00
// MoveTo moves the sprite to the position (x, y).
func (s *Sprite) MoveTo(x, y int) {
w, h := s.image.Bounds().Dx(), s.image.Bounds().Dy()
2018-04-30 17:50:53 +02:00
2023-10-27 15:55:55 +02:00
s.x = x
s.y = y
2018-04-30 17:50:53 +02:00
if s.x < 0 {
s.x = 0
}
if s.x > screenWidth-w {
s.x = screenWidth - w
}
if s.y < 0 {
s.y = 0
}
if s.y > screenHeight-h {
s.y = screenHeight - h
}
}
// Draw draws the sprite.
2023-10-27 15:55:55 +02:00
func (s *Sprite) Draw(screen *ebiten.Image, alpha float32) {
2018-04-30 17:50:53 +02:00
op := &ebiten.DrawImageOptions{}
2023-10-27 15:55:55 +02:00
op.GeoM.Translate(float64(s.x), float64(s.y))
op.ColorScale.ScaleAlpha(alpha)
2018-04-30 17:50:53 +02:00
screen.DrawImage(s.image, op)
}
2018-05-09 20:19:07 +02:00
// StrokeSource represents a input device to provide strokes.
type StrokeSource interface {
Position() (int, int)
IsJustReleased() bool
}
// MouseStrokeSource is a StrokeSource implementation of mouse.
type MouseStrokeSource struct{}
2018-04-30 17:50:53 +02:00
2018-05-09 20:19:07 +02:00
func (m *MouseStrokeSource) Position() (int, int) {
return ebiten.CursorPosition()
}
2018-04-30 17:50:53 +02:00
2018-05-09 20:19:07 +02:00
func (m *MouseStrokeSource) IsJustReleased() bool {
return inpututil.IsMouseButtonJustReleased(ebiten.MouseButtonLeft)
}
// TouchStrokeSource is a StrokeSource implementation of touch.
type TouchStrokeSource struct {
ID ebiten.TouchID
2018-05-09 20:19:07 +02:00
}
func (t *TouchStrokeSource) Position() (int, int) {
return ebiten.TouchPosition(t.ID)
}
func (t *TouchStrokeSource) IsJustReleased() bool {
return inpututil.IsTouchJustReleased(t.ID)
}
// Stroke manages the current drag state by mouse.
type Stroke struct {
source StrokeSource
2018-04-30 17:50:53 +02:00
2023-10-27 15:55:55 +02:00
// offsetX and offsetY represents a relative value from the sprite's upper-left position to the cursor position.
offsetX int
offsetY int
2018-04-30 17:50:53 +02:00
2023-10-27 15:55:55 +02:00
// sprite represents a sprite being dragged.
sprite *Sprite
2018-04-30 17:50:53 +02:00
}
2023-10-27 15:55:55 +02:00
func NewStroke(source StrokeSource, sprite *Sprite) *Stroke {
sprite.dragged = true
x, y := source.Position()
2018-05-09 20:19:07 +02:00
return &Stroke{
2023-10-27 15:55:55 +02:00
source: source,
offsetX: x - sprite.x,
offsetY: y - sprite.y,
sprite: sprite,
2018-04-30 17:50:53 +02:00
}
}
2018-05-09 20:19:07 +02:00
func (s *Stroke) Update() {
2023-10-27 15:55:55 +02:00
if !s.sprite.dragged {
2018-05-09 20:19:07 +02:00
return
}
if s.source.IsJustReleased() {
2023-10-27 15:55:55 +02:00
s.sprite.dragged = false
2018-05-09 20:19:07 +02:00
return
}
2023-10-27 15:55:55 +02:00
x, y := s.source.Position()
x -= s.offsetX
y -= s.offsetY
s.sprite.MoveTo(x, y)
2018-05-09 20:19:07 +02:00
}
2023-10-27 15:55:55 +02:00
func (s *Stroke) Sprite() *Sprite {
return s.sprite
2018-05-09 20:19:07 +02:00
}
2018-04-30 17:50:53 +02:00
2018-05-09 20:19:07 +02:00
type Game struct {
touchIDs []ebiten.TouchID
strokes map[*Stroke]struct{}
sprites []*Sprite
2018-04-30 17:50:53 +02:00
}
var (
ebitenImage *ebiten.Image
ebitenAlphaImage *image.Alpha
)
2018-04-30 17:50:53 +02:00
func init() {
// Decode an image from the image file's byte slice.
2018-04-30 17:50:53 +02:00
img, _, err := image.Decode(bytes.NewReader(images.Ebiten_png))
if err != nil {
log.Fatal(err)
}
ebitenImage = ebiten.NewImageFromImage(img)
// Clone an image but only with alpha values.
// This is used to detect a user cursor touches the image.
b := img.Bounds()
ebitenAlphaImage = image.NewAlpha(b)
for j := b.Min.Y; j < b.Max.Y; j++ {
for i := b.Min.X; i < b.Max.X; i++ {
ebitenAlphaImage.Set(i, j, img.At(i, j))
}
}
2020-04-12 12:12:57 +02:00
}
2018-04-30 17:50:53 +02:00
2020-04-12 12:12:57 +02:00
func NewGame() *Game {
2018-04-30 17:50:53 +02:00
// Initialize the sprites.
sprites := []*Sprite{}
w, h := ebitenImage.Bounds().Dx(), ebitenImage.Bounds().Dy()
2018-04-30 17:50:53 +02:00
for i := 0; i < 50; i++ {
s := &Sprite{
image: ebitenImage,
alphaImage: ebitenAlphaImage,
x: rand.Intn(screenWidth - w),
y: rand.Intn(screenHeight - h),
2018-04-30 17:50:53 +02:00
}
sprites = append(sprites, s)
}
// Initialize the game.
2020-04-12 12:12:57 +02:00
return &Game{
2018-05-09 20:19:07 +02:00
strokes: map[*Stroke]struct{}{},
sprites: sprites,
2018-04-30 17:50:53 +02:00
}
}
2018-05-09 20:19:07 +02:00
func (g *Game) spriteAt(x, y int) *Sprite {
// As the sprites are ordered from back to front,
// search the clicked/touched sprite in reverse order.
for i := len(g.sprites) - 1; i >= 0; i-- {
s := g.sprites[i]
if s.In(x, y) {
return s
2018-04-30 17:50:53 +02:00
}
2018-05-09 20:19:07 +02:00
}
return nil
}
2023-10-27 15:55:55 +02:00
func (g *Game) moveSpriteToFront(sprite *Sprite) {
2018-05-09 20:19:07 +02:00
index := -1
for i, ss := range g.sprites {
2023-10-27 15:55:55 +02:00
if ss == sprite {
2018-05-09 20:19:07 +02:00
index = i
break
}
}
g.sprites = append(g.sprites[:index], g.sprites[index+1:]...)
2023-10-27 15:55:55 +02:00
g.sprites = append(g.sprites, sprite)
2018-05-09 20:19:07 +02:00
}
func (g *Game) Update() error {
2018-05-09 20:19:07 +02:00
if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) {
2023-10-27 15:55:55 +02:00
if sp := g.spriteAt(ebiten.CursorPosition()); sp != nil {
s := NewStroke(&MouseStrokeSource{}, sp)
g.strokes[s] = struct{}{}
g.moveSpriteToFront(sp)
}
2018-05-09 20:19:07 +02:00
}
g.touchIDs = inpututil.AppendJustPressedTouchIDs(g.touchIDs[:0])
for _, id := range g.touchIDs {
2023-10-27 15:55:55 +02:00
if sp := g.spriteAt(ebiten.TouchPosition(id)); sp != nil {
s := NewStroke(&TouchStrokeSource{id}, sp)
g.strokes[s] = struct{}{}
g.moveSpriteToFront(sp)
}
2018-05-09 20:19:07 +02:00
}
for s := range g.strokes {
2023-10-27 15:55:55 +02:00
s.Update()
if !s.sprite.dragged {
2018-05-09 20:19:07 +02:00
delete(g.strokes, s)
2018-04-30 17:50:53 +02:00
}
}
2020-04-12 12:12:57 +02:00
return nil
}
2018-04-30 17:50:53 +02:00
2020-04-12 12:12:57 +02:00
func (g *Game) Draw(screen *ebiten.Image) {
2018-05-09 20:19:07 +02:00
for _, s := range g.sprites {
2023-10-27 15:55:55 +02:00
if s.dragged {
s.Draw(screen, 0.5)
} else {
s.Draw(screen, 1)
}
2018-04-30 17:50:53 +02:00
}
ebitenutil.DebugPrint(screen, "Drag & Drop the sprites!")
2020-04-12 12:12:57 +02:00
}
2018-04-30 17:50:53 +02:00
2020-04-12 12:12:57 +02:00
func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
return screenWidth, screenHeight
2018-04-30 17:50:53 +02:00
}
func main() {
ebiten.SetWindowSize(screenWidth, screenHeight)
ebiten.SetWindowTitle("Drag & Drop (Ebitengine Demo)")
2020-04-12 12:12:57 +02:00
if err := ebiten.RunGame(NewGame()); err != nil {
2018-04-30 17:50:53 +02:00
log.Fatal(err)
}
}