diff --git a/game/builtin/moving2d.go b/game/builtin/moving2d.go index e43700f..a40ab63 100644 --- a/game/builtin/moving2d.go +++ b/game/builtin/moving2d.go @@ -7,58 +7,135 @@ import ( "time" ) -func NewMoving2D() *Moving2D { +func NewMoving2D(options ...Moving2DOption) *Moving2D { moving2D := &Moving2D{ entities: map[int64]*moving2DTarget{}, + timeUnit: float64(time.Millisecond), + idle: time.Millisecond * 100, + event: make(chan func(), 1000), + } + for _, option := range options { + option(moving2D) } go moving2D.handle() + go func() { + for event := range moving2D.event { + event() + } + }() return moving2D } +type Moving2D struct { + rw sync.RWMutex + entities map[int64]*moving2DTarget + timeUnit float64 + idle time.Duration + event chan func() + close bool + + position2DChangeEventHandles []game.Position2DChangeEventHandle + position2DDestinationEventHandles []game.Position2DDestinationEventHandle +} + +func (slf *Moving2D) MoveTo(entity game.Moving2DEntity, x float64, y float64) { + guid := entity.GetGuid() + current := time.Now().UnixMilli() + slf.rw.Lock() + defer slf.rw.Unlock() + if slf.close { + return + } + entityTarget, exist := slf.entities[guid] + if !exist { + entityTarget = &moving2DTarget{ + Moving2DEntity: entity, + x: x, + y: y, + lastMoveTime: current, + } + slf.entities[guid] = entityTarget + return + } + entityTarget.x = x + entityTarget.y = y + entityTarget.lastMoveTime = current +} + +func (slf *Moving2D) StopMove(guid int64) { + slf.rw.Lock() + delete(slf.entities, guid) + slf.rw.Unlock() +} + +func (slf *Moving2D) RegPosition2DChangeEvent(handle game.Position2DChangeEventHandle) { + slf.position2DChangeEventHandles = append(slf.position2DChangeEventHandles, handle) +} + +func (slf *Moving2D) OnPosition2DChangeEvent(entity game.Moving2DEntity, oldX, oldY float64) { + for _, handle := range slf.position2DChangeEventHandles { + handle(slf, entity, oldX, oldY) + } +} + +func (slf *Moving2D) RegPosition2DDestinationEvent(handle game.Position2DDestinationEventHandle) { + slf.position2DDestinationEventHandles = append(slf.position2DDestinationEventHandles, handle) +} + +func (slf *Moving2D) OnPosition2DDestinationEvent(entity game.Moving2DEntity) { + for _, handle := range slf.position2DDestinationEventHandles { + handle(slf, entity) + } +} + type moving2DTarget struct { game.Moving2DEntity x, y float64 lastMoveTime int64 } -func (slf *Moving2D) MoveTo(entity game.Moving2DEntity, x float64, y float64) { +func (slf *Moving2D) Release() { slf.rw.Lock() - slf.entities[entity.GetGuid()] = &moving2DTarget{ - Moving2DEntity: entity, - x: x, - y: y, - lastMoveTime: time.Now().UnixMilli(), - } - slf.rw.Unlock() -} - -type Moving2D struct { - rw sync.RWMutex - entities map[int64]*moving2DTarget + defer slf.rw.Unlock() + slf.close = true + close(slf.event) } func (slf *Moving2D) handle() { for { slf.rw.Lock() + if slf.close { + slf.rw.Unlock() + return + } for guid, entity := range slf.entities { + entity := entity x, y := entity.GetPosition() angle := g2d.CalcAngle(x, y, entity.x, entity.y) moveTime := time.Now().UnixMilli() interval := float64(moveTime - entity.lastMoveTime) - distance := interval * entity.GetSpeed() - nx, ny := g2d.CalculateNewCoordinate(x, y, angle, distance) - if g2d.CalcDistance(nx, ny, entity.x, entity.y) <= distance { + distance := g2d.CalcDistance(x, y, entity.x, entity.y) + moveDistance := interval * entity.GetSpeed() / slf.timeUnit + if moveDistance > distance || (x == entity.x && y == entity.y) { entity.SetPosition(entity.x, entity.y) delete(slf.entities, guid) + slf.event <- func() { + slf.OnPosition2DDestinationEvent(entity) + } return + } else { + nx, ny := g2d.CalculateNewCoordinate(x, y, angle, distance) + entity.SetPosition(nx, ny) + entity.lastMoveTime = moveTime + slf.event <- func() { + slf.OnPosition2DChangeEvent(entity, x, y) + } } - entity.SetPosition(nx, ny) - entity.lastMoveTime = moveTime } if len(slf.entities) == 0 { slf.rw.Unlock() - time.Sleep(100 * time.Millisecond) + time.Sleep(slf.idle) } else { slf.rw.Unlock() } diff --git a/game/builtin/moving2d_options.go b/game/builtin/moving2d_options.go new file mode 100644 index 0000000..e4bf16d --- /dev/null +++ b/game/builtin/moving2d_options.go @@ -0,0 +1,27 @@ +package builtin + +import ( + "errors" + "time" +) + +type Moving2DOption func(moving *Moving2D) + +// WithMoving2DTimeUnit 通过特定时间单位创建 +// - 默认单位为1毫秒,最小单位也为1毫秒 +func WithMoving2DTimeUnit(duration time.Duration) Moving2DOption { + return func(moving *Moving2D) { + if duration < time.Millisecond { + panic(errors.New("time unit milliseconds minimum")) + } + moving.timeUnit = float64(duration) + } +} + +// WithMoving2DIdleWaitTime 通过特定的空闲等待时间创建 +// - 默认情况下在没有新的移动计划时将限制 100 毫秒 +func WithMoving2DIdleWaitTime(duration time.Duration) Moving2DOption { + return func(moving *Moving2D) { + moving.idle = duration + } +} diff --git a/game/moving2d.go b/game/moving2d.go index b7da67a..6b00ea8 100644 --- a/game/moving2d.go +++ b/game/moving2d.go @@ -2,5 +2,21 @@ package game // Moving2D 2D移动功能接口定义 type Moving2D interface { + // MoveTo 设置对象移动至特定位置 MoveTo(entity Moving2DEntity, x float64, y float64) + // StopMove 终止特定对象的移动 + StopMove(guid int64) + + // RegPosition2DChangeEvent 对象位置改变时将立即执行被注册的事件处理函数 + RegPosition2DChangeEvent(handle Position2DChangeEventHandle) + OnPosition2DChangeEvent(entity Moving2DEntity, oldX, oldY float64) + + // RegPosition2DDestinationEvent 对象抵达终点时将立即执行被注册的事件处理函数 + RegPosition2DDestinationEvent(handle Position2DDestinationEventHandle) + OnPosition2DDestinationEvent(entity Moving2DEntity) } + +type ( + Position2DChangeEventHandle func(moving Moving2D, entity Moving2DEntity, oldX, oldY float64) + Position2DDestinationEventHandle func(moving Moving2D, entity Moving2DEntity) +)