main.go 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. // Copyright 2018 The Ebiten Authors
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package main
  15. import (
  16. "bytes"
  17. "image"
  18. "image/color"
  19. _ "image/png"
  20. "log"
  21. "math/rand/v2"
  22. "github.com/hajimehoshi/ebiten/v2"
  23. "github.com/hajimehoshi/ebiten/v2/ebitenutil"
  24. "github.com/hajimehoshi/ebiten/v2/examples/resources/images"
  25. "github.com/hajimehoshi/ebiten/v2/inpututil"
  26. )
  27. const (
  28. screenWidth = 640
  29. screenHeight = 480
  30. )
  31. // Sprite represents an image.
  32. type Sprite struct {
  33. image *ebiten.Image
  34. alphaImage *image.Alpha
  35. x int
  36. y int
  37. dragged bool
  38. }
  39. // In returns true if (x, y) is in the sprite, and false otherwise.
  40. func (s *Sprite) In(x, y int) bool {
  41. // Check the actual color (alpha) value at the specified position
  42. // so that the result of In becomes natural to users.
  43. //
  44. // Use alphaImage (*image.Alpha) instead of image (*ebiten.Image) here.
  45. // It is because (*ebiten.Image).At is very slow as this reads pixels from GPU,
  46. // and should be avoided whenever possible.
  47. return s.alphaImage.At(x-s.x, y-s.y).(color.Alpha).A > 0
  48. }
  49. // MoveTo moves the sprite to the position (x, y).
  50. func (s *Sprite) MoveTo(x, y int) {
  51. w, h := s.image.Bounds().Dx(), s.image.Bounds().Dy()
  52. s.x = x
  53. s.y = y
  54. if s.x < 0 {
  55. s.x = 0
  56. }
  57. if s.x > screenWidth-w {
  58. s.x = screenWidth - w
  59. }
  60. if s.y < 0 {
  61. s.y = 0
  62. }
  63. if s.y > screenHeight-h {
  64. s.y = screenHeight - h
  65. }
  66. }
  67. // Draw draws the sprite.
  68. func (s *Sprite) Draw(screen *ebiten.Image, alpha float32) {
  69. op := &ebiten.DrawImageOptions{}
  70. op.GeoM.Translate(float64(s.x), float64(s.y))
  71. op.ColorScale.ScaleAlpha(alpha)
  72. screen.DrawImage(s.image, op)
  73. }
  74. // StrokeSource represents a input device to provide strokes.
  75. type StrokeSource interface {
  76. Position() (int, int)
  77. IsJustReleased() bool
  78. }
  79. // MouseStrokeSource is a StrokeSource implementation of mouse.
  80. type MouseStrokeSource struct{}
  81. func (m *MouseStrokeSource) Position() (int, int) {
  82. return ebiten.CursorPosition()
  83. }
  84. func (m *MouseStrokeSource) IsJustReleased() bool {
  85. return inpututil.IsMouseButtonJustReleased(ebiten.MouseButtonLeft)
  86. }
  87. // TouchStrokeSource is a StrokeSource implementation of touch.
  88. type TouchStrokeSource struct {
  89. ID ebiten.TouchID
  90. }
  91. func (t *TouchStrokeSource) Position() (int, int) {
  92. return ebiten.TouchPosition(t.ID)
  93. }
  94. func (t *TouchStrokeSource) IsJustReleased() bool {
  95. return inpututil.IsTouchJustReleased(t.ID)
  96. }
  97. // Stroke manages the current drag state by mouse.
  98. type Stroke struct {
  99. source StrokeSource
  100. // offsetX and offsetY represents a relative value from the sprite's upper-left position to the cursor position.
  101. offsetX int
  102. offsetY int
  103. // sprite represents a sprite being dragged.
  104. sprite *Sprite
  105. }
  106. func NewStroke(source StrokeSource, sprite *Sprite) *Stroke {
  107. sprite.dragged = true
  108. x, y := source.Position()
  109. return &Stroke{
  110. source: source,
  111. offsetX: x - sprite.x,
  112. offsetY: y - sprite.y,
  113. sprite: sprite,
  114. }
  115. }
  116. func (s *Stroke) Update() {
  117. if !s.sprite.dragged {
  118. return
  119. }
  120. if s.source.IsJustReleased() {
  121. s.sprite.dragged = false
  122. return
  123. }
  124. x, y := s.source.Position()
  125. x -= s.offsetX
  126. y -= s.offsetY
  127. s.sprite.MoveTo(x, y)
  128. }
  129. func (s *Stroke) Sprite() *Sprite {
  130. return s.sprite
  131. }
  132. type Game struct {
  133. touchIDs []ebiten.TouchID
  134. strokes map[*Stroke]struct{}
  135. sprites []*Sprite
  136. }
  137. var (
  138. ebitenImage *ebiten.Image
  139. ebitenAlphaImage *image.Alpha
  140. )
  141. func init() {
  142. // Decode an image from the image file's byte slice.
  143. img, _, err := image.Decode(bytes.NewReader(images.Ebiten_png))
  144. if err != nil {
  145. log.Fatal(err)
  146. }
  147. ebitenImage = ebiten.NewImageFromImage(img)
  148. // Clone an image but only with alpha values.
  149. // This is used to detect a user cursor touches the image.
  150. b := img.Bounds()
  151. ebitenAlphaImage = image.NewAlpha(b)
  152. for j := b.Min.Y; j < b.Max.Y; j++ {
  153. for i := b.Min.X; i < b.Max.X; i++ {
  154. ebitenAlphaImage.Set(i, j, img.At(i, j))
  155. }
  156. }
  157. }
  158. func NewGame() *Game {
  159. // Initialize the sprites.
  160. sprites := []*Sprite{}
  161. w, h := ebitenImage.Bounds().Dx(), ebitenImage.Bounds().Dy()
  162. for i := 0; i < 50; i++ {
  163. s := &Sprite{
  164. image: ebitenImage,
  165. alphaImage: ebitenAlphaImage,
  166. x: rand.IntN(screenWidth - w),
  167. y: rand.IntN(screenHeight - h),
  168. }
  169. sprites = append(sprites, s)
  170. }
  171. // Initialize the game.
  172. return &Game{
  173. strokes: map[*Stroke]struct{}{},
  174. sprites: sprites,
  175. }
  176. }
  177. func (g *Game) spriteAt(x, y int) *Sprite {
  178. // As the sprites are ordered from back to front,
  179. // search the clicked/touched sprite in reverse order.
  180. for i := len(g.sprites) - 1; i >= 0; i-- {
  181. s := g.sprites[i]
  182. if s.In(x, y) {
  183. return s
  184. }
  185. }
  186. return nil
  187. }
  188. func (g *Game) moveSpriteToFront(sprite *Sprite) {
  189. index := -1
  190. for i, ss := range g.sprites {
  191. if ss == sprite {
  192. index = i
  193. break
  194. }
  195. }
  196. g.sprites = append(g.sprites[:index], g.sprites[index+1:]...)
  197. g.sprites = append(g.sprites, sprite)
  198. }
  199. func (g *Game) Update() error {
  200. if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) {
  201. if sp := g.spriteAt(ebiten.CursorPosition()); sp != nil {
  202. s := NewStroke(&MouseStrokeSource{}, sp)
  203. g.strokes[s] = struct{}{}
  204. g.moveSpriteToFront(sp)
  205. }
  206. }
  207. g.touchIDs = inpututil.AppendJustPressedTouchIDs(g.touchIDs[:0])
  208. for _, id := range g.touchIDs {
  209. if sp := g.spriteAt(ebiten.TouchPosition(id)); sp != nil {
  210. s := NewStroke(&TouchStrokeSource{id}, sp)
  211. g.strokes[s] = struct{}{}
  212. g.moveSpriteToFront(sp)
  213. }
  214. }
  215. for s := range g.strokes {
  216. s.Update()
  217. if !s.sprite.dragged {
  218. delete(g.strokes, s)
  219. }
  220. }
  221. return nil
  222. }
  223. func (g *Game) Draw(screen *ebiten.Image) {
  224. for _, s := range g.sprites {
  225. if s.dragged {
  226. s.Draw(screen, 0.5)
  227. } else {
  228. s.Draw(screen, 1)
  229. }
  230. }
  231. ebitenutil.DebugPrint(screen, "Drag & Drop the sprites!")
  232. }
  233. func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
  234. return screenWidth, screenHeight
  235. }
  236. func main() {
  237. ebiten.SetWindowSize(screenWidth, screenHeight)
  238. ebiten.SetWindowTitle("Drag & Drop (Ebitengine Demo)")
  239. if err := ebiten.RunGame(NewGame()); err != nil {
  240. log.Fatal(err)
  241. }
  242. }