简单2dAOI系统实现

This commit is contained in:
kercylan98 2023-05-29 13:26:42 +08:00
parent 5ed1b3b78f
commit 9b86108eb8
5 changed files with 273 additions and 19 deletions

19
game/aoi2d.go Normal file
View File

@ -0,0 +1,19 @@
package game
// AOI2D 感兴趣的领域(Area Of Interest)接口定义
type AOI2D interface {
// AddEntity 添加对象
AddEntity(entity AOIEntity2D)
// DeleteEntity 移除对象
DeleteEntity(entity AOIEntity2D)
// Refresh 刷新对象焦点
Refresh(entity AOIEntity2D)
// GetFocus 获取对象焦点列表
GetFocus(guid int64) map[int64]AOIEntity2D
// SetSize 设置总区域大小
// - 将会导致区域的重新划分
SetSize(width, height int)
// SetAreaSize 设置区域大小
// - 将会导致区域的重新划分
SetAreaSize(width, height int)
}

9
game/aoi2d_entity.go Normal file
View File

@ -0,0 +1,9 @@
package game
type AOIEntity2D interface {
Actor
// GetPosition 获取对象位置
GetPosition() (x, y float64)
// GetVision 获取视距
GetVision() float64
}

238
game/builtin/aoi2d.go Normal file
View File

