目录
Overview
应用
初始化aoi
场景操作aoi
视野消息处理器
用户对象实现aoi.AgentEntity
源码接口
import
Entity 进入aoi的基类接口
带链接的Entity,AgentEntity
其它Entity
邻居Neighbour接口
AOI接口
AoiCaller接口
游戏业务AOI业务部分,视野热区,多人同步大体是相同的;在这里easy中精简出aoi的逻辑部分。位置easy/vendors/aoi。easy的AOI是可以限制视野人数,但依据邻居关系限定就近原则缺少自定义化。若是没有限制人数,则也不需要什么就近原则自定义化了。
核心算法:十字链表和跳表综合实现的aoi;内存占用小,不需要提前创建内存块;高性能;
func newaoi() aoi.AOI {
return aoi.New(option.OptionWith(&aoi.Option{
NeighbourCount: conf.TomlConf.Line.AoiPlayerCount,
Radius: float32(conf.TomlConf.Line.Radius),
Axis: []int{0, 1, 2},
}).Default(option.DEFAULT_IGNORE_ZERO))
}
enter/leave/move
func (this *Scene) Enter(entity aoi.Entity) {
// 默认将人物放到空中
// 由客户端 的 send transform 纠正坐标
if usr, ok := entity.(*UserEntity); ok {
usr.MoveHandle([]float32{0, 180, 0}, []float32{})
}
//
this.aoi.Enter(entity)
logq.DebugF("LINE.ENTER-SCENE scene:%v", this.SceneID)
}
func (this *Scene) Leave(entity aoi.Entity) {
oper := this.Oper(entity, aoi.OPER_NOAGENT_LEAVE, nil)
if _, ok := entity.(aoi.AgentEntity); ok {
oper.Op = aoi.OPER_AGENT_LEAVE
}
logq.DebugF("LINE.LEAVE-SCENE scene:%v", this.SceneID)
this.aoi.Leave(entity)
}
func (this *Scene) Move(entity aoi.Entity) {
//return // PPROF.DELETE
oper := this.Oper(entity, aoi.OPER_NOAGENT_MOVE, nil)
if _, ok := entity.(aoi.AgentEntity); ok {
oper.Op = aoi.OPER_AGENT_MOVE
}
//this.aoi.Handle(oper)
this.aoi.Move(entity)
}
实现接口aoi.AoiMessage;
需要将MessageUser绑定到User类上;
type MessageUser struct {
master *UserEntity
mu sync.Locker
move_entitys []aoi.Entity
aoi.Message
}
func (self *MessageUser) Appear(arr []aoi.Entity) {
if len(arr) == 0 {
return
}
ms := &goproto.AppearRoleAoi_S2C{Players: []*goproto.AppearRoleAoi_S2C_Player{}}
ms.MsgCode = code.SUCCESS
for i := 0; i < len(arr); i++ {
switch u := arr[i].(type) {
case *UserEntity:
p := &goproto.AppearRoleAoi_S2C_Player{
PID: u.GetPlayerID(),
Position: u.Position(),
Rotate: u.Rotation(),
SceneID: u.SceneID,
Sex: u.GetSex(),
ClothID: u.GetClothID(),
NickName: u.GetNickName(),
}
ms.Players = append(ms.Players, p)
}
}
ants.AntsPool().Submit(func() {
self.master.WriteMessage(ms)
})
logq.Debugf("APPEAR-------------master.ID:%v ------- neight.len:%v", self.master.ID(), len(ms.Players))
}
func (self *MessageUser) Disappear(arr []aoi.Entity) {
if len(arr) == 0 {
return
}
ms := &goproto.DisappearRoleAoi_S2C{Players: []*goproto.DisappearRoleAoi_S2C_Player{}}
ms.MsgCode = code.SUCCESS
for i := 0; i < len(arr); i++ {
switch u := arr[i].(type) {
case *UserEntity:
p := &goproto.DisappearRoleAoi_S2C_Player{
PID: u.GetPlayerID(),
SceneID: u.SceneID,
ClothID: u.GetClothID(),
NickName: u.GetNickName(),
}
ms.Players = append(ms.Players, p)
}
}
ants.AntsPool().Submit(func() {
self.master.WriteMessage(ms)
})
}
func (self *MessageUser) Move(arr []aoi.Entity) {
self.mu.Lock()
defer self.mu.Unlock()
self.move_entitys = append(self.move_entitys, arr...)
}
func (self *MessageUser) MoveTick() {
ms := &goproto.FrameTransform_S2C{Players: []*goproto.FrameTransform_S2C_Player{}}
ms.MsgCode = code.SUCCESS
for i := 0; i < len(self.move_entitys); i++ {
if self.move_entitys[i] == nil {
continue
}
switch u := self.move_entitys[i].(type) {
case *UserEntity:
if u.GetActive() == ENTITY_IDEL {
break
}
p := &goproto.FrameTransform_S2C_Player{
PID: u.GetPlayerID(),
Position: u.Position(),
Rotate: u.Rotation(),
ActID: u.GetActID(),
}
ms.Players = append(ms.Players, p)
}
}
self.mu.Lock()
self.move_entitys = []aoi.Entity{}
self.mu.Unlock()
ants.AntsPool().Submit(func() {
self.master.WriteMessage(ms)
})
}
func (this *UserEntity) Agent() agent.Agent {
return this.User.GetAgent()
}
func (this *UserEntity) Neighbour() aoi.Neighbour {
return this.neighbour
}
func (this *UserEntity) AoiMessage() aoi.AoiMessage {
return this.aoiMessage
}
func (this *UserEntity) PositionPre(args ...float32) []float32 {
if len(args) > 0 {
this.move_tran.Position = args
}
return this.move_tran.Position
}
aoi.Behaivour;aoi执行对应的操作时,调用Entity的行为事件;
// Behaivour
func (this *UserEntity) Select(aoiobj any) {
this.aoi, _ = aoiobj.(aoi.AOI)
}
func (this *UserEntity) Enter() {
this.SetActive(ENTITY_IDEL)
this.ValideAoi(VALID_AOI_OK)
}
func (this *UserEntity) Leave() {
this.SetActive(ENTITY_IDEL)
this.ValideAoi(VALID_AOI_NO)
}
func (this *UserEntity) Move() {
this.moveLock.Lock()
defer this.moveLock.Unlock()
// move aoi 之后 旧的旧没有用了,可以做参数
this.Position(this.PositionPre()...)
this.Rotation(this.move_tran.Rotation...)
}
ID和坐标相关实现AgentEntity
func (this *UserEntity) ID() int {
return int(this.GetPlayerID())
}
func (this *UserEntity) Position(args ...float32) []float32 {
n := len(args)
if n == 0 {
return this.transform.Position
}
// 新值换旧值
n--
for i, m := 0, len(this.transform.Position); i < m; i++ {
this.transform_old.Position[i] = this.transform.Position[i]
if i <= n {
this.transform.Position[i] = args[i]
}
}
return this.transform.Position
}
func (this *UserEntity) PositionOld() []float32 {
return this.transform_old.Position
}
func (this *UserEntity) Rotation(args ...float32) []float32 {
n := len(args)
if n == 0 {
return this.transform.Rotation
} // 新值换旧值
n--
for i, m := 0, len(this.transform.Rotation); i < m; i++ {
this.transform_old.Rotation[i] = this.transform_old.Rotation[i]
if i <= n {
this.transform.Rotation[i] = args[i]
}
}
return this.transform.Rotation
}
func (this *UserEntity) RotationOld() []float32 {
return this.transform_old.Rotation
}
import (
"github.com/slclub/easy/nets/agent"
)
// The game player object should implements the Entity interface.
// It is a basic operating unit in aoi.
// Entity interface is mainly compatible with three parts.
// Object displacement;
// Callback of aoi event handle;
// Aoi messages handle;
type Entity interface {
ID() int
Position(args ...float32) []float32 //位置
PositionPre(args ...float32) []float32 //位置
PositionOld() []float32
AoiCaller
AoiMessage() AoiMessage
}
// Entity object with link.
// Your game player should implement AgentEntity interface.
// Neighbour() method return the neighbour object. So, your AgentEntiy object should include a field for neighbour object.
// and use aoi.NewNeighbour(aoi.Option()) to initialized it.
type AgentEntity interface {
Entity
Neighbour() Neighbour
Agent() agent.Agent
}
type Monster interface {
Entity
Monster()
}
type Npc interface {
Entity
Npc()
}
// The number of people displayed on the same screen in mobile games is limited.
// So, we need neighbour moudule to controll the players of interessting area.
type Neighbour interface {
neighbourInternel
RangeIncrease(fn func(entity Entity) bool)
RangeMove(fn func(entity Entity) bool)
RangeLeave(fn func(entity Entity) bool)
RangeBeenObservedSet(fn func(entity Entity) bool)
}
// This module was born solely for the convenience of interal code invoked.
type neighbourInternel interface {
join(v any) int
beenJoin(v any) int
leave(v any) int
beenLeave(v any) int
// v:nil将 move和increase 集合的entity 移动到 leave集合中
// v:clean 将increase 合并到move集合,清空 leave 和 increase 集合
reset(v any)
clear()
increaseEntitys() []Entity
moveEntitys() []Entity
leaveEntitys() []Entity
cutIncrease()
}
// It is the mainly interface of AOI
// Your scene object should prepare a field for AOI interface.
// It implement the AOIHandle interface.
// The AoiHandle interface is related to your scene logic.
type AOI interface {
Count(string) int
Range(func(entity Entity) bool)
Clear()
Option() *Option
AOIHandler
}
type AOIHandler interface {
Enter(entity Entity)
Leave(entity Entity)
Move(entity Entity)
BroadcastInterstingAll(entity Entity, fn func(mine, other Entity))
BroadcastAgentAll(fn func(mine, other Entity))
}
// Entity Callback
// Your entity object should implement the AoiCaller interface.
// Please, do not use it as message handler.
// It just your entity should do when aoi was event happened.
// it is suitable for the changing logic of your entity object when aoi event is happening.
type AoiCaller interface {
Enter()
Move()
Leave()
}
消息接收器接口
// 消息接收器
// Aoi message handle interface.
// It should be Hang on your entity.
// It is reasonable for different type of entity generate the different messages.
type AoiMessage interface {
Appear([]Entity)
Move([]Entity)
Disappear([]Entity)
}