180课时吃透Go语言游戏后端开发29:如何让游戏中的怪物“觉醒智力”

leicw CodeAI世界
2025年11月06日 20:05


第29课咱们讲讲在Go游戏后端开发中,如何让游戏中的怪物或NPC能够表现出一定的智能行为。我们以简单的怪物AI为例,让怪物根据玩家的位置做出移动决策,使之不断地靠近玩家,当怪物碰到玩家的时候向玩家发起攻击。

本文涉及的Go语言知识点

  • • 结构体
  • • 方法
  • • Goroutine
  • • 通道

数学知识准备

要实现怪物根据玩家的位置做出移动决策,我们需要直到怪物与玩家之间的距离,因为我们的例子是二维游戏,所以我们使用二维空间的 欧几里得距离 来计算。
在二维空间中,对于两点 ,它们之间的欧几里得距离 的计算公式为:

例如,若有点 ,则它们之间的欧几里得距离为:

编码步骤

首先,我们要将游戏中的对象抽象成结构体,本例中涉及玩家和怪物两种游戏对象,我们使用 Player Monster 结构体来表示它们。位置是它们共有的属性,这里使用内嵌 Postion 结构体来表示位置。

内嵌结构体的字段会被提升到外层结构体中,这样我们就可以直接访问嵌套结构体的字段,例如 player.X player.Position.X 是同一个数据。代码如下:



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

// 定义位置结构体
type Position struct {
    X int
    Y int
}

// 定义玩家结构体
type Player struct {
    ID   int
    Name string
    Position
}

// 定义怪物结构体
type Monster struct {
    ID    int
    Speed int
    Position
}
 



为了更好的展示玩家的位置,我们为 Player 结构体添加一个显示位置的方法,如下所示:



1
2
3

func (p Player) ShowPosition() {
    fmt.Printf("玩家:%s 的位置在 (15, 10)\n", p.Name)
}



接着我们使用前面介绍的距离公式,编写计算两点之前距离的函数,如下所示:



1
2
3
4
5
6

// 计算两点之间的距离
func distance(p1, p2 Position) float64 {
    return math.Sqrt(math.Pow(float64(p1.X-p2.X), 2) +
        math.Pow(float64(p1.Y-p2.Y), 2))
}
 



接下来,本文的重点来了,我们编写怪物AI的核心逻辑,如下所示:



1
2
3
4
5
6
7
8
9
10
11
12
13
14

// 怪物向玩家移动的逻辑
func (m *Monster) moveTowards(playerPos Position) {
    dx := playerPos.X - m.Position.X
    dy := playerPos.Y - m.Position.Y
    dist := distance(m.Position, playerPos)
 
    if dist > 0 {
        // 根据速度和方向更新怪物位置
        m.Position.X += int(float64(dx) / dist * float64(m.Speed))
        m.Position.Y += int(float64(dy) / dist * float64(m.Speed))
    }
 
    fmt.Printf("怪物 %d 移动到: (%d, %d)\n", m.ID, m.Position.X, m.Position.Y)
}



上面这个方法让怪物具有一定的智能行为,它会根据玩家的位置来计算出移动方向,并按照怪物的速度更新怪物的位置,使怪物不断地接近玩家。

当玩家位置更新后,怪物也会根据新的位置来计算移动方向,并更新自己的位置。就好像怪物具有意识一样,它会根据玩家的位置来做出反应。

最后,我们在 main 函数中演示一下怪物靠近玩家的过程,代码如下:



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

func main() {
    // 创建玩家实例
    player := Player{
        ID:       1,
        Name:     "leicw",
        Position: Position{15, 10},
    }

    // 创建怪物实例
    monster := Monster{
        ID:       1,
        Position: Position{X: 5, Y: 5},
        Speed:    2,
    }

    // 展示玩家位置
    player.ShowPosition()

    ch := make(chan bool)
    gofunc() {
        for {
            // 怪物向玩家移动
            monster.moveTowards(player.Position)
            if player.Position.X == monster.Position.X &&
                player.Position.Y == monster.Position.Y {
                fmt.Printf("怪物 1 遇上了玩家:%s,准备攻击...\n", player.Name)
                break
            }
            time.Sleep(1 * time.Second)
        }
        ch <- true
    }()

    <-ch
}



main 函数中,我们首先创建了玩家和怪物两个实例,然后展示玩家的位置,接着创建了一个 ch 通道。

然后使用 go 关键字加一个匿名函数的调用启动了一个 Goroutine ,这个匿名函数的逻辑是在一个死循环中,不断地调用怪物的 moveTowards 方法靠近玩家,然后判断玩家的位置是否和怪物的位置相同,如果相同,则打印出 怪物 1 遇上了玩家:leicw,准备攻击... 的信息后使用 break 退出循坏, 否则该 Goroutine 睡眠一秒后进入下一轮循坏。

有读者可能会有疑问,为什么这里要使用一个 ch 通道?这是因为 main 函数是在是 主Goroutine 中执行的,本例中使用 go 关键字启动了另外一个 Goroutine,这两个 Goroutine 是并发执行的,如果不使用通道的话,我们无法在主协程中等待 Goroutine 执行完毕,这样就会导致该 Goroutine 还未执行完就因 主协程退出而提前结束了。

这里通过创建一个没有缓冲的通道 ch , main函数的末尾使用 <-ch 来等待 Goroutine 执行完毕,当 ch 中没有数据时,main 函数会一直在这里等待,当 Goroutine 执行完毕后,会向 ch 中写入一个 true 的值,main 函数会从 ch 中读取到这个值,然后退出程序。

写在最后

通过这个例子,读者可以了解到如何使用 Go 语言来编写游戏后端代码,以及如何使用 Go 语言的结构体、方法、Goroutine 和通道等特性来实现游戏中的怪物AI。

当然这只是一个简单的例子,实际的游戏中,怪物AI的逻辑会更加复杂,例如根据玩家的攻击距离来计算移动方向,根据玩家的攻击方式来调整移动速度等。

每一次 点赞 ,都是你无声的鼓励。每一颗爱心,都是我前行的动力。每一次 分享 ,都是知识的传递。 关注我 ,追更不迷路!