@ -0,0 +1,238 @@
package builtin
import (
"github.com/kercylan98/minotaur/game"
"github.com/kercylan98/minotaur/utils/g2d"
"github.com/kercylan98/minotaur/utils/hash"
"math"
"sync"
)
func NewAOI2D(width, height, areaWidth, areaHeight int) *AOI2D {
aoi := &AOI2D{
width: float64(width),
height: float64(height),
focus: map[int64]map[int64]game.AOIEntity2D{},
}
aoi.SetAreaSize(areaWidth, areaHeight)
return aoi
}
type AOI2D struct {
rw sync.RWMutex
width float64
height float64
areaWidth float64
areaHeight float64
areaWidthLimit int
areaHeightLimit int
areas [][]map[int64]game.AOIEntity2D
focus map[int64]map[int64]game.AOIEntity2D
repartitionQueue []func()
}
func (slf *AOI2D) AddEntity(entity game.AOIEntity2D) {
slf.rw.Lock()
slf.addEntity(entity)
slf.rw.Unlock()
}
func (slf *AOI2D) DeleteEntity(entity game.AOIEntity2D) {
slf.rw.Lock()
slf.deleteEntity(entity)
slf.rw.Unlock()
}
func (slf *AOI2D) Refresh(entity game.AOIEntity2D) {
slf.rw.Lock()
defer slf.rw.Unlock()
slf.refresh(entity)
}
func (slf *AOI2D) GetFocus(guid int64) map[int64]game.AOIEntity2D {
slf.rw.RLock()
defer slf.rw.RUnlock()
return hash.Copy(slf.focus[guid])
}
func (slf *AOI2D) SetSize(width, height int) {
fw, fh := float64(width), float64(height)
if fw == slf.width && fh == slf.height {
return
}
slf.rw.Lock()
defer slf.rw.Unlock()
slf.width = fw
slf.height = fh
slf.setAreaSize(int(slf.areaWidth), int(slf.areaHeight))
}
func (slf *AOI2D) SetAreaSize(width, height int) {
fw, fh := float64(width), float64(height)
if fw == slf.areaWidth && fh == slf.areaHeight {
return
}
slf.rw.Lock()
defer slf.rw.Unlock()
slf.setAreaSize(width, height)
}
func (slf *AOI2D) setAreaSize(width, height int) {
// 旧分区备份
var oldAreas = make([][]map[int64]game.AOIEntity2D, len(slf.areas))
for w := 0; w < len(slf.areas); w++ {
hs := slf.areas[w]
ohs := make([]map[int64]game.AOIEntity2D, len(hs))
for h := 0; h < len(hs); h++ {
es := map[int64]game.AOIEntity2D{}
for g, e := range hs[h] {
es[g] = e
}
ohs[h] = es
}
oldAreas[w] = ohs
}
// 清理分区
for i := 0; i < len(oldAreas); i++ {
area := slf.areas[i]
for a := 0; a < len(area); a++ {
entities := area[a]
for _, entity := range entities {
slf.deleteEntity(entity)
}
}
}
// 生成区域
slf.areaWidth = float64(width)
slf.areaHeight = float64(height)
slf.areaWidthLimit = int(math.Ceil(slf.width / slf.areaWidth))
slf.areaHeightLimit = int(math.Ceil(slf.height / slf.areaHeight))
areas := make([][]map[int64]game.AOIEntity2D, slf.areaWidthLimit)
for i := 0; i < len(areas); i++ {
entities := make([]map[int64]game.AOIEntity2D, slf.areaHeightLimit)
for e := 0; e < len(entities); e++ {
entities[e] = map[int64]game.AOIEntity2D{}
}
areas[i] = entities
}
slf.areas = areas
// 重新分区
for i := 0; i < len(oldAreas); i++ {
area := oldAreas[i]
for a := 0; a < len(area); a++ {
entities := area[a]
for _, entity := range entities {
slf.addEntity(entity)
}
}
}
}
func (slf *AOI2D) addEntity(entity game.AOIEntity2D) {
x, y := entity.GetPosition()
widthArea := int(x / slf.areaWidth)
heightArea := int(y / slf.areaHeight)
guid := entity.GetGuid()
slf.areas[widthArea][heightArea][guid] = entity
focus := map[int64]game.AOIEntity2D{}
slf.focus[guid] = focus
slf.rangeVisionAreaEntities(entity, func(eg int64, e game.AOIEntity2D) {
focus[eg] = e
slf.refresh(e)
})
}
func (slf *AOI2D) refresh(entity game.AOIEntity2D) {
x, y := entity.GetPosition()
vision := entity.GetVision()
guid := entity.GetGuid()
focus := slf.focus[guid]
for eg, e := range focus {
ex, ey := e.GetPosition()
if g2d.CalcDistance(x, y, ex, ey) > vision {
delete(focus, eg)
delete(slf.focus[eg], guid)
}
}
slf.rangeVisionAreaEntities(entity, func(guid int64, entity game.AOIEntity2D) {
if _, exist := focus[guid]; !exist {
focus[guid] = entity
}
})
}
func (slf *AOI2D) rangeVisionAreaEntities(entity game.AOIEntity2D, handle func(guid int64, entity game.AOIEntity2D)) {
x, y := entity.GetPosition()
widthArea := int(x / slf.areaWidth)
heightArea := int(y / slf.areaHeight)
vision := entity.GetVision()
widthSpan := int(math.Ceil(vision / slf.areaWidth))
heightSpan := int(math.Ceil(vision / slf.areaHeight))
guid := entity.GetGuid()
sw := widthArea - widthSpan
if sw < 0 {
sw = 0
} else if sw > slf.areaWidthLimit {
sw = slf.areaWidthLimit
}
for w := sw; w <= widthArea+widthSpan; w++ {
sh := heightArea - heightSpan
if sh < 0 {
sh = 0
} else if sh > slf.areaHeightLimit {
sh = slf.areaHeightLimit
}
for h := sh; h <= heightArea+heightSpan; h++ {
var areaX, areaY float64
if w < widthArea {
// H同理直接使用 float64((w + 1) * 100) 会导致 h 的值被加1why
tempW := w + 1
areaX = float64(tempW * int(slf.areaWidth))
} else if w > widthArea {
areaX = float64(w * int(slf.areaWidth))
} else {
areaX = x
}
if h < heightArea {
tempH := h + 1
areaY = float64(tempH * int(slf.areaHeight))
} else if h > heightArea {
areaY = float64(h * int(slf.areaHeight))
} else {
areaY = y
}
areaDistance := g2d.CalcDistance(x, y, areaX, areaY)
if areaDistance <= vision {
for eg, e := range slf.areas[w][h] {
if eg == guid {
continue
}
if ex, ey := e.GetPosition(); g2d.CalcDistance(x, y, ex, ey) > vision {
continue
}
handle(eg, e)
}
}
}
}
}
func (slf *AOI2D) deleteEntity(entity game.AOIEntity2D) {
x, y := entity.GetPosition()
widthArea := int(x / slf.areaWidth)
heightArea := int(y / slf.areaHeight)
guid := entity.GetGuid()
delete(slf.areas[widthArea][heightArea], guid)
focus := slf.focus[guid]
for g := range focus {
delete(slf.focus[g], guid)
}
delete(slf.focus, guid)
}

