From db2bc0e3cdaa6c3a43aac379907b49000fdb39c5 Mon Sep 17 00:00:00 2001 From: genxium Date: Thu, 15 Dec 2022 10:03:48 +0800 Subject: [PATCH] Updated barrier boundaries and player collider paddings to feature almost-all-integer coordinates. --- battle_srv/models/room.go | 24 ++- collider_visualizer/worldColliderDisplay.go | 17 +- dnmshared/geometry.go | 22 +- dnmshared/resolv_helper.go | 48 +++-- frontend/assets/resources/map/dungeon/map.tmx | 200 ++++-------------- .../prefabs/ControlledCharacter.prefab | 4 +- frontend/assets/scenes/login.fire | 2 +- frontend/assets/scenes/offline_map_1.fire | 2 +- frontend/assets/scripts/Map.js | 55 +++-- frontend/assets/scripts/OfflineMap.js | 8 +- 10 files changed, 146 insertions(+), 236 deletions(-) diff --git a/battle_srv/models/room.go b/battle_srv/models/room.go index c7b3353..1dc4db2 100644 --- a/battle_srv/models/room.go +++ b/battle_srv/models/room.go @@ -775,7 +775,7 @@ func (pR *Room) OnDismissed() { }, HitboxOffset: float64(12.0), // should be about the radius of the PlayerCollider HitboxSize: &Vec2D{ - X: float64(23.0), + X: float64(24.0), Y: float64(32.0), }, @@ -789,9 +789,9 @@ func (pR *Room) OnDismissed() { pR.SnapIntoPlatformOverlap = float64(0.1) pR.SnapIntoPlatformThreshold = float64(0.5) - pR.JumpingInitVelY = int32(float64(6) * pR.WorldToVirtualGridRatio) + pR.JumpingInitVelY = int32(float64(7) * pR.WorldToVirtualGridRatio) pR.GravityX = 0 - pR.GravityY = -(4*pR.JumpingInitVelY + (pR.ServerFps - 1)) / pR.ServerFps // i.e. -Math.ceil(4*jumpingInitVelY / serverFps) + pR.GravityY = -int32(float64(0.5)*pR.WorldToVirtualGridRatio) // makes all "playerCollider.Y" a multiple of 0.5 in all cases pR.ChooseStage() pR.EffectivePlayerCount = 0 @@ -1396,7 +1396,8 @@ func (pR *Room) applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputF // Reset playerCollider position from the "virtual grid position" newVx, newVy := currPlayerDownsync.VirtualGridX+currPlayerDownsync.VelX, currPlayerDownsync.VirtualGridY+currPlayerDownsync.VelY - playerCollider.X, playerCollider.Y = VirtualGridToPolygonColliderAnchorPos(newVx, newVy, player.ColliderRadius, player.ColliderRadius, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY, pR.VirtualGridToWorldRatio) + colliderWidth, colliderHeight := player.ColliderRadius*2, player.ColliderRadius*4 + playerCollider.X, playerCollider.Y = VirtualGridToPolygonColliderBLPos(newVx, newVy, colliderWidth*0.5, colliderHeight*0.5, pR.SnapIntoPlatformOverlap, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY, pR.VirtualGridToWorldRatio) // Update in the collision system playerCollider.Update() @@ -1423,7 +1424,7 @@ func (pR *Room) applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputF offenderWx, offenderWy := VirtualGridToWorldPos(offender.VirtualGridX, offender.VirtualGridY, pR.VirtualGridToWorldRatio) bulletWx, bulletWy := offenderWx+xfac*meleeBullet.HitboxOffset, offenderWy - newBulletCollider := GenerateRectCollider(bulletWx, bulletWy, meleeBullet.HitboxSize.X, meleeBullet.HitboxSize.Y, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY, "MeleeBullet") + newBulletCollider := GenerateRectCollider(bulletWx, bulletWy, meleeBullet.HitboxSize.X, meleeBullet.HitboxSize.Y, 0, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY, "MeleeBullet") newBulletCollider.Data = meleeBullet pR.Space.Add(newBulletCollider) collisionSysMap[collisionBulletIndex] = newBulletCollider @@ -1601,17 +1602,18 @@ func (pR *Room) applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputF playerShape := playerCollider.Shape.(*resolv.ConvexPolygon) // Update "virtual grid position" currPlayerDownsync, thatPlayerInNextFrame := currRenderFrame.Players[playerId], nextRenderFramePlayers[playerId] - thatPlayerInNextFrame.VirtualGridX, thatPlayerInNextFrame.VirtualGridY = PolygonColliderAnchorToVirtualGridPos(playerCollider.X-effPushbacks[joinIndex-1].X, playerCollider.Y-effPushbacks[joinIndex-1].Y, player.ColliderRadius, player.ColliderRadius, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY, pR.WorldToVirtualGridRatio) + colliderWidth, colliderHeight := player.ColliderRadius*2, player.ColliderRadius*4 + thatPlayerInNextFrame.VirtualGridX, thatPlayerInNextFrame.VirtualGridY = PolygonColliderBLToVirtualGridPos(playerCollider.X-effPushbacks[joinIndex-1].X, playerCollider.Y-effPushbacks[joinIndex-1].Y, colliderWidth*0.5, colliderHeight*0.5, pR.SnapIntoPlatformOverlap, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY, pR.WorldToVirtualGridRatio) if 1 == thatPlayerInNextFrame.JoinIndex { if thatPlayerInNextFrame.InAir && (0 != thatPlayerInNextFrame.VelY) { - Logger.Info(fmt.Sprintf("playerId=%d, joinIndex=%d inAir trajectory: {nextRenderFrame.id: %d, nextVirtualX: %d, nextVirtualY: %d, nextVelX: %d, nextVelX: %d}, with playerColliderPos={%.3f, %.3f}, effPushback={%.3f, %.3f}", playerId, joinIndex, currRenderFrame.Id+1, thatPlayerInNextFrame.VirtualGridX, thatPlayerInNextFrame.VirtualGridY, thatPlayerInNextFrame.VelX, thatPlayerInNextFrame.VelY, playerShape.X-pR.collisionSpaceOffsetX, playerShape.Y-pR.collisionSpaceOffsetY, effPushbacks[joinIndex-1].X, effPushbacks[joinIndex-1].Y)) + Logger.Info(fmt.Sprintf("playerId=%d, joinIndex=%d inAir trajectory: {nextRenderFrame.id: %d, nextVirtualX: %d, nextVirtualY: %d, nextVelX: %d, nextVelY: %d}, with playerColliderPos={%.3f, %.3f}, effPushback={%.3f, %.3f}", playerId, joinIndex, currRenderFrame.Id+1, thatPlayerInNextFrame.VirtualGridX, thatPlayerInNextFrame.VirtualGridY, thatPlayerInNextFrame.VelX, thatPlayerInNextFrame.VelY, playerShape.X-pR.collisionSpaceOffsetX, playerShape.Y-pR.collisionSpaceOffsetY, effPushbacks[joinIndex-1].X, effPushbacks[joinIndex-1].Y)) } if currPlayerDownsync.InAir && !thatPlayerInNextFrame.InAir { - Logger.Warn(fmt.Sprintf("playerId=%d, joinIndex=%d fallStopping#2 at {nextRenderFrame.id: %d, nextVirtualX: %d, nextVirtualY: %d, nextVelX: %d, nextVelX: %d}, with playerColliderPos={%.3f, %.3f}, effPushback={%.3f, %.3f}", playerId, joinIndex, currRenderFrame.Id+1, thatPlayerInNextFrame.VirtualGridX, thatPlayerInNextFrame.VirtualGridY, thatPlayerInNextFrame.VelX, thatPlayerInNextFrame.VelY, playerShape.X-pR.collisionSpaceOffsetX, playerShape.Y-pR.collisionSpaceOffsetY, effPushbacks[joinIndex-1].X, effPushbacks[joinIndex-1].Y)) + Logger.Warn(fmt.Sprintf("playerId=%d, joinIndex=%d fallStopping#2 at {nextRenderFrame.id: %d, nextVirtualX: %d, nextVirtualY: %d, nextVelX: %d, nextVelY: %d}, with playerColliderPos={%.3f, %.3f}, effPushback={%.3f, %.3f}", playerId, joinIndex, currRenderFrame.Id+1, thatPlayerInNextFrame.VirtualGridX, thatPlayerInNextFrame.VirtualGridY, thatPlayerInNextFrame.VelX, thatPlayerInNextFrame.VelY, playerShape.X-pR.collisionSpaceOffsetX, playerShape.Y-pR.collisionSpaceOffsetY, effPushbacks[joinIndex-1].X, effPushbacks[joinIndex-1].Y)) } if !currPlayerDownsync.InAir && thatPlayerInNextFrame.InAir { - Logger.Warn(fmt.Sprintf("playerId=%d, joinIndex=%d took off at {nextRenderFrame.id: %d, nextVirtualX: %d, nextVirtualY: %d, nextVelX: %d, nextVelX: %d}, with playerColliderPos={%.3f, %.3f}, effPushback={%.3f, %.3f}", playerId, joinIndex, currRenderFrame.Id+1, thatPlayerInNextFrame.VirtualGridX, thatPlayerInNextFrame.VirtualGridY, thatPlayerInNextFrame.VelX, thatPlayerInNextFrame.VelY, playerShape.X-pR.collisionSpaceOffsetX, playerShape.Y-pR.collisionSpaceOffsetY, effPushbacks[joinIndex-1].X, effPushbacks[joinIndex-1].Y)) + Logger.Warn(fmt.Sprintf("playerId=%d, joinIndex=%d took off at {nextRenderFrame.id: %d, nextVirtualX: %d, nextVirtualY: %d, nextVelX: %d, nextVelY: %d}, with playerColliderPos={%.3f, %.3f}, effPushback={%.3f, %.3f}", playerId, joinIndex, currRenderFrame.Id+1, thatPlayerInNextFrame.VirtualGridX, thatPlayerInNextFrame.VirtualGridY, thatPlayerInNextFrame.VelX, thatPlayerInNextFrame.VelY, playerShape.X-pR.collisionSpaceOffsetX, playerShape.Y-pR.collisionSpaceOffsetY, effPushbacks[joinIndex-1].X, effPushbacks[joinIndex-1].Y)) } } } @@ -1647,8 +1649,8 @@ func (pR *Room) refreshColliders(spaceW, spaceH int32) { pR.Space = resolv.NewSpace(int(spaceW), int(spaceH), minStep, minStep) // allocate a new collision space everytime after a battle is settled for _, player := range pR.Players { wx, wy := VirtualGridToWorldPos(player.VirtualGridX, player.VirtualGridY, pR.VirtualGridToWorldRatio) - colliderWidth, colliderHeight := player.ColliderRadius*2, player.ColliderRadius*4 - playerCollider := GenerateRectCollider(wx, wy, colliderWidth, colliderHeight, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY, "Player") + colliderWidth, colliderHeight := player.ColliderRadius*2, player.ColliderRadius*4 + playerCollider := GenerateRectCollider(wx, wy, colliderWidth, colliderHeight, pR.SnapIntoPlatformOverlap, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY, "Player") // the coords of all barrier boundaries are multiples of tileWidth(i.e. 16), by adding snapping y-padding when "landedOnGravityPushback" all "playerCollider.Y" would be a multiple of 1.0 playerCollider.Data = player pR.Space.Add(playerCollider) // Keep track of the collider in "pR.CollisionSysMap" diff --git a/collider_visualizer/worldColliderDisplay.go b/collider_visualizer/worldColliderDisplay.go index 8a9e566..537bd47 100644 --- a/collider_visualizer/worldColliderDisplay.go +++ b/collider_visualizer/worldColliderDisplay.go @@ -40,10 +40,12 @@ func NewWorldColliderDisplay(game *Game, stageDiscreteW, stageDiscreteH, stageTi minStep := (int(float64(playerDefaultSpeed)*virtualGridToWorldRatio) << 2) playerColliderRadius := float64(12) playerColliders := make([]*resolv.Object, len(playerPosList.Eles)) + snapIntoPlatformOverlap := float64(0.1) space := resolv.NewSpace(int(spaceW), int(spaceH), minStep, minStep) for i, playerPos := range playerPosList.Eles { - playerCollider := GenerateRectCollider(playerPos.X, playerPos.Y, playerColliderRadius*2, playerColliderRadius*4, spaceOffsetX, spaceOffsetY, "Player") // [WARNING] Deliberately not using a circle because "resolv v0.5.1" doesn't yet align circle center with space cell center, regardless of the "specified within-object offset" - Logger.Info(fmt.Sprintf("Player Collider#%d: player world pos =(%.2f, %.2f), shape=%v", i, playerPos.X, playerPos.Y, ConvexPolygonStr(playerCollider.Shape.(*resolv.ConvexPolygon)))) + colliderWidth, colliderHeight := playerColliderRadius*2, playerColliderRadius*4 + playerCollider := GenerateRectCollider(playerPos.X, playerPos.Y, colliderWidth, colliderHeight, snapIntoPlatformOverlap, spaceOffsetX, spaceOffsetY, "Player") // [WARNING] Deliberately not using a circle because "resolv v0.5.1" doesn't yet align circle center with space cell center, regardless of the "specified within-object offset" + Logger.Info(fmt.Sprintf("Player Collider#%d: player world pos=(%.2f, %.2f), shape=%v", i, playerPos.X, playerPos.Y, ConvexPolygonStr(playerCollider.Shape.(*resolv.ConvexPolygon)))) playerColliders[i] = playerCollider space.Add(playerCollider) } @@ -58,12 +60,13 @@ func NewWorldColliderDisplay(game *Game, stageDiscreteW, stageDiscreteH, stageTi world.Space = space - moveToCollide := true + moveToCollide := false if moveToCollide { effPushback := Vec2D{X: float64(0), Y: float64(0)} toTestPlayerCollider := playerColliders[0] - newVx, newVy := int32(43900), int32(-451350) - toTestPlayerCollider.X, toTestPlayerCollider.Y = VirtualGridToPolygonColliderAnchorPos(newVx, newVy, playerColliderRadius, playerColliderRadius, spaceOffsetX, spaceOffsetY, virtualGridToWorldRatio) + newVx, newVy := int32(27999), int32(-420270) + colliderWidth, colliderHeight := playerColliderRadius*2, playerColliderRadius*4 + toTestPlayerCollider.X, toTestPlayerCollider.Y = VirtualGridToPolygonColliderTLPos(newVx, newVy, colliderWidth, colliderHeight, spaceOffsetX, spaceOffsetY, virtualGridToWorldRatio) Logger.Info(fmt.Sprintf("Checking collision for playerShape=%v", ConvexPolygonStr(toTestPlayerCollider.Shape.(*resolv.ConvexPolygon)))) @@ -117,7 +120,7 @@ func NewWorldColliderDisplay(game *Game, stageDiscreteW, stageDiscreteH, stageTi offenderWx, offenderWy := playerPosList.Eles[0].X, playerPosList.Eles[0].Y bulletWx, bulletWy := offenderWx+xfac*meleeBullet.HitboxOffset, offenderWy - newBulletCollider := GenerateRectCollider(bulletWx, bulletWy, meleeBullet.HitboxSize.X, meleeBullet.HitboxSize.Y, spaceOffsetX, spaceOffsetY, "MeleeBullet") + newBulletCollider := GenerateRectCollider(bulletWx, bulletWy, meleeBullet.HitboxSize.X, meleeBullet.HitboxSize.Y, 0, spaceOffsetX, spaceOffsetY, "MeleeBullet") space.Add(newBulletCollider) bulletShape := newBulletCollider.Shape.(*resolv.ConvexPolygon) Logger.Warn(fmt.Sprintf("bullet ->: Added bullet collider to space: a=%v", ConvexPolygonStr(bulletShape))) @@ -140,7 +143,7 @@ func NewWorldColliderDisplay(game *Game, stageDiscreteW, stageDiscreteH, stageTi offenderWx, offenderWy := playerPosList.Eles[1].X, playerPosList.Eles[1].Y bulletWx, bulletWy := offenderWx+xfac*meleeBullet.HitboxOffset, offenderWy - newBulletCollider := GenerateRectCollider(bulletWx, bulletWy, meleeBullet.HitboxSize.X, meleeBullet.HitboxSize.Y, spaceOffsetX, spaceOffsetY, "MeleeBullet") + newBulletCollider := GenerateRectCollider(bulletWx, bulletWy, meleeBullet.HitboxSize.X, meleeBullet.HitboxSize.Y, 0, spaceOffsetX, spaceOffsetY, "MeleeBullet") space.Add(newBulletCollider) bulletShape := newBulletCollider.Shape.(*resolv.ConvexPolygon) Logger.Warn(fmt.Sprintf("bullet <-: Added bullet collider to space: a=%v", ConvexPolygonStr(bulletShape))) diff --git a/dnmshared/geometry.go b/dnmshared/geometry.go index a084208..1b8e4c8 100644 --- a/dnmshared/geometry.go +++ b/dnmshared/geometry.go @@ -10,33 +10,33 @@ func NormVec2D(dx, dy float64) Vec2D { } func AlignPolygon2DToBoundingBox(input *Polygon2D) *Polygon2D { - // Transform again to put "anchor" at the top-left point of the bounding box for "resolv" - boundingBoxTL := &Vec2D{ + // Transform again to put "anchor" at the "bottom-left point (w.r.t. world space)" of the bounding box for "resolv" + boundingBoxBL := &Vec2D{ X: math.MaxFloat64, Y: math.MaxFloat64, } for _, p := range input.Points { - if p.X < boundingBoxTL.X { - boundingBoxTL.X = p.X + if p.X < boundingBoxBL.X { + boundingBoxBL.X = p.X } - if p.Y < boundingBoxTL.Y { - boundingBoxTL.Y = p.Y + if p.Y < boundingBoxBL.Y { + boundingBoxBL.Y = p.Y } } - // Now "input.Anchor" should move to "input.Anchor+boundingBoxTL", thus "boundingBoxTL" is also the value of the negative diff for all "input.Points" + // Now "input.Anchor" should move to "input.Anchor+boundingBoxBL", thus "boundingBoxBL" is also the value of the negative diff for all "input.Points" output := &Polygon2D{ Anchor: &Vec2D{ - X: input.Anchor.X + boundingBoxTL.X, - Y: input.Anchor.Y + boundingBoxTL.Y, + X: input.Anchor.X + boundingBoxBL.X, + Y: input.Anchor.Y + boundingBoxBL.Y, }, Points: make([]*Vec2D, len(input.Points)), } for i, p := range input.Points { output.Points[i] = &Vec2D{ - X: p.X - boundingBoxTL.X, - Y: p.Y - boundingBoxTL.Y, + X: p.X - boundingBoxBL.X, + Y: p.Y - boundingBoxBL.Y, } } diff --git a/dnmshared/resolv_helper.go b/dnmshared/resolv_helper.go index 67d2258..2f3ca76 100644 --- a/dnmshared/resolv_helper.go +++ b/dnmshared/resolv_helper.go @@ -12,19 +12,19 @@ import ( func ConvexPolygonStr(body *resolv.ConvexPolygon) string { var s []string = make([]string, len(body.Points)) for i, p := range body.Points { - s[i] = fmt.Sprintf("[%.3f, %.3f]", p[0]+body.X, p[1]+body.Y) + s[i] = fmt.Sprintf("[%.2f, %.2f]", p[0]+body.X, p[1]+body.Y) } return fmt.Sprintf("{\n%s\n}", strings.Join(s, ",\n")) } -func GenerateRectCollider(origX, origY, w, h, spaceOffsetX, spaceOffsetY float64, tag string) *resolv.Object { - cx, cy := WorldToPolygonColliderAnchorPos(origX, origY, w*0.5, h*0.5, spaceOffsetX, spaceOffsetY) - return GenerateRectColliderInCollisionSpace(cx, cy, w, h, tag) +func GenerateRectCollider(wx, wy, w, h, bottomPadding, spaceOffsetX, spaceOffsetY float64, tag string) *resolv.Object { + blX, blY := WorldToPolygonColliderBLPos(wx, wy, w*0.5, h*0.5, bottomPadding, spaceOffsetX, spaceOffsetY) + return generateRectColliderInCollisionSpace(blX, blY, w, h+bottomPadding, tag) } -func GenerateRectColliderInCollisionSpace(cx, cy, w, h float64, tag string) *resolv.Object { - collider := resolv.NewObject(cx, cy, w, h, tag) +func generateRectColliderInCollisionSpace(blX, blY, w, h float64, tag string) *resolv.Object { + collider := resolv.NewObject(blX, blY, w, h, tag) // Unlike its frontend counter part, the position of a "resolv.Object" must be specified by "bottom-left point" because "w" and "h" must be positive, see "resolv.Object.BoundsToSpace" for details shape := resolv.NewRectangle(0, 0, w, h) collider.SetShape(shape) return collider @@ -246,20 +246,38 @@ func VirtualGridToWorldPos(vx, vy int32, virtualGridToWorldRatio float64) (float return wx, wy } -func WorldToPolygonColliderAnchorPos(wx, wy, halfBoundingW, halfBoundingH, collisionSpaceOffsetX, collisionSpaceOffsetY float64) (float64, float64) { - return wx - halfBoundingW + collisionSpaceOffsetX, wy - halfBoundingH + collisionSpaceOffsetY +func WorldToPolygonColliderBLPos(wx, wy, halfBoundingW, halfBoundingH, bottomPadding, collisionSpaceOffsetX, collisionSpaceOffsetY float64) (float64, float64) { + return wx - halfBoundingW + collisionSpaceOffsetX, wy - halfBoundingH - bottomPadding + collisionSpaceOffsetY } -func PolygonColliderAnchorToWorldPos(cx, cy, halfBoundingW, halfBoundingH, collisionSpaceOffsetX, collisionSpaceOffsetY float64) (float64, float64) { - return cx + halfBoundingW - collisionSpaceOffsetX, cy + halfBoundingH - collisionSpaceOffsetY +func WorldToPolygonColliderTLPos(wx, wy, halfBoundingW, halfBoundingH, collisionSpaceOffsetX, collisionSpaceOffsetY float64) (float64, float64) { + return wx - halfBoundingW + collisionSpaceOffsetX, wy + halfBoundingH + collisionSpaceOffsetY } -func PolygonColliderAnchorToVirtualGridPos(cx, cy, halfBoundingW, halfBoundingH, collisionSpaceOffsetX, collisionSpaceOffsetY float64, worldToVirtualGridRatio float64) (int32, int32) { - wx, wy := PolygonColliderAnchorToWorldPos(cx, cy, halfBoundingW, halfBoundingH, collisionSpaceOffsetX, collisionSpaceOffsetY) +func PolygonColliderBLToWorldPos(cx, cy, halfBoundingW, halfBoundingH, bottomPadding, collisionSpaceOffsetX, collisionSpaceOffsetY float64) (float64, float64) { + return cx + halfBoundingW - collisionSpaceOffsetX, cy + halfBoundingH + bottomPadding - collisionSpaceOffsetY +} + +func PolygonColliderTLToWorldPos(cx, cy, halfBoundingW, halfBoundingH, collisionSpaceOffsetX, collisionSpaceOffsetY float64) (float64, float64) { + return cx + halfBoundingW - collisionSpaceOffsetX, cy - halfBoundingH - collisionSpaceOffsetY +} + +func PolygonColliderBLToVirtualGridPos(cx, cy, halfBoundingW, halfBoundingH, bottomPadding, collisionSpaceOffsetX, collisionSpaceOffsetY float64, worldToVirtualGridRatio float64) (int32, int32) { + wx, wy := PolygonColliderBLToWorldPos(cx, cy, halfBoundingW, halfBoundingH, bottomPadding, collisionSpaceOffsetX, collisionSpaceOffsetY) return WorldToVirtualGridPos(wx, wy, worldToVirtualGridRatio) } -func VirtualGridToPolygonColliderAnchorPos(vx, vy int32, halfBoundingW, halfBoundingH, collisionSpaceOffsetX, collisionSpaceOffsetY float64, virtualGridToWorldRatio float64) (float64, float64) { - wx, wy := VirtualGridToWorldPos(vx, vy, virtualGridToWorldRatio) - return WorldToPolygonColliderAnchorPos(wx, wy, halfBoundingW, halfBoundingH, collisionSpaceOffsetX, collisionSpaceOffsetY) +func PolygonColliderTLToVirtualGridPos(cx, cy, halfBoundingW, halfBoundingH, collisionSpaceOffsetX, collisionSpaceOffsetY float64, worldToVirtualGridRatio float64) (int32, int32) { + wx, wy := PolygonColliderTLToWorldPos(cx, cy, halfBoundingW, halfBoundingH, collisionSpaceOffsetX, collisionSpaceOffsetY) + return WorldToVirtualGridPos(wx, wy, worldToVirtualGridRatio) +} + +func VirtualGridToPolygonColliderBLPos(vx, vy int32, halfBoundingW, halfBoundingH, bottomPadding, collisionSpaceOffsetX, collisionSpaceOffsetY float64, virtualGridToWorldRatio float64) (float64, float64) { + wx, wy := VirtualGridToWorldPos(vx, vy, virtualGridToWorldRatio) + return WorldToPolygonColliderBLPos(wx, wy, halfBoundingW, halfBoundingH, bottomPadding, collisionSpaceOffsetX, collisionSpaceOffsetY) +} + +func VirtualGridToPolygonColliderTLPos(vx, vy int32, halfBoundingW, halfBoundingH, collisionSpaceOffsetX, collisionSpaceOffsetY float64, virtualGridToWorldRatio float64) (float64, float64) { + wx, wy := VirtualGridToWorldPos(vx, vy, virtualGridToWorldRatio) + return WorldToPolygonColliderTLPos(wx, wy, halfBoundingW, halfBoundingH, collisionSpaceOffsetX, collisionSpaceOffsetY) } diff --git a/frontend/assets/resources/map/dungeon/map.tmx b/frontend/assets/resources/map/dungeon/map.tmx index 94ed106..8a35325 100644 --- a/frontend/assets/resources/map/dungeon/map.tmx +++ b/frontend/assets/resources/map/dungeon/map.tmx @@ -1,18 +1,18 @@ - + - eJzt3DFv00AYgOEoFUuHigqBVGYGJP4EI+qAOsHG1H/CzsjIRP8njhRLwUpi52L7O/t7hmdpOiT33tmXtJebzWZzAwAAAAAAAAAAAAAAAAAABW73op8H83k+0PZ/Tii6Q2T/7V7b/0fj8YTtCun/f/9T9F+fw/6PPf1vK2il/3T9j9F/3Xav/e3esbHpXhOiW+k/b/8M9Nc/ukNk/1cFopvpr7/++usf078G+uuvv/7666//svr/Gvh793v6r6v/38afE4/t/v780viqf3j/0jFtP1P8eSX99ddf/91zeejQf/39Hzr9rX/99Y/v/7rHmP0P6R/bv6/7sf6/G98u1Pa3/6un/9D2Txv7/6UY2n9o+63+i9LX/1jjT427E+27/bueLqR/XP9j7e8OdLtP0f9d44P+1fQ/d90/7D82/evtX7JHvJT+8/cfMge6Yzlm3z5j9t9W0KLG/mON8Tkl7fVfT/9o+ueeA/rrr7/+0S2i+kePf7Ts7/+ixz+a/rnpn5v+uemfm/656Z+b/rnpn5v+uemfm/656Z+b/rnpn5v+uelf7k1HdEv9l93/vscU/6Omfz39rf+6+3d7679s9n/6zz3ep+7tkf23FbSouf/3ve71vv159BrWf9r+5+75+i/XkP7nzgb1vV+LvK5f0j+6Q639284fr1D7GTL9p1X7OcLs/ac+81n7WdLs/eda/63aPifSf97+7Ry4lP7z9R9zP1/y3T7617P+r50Hpd/vNNd9Qv9hSufB+8L+L40v+lfTv1TT/3Np/9aU8yB7f+I7AAAAAAAAAAAAAAAAAPP7B3wkkY4= + eJzt3DFq3EAUgOFlTRoXJiEkkNQpAr5EyuDKVdKl8k1yh9wg94wMK1DEaqXVjvRGel/xgXFh7PnfSBqx+O5wONwBAAAAAAAAAAAAAAAAAMAM9yfRvwfreelo+78kFN0hsv/xpO3/q/E04LhD+v/ff4j++9Pt/zTS/76CVvov1/8c/fft9W//cHJubfrXhOhW+q/bPwP99Y/uENn//QzRzfSP7b+nGdBf/+gOW+xfE/1z9rf/c/cvMQP6b5f9r7/+efuXmAH9y69t+07x942W3vv666//Nvp/OtE/T/9u7+7X+uuvf1z/tyNK9u/SP7b/WPdz/f80flyp7e/5r57+U9s/Hzz/b8XU/lPbH/XflLH+5xo/Nh4G2vf79z1fSf+4/ufaP3T0uy/R/2Pji/7V9L903e/2L03/evvPeUa8lv7r958yA/21LNl3TMn+xwpa1Ni/1BpfMqe9/vvpf82MLPGz9d/GDCxF/9wzoL/+mftHr3+07Oe/6PWPpn9u+uemf27656Z/bvrnpn9u+uemf27656Z/bvrnpn9u+uemf276z7eHzwnpX0//dyPeDNB/H/3t/7r77/Fzovrnpv+6hu7tkf2PFbSouf/Pk/71vv1+9B7Wf9n+l+75+m/XlP5DZ65XY+e1yOv6Nf2jO9Tav+389Qa3ns/1j93/tyrxjkb/5fqXfp821L/WGcjef63936rtPZH+6/ZvZyDq/73oP96/5PP8pfPDEP3r2f+3zsGc/mveJ/SfZu4cfJ7Z/2/ju/7V9J+r6f9tbv/WknOQvT/xHQAAAAAAAAAAAAAAAID1/QPUuKyX - + - + @@ -20,151 +20,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -174,52 +30,52 @@ - + - + - + - + - + - + - + - + - + - + @@ -234,22 +90,42 @@ - + - + - + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/assets/resources/prefabs/ControlledCharacter.prefab b/frontend/assets/resources/prefabs/ControlledCharacter.prefab index 31216f6..de1bae9 100644 --- a/frontend/assets/resources/prefabs/ControlledCharacter.prefab +++ b/frontend/assets/resources/prefabs/ControlledCharacter.prefab @@ -97,7 +97,7 @@ "__id__": 1 }, "_children": [], - "_active": false, + "_active": true, "_components": [ { "__id__": 3 @@ -129,7 +129,7 @@ "ctor": "Float64Array", "array": [ 0, - 20, + 0, 0, 0, 0, diff --git a/frontend/assets/scenes/login.fire b/frontend/assets/scenes/login.fire index e5bc0e5..afb1111 100644 --- a/frontend/assets/scenes/login.fire +++ b/frontend/assets/scenes/login.fire @@ -440,7 +440,7 @@ "array": [ 0, 0, - 216.19964242526865, + 209.7905580006813, 0, 0, 0, diff --git a/frontend/assets/scenes/offline_map_1.fire b/frontend/assets/scenes/offline_map_1.fire index 840a0db..0629f7a 100644 --- a/frontend/assets/scenes/offline_map_1.fire +++ b/frontend/assets/scenes/offline_map_1.fire @@ -454,7 +454,7 @@ "array": [ 0, 0, - 215.64032554232523, + 209.7905580006813, 0, 0, 0, diff --git a/frontend/assets/scripts/Map.js b/frontend/assets/scripts/Map.js index 8c50693..92c4285 100644 --- a/frontend/assets/scripts/Map.js +++ b/frontend/assets/scripts/Map.js @@ -786,11 +786,11 @@ cc.Class({ playerScriptIns.mapNode = self.node; const colliderWidth = playerDownsyncInfo.colliderRadius * 2, colliderHeight = playerDownsyncInfo.colliderRadius * 4; - const [x0, y0] = self.virtualGridToPolygonColliderAnchorPos(vx, vy, colliderWidth, colliderHeight), - pts = [[0, 0], [colliderWidth, 0], [colliderWidth, colliderHeight], [0, colliderHeight]]; + const cpos = self.virtualGridToPolygonColliderTLPos(vx, vy, colliderWidth*0.5, colliderHeight*0.5); // the top-left corner is kept having integer coords + const pts = [[0, 0], [colliderWidth, 0], [colliderWidth, -colliderHeight-self.snapIntoPlatformOverlap], [0, -colliderHeight-self.snapIntoPlatformOverlap]]; // [WARNING] The animNode "anchor & offset" are tuned to fit in this collider by "ControlledCharacter prefab & AttackingCharacter.js"! - const newPlayerCollider = self.collisionSys.createPolygon(x0, y0, pts); + const newPlayerCollider = self.collisionSys.createPolygon(cpos[0], cpos[1], pts); const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex; newPlayerCollider.data = playerDownsyncInfo; self.collisionSysMap.set(collisionPlayerIndex, newPlayerCollider); @@ -1048,8 +1048,8 @@ cc.Class({ const [offenderWx, offenderWy] = self.virtualGridToWorldPos(offender.virtualGridX, offender.virtualGridY); const bulletWx = offenderWx + xfac * meleeBullet.hitboxOffset; const bulletWy = offenderWy + 0.5 * meleeBullet.hitboxSize.y; - const [bulletCx, bulletCy] = self.worldToPolygonColliderAnchorPos(bulletWx, bulletWy, meleeBullet.hitboxSize.x * 0.5, meleeBullet.hitboxSize.y * 0.5), - pts = [[0, 0], [meleeBullet.hitboxSize.x, 0], [meleeBullet.hitboxSize.x, meleeBullet.hitboxSize.y], [0, meleeBullet.hitboxSize.y]]; + const [bulletCx, bulletCy] = self.worldToPolygonColliderTLPos(bulletWx, bulletWy, meleeBullet.hitboxSize.x * 0.5, meleeBullet.hitboxSize.y * 0.5); + const pts = [[0, 0], [meleeBullet.hitboxSize.x, 0], [meleeBullet.hitboxSize.x, -meleeBullet.hitboxSize.y], [0, -meleeBullet.hitboxSize.y]]; g.moveTo(bulletCx, bulletCy); for (let j = 0; j < pts.length; j += 1) { @@ -1118,7 +1118,8 @@ cc.Class({ const joinIndex = parseInt(j) + 1; const playerRichInfo = self.playerRichInfoArr[j]; const playerId = playerRichInfo.id; - const [currPlayerDownsync, thatPlayerInNextFrame] = [currRenderFrame.players[playerId], nextRenderFramePlayers[playerId]]; + const currPlayerDownsync = currRenderFrame.players[playerId]; + const thatPlayerInNextFrame = nextRenderFramePlayers[playerId]; if (0 < thatPlayerInNextFrame.framesToRecover) { // No need to process inputs for this player, but there might be bullet pushbacks on this player continue; @@ -1184,11 +1185,15 @@ cc.Class({ const playerId = playerRichInfo.id; const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex; const playerCollider = collisionSysMap.get(collisionPlayerIndex); - const [currPlayerDownsync, thatPlayerInNextFrame] = [currRenderFrame.players[playerId], nextRenderFramePlayers[playerId]]; - + const currPlayerDownsync = currRenderFrame.players[playerId]; + const thatPlayerInNextFrame = nextRenderFramePlayers[playerId]; // Reset playerCollider position from the "virtual grid position" const [newVx, newVy] = [currPlayerDownsync.virtualGridX + currPlayerDownsync.velX, currPlayerDownsync.virtualGridY + currPlayerDownsync.velY]; - [playerCollider.x, playerCollider.y] = self.virtualGridToPolygonColliderAnchorPos(newVx, newVy, self.playerRichInfoArr[joinIndex - 1].colliderRadius, self.playerRichInfoArr[joinIndex - 1].colliderRadius); + const colliderWidth = self.playerRichInfoArr[joinIndex - 1].colliderRadius * 2, + colliderHeight = self.playerRichInfoArr[joinIndex - 1].colliderRadius * 4; + const newCpos = self.virtualGridToPolygonColliderTLPos(newVx, newVy, colliderWidth*0.5, colliderHeight*0.5); + playerCollider.x = newCpos[0]; + playerCollider.y = newCpos[1]; if (currPlayerDownsync.inAir) { thatPlayerInNextFrame.velX += self.gravityX; @@ -1218,8 +1223,8 @@ cc.Class({ const [offenderWx, offenderWy] = self.virtualGridToWorldPos(offender.virtualGridX, offender.virtualGridY); const bulletWx = offenderWx + xfac * meleeBullet.hitboxOffset; const bulletWy = offenderWy + 0.5 * meleeBullet.hitboxSize.y; - const [bulletCx, bulletCy] = self.worldToPolygonColliderAnchorPos(bulletWx, bulletWy, meleeBullet.hitboxSize.x * 0.5, meleeBullet.hitboxSize.y * 0.5), - pts = [[0, 0], [meleeBullet.hitboxSize.x, 0], [meleeBullet.hitboxSize.x, meleeBullet.hitboxSize.y], [0, meleeBullet.hitboxSize.y]]; + const [bulletCx, bulletCy] = self.worldToPolygonColliderTLPos(bulletWx, bulletWy, meleeBullet.hitboxSize.x * 0.5, meleeBullet.hitboxSize.y * 0.5), + pts = [[0, 0], [meleeBullet.hitboxSize.x, 0], [meleeBullet.hitboxSize.x, -meleeBullet.hitboxSize.y], [0, -meleeBullet.hitboxSize.y]]; const newBulletCollider = collisionSys.createPolygon(bulletCx, bulletCy, pts); newBulletCollider.data = meleeBullet; collisionSysMap.set(collisionBulletIndex, newBulletCollider); @@ -1241,7 +1246,8 @@ cc.Class({ const potentials = playerCollider.potentials(); hardPushbackNorms[joinIndex - 1] = self.calcHardPushbacksNorms(playerCollider, potentials, result, self.snapIntoPlatformOverlap, effPushbacks[joinIndex - 1]); - const [currPlayerDownsync, thatPlayerInNextFrame] = [currRenderFrame.players[playerId], nextRenderFramePlayers[playerId]]; + const currPlayerDownsync = currRenderFrame.players[playerId]; + const thatPlayerInNextFrame = nextRenderFramePlayers[playerId]; let fallStopping = false; let possiblyFallStoppedOnAnotherPlayer = false; for (const potential of potentials) { @@ -1370,8 +1376,13 @@ cc.Class({ const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex; const playerCollider = collisionSysMap.get(collisionPlayerIndex); // Update "virtual grid position" - const [currPlayerDownsync, thatPlayerInNextFrame] = [currRenderFrame.players[playerId], nextRenderFramePlayers[playerId]]; - [thatPlayerInNextFrame.virtualGridX, thatPlayerInNextFrame.virtualGridY] = self.polygonColliderAnchorToVirtualGridPos(playerCollider.x - effPushbacks[joinIndex - 1][0], playerCollider.y - effPushbacks[joinIndex - 1][1], self.playerRichInfoArr[j].colliderRadius, self.playerRichInfoArr[j].colliderRadius); + const currPlayerDownsync = currRenderFrame.players[playerId]; + const thatPlayerInNextFrame = nextRenderFramePlayers[playerId]; + const colliderWidth = self.playerRichInfoArr[joinIndex - 1].colliderRadius * 2, + colliderHeight = self.playerRichInfoArr[joinIndex - 1].colliderRadius * 4; + const newVpos = self.polygonColliderTLToVirtualGridPos(playerCollider.x - effPushbacks[joinIndex - 1][0], playerCollider.y - effPushbacks[joinIndex - 1][1], colliderWidth*0.5, colliderHeight*0.5); + thatPlayerInNextFrame.virtualGridX = newVpos[0]; + thatPlayerInNextFrame.virtualGridY = newVpos[1]; if (1 == thatPlayerInNextFrame.joinIndex) { if (thatPlayerInNextFrame.inAir && 0 != thatPlayerInNextFrame.velY) { @@ -1507,24 +1518,24 @@ cc.Class({ return [wx, wy]; }, - worldToPolygonColliderAnchorPos(wx, wy, halfBoundingW, halfBoundingH) { - return [wx - halfBoundingW, wy - halfBoundingH]; + worldToPolygonColliderTLPos(wx, wy, halfBoundingW, halfBoundingH) { + return [wx - halfBoundingW, wy + halfBoundingH]; }, - polygonColliderAnchorToWorldPos(cx, cy, halfBoundingW, halfBoundingH) { - return [cx + halfBoundingW, cy + halfBoundingH]; + polygonColliderTLToWorldPos(cx, cy, halfBoundingW, halfBoundingH) { + return [cx + halfBoundingW, cy - halfBoundingH]; }, - polygonColliderAnchorToVirtualGridPos(cx, cy, halfBoundingW, halfBoundingH) { + polygonColliderTLToVirtualGridPos(cx, cy, halfBoundingW, halfBoundingH) { const self = this; - const [wx, wy] = self.polygonColliderAnchorToWorldPos(cx, cy, halfBoundingW, halfBoundingH); + const [wx, wy] = self.polygonColliderTLToWorldPos(cx, cy, halfBoundingW, halfBoundingH); return self.worldToVirtualGridPos(wx, wy) }, - virtualGridToPolygonColliderAnchorPos(vx, vy, halfBoundingW, halfBoundingH) { + virtualGridToPolygonColliderTLPos(vx, vy, halfBoundingW, halfBoundingH) { const self = this; const [wx, wy] = self.virtualGridToWorldPos(vx, vy); - return self.worldToPolygonColliderAnchorPos(wx, wy, halfBoundingW, halfBoundingH) + return self.worldToPolygonColliderTLPos(wx, wy, halfBoundingW, halfBoundingH) }, calcHardPushbacksNorms(collider, potentials, result, snapIntoPlatformOverlap, effPushback) { diff --git a/frontend/assets/scripts/OfflineMap.js b/frontend/assets/scripts/OfflineMap.js index 5dcfb3e..61faa59 100644 --- a/frontend/assets/scripts/OfflineMap.js +++ b/frontend/assets/scripts/OfflineMap.js @@ -75,10 +75,10 @@ cc.Class({ Moreover, "snapIntoPlatformOverlap" should be small enough such that the walking "velX" or jumping initial "velY" can escape from it by 1 renderFrame (when jumping is triggered, the character is waived from snappig for 1 renderFrame). */ - self.snapIntoPlatformOverlap = 0.01; + self.snapIntoPlatformOverlap = 0.1; self.snapIntoPlatformThreshold = 0.5; // a platform must be "horizontal enough" for a character to "stand on" - self.jumpingInitVelY = 6 * self.worldToVirtualGridRatio; // unit: (virtual grid length/renderFrame) - [self.gravityX, self.gravityY] = [0, -Math.ceil(4 * self.jumpingInitVelY / self.serverFps)]; // unit: (virtual grid length/renderFrame^2) + self.jumpingInitVelY = 7 * self.worldToVirtualGridRatio; // unit: (virtual grid length/renderFrame) + [self.gravityX, self.gravityY] = [0, -0.5*self.worldToVirtualGridRatio]; // unit: (virtual grid length/renderFrame^2) const tiledMapIns = self.node.getComponent(cc.TiledMap); @@ -196,7 +196,7 @@ cc.Class({ } }); self.selfPlayerInfo = { - id: 11 + id: 10 }; self._initPlayerRichInfoDict(startRdf.players); self.onRoomDownsyncFrame(startRdf);