ebiten/examples/drag/main.go
2018-05-11 02:02:33 +09:00

314 lines
6.9 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 jsgo
package main
import (
"bytes"
"image"
"image/color"
_ "image/png"
"log"
"math/rand"
"time"
"github.com/hajimehoshi/ebiten"
"github.com/hajimehoshi/ebiten/ebitenutil"
"github.com/hajimehoshi/ebiten/examples/resources/images"
"github.com/hajimehoshi/ebiten/inpututil"
)
func init() {
rand.Seed(time.Now().UnixNano())
}
const (
screenWidth = 320
screenHeight = 240
)
// 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 int
}
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 theGame *Game
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, ebiten.FilterDefault)
// 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.
theGame = &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(screen *ebiten.Image) 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)
}
}
if ebiten.IsRunningSlowly() {
return nil
}
ss := map[*Sprite]*Stroke{}
for s := range g.strokes {
ss[s.DraggingObject().(*Sprite)] = s
}
for _, s := range g.sprites {
if stroke, ok := ss[s]; ok {
dx, dy := stroke.PositionDiff()
s.Draw(screen, dx, dy, 0.5)
} else {
s.Draw(screen, 0, 0, 1)
}
}
ebitenutil.DebugPrint(screen, "Drag & Drop the sprites!")
return nil
}
func main() {
if err := ebiten.Run(theGame.update, screenWidth, screenHeight, 2, "Drag & Drop (Ebiten Demo)"); err != nil {
log.Fatal(err)
}
}