From 9b86108eb8321c60cbcd23820ca412556317db28 Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Mon, 29 May 2023 13:26:42 +0800 Subject: [PATCH] =?UTF-8?q?=E7=AE=80=E5=8D=952dAOI=E7=B3=BB=E7=BB=9F?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/aoi2d.go | 19 ++++ game/aoi2d_entity.go | 9 ++ game/builtin/aoi2d.go | 238 ++++++++++++++++++++++++++++++++++++++++++ go.mod | 10 +- go.sum | 16 +-- 5 files changed, 273 insertions(+), 19 deletions(-) create mode 100644 game/aoi2d.go create mode 100644 game/aoi2d_entity.go create mode 100644 game/builtin/aoi2d.go diff --git a/game/aoi2d.go b/game/aoi2d.go new file mode 100644 index 0000000..0f9a41d --- /dev/null +++ b/game/aoi2d.go @@ -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) +} diff --git a/game/aoi2d_entity.go b/game/aoi2d_entity.go new file mode 100644 index 0000000..a8c2a44 --- /dev/null +++ b/game/aoi2d_entity.go @@ -0,0 +1,9 @@ +package game + +type AOIEntity2D interface { + Actor + // GetPosition 获取对象位置 + GetPosition() (x, y float64) + // GetVision 获取视距 + GetVision() float64 +} diff --git a/game/builtin/aoi2d.go b/game/builtin/aoi2d.go new file mode 100644 index 0000000..00dc267 --- /dev/null +++ b/game/builtin/aoi2d.go @@ -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) +} diff --git a/go.mod b/go.mod index dfdd6ee..190c129 100644 --- a/go.mod +++ b/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 diff --git a/go.sum b/go.sum index 3a03056..bdb9e3c 100644 --- a/go.sum +++ b/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=