Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Golang语言情怀--第130期 Go语言Ebiten引擎全栈游戏开发:第1节:Ebiten介绍

Golang语言情怀--第130期 Go语言Ebiten引擎全栈游戏开发:第1节:Ebiten介绍

作者头像
李海彬
发布于 2024-11-07 09:20:13
发布于 2024-11-07 09:20:13
14500
代码可运行
举报
文章被收录于专栏:Golang语言社区Golang语言社区
运行总次数:0
代码可运行

Ebiten库介绍

Ebiten是一个使用Go语言编程库,用于2D游戏开发,可以跨平台。 Ebiten官网,https://ebiten.org/ Ebiten官方API文档,https://pkg.go.dev/github.com/hajimehoshi/ebiten

同时,在www.Golang.Ltd技术网站已经增加Ebiten入口:

Ebiten在窗口显示文字

功能就是弹出一个窗口,输出Hello, World。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package main

import (
    "fmt"
    "log"

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

/*
    空结构体,实现了ebiten.Game接口。
*/
type Game struct{}

/*
    Update()是一个成员函数,自动调用
    因为这是一个游戏开发的库,界面是需要实时更新的
    因此每一个周期,都会更新一次,也就是调用一次Update函数
    更新周期是1/60秒,也就是一秒会更新60次
*/
func (g *Game) Update() error {
    return nil
}

/*
    Draw()用于渲染界面,也是个会自动调用的函数
    这个函数的自动调用频率和你电脑显示器的刷新频率一样
    screen表示GUI窗口显示的对象,这里是在该窗口输出"Hello, World!"
*/
func (g *Game) Draw(screen *ebiten.Image) {
    ebitenutil.DebugPrint(screen, "Hello, World!")
}

/*
    Layout()函数的返回值表示显示窗口里面逻辑上屏幕的大小
    官网上说参数outsideWidth和outsideHeight是显示在桌面的窗口大小

    这里是固定大小640*480
*/
func (g *Game) Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeight int) {
    return 640, 480
}

func main() {
    // 设置窗口大小是640*480
    ebiten.SetWindowSize(640, 480)
    // 设置窗口头部,显示Hello, World
    ebiten.SetWindowTitle("Hello, World!")
    // 运行游戏
    if err := ebiten.RunGame(&Game{}); err != nil {
        log.Fatal(err)
    }
}

效果如下:

上述代码注释部分写了各个函数的作用,使用Ebiten时,格式都是一样的,都是需要创建Game结构体、Update()函数、Draw()函数、Layout()函数,然后在main函数里面启动游戏即可。

Ebiten在窗口显示图片

Ebiten包提供了一个函数,用于展示一个图片,如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func (i *Image) DrawImage(img *Image, options *DrawImageOptions) error

参数简单说一下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
img *Image: 被展示的图片
option  *DrawImageOption: 展示选项,比如图片的位置,暂不设置

直接展示图片

把上面例子输出Hello, World的Draw()函数替换成下面:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func (g *Game) Draw(screen *ebiten.Image) {
    // 1. 读取图片文件
    f, err := os.Open("gopher.png")
    if err != nil {
        log.Fatal(err)
    }
    img, err := png.Decode(f)
    if err != nil {
        log.Fatal(err)
    }
    // 把Image文件转成ebiten.Image文件,用于展示
    eImg := ebiten.NewImageFromImage(img)
    // 在屏幕上展示出图片
    screen.DrawImage(eImg, nil)
}

显示效果如下图:

图片要放在屏幕的某个位置,怎样指定坐标呢? 使用DrawImage()函数

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func (i *Image) DrawImage(img *Image, options *DrawImageOptions) error

但是此时需要指定第二个参数,比如我要把图片显示在窗口的(100,100)坐标处:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func (g *Game) Draw(screen *ebiten.Image) {
    // 1. 读取图片文件
    f, err := os.Open("gopher.png")
    if err != nil {
        log.Fatal(err)
    }
    img, err := png.Decode(f)
    if err != nil {
        log.Fatal(err)
    }
    // 把Image文件转成ebiten.Image文件,用于展示
    eImg := ebiten.NewImageFromImage(img)
    op := &ebiten.DrawImageOptions{}
    op.GeoM.Translate(float64(100), float64(100))
    // 在屏幕上展示出图片
    screen.DrawImage(eImg, op)
}

显示效果:

Ebiten监听鼠标事件

监听鼠标事件,这里使用一个简单的例子,刚开始是显示图片的,按下左键,图片消失,松开左键,图片显示。 再次只需更改一下Draw()函数即可:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var flag bool

