> 技术文档 > Ebitengine UI系统构建:从按钮到复杂界面组件开发

Ebitengine UI系统构建:从按钮到复杂界面组件开发


Ebitengine UI系统构建:从按钮到复杂界面组件开发

【免费下载链接】ebiten Ebitengine - A dead simple 2D game engine for Go 【免费下载链接】ebiten 项目地址: https://gitcode.com/GitHub_Trending/eb/ebiten

还在为Ebitengine游戏开发中缺乏现成的UI组件而烦恼?本文将带你从零开始构建完整的UI系统,涵盖按钮、复选框、文本框、滚动条等核心组件,并教你如何组合这些基础组件构建复杂的游戏界面。

UI系统架构设计

一个完整的UI系统需要包含以下核心组件:

mermaid

核心UI组件实现

1. 按钮组件(Button)

按钮是最基础的UI组件,需要处理鼠标点击和悬停状态:

type Button struct { Rect image.Rectangle Text string mouseDown bool onPressed func(b *Button)}func (b *Button) Update() { if ebiten.IsMouseButtonPressed(ebiten.MouseButtonLeft) { x, y := ebiten.CursorPosition() if b.Rect.Min.X <= x && x < b.Rect.Max.X && b.Rect.Min.Y <= y && y < b.Rect.Max.Y { b.mouseDown = true } else { b.mouseDown = false } } else { if b.mouseDown { if b.onPressed != nil { b.onPressed(b) } } b.mouseDown = false }}func (b *Button) Draw(dst *ebiten.Image) { t := imageTypeButton if b.mouseDown { t = imageTypeButtonPressed } drawNinePatches(dst, b.Rect, imageSrcRects[t]) // 绘制文本 op := &text.DrawOptions{} op.GeoM.Translate(float64(b.Rect.Min.X+b.Rect.Max.X)/2, float64(b.Rect.Min.Y+b.Rect.Max.Y)/2) op.ColorScale.ScaleWithColor(color.Black) op.LineSpacing = lineSpacingInPixels op.PrimaryAlign = text.AlignCenter op.SecondaryAlign = text.AlignCenter text.Draw(dst, b.Text, &text.GoTextFace{ Source: uiFaceSource, Size: uiFontSize, }, op)}

2. 复选框组件(CheckBox)

复选框提供二进制选择功能,支持点击切换状态:

type CheckBox struct { X int Y int Text string checked bool mouseDown bool onCheckChanged func(c *CheckBox)}func (c *CheckBox) Update() { if ebiten.IsMouseButtonPressed(ebiten.MouseButtonLeft) { x, y := ebiten.CursorPosition() if c.X <= x && x < c.X+c.width() && c.Y <= y && y < c.Y+checkboxHeight { c.mouseDown = true } else { c.mouseDown = false } } else { if c.mouseDown { c.checked = !c.checked if c.onCheckChanged != nil { c.onCheckChanged(c) } } c.mouseDown = false }}func (c *CheckBox) Draw(dst *ebiten.Image) { t := imageTypeCheckBox if c.mouseDown { t = imageTypeCheckBoxPressed } r := image.Rect(c.X, c.Y, c.X+checkboxWidth, c.Y+checkboxHeight) drawNinePatches(dst, r, imageSrcRects[t]) if c.checked { drawNinePatches(dst, r, imageSrcRects[imageTypeCheckBoxMark]) } // 绘制标签文本 x := c.X + checkboxWidth + checkboxPaddingLeft y := c.Y + checkboxHeight/2 op := &text.DrawOptions{} op.GeoM.Translate(float64(x), float64(y)) op.ColorScale.ScaleWithColor(color.Black) op.LineSpacing = lineSpacingInPixels op.PrimaryAlign = text.AlignStart op.SecondaryAlign = text.AlignCenter text.Draw(dst, c.Text, &text.GoTextFace{ Source: uiFaceSource, Size: uiFontSize, }, op)}

3. 文本框组件(TextBox)

文本框支持多行文本显示和垂直滚动:

type TextBox struct { Rect image.Rectangle Text string vScrollBar *VScrollBar offsetX int offsetY int}func (t *TextBox) AppendLine(line string) { if t.Text == \"\" { t.Text = line } else { t.Text += \"\\n\" + line }}func (t *TextBox) Update() { if t.vScrollBar == nil { t.vScrollBar = &VScrollBar{} } t.vScrollBar.X = t.Rect.Max.X - VScrollBarWidth t.vScrollBar.Y = t.Rect.Min.Y t.vScrollBar.Height = t.Rect.Dy() _, h := t.contentSize() t.vScrollBar.Update(h) t.offsetY = t.vScrollBar.ContentOffset()}func (t *TextBox) Draw(dst *ebiten.Image) { drawNinePatches(dst, t.Rect, imageSrcRects[imageTypeTextBox]) // 绘制文本内容 textOp := &text.DrawOptions{} x := -float64(t.offsetX) + textBoxPaddingLeft y := -float64(t.offsetY) + textBoxPaddingTop textOp.GeoM.Translate(x, y) textOp.GeoM.Translate(float64(t.Rect.Min.X), float64(t.Rect.Min.Y)) textOp.ColorScale.ScaleWithColor(color.Black) textOp.LineSpacing = lineSpacingInPixels text.Draw(dst.SubImage(t.Rect).(*ebiten.Image), t.Text, &text.GoTextFace{ Source: uiFaceSource, Size: uiFontSize, }, textOp) t.vScrollBar.Draw(dst)}

4. 垂直滚动条组件(VScrollBar)

滚动条处理内容滚动逻辑,支持拖动操作:

type VScrollBar struct { X int Y int Height int thumbRate  float64 thumbOffset int dragging bool draggingStartOffset int draggingStartY int contentOffset int}func (v *VScrollBar) Update(contentHeight int) { v.thumbRate = float64(v.Height) / float64(contentHeight) if !v.dragging && inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) { x, y := ebiten.CursorPosition() tr := v.thumbRect() if tr.Min.X <= x && x < tr.Max.X && tr.Min.Y <= y && y < tr.Max.Y { v.dragging = true v.draggingStartOffset = v.thumbOffset v.draggingStartY = y } } if v.dragging { if ebiten.IsMouseButtonPressed(ebiten.MouseButtonLeft) { _, y := ebiten.CursorPosition() v.thumbOffset = v.draggingStartOffset + (y - v.draggingStartY) if v.thumbOffset  v.maxThumbOffset() { v.thumbOffset = v.maxThumbOffset() } } else { v.dragging = false } } v.contentOffset = 0 if v.thumbRate < 1 { v.contentOffset = int(float64(contentHeight) * float64(v.thumbOffset) / float64(v.Height)) }}

九宫格绘制技术

九宫格技术是实现可伸缩UI元素的关键:

func drawNinePatches(dst *ebiten.Image, dstRect image.Rectangle, srcRect image.Rectangle) { srcX := srcRect.Min.X srcY := srcRect.Min.Y srcW := srcRect.Dx() srcH := srcRect.Dy() dstX := dstRect.Min.X dstY := dstRect.Min.Y dstW := dstRect.Dx() dstH := dstRect.Dy() op := &ebiten.DrawImageOptions{} for j := 0; j < 3; j++ { for i := 0; i < 3; i++ { op.GeoM.Reset() sx := srcX sy := srcY sw := srcW / 4 sh := srcH / 4 dx := 0 dy := 0 dw := sw dh := sh switch i { case 1: sx = srcX + srcW/4 sw = srcW / 2 dx = srcW / 4 dw = dstW - 2*srcW/4 case 2: sx = srcX + 3*srcW/4 dx = dstW - srcW/4 } switch j { case 1: sy = srcY + srcH/4 sh = srcH / 2 dy = srcH / 4 dh = dstH - 2*srcH/4 case 2: sy = srcY + 3*srcH/4 dy = dstH - srcH/4 } op.GeoM.Scale(float64(dw)/float64(sw), float64(dh)/float64(sh)) op.GeoM.Translate(float64(dx), float64(dy)) op.GeoM.Translate(float64(dstX), float64(dstY)) dst.DrawImage(uiImage.SubImage(image.Rect(sx, sy, sx+sw, sy+sh)).(*ebiten.Image), op) } }}

文本渲染系统

Ebitengine v2提供了强大的文本渲染能力:

// 初始化字体uiFaceSource, err := text.NewGoTextFaceSource(bytes.NewReader(goregular.TTF))if err != nil { log.Fatal(err)}// 测量文本尺寸width, height := text.Measure(\"Hello World\", &text.GoTextFace{ Source: uiFaceSource, Size: uiFontSize,}, lineSpacingInPixels)// 绘制文本op := &text.DrawOptions{}op.GeoM.Translate(x, y)op.ColorScale.ScaleWithColor(color.Black)op.LineSpacing = lineSpacingInPixelsop.PrimaryAlign = text.AlignCenterop.SecondaryAlign = text.AlignCentertext.Draw(dst, \"Hello World\", &text.GoTextFace{ Source: uiFaceSource, Size: uiFontSize,}, op)

输入处理系统

使用inpututil包处理精确的输入事件:

输入类型 检测函数 描述 键盘 inpututil.IsKeyJustPressed(key) 检测按键刚按下 鼠标 inpututil.IsMouseButtonJustPressed(button) 检测鼠标按钮刚按下 触摸 inpututil.AppendJustPressedTouchIDs() 获取刚触摸的ID 游戏手柄 inpututil.IsGamepadButtonJustPressed(id, button) 检测游戏手柄按钮按下
func handleInput() { // 检测鼠标点击 if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) { x, y := ebiten.CursorPosition() // 处理点击逻辑 } // 检测键盘输入 if inpututil.IsKeyJustPressed(ebiten.KeyEnter) { // 处理回车键 }}

完整UI系统集成

将各个组件组合成完整的UI系统:

type Game struct { button1 *Button button2 *Button checkBox *CheckBox textBoxLog *TextBox}func NewGame() *Game { g := &Game{} g.button1 = &Button{ Rect: image.Rect(16, 16, 144, 48), Text: \"Button 1\", } g.button2 = &Button{ Rect: image.Rect(160, 16, 288, 48), Text: \"Button 2\", } g.checkBox = &CheckBox{ X: 16, Y: 64, Text: \"Check Box!\", } g.textBoxLog = &TextBox{ Rect: image.Rect(16, 96, 624, 464), } // 设置事件回调 g.button1.SetOnPressed(func(b *Button) { g.textBoxLog.AppendLine(\"Button 1 Pressed\") }) g.button2.SetOnPressed(func(b *Button) { g.textBoxLog.AppendLine(\"Button 2 Pressed\") }) g.checkBox.SetOnCheckChanged(func(c *CheckBox) { msg := \"Check box check changed\" if c.Checked() { msg += \" (Checked)\" } else { msg += \" (Unchecked)\" } g.textBoxLog.AppendLine(msg) }) return g}func (g *Game) Update() error { g.button1.Update() g.button2.Update() g.checkBox.Update() g.textBoxLog.Update() return nil}func (g *Game) Draw(screen *ebiten.Image) { screen.Fill(color.RGBA{0xeb, 0xeb, 0xeb, 0xff}) g.button1.Draw(screen) g.button2.Draw(screen) g.checkBox.Draw(screen) g.textBoxLog.Draw(screen)}

性能优化技巧

1. 批处理绘制

// 避免每帧重新创建DrawOptionsvar drawOp ebiten.DrawImageOptionsfunc drawUI() { drawOp.Reset() drawOp.GeoM.Translate(x, y) // 使用同一个op对象进行多次绘制}

2. 纹理图集优化

// 使用SubImage避免创建新纹理buttonImage := uiImage.SubImage(image.Rect(0, 0, 16, 16)).(*ebiten.Image)

3. 文本缓存

// 预缓存常用文本text.CacheGlyphs(\"常用文本\", uiFace)

高级UI组件扩展

基于基础组件构建更复杂的UI元素:

滑动条(Slider)

type Slider struct { Rect image.Rectangle Min float64 Max float64 Value float64 dragging bool onValueChanged func(value float64)}func (s *Slider) Update() { // 实现滑动逻辑}func (s *Slider) Draw(dst *ebiten.Image) { // 绘制滑动条和滑块}

下拉菜单(Dropdown)

type Dropdown struct { Rect image.Rectangle Options []string Selected int expanded bool onSelectionChanged func(index int)}func (d *Dropdown) Update() { // 处理展开/选择逻辑}func (d *Dropdown) Draw(dst *ebiten.Image) { // 绘制下拉菜单}

响应式布局系统

实现自适应不同屏幕尺寸的布局:

type LayoutSystem struct { components []LayoutComponent constraints []Constraint}type Constraint struct { componentIndex int attribute LayoutAttribute relation LayoutRelation targetIndex int targetAttribute LayoutAttribute multiplier float64 constant float64}func (l *LayoutSystem) UpdateLayout(width, height int) { // 实现自动布局算法}

测试与调试

编写UI组件测试用例:

func TestButtonClick(t *testing.T) { button := &Button{Rect: image.Rect(0, 0, 100, 50), Text: \"Test\"} clicked := false button.SetOnPressed(func(b *Button) { clicked = true }) // 模拟点击 simulateMouseClick(50, 25) button.Update() if !clicked { t.Error(\"Button click not detected\") }}

总结

通过本文的学习,你已经掌握了Ebitengine UI系统的核心构建技术:

  1. 基础组件实现:按钮、复选框、文本框、滚动条
  2. 九宫格绘制技术:实现可伸缩的UI元素
  3. 文本渲染系统:使用text/v2包进行高质量文本渲染
  4. 输入处理:精确的鼠标、键盘、触摸事件处理
  5. 性能优化:批处理、纹理复用、文本缓存
  6. 扩展组件:滑动条、下拉菜单等高级组件
  7. 响应式布局:自适应不同屏幕尺寸

这些技术为你构建复杂的游戏界面提供了坚实的基础。记住,良好的UI系统不仅能提升用户体验,还能显著提高开发效率。

现在就开始构建你的Ebitengine UI系统吧!通过组合这些基础组件,你可以创建出功能丰富、视觉效果出色的游戏界面。

【免费下载链接】ebiten Ebitengine - A dead simple 2D game engine for Go 【免费下载链接】ebiten 项目地址: https://gitcode.com/GitHub_Trending/eb/ebiten

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考