mirror of
				https://github.com/hajimehoshi/ebiten.git
				synced 2025-10-31 19:52:47 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			325 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			325 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // 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.
 | |
| 
 | |
| // +build example
 | |
| 
 | |
| package main
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"image"
 | |
| 	"image/color"
 | |
| 	_ "image/png"
 | |
| 	"log"
 | |
| 	"math/rand"
 | |
| 	"time"
 | |
| 
 | |
| 	"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"
 | |
| )
 | |
| 
 | |
| func init() {
 | |
| 	rand.Seed(time.Now().UnixNano())
 | |
| }
 | |
| 
 | |
| const (
 | |
| 	screenWidth  = 640
 | |
| 	screenHeight = 480
 | |
| )
 | |
| 
 | |
| // 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.
 | |
| 	//
 | |
| 	// 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.
 | |
| 	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)
 | |
| }
 | |
| 
 | |
| // 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{}
 | |
| 
 | |
| func (m *MouseStrokeSource) Position() (int, int) {
 | |
| 	return ebiten.CursorPosition()
 | |
| }
 | |
| 
 | |
| func (m *MouseStrokeSource) IsJustReleased() bool {
 | |
| 	return inpututil.IsMouseButtonJustReleased(ebiten.MouseButtonLeft)
 | |
| }
 | |
| 
 | |
| // TouchStrokeSource is a StrokeSource implementation of touch.
 | |
| type TouchStrokeSource struct {
 | |
| 	ID ebiten.TouchID
 | |
| }
 | |
| 
 | |
| 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
 | |
| 
 | |
| 	// initX and initY represents the position when dragging starts.
 | |
| 	initX int
 | |
| 	initY int
 | |
| 
 | |
| 	// currentX and currentY represents the current position
 | |
| 	currentX int
 | |
| 	currentY int
 | |
| 
 | |
| 	released bool
 | |
| 
 | |
| 	// draggingObject represents a object (sprite in this case)
 | |
| 	// that is being dragged.
 | |
| 	draggingObject interface{}
 | |
| }
 | |
| 
 | |
| func NewStroke(source StrokeSource) *Stroke {
 | |
| 	cx, cy := source.Position()
 | |
| 	return &Stroke{
 | |
| 		source:   source,
 | |
| 		initX:    cx,
 | |
| 		initY:    cy,
 | |
| 		currentX: cx,
 | |
| 		currentY: cy,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 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
 | |
| }
 | |
| 
 | |
| type Game struct {
 | |
| 	strokes map[*Stroke]struct{}
 | |
| 	sprites []*Sprite
 | |
| }
 | |
| 
 | |
| var ebitenImage *ebiten.Image
 | |
| 
 | |
| 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)
 | |
| 	}
 | |
| 	ebitenImage = ebiten.NewImageFromImage(img)
 | |
| }
 | |
| 
 | |
| func NewGame() *Game {
 | |
| 	// 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.
 | |
| 	return &Game{
 | |
| 		strokes: map[*Stroke]struct{}{},
 | |
| 		sprites: sprites,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 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
 | |
| 		}
 | |
| 	}
 | |
| 	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)
 | |
| }
 | |
| 
 | |
| func (g *Game) Update() error {
 | |
| 	if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) {
 | |
| 		s := NewStroke(&MouseStrokeSource{})
 | |
| 		s.SetDraggingObject(g.spriteAt(s.Position()))
 | |
| 		g.strokes[s] = struct{}{}
 | |
| 	}
 | |
| 	for _, id := range inpututil.JustPressedTouchIDs() {
 | |
| 		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)
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (g *Game) Draw(screen *ebiten.Image) {
 | |
| 	draggingSprites := map[*Sprite]struct{}{}
 | |
| 	for s := range g.strokes {
 | |
| 		if sprite := s.DraggingObject().(*Sprite); sprite != nil {
 | |
| 			draggingSprites[sprite] = struct{}{}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	for _, s := range g.sprites {
 | |
| 		if _, ok := draggingSprites[s]; ok {
 | |
| 			continue
 | |
| 		}
 | |
| 		s.Draw(screen, 0, 0, 1)
 | |
| 	}
 | |
| 	for s := range g.strokes {
 | |
| 		dx, dy := s.PositionDiff()
 | |
| 		if sprite := s.DraggingObject().(*Sprite); sprite != nil {
 | |
| 			sprite.Draw(screen, dx, dy, 0.5)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	ebitenutil.DebugPrint(screen, "Drag & Drop the sprites!")
 | |
| }
 | |
| 
 | |
| func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
 | |
| 	return screenWidth, screenHeight
 | |
| }
 | |
| 
 | |
| func main() {
 | |
| 	ebiten.SetWindowSize(screenWidth, screenHeight)
 | |
| 	ebiten.SetWindowTitle("Drag & Drop (Ebiten Demo)")
 | |
| 	if err := ebiten.RunGame(NewGame()); err != nil {
 | |
| 		log.Fatal(err)
 | |
| 	}
 | |
| }
 | 
