package tower import ( "f1-game/internal/algorithm/aoi" "math" clog "github.com/cherry-game/cherry/logger" ) type ( Config struct { Width int32 // 地图宽(x) Height int32 // 地图高(y) TowerWidth int32 // 灯塔宽(x) TowerHeight int32 // 灯塔高(y) TowerOffset int32 // 灯塔宽高偏移值 RangeLimit int32 // 观察者视野 PrintTowerInfo bool // 初始化时,是否打印Tower的信息 } AOI struct { aoi.EventBase config Config // config towerNumX int32 // x轴的灯塔数量 towerNumY int32 // y轴的灯塔数量 towers map[int32]map[int32]*Tower // 灯塔列表 key:灯塔y, value:{key:灯塔x, value:*Tower} objects aoi.Objects // 所有对象 key:ObjectID, value:*aoi.Object } ) // 实现一个灯塔的aoi算法,对象分为观察者和被观察者, 对象可以 “进入”、“离开”、 “移动”、“属性更新”四个动作, // 当上层业务触发这些动作时,过滤出要广播的对象,并进行函数回调。 // 1、观察者的动作主要是移动屏幕时同步灯塔相关的被观察者。 // 2、被观察会进行移动、属性更新的触发操作,这时需要通知观察者。 // 3、观察者“进入”时,触发回调函数,下发被观察者的对象列表。 // 4、观察者移动屏幕时,触发新的灯塔被观察者对象。如果没有移动出当前灯塔,被观察者对象也不用再重复下发广播了。之前已经同步过的被观察者无需再同步, // 除非被观察者有触发新的移动和属性更新。 // 5、被观察者中途改变了寻路列表,则再次广播给灯塔相关的观察者。 当被观察者触发更新属性动作时,也通知灯塔相关的观察者。 func New(config Config) aoi.AOI { towerAOI := &AOI{ EventBase: aoi.EventBase{}, config: config, towers: make(map[int32]map[int32]*Tower), objects: aoi.Objects{}, } towerAOI.init() return towerAOI } func (p *AOI) init() { if p.config.Width < 1 || p.config.Height < 1 { clog.Panicf("Config params error. %v", p) } if p.config.TowerWidth < 1 /*|| p.config.TowerWidth > p.config.Width*/ { clog.Panicf("Config params error. %v", p) } if p.config.TowerHeight < 1 /*|| p.config.TowerHeight > p.config.Height*/ { clog.Panicf("Config params error. %v", p) } if p.config.TowerOffset < 0 || p.config.RangeLimit < 1 { clog.Panicf("Config params error. %v", p) } p.towerNumX = int32(math.Ceil(float64(p.config.Width) / float64(p.config.TowerWidth))) p.towerNumY = int32(math.Ceil(float64(p.config.Height) / float64(p.config.TowerHeight))) towerID := int32(0) for x := range p.towerNumX { p.towers[x] = make(map[int32]*Tower) for y := range p.towerNumY { p.towers[x][y] = p.newTower(towerID, x, y) towerID++ } } } func (p *AOI) Get(objectID int64) *aoi.Object { return p.objects.Get(objectID) } func (p *AOI) Enter(object *aoi.Object) bool { if !p.CheckPoint(object.Point.X, object.Point.Y) { clog.Warnf("[Enter] check point error. %s", object.Info()) return false } if p.objects.Contains(object.ObjectID) { clog.Warnf("[Enter] object has entered. %s", object.Info()) return false } if object.IsWatcher() { towers := p.GetRangeTowers(object.Point.X, object.Point.Y) if len(towers) < 1 { clog.Warnf("[Enter] get tower fail. %s", object.Info()) return false } p.objects.Add(object) // 获取相关范围的tower,并把观察者加入到观察列表 for _, tower := range towers { tower.AddWatcher(object) } if p.WatcherEnterFunc != nil { markers := towers.MakersFilter(object) if !markers.IsEmpty() { p.WatcherEnterFunc(object, markers) } } } if object.IsMarker() { tower := p.GetTowerByPoint(object.Point.X, object.Point.Y) if tower == nil { clog.Warnf("[Enter] get tower fail. %s", object.Info()) return false } // add object p.objects.Add(object) tower.AddMarker(object) if p.MarkerEnterFunc != nil { if !tower.watchers.IsEmpty() { p.MarkerEnterFunc(object, tower.watchers) } } } return true } func (p *AOI) Move(objectID int64, x, y int32) bool { object := p.objects.Get(objectID) if object == nil { return false } if object.Point.X == x && object.Point.Y == y { return false } if !p.CheckPoint(x, y) { clog.Warnf("[Move] check point error. %s", object.Info()) return false } // old tower point oldTower := p.GetTowerByPoint(object.Point.X, object.Point.Y) if oldTower == nil { clog.Warnf("[Move] old tower not found. %s", object.Info()) return false } // new tower point newTower := p.GetTowerByPoint(x, y) if newTower == nil { clog.Warnf("[Move] new tower not found. %s", object.Info()) return false } var ( oldX = object.Point.X oldY = object.Point.Y ) // 先把对象设置到新的位置 object.SetPoint(x, y) // 移动时,如果object没有改变tower,则不进行对象同步 if oldTower.id == newTower.id { return false } if object.IsWatcher() { // 获取需要添加观察者、需要删除观察者的towers addTowers, delTowers := p.GetDiffTowers(oldX, oldY, x, y) // object作为观察者加入tower列表 for _, dt := range addTowers { dt.AddWatcher(object) } // object从旧的tower列表移除 for _, dt := range delTowers { dt.DelWatcher(object) } if p.WatcherMoveFunc != nil { markers := addTowers.MakersFilter(object) if !markers.IsEmpty() { p.WatcherMoveFunc(object, markers) // , delTowers.Watchers() } } } if object.IsMarker() { oldTower.DelMarker(object) newTower.AddMarker(object) // 如果对象是移动的第一个路点,则需要同步对象给观察者列表 if p.MarkerMoveFunc != nil && !newTower.watchers.IsEmpty() { p.MarkerMoveFunc(object, newTower.watchers) } } return true } func (p *AOI) Leave(objectID int64) { object := p.objects.Get(objectID) if object == nil { clog.Debugf("[Leave] objectID = %d not found.", objectID) return } // remove object p.objects.Remove(objectID) tower := p.GetTowerByPoint(object.Point.X, object.Point.Y) if tower == nil { return } if object.IsWatcher() { rangeTowers := p.GetRangeTowers(object.Point.X, object.Point.Y) for _, ranteTower := range rangeTowers { ranteTower.DelWatcher(object) } // TODO trigger watcher leave func ? } if object.IsMarker() { tower.DelMarker(object) if p.MarkerLeaveFunc != nil && !tower.watchers.IsEmpty() { p.MarkerLeaveFunc(object, tower.watchers) } } // object clean object.Clean() } func (p *AOI) Update(objectID int64, updateID uint32, funcName string) { object := p.objects.Get(objectID) if object == nil { clog.Debugf("[Update] objectID = %d not found.", objectID) return } if object.IsWatcher() { return } thisTower := p.GetTowerByPoint(object.Point.X, object.Point.Y) if thisTower == nil { clog.Warnf("[Update] objectID = {%s}, tower not found. %s", object.Info()) return } watchers := aoi.Objects{} for _, watcher := range thisTower.watchers { watchers.Add(watcher) } if p.MarkerUpdateFunc != nil { p.MarkerUpdateFunc(object, watchers, updateID, funcName) } } func (p *AOI) newTower(id, tx, ty int32) *Tower { var ( minX = tx*p.config.TowerWidth - p.config.TowerOffset minY = ty*p.config.TowerHeight - p.config.TowerOffset maxX = minX + p.config.TowerWidth maxY = minY + p.config.TowerHeight ) if p.config.PrintTowerInfo { clog.Infof("id = %d, min(X = %d, Y = %d), max(X = %d, Y = %d)", id, minX, minY, maxX, maxY) } return &Tower{ id: id, markers: aoi.Objects{}, watchers: aoi.Objects{}, minX: minX, minY: minY, maxX: maxX, maxY: maxY, } } // CheckPoint 坐标是否合法 func (p *AOI) CheckPoint(x, y int32) bool { return x >= 0 && y >= 0 && x < p.config.Width && y < p.config.Height } func (p *AOI) InArea(x, y int32, sx, sy, ex, ey int32) bool { return x >= sx && x <= ex && y >= sy && y <= ey } // ToTowerPoint 场景坐标切换为灯塔坐标 func (p *AOI) ToTowerPoint(x, y int32) (tx int32, ty int32) { tx = x / p.config.TowerWidth ty = y / p.config.TowerHeight return } // GetTower 根据对象的场景坐标获取灯塔 func (p *AOI) GetTower(tx, ty int32) *Tower { if tower, found := p.towers[tx][ty]; found { return tower } return nil } func (p *AOI) GetTowerByPoint(x, y int32) *Tower { tx, ty := p.ToTowerPoint(x, y) return p.GetTower(tx, ty) } func (p *AOI) GetDiffTowers(ox, oy, nx, ny int32) (addTowers Towers, delTowers Towers) { oldTowers := p.GetRangeTowers(ox, oy) newTowers := p.GetRangeTowers(nx, ny) oldIDSet := oldTowers.IDSet() for _, newTower := range newTowers { if !oldIDSet.Contains(newTower.id) { addTowers = append(addTowers, newTower) } } newIDSet := newTowers.IDSet() for _, oldTower := range oldTowers { if !newIDSet.Contains(oldTower.id) { delTowers = append(delTowers, oldTower) } } return } func (p *AOI) GetRangeTowers(x, y int32) Towers { var list Towers tx, ty := p.ToTowerPoint(x, y) sx, sy, ex, ey := p.GetRangeTowerPoint(tx, ty) for x := sx; x <= ex; x++ { for y := sy; y <= ey; y++ { if rangeTower := p.GetTower(x, y); rangeTower != nil { list = append(list, rangeTower) } } } return list } func (p *AOI) GetRangeTowerPoint(tx, ty int32) (sx, sy, ex, ey int32) { sx = tx - p.config.RangeLimit ex = tx + p.config.RangeLimit sy = ty - p.config.RangeLimit ey = ty + p.config.RangeLimit if sx < 0 { sx = 0 } if sy < 0 { sy = 0 } if ex > p.towerNumX { ex = p.towerNumX } if ey > p.towerNumY { ey = p.towerNumY } return sx, sy, ex, ey }