使用Ebitengine检测鼠标是否悬停在图像上

问题描述 投票:0回答:1

我正在尝试将Ebitengine(Golang)作为一个业余爱好项目来编写一些非常简单的游戏:给出一些图像,每当单击某些部分时,它就会消失。

但是,在教程中,引擎开发者说:

这里使用 alphaImage (*image.Alpha) 而不是 image (*ebiten.Image)。这是因为(*ebiten.Image)。 At 非常慢,因为它从 GPU 读取像素,应尽可能避免。

克隆图像,但仅使用 alpha 值。这用于检测用户光标触摸图像。

    // (Example code)
    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))
        }
    }

...但就我而言,我的示例图像是这样的:

example image

...我想点击某个器官,它就会消失(只是一个展示...),所以如果我只是在Photoshop中剪切图像并隐藏相应的图层,所有图像将具有相同的尺寸,并且这种“alpha 图像”解决方案不起作用,因为所有图像都具有相同的大小/位置。

所以,目前我的解决方案是每次点击时循环遍历所有图像,并检查光标处对应的 Alpha 值不为空:

package main

import (
    "fmt"
    "image/color"
    _ "image/png"
    "log"

    "github.com/hajimehoshi/ebiten/v2"
    "github.com/hajimehoshi/ebiten/v2/ebitenutil"
)

// var example_img *ebiten.Image
var body *ebiten.Image
var digest *ebiten.Image
var head *ebiten.Image
var heart *ebiten.Image
var lung *ebiten.Image

var parts map[string]*ebiten.Image
var hide map[string]bool

func init() {
    var errs []error
    var temp_err error

    // https://ebitengine.org/en/tour/image.html
    body, _, temp_err = ebitenutil.NewImageFromFile("img/body.png")
    errs = append(errs, temp_err)
    digest, _, temp_err = ebitenutil.NewImageFromFile("img/digest.png")
    errs = append(errs, temp_err)
    head, _, temp_err = ebitenutil.NewImageFromFile("img/head.png")
    errs = append(errs, temp_err)
    heart, _, temp_err = ebitenutil.NewImageFromFile("img/heart.png")
    errs = append(errs, temp_err)
    lung, _, temp_err = ebitenutil.NewImageFromFile("img/lung.png")
    errs = append(errs, temp_err)

    parts = map[string]*ebiten.Image{
        "body":   body,
        "digest": digest,
        "head":   head,
        "heart":  heart,
        "lung":   lung,
    }
    hide = map[string]bool{
        "body":   false,
        "digest": false,
        "head":   false,
        "heart":  false,
        "lung":   false,
    }

    is_err := false
    for _, err := range errs {
        if err != nil {
            log.Fatal(err)
            is_err = true
        }
    }

    if is_err {
        log.Fatal("Error loading images")
    }

    /*
        var err error
        example_img, _, err = ebitenutil.NewImageFromFile("img/model.png")
        if err != nil {
            log.Fatal(err)
        }
    */
}

type Game struct{}

// Tick, called 60 times per second (default)
func (g *Game) Update() error {
    if ebiten.IsMouseButtonPressed(ebiten.MouseButtonLeft) {
        x, y := ebiten.CursorPosition()
        part := CheckPart(x, y)
        fmt.Printf("Mouse position: x=%d, y=%d, part=%s\n", x, y, part)

        if part != "None" {
            hide[part] = true
        }
    }

    return nil
}

// Called every frame (depending on the monitor refresh rate).
// Screen argument is the final destination of rendering.
// A ebitenutil.DebugPrint is a utility function to render a debug message on an image.
func (g *Game) Draw(screen *ebiten.Image) {
    // Print debug message on the screen
    // ebitenutil.DebugPrint(screen, "Hello, World!")

    // Fill the screen with a color
    screen.Fill(color.RGBA{0xff, 0xff, 0xff, 0xff})

    // Draw the image on the screen
    // screen.DrawImage(example_img, nil)
    for name, part := range parts {
        if !hide[name] {
            screen.DrawImage(part, nil)
        }
    }
}

// Layout accepts an outside size, which is a window size on desktop, and returns the game's logical screen size.
// Layout will be more meaningful e.g., when the window is resizable.
func (g *Game) Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeight int) {
    return 640, 480
}

func main() {
    ebiten.SetWindowSize(640, 480)
    ebiten.SetWindowTitle("Demo")

    // Run the main loop of an Ebitengine game
    if err := ebiten.RunGame(&Game{}); err != nil {
        log.Fatal(err)
    }
}

func CheckPart(posX int, posY int) string {
    // Check alpha value of all parts at the given position
    // If alpha value is not 0, return the part name
    // Otherwise, return an empty string

    for name, part := range parts {
        // Get the color of the pixel at the given position
        _, _, _, alpha := part.At(posX, posY).RGBA()

        // Check if the alpha value is not 0
        if alpha != 0 {
            return name
        }
    }

    return "None"
}

我想知道是否有更好的解决方案?或者我必须制作不同尺寸的每个部分,并将它们精确地放置在实际情况下的图像中?

go game-development game-engine
1个回答
0
投票

如果您想更紧密地遵循链接的教程并将您的器官保持为相同大小和位置的重叠图像,您可以考虑制作一个 Sprite 结构来存储 *ebiten.Image 以及 *image.Alpha。此代码与您的代码之间的唯一区别是通过 *image.Alpha 而不是 *ebiten.Image.At 进行检测。

type Sprite struct {
    image      *ebiten.Image
    alphaImage *image.Alpha
    // x and y could be stored here, or just constants if the "body" will never move
    x          int
    y          int
}

可以通过您共享的代码片段克隆 alphaImage。然后你的 In 方法可以通过其 alphaImage 检查光标是否位于器官上:

func (s *Sprite) In(x, y int) bool {
    return s.alphaImage.At(x-s.x, y-s.y).(color.Alpha).A > 0
}

您的身体每个器官都会有一个精灵,并且由于像素 alpha 仅在该器官的像素上不为零,因此如果鼠标位于该器官上方,则会返回 true。然后你的循环将被修改为

func CheckPart(posX int, posY int) string {
    // Check alpha value of all parts at the given position
    // If alpha value is not 0, return the part name
    // Otherwise, return an empty string

    for name, part := range parts {
        if part.In(posX, posY) {
            return name
        }
    }

    return nil
}
© www.soinside.com 2019 - 2024. All rights reserved.