简单2dAOI系统实现
This commit is contained in:
parent
5ed1b3b78f
commit
9b86108eb8
|
@ -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)
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package game
|
||||
|
||||
type AOIEntity2D interface {
|
||||
Actor
|
||||
// GetPosition 获取对象位置
|
||||
GetPosition() (x, y float64)
|
||||
// GetVision 获取视距
|
||||
GetVision() float64
|
||||
}
|
|
@ -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 的值被加1(why?)
|
||||
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
10
go.mod
|
@ -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
16
go.sum
|
@ -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=
|
||||
|
|
Loading…
Reference in New Issue