diff --git a/game/actor.go b/game/actor.go index 86d2998..bac6c6f 100644 --- a/game/actor.go +++ b/game/actor.go @@ -2,6 +2,9 @@ package game // Actor 表示游戏中的对象,具有唯一标识符 type Actor interface { + // SetGuid 设置对象的唯一标识符 + // - 需要注意的是该函数不应该主动执行,否则可能产生意想不到的情况 + SetGuid(guid int64) // GetGuid 获取对象的唯一标识符 GetGuid() int64 } diff --git a/game/builtin/actor_move.go b/game/builtin/actor_move.go index 19a06f1..25cfb51 100644 --- a/game/builtin/actor_move.go +++ b/game/builtin/actor_move.go @@ -11,61 +11,49 @@ type ActorMove struct { } func (slf *ActorMove) MoveTo2D(x, y float64) { - //TODO implement me - panic("implement me") + slf.SetXY(x, y) } func (slf *ActorMove) MoveBy2D(dx, dy float64) { - //TODO implement me - panic("implement me") + slf.SetXY(slf.GetX()+dx, slf.GetY()+dy) } func (slf *ActorMove) MoveTo3D(x, y, z float64) { - //TODO implement me - panic("implement me") + slf.SetXYZ(x, y, z) } func (slf *ActorMove) MoveBy3D(dx, dy, dz float64) { - //TODO implement me - panic("implement me") + slf.SetXYZ(slf.GetX()+dx, slf.GetY()+dy, slf.GetZ()+dz) } func (slf *ActorMove) MoveToX(x float64) { - //TODO implement me - panic("implement me") + slf.SetX(x) } func (slf *ActorMove) MoveByX(dx float64) { - //TODO implement me - panic("implement me") + slf.SetX(slf.GetX() + dx) } func (slf *ActorMove) MoveToY(y float64) { - //TODO implement me - panic("implement me") + slf.SetY(y) } func (slf *ActorMove) MoveByY(dy float64) { - //TODO implement me - panic("implement me") + slf.SetY(slf.GetY() + dy) } func (slf *ActorMove) MoveToZ(z float64) { - //TODO implement me - panic("implement me") + slf.SetZ(z) } func (slf *ActorMove) MoveByZ(dz float64) { - //TODO implement me - panic("implement me") + slf.SetZ(slf.GetZ() + dz) } func (slf *ActorMove) GetSpeed() float64 { - //TODO implement me - panic("implement me") + return slf.speed } func (slf *ActorMove) SetSpeed(speed float64) { - //TODO implement me - panic("implement me") + slf.speed = speed } diff --git a/game/builtin/position.go b/game/builtin/position.go index 73145ee..b107ec8 100644 --- a/game/builtin/position.go +++ b/game/builtin/position.go @@ -1,5 +1,7 @@ package builtin +import "minotaur/game" + // NewPosition 创建一个新的Position对象。 func NewPosition(x, y, z float64) *Position { return &Position{ @@ -11,58 +13,93 @@ func NewPosition(x, y, z float64) *Position { // Position 是一个具有位置信息的对象。 type Position struct { - x, y, z float64 + x, y, z float64 + positionChangeEventHandles []game.PositionChangeEventHandle } // GetX 返回Position对象的X坐标。 -func (p *Position) GetX() float64 { - return p.x +func (slf *Position) GetX() float64 { + return slf.x } // GetY 返回Position对象的Y坐标。 -func (p *Position) GetY() float64 { - return p.y +func (slf *Position) GetY() float64 { + return slf.y } // GetZ 返回Position对象的Z坐标。 -func (p *Position) GetZ() float64 { - return p.z +func (slf *Position) GetZ() float64 { + return slf.z } // GetXY 返回Position对象的X和Y坐标。 -func (p *Position) GetXY() (float64, float64) { - return p.x, p.y +func (slf *Position) GetXY() (float64, float64) { + return slf.x, slf.y } // GetXYZ 返回Position对象的X、Y和Z坐标。 -func (p *Position) GetXYZ() (float64, float64, float64) { - return p.x, p.y, p.z +func (slf *Position) GetXYZ() (float64, float64, float64) { + return slf.x, slf.y, slf.z } // SetX 设置Position对象的X坐标。 -func (p *Position) SetX(x float64) { - p.x = x +func (slf *Position) SetX(x float64) { + old := slf.Clone() + defer slf.OnPositionChangeEvent(old, slf) + slf.x = x } // SetY 设置Position对象的Y坐标。 -func (p *Position) SetY(y float64) { - p.y = y +func (slf *Position) SetY(y float64) { + old := slf.Clone() + defer slf.OnPositionChangeEvent(old, slf) + slf.y = y } // SetZ 设置Position对象的Z坐标。 -func (p *Position) SetZ(z float64) { - p.z = z +func (slf *Position) SetZ(z float64) { + old := slf.Clone() + defer slf.OnPositionChangeEvent(old, slf) + slf.z = z } // SetXY 设置Position对象的X和Y坐标。 -func (p *Position) SetXY(x, y float64) { - p.x = x - p.y = y +func (slf *Position) SetXY(x, y float64) { + old := slf.Clone() + defer slf.OnPositionChangeEvent(old, slf) + slf.x = x + slf.y = y } // SetXYZ 设置Position对象的X、Y和Z坐标。 -func (p *Position) SetXYZ(x, y, z float64) { - p.x = x - p.y = y - p.z = z +func (slf *Position) SetXYZ(x, y, z float64) { + old := slf.Clone() + defer slf.OnPositionChangeEvent(old, slf) + slf.x = x + slf.y = y + slf.z = z +} + +func (slf *Position) Clone() game.Position { + return &Position{ + x: slf.x, + y: slf.y, + z: slf.z, + } +} + +func (slf *Position) Compare(position game.Position) bool { + return slf.x == position.GetX() && slf.y == position.GetY() && slf.z == position.GetZ() +} + +func (slf *Position) RegPositionChangeEvent(handle game.PositionChangeEventHandle) { + slf.positionChangeEventHandles = append(slf.positionChangeEventHandles, handle) +} + +func (slf *Position) OnPositionChangeEvent(old, new game.Position) { + if !old.Compare(new) { + for _, handle := range slf.positionChangeEventHandles { + handle(old, new) + } + } } diff --git a/game/builtin/world.go b/game/builtin/world.go new file mode 100644 index 0000000..5a66543 --- /dev/null +++ b/game/builtin/world.go @@ -0,0 +1,237 @@ +package builtin + +import ( + "minotaur/game" + "minotaur/utils/synchronization" + "sync/atomic" +) + +func NewWorld[PlayerID comparable](guid int64, options ...WorldOption[PlayerID]) *World[PlayerID] { + world := &World[PlayerID]{ + guid: guid, + players: synchronization.NewMap[PlayerID, game.Player[PlayerID]](), + playerActors: synchronization.NewMap[PlayerID, *synchronization.Map[int64, game.Actor]](), + owners: synchronization.NewMap[int64, PlayerID](), + actors: synchronization.NewMap[int64, game.Actor](), + } + for _, option := range options { + option(world) + } + return world +} + +type World[PlayerID comparable] struct { + guid int64 + actorGuid atomic.Int64 + playerLimit int + players *synchronization.Map[PlayerID, game.Player[PlayerID]] + playerActors *synchronization.Map[PlayerID, *synchronization.Map[int64, game.Actor]] + owners *synchronization.Map[int64, PlayerID] + actors *synchronization.Map[int64, game.Actor] + + playerJoinWorldEventHandles []game.PlayerJoinWorldEventHandle[PlayerID] + playerLeaveWorldEventHandles []game.PlayerLeaveWorldEventHandle[PlayerID] + actorGeneratedEventHandles []game.ActorGeneratedEventHandle + actorAnnihilationEventHandles []game.ActorAnnihilationEventHandle + actorOwnerChangeEventHandles []game.ActorOwnerChangeEventHandle[PlayerID] + + released atomic.Bool +} + +func (slf *World[PlayerID]) GetGuid() int64 { + return slf.guid +} + +func (slf *World[PlayerID]) GetPlayerLimit() int { + return slf.playerLimit +} + +func (slf *World[PlayerID]) GetPlayer(id PlayerID) game.Player[PlayerID] { + return slf.players.Get(id) +} + +func (slf *World[PlayerID]) GetPlayers() map[PlayerID]game.Player[PlayerID] { + return slf.players.Map() +} + +func (slf *World[PlayerID]) GetActor(guid int64) game.Actor { + return slf.actors.Get(guid) +} + +func (slf *World[PlayerID]) GetActors() map[int64]game.Actor { + return slf.actors.Map() +} + +func (slf *World[PlayerID]) GetPlayerActor(id PlayerID, guid int64) game.Actor { + if actors := slf.playerActors.Get(id); actors != nil { + return actors.Get(guid) + } + return nil +} + +func (slf *World[PlayerID]) GetPlayerActors(id PlayerID) map[int64]game.Actor { + return slf.playerActors.Get(id).Map() +} + +func (slf *World[PlayerID]) IsExistPlayer(id PlayerID) bool { + return slf.players.Exist(id) +} + +func (slf *World[PlayerID]) IsExistActor(guid int64) bool { + return slf.actors.Exist(guid) +} + +func (slf *World[PlayerID]) IsOwner(id PlayerID, guid int64) bool { + actors := slf.playerActors.Get(id) + if actors != nil { + return actors.Exist(guid) + } + return false +} + +func (slf *World[PlayerID]) Join(player game.Player[PlayerID]) error { + if slf.released.Load() { + return ErrWorldReleased + } + if slf.players.Size() >= slf.playerLimit && slf.playerLimit > 0 { + return ErrWorldPlayerLimit + } + slf.players.Set(player.GetID(), player) + if actors := slf.playerActors.Get(player.GetID()); actors == nil { + actors = synchronization.NewMap[int64, game.Actor]() + slf.playerActors.Set(player.GetID(), actors) + } + slf.OnPlayerJoinWorldEvent(player) + return nil +} + +func (slf *World[PlayerID]) Leave(player game.Player[PlayerID]) { + if !slf.players.Exist(player.GetID()) { + return + } + slf.OnPlayerLeaveWorldEvent(player) + slf.playerActors.Get(player.GetID()).Range(func(guid int64, actor game.Actor) { + slf.OnActorAnnihilationEvent(actor) + slf.owners.Delete(guid) + }) + slf.playerActors.Delete(player.GetID()) + slf.players.Delete(player.GetID()) +} + +func (slf *World[PlayerID]) AddActor(actor game.Actor) { + guid := slf.actorGuid.Add(1) + actor.SetGuid(guid) + slf.actors.Set(actor.GetGuid(), actor) + slf.OnActorGeneratedEvent(actor) +} + +func (slf *World[PlayerID]) RemoveActor(guid int64) { + if actor, exist := slf.actors.GetExist(guid); exist { + slf.OnActorAnnihilationEvent(actor) + if id, exist := slf.owners.DeleteGetExist(guid); exist { + slf.playerActors.Get(id).Delete(guid) + } + slf.actors.Delete(guid) + } +} + +func (slf *World[PlayerID]) SetActorOwner(guid int64, id PlayerID) { + oldId, exist := slf.owners.GetExist(guid) + if exist && oldId == id { + return + } + actor := slf.GetActor(guid) + if actor == nil { + return + } + slf.owners.Set(guid, id) + slf.playerActors.Get(id).Set(guid, actor) + slf.OnActorOwnerChangeEvent(actor, oldId, id, false) +} + +func (slf *World[PlayerID]) RemoveActorOwner(guid int64) { + id, exist := slf.owners.GetExist(guid) + if !exist { + return + } + slf.owners.Delete(guid) + slf.playerActors.Get(id).Delete(guid) + slf.OnActorOwnerChangeEvent(slf.GetActor(guid), id, id, true) +} + +func (slf *World[PlayerID]) Reset() { + slf.players.Clear() + slf.playerActors.Range(func(id PlayerID, actors *synchronization.Map[int64, game.Actor]) { + actors.Clear() + }) + slf.playerActors.Clear() + slf.owners.Clear() + slf.actors.Clear() + slf.actorGuid.Store(0) +} + +func (slf *World[PlayerID]) Release() { + if !slf.released.Swap(true) { + slf.Reset() + slf.players = nil + slf.playerActors = nil + slf.owners = nil + slf.actors = nil + + slf.playerJoinWorldEventHandles = nil + slf.playerLeaveWorldEventHandles = nil + slf.actorGeneratedEventHandles = nil + slf.actorAnnihilationEventHandles = nil + slf.actorOwnerChangeEventHandles = nil + } +} + +func (slf *World[PlayerID]) RegPlayerJoinWorldEvent(handle game.PlayerJoinWorldEventHandle[PlayerID]) { + slf.playerJoinWorldEventHandles = append(slf.playerJoinWorldEventHandles, handle) +} + +func (slf *World[PlayerID]) OnPlayerJoinWorldEvent(player game.Player[PlayerID]) { + for _, handle := range slf.playerJoinWorldEventHandles { + handle(player) + } +} + +func (slf *World[PlayerID]) RegPlayerLeaveWorldEvent(handle game.PlayerLeaveWorldEventHandle[PlayerID]) { + slf.playerLeaveWorldEventHandles = append(slf.playerLeaveWorldEventHandles, handle) +} + +func (slf *World[PlayerID]) OnPlayerLeaveWorldEvent(player game.Player[PlayerID]) { + for _, handle := range slf.playerLeaveWorldEventHandles { + handle(player) + } +} + +func (slf *World[PlayerID]) RegActorGeneratedEvent(handle game.ActorGeneratedEventHandle) { + slf.actorGeneratedEventHandles = append(slf.actorGeneratedEventHandles, handle) +} + +func (slf *World[PlayerID]) OnActorGeneratedEvent(actor game.Actor) { + for _, handle := range slf.actorGeneratedEventHandles { + handle(actor) + } +} + +func (slf *World[PlayerID]) RegActorAnnihilationEvent(handle game.ActorAnnihilationEventHandle) { + slf.actorAnnihilationEventHandles = append(slf.actorAnnihilationEventHandles, handle) +} + +func (slf *World[PlayerID]) OnActorAnnihilationEvent(actor game.Actor) { + for _, handle := range slf.actorAnnihilationEventHandles { + handle(actor) + } +} + +func (slf *World[PlayerID]) RegActorOwnerChangeEvent(handle game.ActorOwnerChangeEventHandle[PlayerID]) { + slf.actorOwnerChangeEventHandles = append(slf.actorOwnerChangeEventHandles, handle) +} + +func (slf *World[PlayerID]) OnActorOwnerChangeEvent(actor game.Actor, old, new PlayerID, isolated bool) { + for _, handle := range slf.actorOwnerChangeEventHandles { + handle(actor, old, new, isolated) + } +} diff --git a/game/builtin/world_errors.go b/game/builtin/world_errors.go new file mode 100644 index 0000000..15c1487 --- /dev/null +++ b/game/builtin/world_errors.go @@ -0,0 +1,8 @@ +package builtin + +import "errors" + +var ( + ErrWorldPlayerLimit = errors.New("the number of players in the world has reached the upper limit") + ErrWorldReleased = errors.New("the world has been released") +) diff --git a/game/builtin/world_options.go b/game/builtin/world_options.go new file mode 100644 index 0000000..1502b4d --- /dev/null +++ b/game/builtin/world_options.go @@ -0,0 +1,9 @@ +package builtin + +type WorldOption[PlayerID comparable] func(world *World[PlayerID]) + +func WithWorldPlayerLimit[PlayerID comparable](playerLimit int) WorldOption[PlayerID] { + return func(world *World[PlayerID]) { + world.playerLimit = playerLimit + } +} diff --git a/game/postion.go b/game/postion.go index 27c9537..4124345 100644 --- a/game/postion.go +++ b/game/postion.go @@ -24,4 +24,15 @@ type Position interface { SetXY(x, y float64) // SetXYZ 设置X、Y和Z轴坐标 SetXYZ(x, y, z float64) + // Clone 克隆当前位置到新结构体 + Clone() Position + // Compare 比较两个坐标是否相同 + Compare(position Position) bool + // RegPositionChangeEvent 当位置发生改变时,将立即执行注册的事件处理函数 + RegPositionChangeEvent(handle PositionChangeEventHandle) + OnPositionChangeEvent(old, new Position) } + +type ( + PositionChangeEventHandle func(old, new Position) +) diff --git a/game/world.go b/game/world.go new file mode 100644 index 0000000..280fadf --- /dev/null +++ b/game/world.go @@ -0,0 +1,70 @@ +package game + +// World 游戏世界接口定义 +type World[PlayerID comparable] interface { + // GetGuid 获取世界的唯一标识符 + GetGuid() int64 + // GetPlayerLimit 获取玩家人数上限 + GetPlayerLimit() int + // GetPlayer 根据玩家id获取玩家 + GetPlayer(id PlayerID) Player[PlayerID] + // GetPlayers 获取世界中的所有玩家 + GetPlayers() map[PlayerID]Player[PlayerID] + // GetActor 根据唯一标识符获取世界中的游戏对象 + GetActor(guid int64) Actor + // GetActors 获取世界中的所有游戏对象 + GetActors() map[int64]Actor + // GetPlayerActor 获取游戏世界中归属特定玩家的特定游戏对象 + GetPlayerActor(id PlayerID, guid int64) Actor + // GetPlayerActors 获取游戏世界中归属特定玩家的所有游戏对象 + GetPlayerActors(id PlayerID) map[int64]Actor + // IsExistPlayer 检查游戏世界中是否存在特定玩家 + IsExistPlayer(id PlayerID) bool + // IsExistActor 检查游戏世界中是否存在特定游戏对象 + IsExistActor(guid int64) bool + // IsOwner 检查游戏世界中的特定游戏对象是否归属特定玩家 + IsOwner(id PlayerID, guid int64) bool + + // Join 使特定玩家加入游戏世界 + Join(player Player[PlayerID]) error + // Leave 使特定玩家离开游戏世界 + Leave(player Player[PlayerID]) + + // AddActor 添加游戏对象 + AddActor(actor Actor) + // RemoveActor 移除游戏对象 + RemoveActor(guid int64) + // SetActorOwner 设置游戏对象归属玩家 + SetActorOwner(guid int64, id PlayerID) + // RemoveActorOwner 移除游戏对象归属,置为无主的 + RemoveActorOwner(guid int64) + + // Reset 重置世界资源 + Reset() + // Release 释放世界资源,释放后世界将不可用 + Release() + + // RegPlayerJoinWorldEvent 玩家进入世界时将立即执行被注册的事件处理函数 + RegPlayerJoinWorldEvent(handle PlayerJoinWorldEventHandle[PlayerID]) + OnPlayerJoinWorldEvent(player Player[PlayerID]) + // RegPlayerLeaveWorldEvent 玩家离开世界时将立即执行被注册的事件处理函数 + RegPlayerLeaveWorldEvent(handle PlayerLeaveWorldEventHandle[PlayerID]) + OnPlayerLeaveWorldEvent(player Player[PlayerID]) + // RegActorGeneratedEvent 游戏世界中的游戏对象生成完成时将立即执行被注册的事件处理函数 + RegActorGeneratedEvent(handle ActorGeneratedEventHandle) + OnActorGeneratedEvent(actor Actor) + // RegActorAnnihilationEvent 游戏世界中的游戏对象被移除前执行被注册的事件处理函数 + RegActorAnnihilationEvent(handle ActorAnnihilationEventHandle) + OnActorAnnihilationEvent(actor Actor) + // RegActorOwnerChangeEvent 游戏对象的归属被改变时立刻执行被注册的事件处理函数 + RegActorOwnerChangeEvent(handle ActorOwnerChangeEventHandle[PlayerID]) + OnActorOwnerChangeEvent(actor Actor, old, new PlayerID, isolated bool) +} + +type ( + PlayerJoinWorldEventHandle[ID comparable] func(player Player[ID]) + PlayerLeaveWorldEventHandle[ID comparable] func(player Player[ID]) + ActorGeneratedEventHandle func(actor Actor) + ActorAnnihilationEventHandle func(actor Actor) + ActorOwnerChangeEventHandle[ID comparable] func(actor Actor, old, new ID, isolated bool) +) diff --git a/utils/synchronization/map.go b/utils/synchronization/map.go index c559a80..21dd227 100644 --- a/utils/synchronization/map.go +++ b/utils/synchronization/map.go @@ -178,3 +178,9 @@ func (slf *Map[Key, Value]) Map() map[Key]Value { slf.lock.RUnlock() return m } + +func (slf *Map[Key, Value]) Size() int { + slf.lock.RLock() + defer slf.lock.RUnlock() + return len(slf.data) +} diff --git a/utils/synchronization/pool.go b/utils/synchronization/pool.go new file mode 100644 index 0000000..4751dd6 --- /dev/null +++ b/utils/synchronization/pool.go @@ -0,0 +1,50 @@ +package synchronization + +import "sync" + +func NewPool[T any](bufferSize int, generator func() T, releaser func(data T)) *Pool[T] { + pool := &Pool[T]{ + bufferSize: bufferSize, + generator: generator, + releaser: releaser, + } + for i := 0; i < bufferSize; i++ { + pool.put(generator()) + } + return pool +} + +type Pool[T any] struct { + mutex sync.Mutex + buffers []T + bufferSize int + generator func() T + releaser func(data T) +} + +func (slf *Pool[T]) Get() T { + slf.mutex.Lock() + if len(slf.buffers) > 0 { + data := slf.buffers[0] + slf.buffers = slf.buffers[1:] + slf.mutex.Unlock() + return data + } + slf.mutex.Unlock() + return slf.generator() +} + +func (slf *Pool[T]) Release(data T) { + slf.releaser(data) + slf.put(data) +} + +func (slf *Pool[T]) put(data T) { + slf.mutex.Lock() + if len(slf.buffers) > slf.bufferSize { + slf.mutex.Unlock() + return + } + slf.buffers = append(slf.buffers, data) + slf.mutex.Unlock() +}