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.
|
|
|
|
|
2021-06-24 14:49:37 +02:00
|
|
|
//go:build example
|
2020-10-06 17:45:54 +02:00
|
|
|
// +build example
|
2018-04-30 17:50:53 +02:00
|
|
|
|
|
|
|
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 (
|
2020-10-30 18:01:11 +01:00
|
|
|
screenWidth = 640
|
|
|
|
screenHeight = 480
|
2018-04-30 17:50:53 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
// Sprite represents an image.
|
|
|
|
type Sprite struct {
|
|
|
|
image *ebiten.Image
|
|
|
|
x int
|
|
|
|
y int
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
|
//
|
|
|
|
// Note that this is not a good manner to use At for logic
|
|
|
|
// since color from At might include some errors on some machines.
|
|
|
|
// As this is not so important logic, it's ok to use it so far.
|
2018-04-30 17:50:53 +02:00
|
|
|
return s.image.At(x-s.x, y-s.y).(color.RGBA).A > 0
|
|
|
|
}
|
|
|
|
|
|
|
|
// MoveBy moves the sprite by (x, y).
|
|
|
|
func (s *Sprite) MoveBy(x, y int) {
|
|
|
|
w, h := s.image.Size()
|
|
|
|
|
|
|
|
s.x += x
|
|
|
|
s.y += y
|
|
|
|
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.
|
|
|
|
func (s *Sprite) Draw(screen *ebiten.Image, dx, dy int, alpha float64) {
|
|
|
|
op := &ebiten.DrawImageOptions{}
|
|
|
|
op.GeoM.Translate(float64(s.x+dx), float64(s.y+dy))
|
|
|
|
op.ColorM.Scale(1, 1, 1, alpha)
|
|
|
|
screen.DrawImage(s.image, op)
|
|
|
|
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 {
|
2020-10-09 20:36:14 +02:00
|
|
|
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
|
|
|
|
|
|
|
// initX and initY represents the position when dragging starts.
|
|
|
|
initX int
|
|
|
|
initY int
|
|
|
|
|
|
|
|
// currentX and currentY represents the current position
|
|
|
|
currentX int
|
|
|
|
currentY int
|
2018-05-09 20:19:07 +02:00
|
|
|
|
|
|
|
released bool
|
|
|
|
|
|
|
|
// draggingObject represents a object (sprite in this case)
|
|
|
|
// that is being dragged.
|
|
|
|
draggingObject interface{}
|
2018-04-30 17:50:53 +02:00
|
|
|
}
|
|
|
|
|
2018-05-09 20:19:07 +02:00
|
|
|
func NewStroke(source StrokeSource) *Stroke {
|
|
|
|
cx, cy := source.Position()
|
|
|
|
return &Stroke{
|
|
|
|
source: source,
|
|
|
|
initX: cx,
|
|
|
|
initY: cy,
|
|
|
|
currentX: cx,
|
|
|
|
currentY: cy,
|
2018-04-30 17:50:53 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-09 20:19:07 +02:00
|
|
|
func (s *Stroke) Update() {
|
|
|
|
if s.released {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if s.source.IsJustReleased() {
|
|
|
|
s.released = true
|
|
|
|
return
|
|
|
|
}
|
|
|
|
x, y := s.source.Position()
|
|
|
|
s.currentX = x
|
|
|
|
s.currentY = y
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Stroke) IsReleased() bool {
|
|
|
|
return s.released
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Stroke) Position() (int, int) {
|
|
|
|
return s.currentX, s.currentY
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Stroke) PositionDiff() (int, int) {
|
|
|
|
dx := s.currentX - s.initX
|
|
|
|
dy := s.currentY - s.initY
|
|
|
|
return dx, dy
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Stroke) DraggingObject() interface{} {
|
|
|
|
return s.draggingObject
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Stroke) SetDraggingObject(object interface{}) {
|
|
|
|
s.draggingObject = object
|
|
|
|
}
|
2018-04-30 17:50:53 +02:00
|
|
|
|
2018-05-09 20:19:07 +02:00
|
|
|
type Game struct {
|
2021-07-10 16:28:32 +02:00
|
|
|
touchIDs []ebiten.TouchID
|
|
|
|
strokes map[*Stroke]struct{}
|
|
|
|
sprites []*Sprite
|
2018-04-30 17:50:53 +02:00
|
|
|
}
|
|
|
|
|
2020-04-12 12:12:57 +02:00
|
|
|
var ebitenImage *ebiten.Image
|
2018-04-30 17:50:53 +02:00
|
|
|
|
|
|
|
func init() {
|
|
|
|
// 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.Ebiten_png))
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
2020-10-05 18:03:30 +02:00
|
|
|
ebitenImage = ebiten.NewImageFromImage(img)
|
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.Size()
|
|
|
|
for i := 0; i < 50; i++ {
|
|
|
|
s := &Sprite{
|
|
|
|
image: ebitenImage,
|
|
|
|
x: rand.Intn(screenWidth - w),
|
|
|
|
y: rand.Intn(screenHeight - h),
|
|
|
|
}
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
func (g *Game) updateStroke(stroke *Stroke) {
|
|
|
|
stroke.Update()
|
|
|
|
if !stroke.IsReleased() {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
s := stroke.DraggingObject().(*Sprite)
|
|
|
|
if s == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
s.MoveBy(stroke.PositionDiff())
|
|
|
|
|
|
|
|
index := -1
|
|
|
|
for i, ss := range g.sprites {
|
|
|
|
if ss == s {
|
|
|
|
index = i
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Move the dragged sprite to the front.
|
|
|
|
g.sprites = append(g.sprites[:index], g.sprites[index+1:]...)
|
|
|
|
g.sprites = append(g.sprites, s)
|
|
|
|
|
|
|
|
stroke.SetDraggingObject(nil)
|
|
|
|
}
|
|
|
|
|
2020-10-04 10:42:54 +02:00
|
|
|
func (g *Game) Update() error {
|
2018-05-09 20:19:07 +02:00
|
|
|
if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) {
|
|
|
|
s := NewStroke(&MouseStrokeSource{})
|
|
|
|
s.SetDraggingObject(g.spriteAt(s.Position()))
|
|
|
|
g.strokes[s] = struct{}{}
|
|
|
|
}
|
2021-07-10 16:28:32 +02:00
|
|
|
g.touchIDs = inpututil.AppendJustPressedTouchIDs(g.touchIDs[:0])
|
|
|
|
for _, id := range g.touchIDs {
|
2018-05-09 20:19:07 +02:00
|
|
|
s := NewStroke(&TouchStrokeSource{id})
|
|
|
|
s.SetDraggingObject(g.spriteAt(s.Position()))
|
|
|
|
g.strokes[s] = struct{}{}
|
|
|
|
}
|
|
|
|
|
|
|
|
for s := range g.strokes {
|
|
|
|
g.updateStroke(s)
|
|
|
|
if s.IsReleased() {
|
|
|
|
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-13 17:06:03 +02:00
|
|
|
draggingSprites := map[*Sprite]struct{}{}
|
2018-05-09 20:19:07 +02:00
|
|
|
for s := range g.strokes {
|
2018-05-13 17:06:03 +02:00
|
|
|
if sprite := s.DraggingObject().(*Sprite); sprite != nil {
|
|
|
|
draggingSprites[sprite] = struct{}{}
|
|
|
|
}
|
2018-05-09 20:19:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, s := range g.sprites {
|
2018-05-13 17:06:03 +02:00
|
|
|
if _, ok := draggingSprites[s]; ok {
|
2018-05-12 16:22:50 +02:00
|
|
|
continue
|
2018-04-30 17:50:53 +02:00
|
|
|
}
|
2018-05-12 16:22:50 +02:00
|
|
|
s.Draw(screen, 0, 0, 1)
|
|
|
|
}
|
|
|
|
for s := range g.strokes {
|
|
|
|
dx, dy := s.PositionDiff()
|
2018-05-13 17:06:03 +02:00
|
|
|
if sprite := s.DraggingObject().(*Sprite); sprite != nil {
|
|
|
|
sprite.Draw(screen, dx, dy, 0.5)
|
|
|
|
}
|
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() {
|
2020-10-30 18:01:11 +01:00
|
|
|
ebiten.SetWindowSize(screenWidth, screenHeight)
|
2020-04-12 12:12:57 +02:00
|
|
|
ebiten.SetWindowTitle("Drag & Drop (Ebiten Demo)")
|
|
|
|
if err := ebiten.RunGame(NewGame()); err != nil {
|
2018-04-30 17:50:53 +02:00
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
}
|