func (g *Game) Draw(screen *ebiten.Image) {

    // flag一开始默认是false,会显示图片
    if !flag {
        screen.DrawImage(eImg, nil)
    } else {
        screen.Clear()
    }
    // 监听鼠标事件,如果左键按下,false变成true,图片就会删除
    if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) {
        flag = !flag
    }
    // 监听鼠标左键松开事件
    if inpututil.IsMouseButtonJustReleased(ebiten.MouseButtonLeft) {
        flag = !flag
    }
}

演示效果如下gif动图所示:

Ebiten监听手柄事件

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// Copyright 2015 Hajime Hoshi
//
// 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.

package main

import (
    "fmt"
    "github.com/hajimehoshi/ebiten/v2"
    "log"
    "sort"
    "strconv"
    "strings"
    "time"

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

const (
    screenWidth  = 640
    screenHeight = 480
)

type Game struct {
    gamepadIDsBuf  []ebiten.GamepadID
    gamepadIDs     map[ebiten.GamepadID]struct{}
    axes           map[ebiten.GamepadID][]string
    pressedButtons map[ebiten.GamepadID][]string
}

func (g *Game) Update() error {
    if g.gamepadIDs == nil {
        g.gamepadIDs = map[ebiten.GamepadID]struct{}{}
    }

    // Log the gamepad connection events.
    g.gamepadIDsBuf = inpututil.AppendJustConnectedGamepadIDs(g.gamepadIDsBuf[:0])
    for _, id := range g.gamepadIDsBuf {
        log.Printf("gamepad connected: id: %d, SDL ID: %s", id, ebiten.GamepadSDLID(id))
        g.gamepadIDs[id] = struct{}{}
    }
    for id := range g.gamepadIDs {
        if inpututil.IsGamepadJustDisconnected(id) {
            log.Printf("gamepad disconnected: id: %d", id)
            delete(g.gamepadIDs, id)
        }
    }

    g.axes = map[ebiten.GamepadID][]string{}
    g.pressedButtons = map[ebiten.GamepadID][]string{}
    for id := range g.gamepadIDs {
        maxAxis := ebiten.GamepadAxisType(ebiten.GamepadAxisCount(id))
        for a := ebiten.GamepadAxisType(0); a < maxAxis; a++ {
            v := ebiten.GamepadAxisValue(id, a)
            g.axes[id] = append(g.axes[id], fmt.Sprintf("%d:%+0.2f", a, v))
        }

        maxButton := ebiten.GamepadButton(ebiten.GamepadButtonCount(id))
        for b := ebiten.GamepadButton(0); b < maxButton; b++ {
            if ebiten.IsGamepadButtonPressed(id, b) {
                g.pressedButtons[id] = append(g.pressedButtons[id], strconv.Itoa(int(b)))
            }

            // Log button events.
            if inpututil.IsGamepadButtonJustPressed(id, b) {
                log.Printf("button pressed: id: %d, button: %d", id, b)
            }
            if inpututil.IsGamepadButtonJustReleased(id, b) {
                log.Printf("button released: id: %d, button: %d", id, b)
            }
        }

        if ebiten.IsStandardGamepadLayoutAvailable(id) {
            for b := ebiten.StandardGamepadButton(0); b <= ebiten.StandardGamepadButtonMax; b++ {
                // Log button events.
                if inpututil.IsStandardGamepadButtonJustPressed(id, b) {
                    var strong float64
                    var weak float64
                    switch b {
                    case ebiten.StandardGamepadButtonLeftTop,
                        ebiten.StandardGamepadButtonLeftLeft,
                        ebiten.StandardGamepadButtonLeftRight,
                        ebiten.StandardGamepadButtonLeftBottom:
                        weak = 0.5
                    case ebiten.StandardGamepadButtonRightTop,
                        ebiten.StandardGamepadButtonRightLeft,
                        ebiten.StandardGamepadButtonRightRight,
                        ebiten.StandardGamepadButtonRightBottom:
                        strong = 0.5
                    }
                    if strong > 0 || weak > 0 {
                        op := &ebiten.VibrateGamepadOptions{
                            Duration:        200 * time.Millisecond,
                            StrongMagnitude: strong,
                            WeakMagnitude:   weak,
                        }
                        ebiten.VibrateGamepad(id, op)
                    }
                    log.Printf("standard button pressed: id: %d, button: %d", id, b)
                }
                if inpututil.IsStandardGamepadButtonJustReleased(id, b) {
                    log.Printf("standard button released: id: %d, button: %d", id, b)
                }
            }
        }
    }
    return nil
}

var standardButtonToString = map[ebiten.StandardGamepadButton]string{
    ebiten.StandardGamepadButtonRightBottom:      "RB",
    ebiten.StandardGamepadButtonRightRight:       "RR",
    ebiten.StandardGamepadButtonRightLeft:        "RL",
    ebiten.StandardGamepadButtonRightTop:         "RT",
    ebiten.StandardGamepadButtonFrontTopLeft:     "FTL",
    ebiten.StandardGamepadButtonFrontTopRight:    "FTR",
    ebiten.StandardGamepadButtonFrontBottomLeft:  "FBL",
    ebiten.StandardGamepadButtonFrontBottomRight: "FBR",
    ebiten.StandardGamepadButtonCenterLeft:       "CL",
    ebiten.StandardGamepadButtonCenterRight:      "CR",
    ebiten.StandardGamepadButtonLeftStick:        "LS",
    ebiten.StandardGamepadButtonRightStick:       "RS",
    ebiten.StandardGamepadButtonLeftBottom:       "LB",
    ebiten.StandardGamepadButtonLeftRight:        "LR",
    ebiten.StandardGamepadButtonLeftLeft:         "LL",
    ebiten.StandardGamepadButtonLeftTop:          "LT",
    ebiten.StandardGamepadButtonCenterCenter:     "CC",
}

func standardMap(id ebiten.GamepadID) string {
    m := `       [FBL ]                    [FBR ]
      [FTL ]                    [FTR ]

      [LT  ]       [CC  ]       [RT  ]
   [LL  ][LR  ] [CL  ][CR  ] [RL  ][RR  ]
      [LB  ]                    [RB  ]
            [LS  ]       [RS  ]
`

    for b, str := range standardButtonToString {
        placeholder := "[" + str + strings.Repeat(" ", 4-len(str)) + "]"
        v := ebiten.StandardGamepadButtonValue(id, b)
        switch {
        case !ebiten.IsStandardGamepadButtonAvailable(id, b):
            m = strings.Replace(m, placeholder, "  --  ", 1)
        case ebiten.IsStandardGamepadButtonPressed(id, b):
            m = strings.Replace(m, placeholder, fmt.Sprintf("[%0.2f]", v), 1)
        default:
            m = strings.Replace(m, placeholder, fmt.Sprintf(" %0.2f ", v), 1)
        }
    }

    // TODO: Use ebiten.IsStandardGamepadAxisAvailable
    m += fmt.Sprintf("    Left Stick:  X: %+0.2f, Y: %+0.2f\n    Right Stick: X: %+0.2f, Y: %+0.2f",
        ebiten.StandardGamepadAxisValue(id, ebiten.StandardGamepadAxisLeftStickHorizontal),
        ebiten.StandardGamepadAxisValue(id, ebiten.StandardGamepadAxisLeftStickVertical),
        ebiten.StandardGamepadAxisValue(id, ebiten.StandardGamepadAxisRightStickHorizontal),
        ebiten.StandardGamepadAxisValue(id, ebiten.StandardGamepadAxisRightStickVertical))
    return m
}

func (g *Game) Draw(screen *ebiten.Image) {
    // Draw the current gamepad status.
    str := ""
    if len(g.gamepadIDs) > 0 {
        ids := make([]ebiten.GamepadID, 0, len(g.gamepadIDs))
        for id := range g.gamepadIDs {
            ids = append(ids, id)
        }
        sort.Slice(ids, func(a, b int) bool {
            return ids[a] < ids[b]
        })
        for _, id := range ids {
            var standard string
            if ebiten.IsStandardGamepadLayoutAvailable(id) {
                standard = " (Standard Layout)"
            }
            str += fmt.Sprintf("Gamepad (ID: %d, SDL ID: %s)%s:\n", id, ebiten.GamepadSDLID(id), standard)
            str += fmt.Sprintf("  Name:    %s\n", ebiten.GamepadName(id))
            str += fmt.Sprintf("  Axes:    %s\n", strings.Join(g.axes[id], ", "))
            str += fmt.Sprintf("  Buttons: %s\n", strings.Join(g.pressedButtons[id], ", "))
            if ebiten.IsStandardGamepadLayoutAvailable(id) {
                str += "\n"
                str += standardMap(id) + "\n"
            }
            str += "\n"
        }
    } else {
        str = "Please connect your gamepad."
    }
    ebitenutil.DebugPrint(screen, str)
}

func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
    return screenWidth, screenHeight
}

func maingamepad() {
    ebiten.SetWindowSize(screenWidth, screenHeight)
    ebiten.SetWindowTitle("Gamepad (Ebitengine Demo)")
    if err := ebiten.RunGame(&Game{}); err != nil {
        log.Fatal(err)
    }
}

关于Ebiten引擎后续

本人一直在做游戏,坦克对决游戏已经内侧;后面PC端计划使用Ebiten引擎完成,期待后续!

同学们,兴趣是最好的老师;只争朝夕,不负韶华!加油!

参考资料:

Go语言中文文档

http://www.golang.ltd/

Golang语言情怀

ID:wwwGolangLtd

www.Golang.Ltd

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-11-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Golang语言情怀 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验