10
go.mod
View File

@ -11,8 +11,6 @@ require (
github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible
github.com/nats-io/nats.go v1.25.0
github.com/panjf2000/gnet v1.6.6
github.com/pkg/errors v0.9.1
github.com/smartystreets/goconvey v1.8.0
github.com/sony/sonyflake v1.1.0
github.com/tealeg/xlsx v1.0.5
github.com/tidwall/gjson v1.14.4
@ -30,9 +28,7 @@ require (
github.com/go-playground/validator/v10 v10.11.2 // indirect
github.com/goccy/go-json v0.10.0 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/gopherjs/gopherjs v1.17.2 // indirect
github.com/jonboulle/clockwork v0.3.0 // indirect
github.com/jtolds/gls v4.20.0+incompatible // indirect
github.com/klauspost/cpuid/v2 v2.2.2 // indirect
github.com/klauspost/reedsolomon v1.11.7 // indirect
github.com/kr/pretty v0.3.1 // indirect
@ -45,7 +41,7 @@ require (
github.com/nats-io/nkeys v0.4.4 // indirect
github.com/nats-io/nuid v1.0.1 // indirect
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
github.com/smartystreets/assertions v1.13.1 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/templexxx/cpu v0.0.9 // indirect
github.com/templexxx/xorsimd v0.4.1 // indirect
github.com/tidwall/match v1.1.1 // indirect
@ -58,8 +54,8 @@ require (
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
golang.org/x/crypto v0.8.0 // indirect
golang.org/x/net v0.9.0 // indirect
golang.org/x/sys v0.7.0 // indirect
golang.org/x/net v0.10.0 // indirect
golang.org/x/sys v0.8.0 // indirect
golang.org/x/text v0.9.0 // indirect
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect
google.golang.org/protobuf v1.28.1 // indirect

16
go.sum
View File

@ -56,16 +56,12 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/jonboulle/clockwork v0.3.0 h1:9BSCMi8C+0qdApAp4auwX0RkLGUjs956h0EkuQymUhg=
github.com/jonboulle/clockwork v0.3.0/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/klauspost/compress v1.16.4 h1:91KN02FnsOYhuunwU4ssRe8lc2JosWmizWa91B5v1PU=
github.com/klauspost/cpuid v1.2.4/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/cpuid v1.3.1/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4=
@ -122,10 +118,6 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/smartystreets/assertions v1.13.1 h1:Ef7KhSmjZcK6AVf9YbJdvPYG9avaF0ZxudX+ThRdWfU=
github.com/smartystreets/assertions v1.13.1/go.mod h1:cXr/IwVfSo/RbCSPhoAPv73p3hlSdrBH/b3SdnW/LMY=
github.com/smartystreets/goconvey v1.8.0 h1:Oi49ha/2MURE0WexF052Z0m+BNSGirfjg5RL+JXWq3w=
github.com/smartystreets/goconvey v1.8.0/go.mod h1:EdX8jtrTIj26jmjCOVNMVSIYAtgexqXKHOXW2Dx9JLg=
github.com/sony/sonyflake v1.1.0 h1:wnrEcL3aOkWmPlhScLEGAXKkLAIslnBteNUq4Bw6MM4=
github.com/sony/sonyflake v1.1.0/go.mod h1:LORtCywH/cq10ZbyfhKrHYgAUGH7mOBa76enV9txy/Y=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@ -213,8 +205,8 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81R
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -235,8 +227,8 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20211204120058-94396e421777/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=