A temp dirty commit having mysterious left moving players.

This commit is contained in:
genxium 2023-02-17 12:26:07 +08:00
parent c6b98855af
commit 2b304eaa75
8 changed files with 478 additions and 450 deletions

View File

@ -518,7 +518,7 @@
"array": [ "array": [
0, 0,
0, 0,
216.50635094610968, 210.4441731196186,
0, 0,
0, 0,
0, 0,

View File

@ -380,6 +380,8 @@ cc.Class({
} }
self.pJumpedOrNotList = []; self.pJumpedOrNotList = [];
for (let i = 0; i < window.boundRoomCapacity; i++) self.pJumpedOrNotList.push(false); for (let i = 0; i < window.boundRoomCapacity; i++) self.pJumpedOrNotList.push(false);
self.dynamicRectangleColliders = gopkgs.NewDynamicRectangleColliders(64);
self.recentRenderCache = gopkgs.NewRingBufferJs(self.renderCacheSize); self.recentRenderCache = gopkgs.NewRingBufferJs(self.renderCacheSize);
self.recentInputCache = gopkgs.NewRingBufferJs((self.renderCacheSize >> 1) + 1); self.recentInputCache = gopkgs.NewRingBufferJs((self.renderCacheSize >> 1) + 1);
@ -1401,7 +1403,7 @@ othersForcedDownsyncRenderFrame=${JSON.stringify(othersForcedDownsyncRenderFrame
}; };
self.rdfIdToActuallyUsedInput.set(i, inputFrameDownsyncClone); self.rdfIdToActuallyUsedInput.set(i, inputFrameDownsyncClone);
} }
const renderRes = gopkgs.ApplyInputFrameDownsyncDynamicsOnSingleRenderFrameJs(self.recentInputCache, i, collisionSys, collisionSysMap, self.spaceOffsetX, self.spaceOffsetY, self.chConfigsOrderedByJoinIndex, self.recentRenderCache, self.collisionHolder, self.pEffPushbacks, self.pHardPushbackNormsArr, self.pJumpedOrNotList); const renderRes = gopkgs.ApplyInputFrameDownsyncDynamicsOnSingleRenderFrameJs(self.recentInputCache, i, collisionSys, collisionSysMap, self.spaceOffsetX, self.spaceOffsetY, self.chConfigsOrderedByJoinIndex, self.recentRenderCache, self.collisionHolder, self.pEffPushbacks, self.pHardPushbackNormsArr, self.pJumpedOrNotList, self.dynamicRectangleColliders);
const nextRdf = gopkgs.GetRoomDownsyncFrame(self.recentRenderCache, self.renderFrameId + 1); const nextRdf = gopkgs.GetRoomDownsyncFrame(self.recentRenderCache, self.renderFrameId + 1);
if (true == isChasing) { if (true == isChasing) {

File diff suppressed because it is too large Load Diff

View File

@ -217,21 +217,23 @@ func calcPushbacks(oldDx, oldDy float64, playerShape, barrierShape *resolv.Conve
} }
func isPolygonPairOverlapped(a, b *resolv.ConvexPolygon, result *SatResult) bool { func isPolygonPairOverlapped(a, b *resolv.ConvexPolygon, result *SatResult) bool {
aCnt, bCnt := len(a.Points), len(b.Points) aCnt, bCnt := a.Points.Cnt, b.Points.Cnt
// Single point case // Single point case
if 1 == aCnt && 1 == bCnt { if 1 == aCnt && 1 == bCnt {
if nil != result { if nil != result {
result.Overlap = 0 result.Overlap = 0
} }
return a.Points[0][0] == b.Points[0][0] && a.Points[0][1] == b.Points[0][1] aPoint := a.GetPointByOffset(0)
bPoint := b.GetPointByOffset(0)
return aPoint[0] == bPoint[0] && aPoint[1] == bPoint[1]
} }
if 1 < aCnt { if 1 < aCnt {
// Deliberately using "Points" instead of "SATAxes" to avoid unnecessary heap memory alloc // Deliberately using "Points" instead of "SATAxes" to avoid unnecessary heap memory alloc
for i, _ := range a.Points { for i := int32(0); i < a.Points.Cnt; i++ {
u, v := a.Points[i], a.Points[0] u, v := a.GetPointByOffset(i), a.GetPointByOffset(0)
if i != len(a.Points)-1 { if i != a.Points.Cnt-1 {
v = a.Points[i+1] v = a.GetPointByOffset(i + 1)
} }
dy := v[1] - u[1] dy := v[1] - u[1]
dx := v[0] - u[0] dx := v[0] - u[0]
@ -243,10 +245,10 @@ func isPolygonPairOverlapped(a, b *resolv.ConvexPolygon, result *SatResult) bool
} }
if 1 < bCnt { if 1 < bCnt {
for i, _ := range b.Points { for i := int32(0); i < b.Points.Cnt; i++ {
u, v := b.Points[i], b.Points[0] u, v := b.GetPointByOffset(i), b.GetPointByOffset(0)
if i != len(b.Points)-1 { if i != b.Points.Cnt-1 {
v = b.Points[i+1] v = b.GetPointByOffset(i + 1)
} }
dy := v[1] - u[1] dy := v[1] - u[1]
dx := v[0] - u[0] dx := v[0] - u[0]
@ -307,7 +309,8 @@ func isPolygonPairSeparatedByDir(a, b *resolv.ConvexPolygon, e resolv.Vector, re
*/ */
var aStart, aEnd, bStart, bEnd float64 = MAX_FLOAT64, -MAX_FLOAT64, MAX_FLOAT64, -MAX_FLOAT64 var aStart, aEnd, bStart, bEnd float64 = MAX_FLOAT64, -MAX_FLOAT64, MAX_FLOAT64, -MAX_FLOAT64
for _, p := range a.Points { for i := int32(0); i < a.Points.Cnt; i++ {
p := a.GetPointByOffset(i)
dot := (p[0]+a.X)*e[0] + (p[1]+a.Y)*e[1] dot := (p[0]+a.X)*e[0] + (p[1]+a.Y)*e[1]
if aStart > dot { if aStart > dot {
@ -319,7 +322,8 @@ func isPolygonPairSeparatedByDir(a, b *resolv.ConvexPolygon, e resolv.Vector, re
} }
} }
for _, p := range b.Points { for i := int32(0); i < b.Points.Cnt; i++ {
p := b.GetPointByOffset(i)
dot := (p[0]+b.X)*e[0] + (p[1]+b.Y)*e[1] dot := (p[0]+b.X)*e[0] + (p[1]+b.Y)*e[1]
if bStart > dot { if bStart > dot {
@ -454,7 +458,7 @@ func calcHardPushbacksNorms(joinIndex int32, currPlayerDownsync, thatPlayerInNex
//playerColliderCenterX, playerColliderCenterY := playerCollider.Center() //playerColliderCenterX, playerColliderCenterY := playerCollider.Center()
//fmt.Printf("joinIndex=%d calcHardPushbacksNorms has non-empty collision;playerColliderPos=(%.2f,%.2f)\n", joinIndex, playerColliderCenterX, playerColliderCenterY) //fmt.Printf("joinIndex=%d calcHardPushbacksNorms has non-empty collision;playerColliderPos=(%.2f,%.2f)\n", joinIndex, playerColliderCenterX, playerColliderCenterY)
for true { for true {
obj := collision.FirstCollidedObject() obj := collision.PopFirstCollidedObject()
if nil == obj { if nil == obj {
break break
} }
@ -558,11 +562,9 @@ func deriveOpPattern(currPlayerDownsync, thatPlayerInNextFrame *PlayerDownsync,
/* /*
[LONG TERM PERFORMANCE ENHANCEMENT PLAN] [LONG TERM PERFORMANCE ENHANCEMENT PLAN]
The function "ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame" is creating new heap-memory blocks at 60fps, e.g. nextRenderFramePlayers & nextRenderFrameMeleeBullets & nextRenderFrameFireballBullets & effPushbacks & hardPushbackNorms & jumpedOrNotList & playerColliders & bulletColliders, which would induce "possibly performance impacting garbage collections" when many rooms are running simultaneously. The function "ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame" is creating new heap-memory blocks at 60fps, e.g. nextRenderFramePlayers & nextRenderFrameMeleeBullets & nextRenderFrameFireballBullets & effPushbacks & hardPushbackNorms & jumpedOrNotList & dynamicRectangleColliders("player" & "bullet"), which would induce "possibly performance impacting garbage collections" when many rooms are running simultaneously.
It's not easy to remove all of the dynamic heap-memory blocks allocation/deallocation, but we can reduce them to some extent.
*/ */
func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *resolv.RingBuffer, currRenderFrameId int32, collisionSys *resolv.Space, collisionSysMap map[int32]*resolv.Object, collisionSpaceOffsetX, collisionSpaceOffsetY float64, chConfigsOrderedByJoinIndex []*CharacterConfig, renderFrameBuffer *resolv.RingBuffer, collision *resolv.Collision, effPushbacks []*Vec2D, hardPushbackNormsArr [][]*Vec2D, jumpedOrNotList []bool) bool { func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *resolv.RingBuffer, currRenderFrameId int32, collisionSys *resolv.Space, collisionSysMap map[int32]*resolv.Object, collisionSpaceOffsetX, collisionSpaceOffsetY float64, chConfigsOrderedByJoinIndex []*CharacterConfig, renderFrameBuffer *resolv.RingBuffer, collision *resolv.Collision, effPushbacks []*Vec2D, hardPushbackNormsArr [][]*Vec2D, jumpedOrNotList []bool, dynamicRectangleColliders []*resolv.Object) bool {
currRenderFrame := renderFrameBuffer.GetByFrameId(currRenderFrameId).(*RoomDownsyncFrame) currRenderFrame := renderFrameBuffer.GetByFrameId(currRenderFrameId).(*RoomDownsyncFrame)
nextRenderFrameId := currRenderFrameId + 1 nextRenderFrameId := currRenderFrameId + 1
roomCapacity := len(currRenderFrame.PlayersArr) roomCapacity := len(currRenderFrame.PlayersArr)
@ -759,8 +761,14 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *resolv.Rin
} }
} }
/*
[WARNING]
1. The dynamic colliders will all be removed from "Space" at the end of this function due to the need for being rollback-compatible.
2. To achieve "zero gc" in "ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame", I deliberately chose a collision system that doesn't use dynamic tree node alloc.
*/
colliderCnt := 0
// 2. Process player movement // 2. Process player movement
playerColliders := make([]*resolv.Object, len(currRenderFrame.PlayersArr), len(currRenderFrame.PlayersArr)) // Will all be removed at the end of this function due to the need for being rollback-compatible
for i, currPlayerDownsync := range currRenderFrame.PlayersArr { for i, currPlayerDownsync := range currRenderFrame.PlayersArr {
joinIndex := currPlayerDownsync.JoinIndex joinIndex := currPlayerDownsync.JoinIndex
effPushbacks[joinIndex-1].X, effPushbacks[joinIndex-1].Y = float64(0), float64(0) effPushbacks[joinIndex-1].X, effPushbacks[joinIndex-1].Y = float64(0), float64(0)
@ -819,8 +827,9 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *resolv.Rin
colliderWorldWidth, colliderWorldHeight := VirtualGridToWorldPos(colliderWidth, colliderHeight) colliderWorldWidth, colliderWorldHeight := VirtualGridToWorldPos(colliderWidth, colliderHeight)
playerCollider := GenerateRectCollider(wx, wy, colliderWorldWidth, colliderWorldHeight, SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP, collisionSpaceOffsetX, collisionSpaceOffsetY, currPlayerDownsync, "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 := dynamicRectangleColliders[colliderCnt]
playerColliders[i] = playerCollider UpdateRectCollider(playerCollider, wx, wy, colliderWorldWidth, colliderWorldHeight, SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP, collisionSpaceOffsetX, collisionSpaceOffsetY, currPlayerDownsync, "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
colliderCnt++
// Add to collision system // Add to collision system
collisionSys.AddSingle(playerCollider) collisionSys.AddSingle(playerCollider)
@ -838,9 +847,8 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *resolv.Rin
} }
} }
// 3. Add bullet colliders into collision system // 3. Add bullet colliders into collision system; [DIRTY TRICK] Players always precede bullets in "dynamicRectangleColliders".
// [WARNING] For rollback compatibility, static data of "BulletConfig" & "BattleAttr(static since instantiated)" can just be copies of the pointers in "RenderFrameBuffer", however, FireballBullets movement data as well as bullet animation data must be copies of instances for each RenderFrame! // [WARNING] For rollback compatibility, static data of "BulletConfig" & "BattleAttr(static since instantiated)" can just be copies of the pointers in "RenderFrameBuffer", however, FireballBullets movement data as well as bullet animation data must be copies of instances for each RenderFrame!
bulletColliders := make([]*resolv.Object, 0, ((len(currRenderFrame.MeleeBullets) + len(currRenderFrame.FireballBullets)) << 1)) // Will all be removed at the end of this function due to the need for being rollback-compatible
for _, prevFireball := range currRenderFrame.FireballBullets { for _, prevFireball := range currRenderFrame.FireballBullets {
if TERMINATING_BULLET_LOCAL_ID == prevFireball.BattleAttr.BulletLocalId { if TERMINATING_BULLET_LOCAL_ID == prevFireball.BattleAttr.BulletLocalId {
break break
@ -862,9 +870,12 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *resolv.Rin
if IsFireballBulletActive(fireballBullet, currRenderFrame) { if IsFireballBulletActive(fireballBullet, currRenderFrame) {
bulletWx, bulletWy := VirtualGridToWorldPos(fireballBullet.VirtualGridX, fireballBullet.VirtualGridY) bulletWx, bulletWy := VirtualGridToWorldPos(fireballBullet.VirtualGridX, fireballBullet.VirtualGridY)
hitboxSizeWx, hitboxSizeWy := VirtualGridToWorldPos(fireballBullet.Bullet.HitboxSizeX, fireballBullet.Bullet.HitboxSizeY) hitboxSizeWx, hitboxSizeWy := VirtualGridToWorldPos(fireballBullet.Bullet.HitboxSizeX, fireballBullet.Bullet.HitboxSizeY)
newBulletCollider := GenerateRectCollider(bulletWx, bulletWy, hitboxSizeWx, hitboxSizeWy, SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP, collisionSpaceOffsetX, collisionSpaceOffsetY, fireballBullet, "FireballBullet")
newBulletCollider := dynamicRectangleColliders[colliderCnt]
UpdateRectCollider(newBulletCollider, bulletWx, bulletWy, hitboxSizeWx, hitboxSizeWy, SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP, collisionSpaceOffsetX, collisionSpaceOffsetY, fireballBullet, "FireballBullet")
colliderCnt++
collisionSys.AddSingle(newBulletCollider) collisionSys.AddSingle(newBulletCollider)
bulletColliders = append(bulletColliders, newBulletCollider)
fireballBullet.BlState = BULLET_ACTIVE fireballBullet.BlState = BULLET_ACTIVE
if fireballBullet.BlState != prevFireball.BlState { if fireballBullet.BlState != prevFireball.BlState {
fireballBullet.FramesInBlState = 0 fireballBullet.FramesInBlState = 0
@ -906,9 +917,12 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *resolv.Rin
} }
bulletWx, bulletWy := VirtualGridToWorldPos(offender.VirtualGridX+xfac*meleeBullet.Bullet.HitboxOffsetX, offender.VirtualGridY) bulletWx, bulletWy := VirtualGridToWorldPos(offender.VirtualGridX+xfac*meleeBullet.Bullet.HitboxOffsetX, offender.VirtualGridY)
hitboxSizeWx, hitboxSizeWy := VirtualGridToWorldPos(meleeBullet.Bullet.HitboxSizeX, meleeBullet.Bullet.HitboxSizeY) hitboxSizeWx, hitboxSizeWy := VirtualGridToWorldPos(meleeBullet.Bullet.HitboxSizeX, meleeBullet.Bullet.HitboxSizeY)
newBulletCollider := GenerateRectCollider(bulletWx, bulletWy, hitboxSizeWx, hitboxSizeWy, SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP, collisionSpaceOffsetX, collisionSpaceOffsetY, meleeBullet, "MeleeBullet")
newBulletCollider := dynamicRectangleColliders[colliderCnt]
UpdateRectCollider(newBulletCollider, bulletWx, bulletWy, hitboxSizeWx, hitboxSizeWy, SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP, collisionSpaceOffsetX, collisionSpaceOffsetY, meleeBullet, "MeleeBullet")
colliderCnt++
collisionSys.AddSingle(newBulletCollider) collisionSys.AddSingle(newBulletCollider)
bulletColliders = append(bulletColliders, newBulletCollider)
meleeBullet.BlState = BULLET_ACTIVE meleeBullet.BlState = BULLET_ACTIVE
if meleeBullet.BlState != prevMelee.BlState { if meleeBullet.BlState != prevMelee.BlState {
meleeBullet.FramesInBlState = 0 meleeBullet.FramesInBlState = 0
@ -921,7 +935,7 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *resolv.Rin
// 4. Calc pushbacks for each player (after its movement) w/o bullets // 4. Calc pushbacks for each player (after its movement) w/o bullets
for i, currPlayerDownsync := range currRenderFrame.PlayersArr { for i, currPlayerDownsync := range currRenderFrame.PlayersArr {
joinIndex := currPlayerDownsync.JoinIndex joinIndex := currPlayerDownsync.JoinIndex
playerCollider := playerColliders[i] playerCollider := dynamicRectangleColliders[i]
playerShape := playerCollider.Shape.(*resolv.ConvexPolygon) playerShape := playerCollider.Shape.(*resolv.ConvexPolygon)
thatPlayerInNextFrame := nextRenderFramePlayers[i] thatPlayerInNextFrame := nextRenderFramePlayers[i]
hardPushbackCnt := calcHardPushbacksNorms(joinIndex, currPlayerDownsync, thatPlayerInNextFrame, playerCollider, playerShape, SNAP_INTO_PLATFORM_OVERLAP, effPushbacks[joinIndex-1], hardPushbackNormsArr[joinIndex-1], collision) hardPushbackCnt := calcHardPushbacksNorms(joinIndex, currPlayerDownsync, thatPlayerInNextFrame, playerCollider, playerShape, SNAP_INTO_PLATFORM_OVERLAP, effPushbacks[joinIndex-1], hardPushbackNormsArr[joinIndex-1], collision)
@ -930,7 +944,7 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *resolv.Rin
if collided := playerCollider.CheckAllWithHolder(0, 0, collision); collided { if collided := playerCollider.CheckAllWithHolder(0, 0, collision); collided {
for true { for true {
obj := collision.FirstCollidedObject() obj := collision.PopFirstCollidedObject()
if nil == obj { if nil == obj {
break break
} }
@ -963,14 +977,12 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *resolv.Rin
// [WARNING] The "zero overlap collision" might be randomly detected/missed on either frontend or backend, to have deterministic result we added paddings to all sides of a playerCollider. As each velocity component of (velX, velY) being a multiple of 0.5 at any renderFrame, each position component of (x, y) can only be a multiple of 0.5 too, thus whenever a 1-dimensional collision happens between players from [player#1: i*0.5, player#2: j*0.5, not collided yet] to [player#1: (i+k)*0.5, player#2: j*0.5, collided], the overlap becomes (i+k-j)*0.5+2*s, and after snapping subtraction the effPushback magnitude for each player is (i+k-j)*0.5, resulting in 0.5-multiples-position for the next renderFrame. // [WARNING] The "zero overlap collision" might be randomly detected/missed on either frontend or backend, to have deterministic result we added paddings to all sides of a playerCollider. As each velocity component of (velX, velY) being a multiple of 0.5 at any renderFrame, each position component of (x, y) can only be a multiple of 0.5 too, thus whenever a 1-dimensional collision happens between players from [player#1: i*0.5, player#2: j*0.5, not collided yet] to [player#1: (i+k)*0.5, player#2: j*0.5, collided], the overlap becomes (i+k-j)*0.5+2*s, and after snapping subtraction the effPushback magnitude for each player is (i+k-j)*0.5, resulting in 0.5-multiples-position for the next renderFrame.
pushbackX, pushbackY = (overlapResult.Overlap-SNAP_INTO_PLATFORM_OVERLAP*2)*overlapResult.OverlapX, (overlapResult.Overlap-SNAP_INTO_PLATFORM_OVERLAP*2)*overlapResult.OverlapY pushbackX, pushbackY = (overlapResult.Overlap-SNAP_INTO_PLATFORM_OVERLAP*2)*overlapResult.OverlapX, (overlapResult.Overlap-SNAP_INTO_PLATFORM_OVERLAP*2)*overlapResult.OverlapY
} }
if 0 < hardPushbackCnt { for i := 0; i < hardPushbackCnt; i++ {
for i := 0; i < hardPushbackCnt; i++ { hardPushbackNorm := hardPushbackNormsArr[joinIndex-1][i]
hardPushbackNorm := hardPushbackNormsArr[joinIndex-1][i] projectedMagnitude := pushbackX*hardPushbackNorm.X + pushbackY*hardPushbackNorm.Y
projectedMagnitude := pushbackX*hardPushbackNorm.X + pushbackY*hardPushbackNorm.Y if isBarrier || (isAnotherPlayer && 0 > projectedMagnitude) {
if isBarrier || (isAnotherPlayer && 0 > projectedMagnitude) { pushbackX -= projectedMagnitude * hardPushbackNorm.X
pushbackX -= projectedMagnitude * hardPushbackNorm.X pushbackY -= projectedMagnitude * hardPushbackNorm.Y
pushbackY -= projectedMagnitude * hardPushbackNorm.Y
}
} }
} }
effPushbacks[joinIndex-1].X += pushbackX effPushbacks[joinIndex-1].X += pushbackX
@ -1028,7 +1040,7 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *resolv.Rin
if chConfig.OnWallEnabled { if chConfig.OnWallEnabled {
if thatPlayerInNextFrame.InAir { if thatPlayerInNextFrame.InAir {
// [WARNING] Sticking to wall MUST BE based on "InAir", otherwise we would get gravity reduction from ground up incorrectly! // [WARNING] Sticking to wall MUST BE based on "InAir", otherwise we would get gravity reduction from ground up incorrectly!
if _, existent := noOpSet[currPlayerDownsync.CharacterState]; !existent && 0 < hardPushbackCnt { if _, existent := noOpSet[currPlayerDownsync.CharacterState]; !existent {
// [WARNING] Sticking to wall could only be triggered by proactive player input // [WARNING] Sticking to wall could only be triggered by proactive player input
for i := 0; i < hardPushbackCnt; i++ { for i := 0; i < hardPushbackCnt; i++ {
hardPushbackNorm := hardPushbackNormsArr[joinIndex-1][i] hardPushbackNorm := hardPushbackNormsArr[joinIndex-1][i]
@ -1051,13 +1063,12 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *resolv.Rin
thatPlayerInNextFrame.OnWallNormX, thatPlayerInNextFrame.OnWallNormY = 0, 0 thatPlayerInNextFrame.OnWallNormX, thatPlayerInNextFrame.OnWallNormY = 0, 0
} }
} }
} }
// 5. Check bullet-anything collisions // 5. Check bullet-anything collisions
for _, bulletCollider := range bulletColliders { for i := len(nextRenderFramePlayers); i < colliderCnt; i++ {
bulletCollider := dynamicRectangleColliders[i]
collided := bulletCollider.CheckAllWithHolder(0, 0, collision) collided := bulletCollider.CheckAllWithHolder(0, 0, collision)
bulletCollider.Space.RemoveSingle(bulletCollider) // Make sure that the bulletCollider is always removed for each renderFrame
if !collided { if !collided {
continue continue
} }
@ -1079,7 +1090,7 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *resolv.Rin
bulletShape := bulletCollider.Shape.(*resolv.ConvexPolygon) bulletShape := bulletCollider.Shape.(*resolv.ConvexPolygon)
offender := currRenderFrame.PlayersArr[bulletBattleAttr.OffenderJoinIndex-1] offender := currRenderFrame.PlayersArr[bulletBattleAttr.OffenderJoinIndex-1]
for true { for true {
obj := collision.FirstCollidedObject() obj := collision.PopFirstCollidedObject()
if nil == obj { if nil == obj {
break break
} }
@ -1153,7 +1164,7 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *resolv.Rin
// 6. Get players out of stuck barriers if there's any // 6. Get players out of stuck barriers if there's any
for i, currPlayerDownsync := range currRenderFrame.PlayersArr { for i, currPlayerDownsync := range currRenderFrame.PlayersArr {
joinIndex := currPlayerDownsync.JoinIndex joinIndex := currPlayerDownsync.JoinIndex
playerCollider := playerColliders[i] playerCollider := dynamicRectangleColliders[i]
// Update "virtual grid position" // Update "virtual grid position"
thatPlayerInNextFrame := nextRenderFramePlayers[i] thatPlayerInNextFrame := nextRenderFramePlayers[i]
thatPlayerInNextFrame.VirtualGridX, thatPlayerInNextFrame.VirtualGridY = PolygonColliderBLToVirtualGridPos(playerCollider.X-effPushbacks[joinIndex-1].X, playerCollider.Y-effPushbacks[joinIndex-1].Y, playerCollider.W*0.5, playerCollider.H*0.5, 0, 0, 0, 0, collisionSpaceOffsetX, collisionSpaceOffsetY) thatPlayerInNextFrame.VirtualGridX, thatPlayerInNextFrame.VirtualGridY = PolygonColliderBLToVirtualGridPos(playerCollider.X-effPushbacks[joinIndex-1].X, playerCollider.Y-effPushbacks[joinIndex-1].Y, playerCollider.W*0.5, playerCollider.H*0.5, 0, 0, 0, 0, collisionSpaceOffsetX, collisionSpaceOffsetY)
@ -1199,9 +1210,10 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *resolv.Rin
} }
} }
for _, playerCollider := range playerColliders { for i := 0; i < colliderCnt; i++ {
playerCollider.Space.RemoveSingle(playerCollider) dynamicCollider := dynamicRectangleColliders[i]
} dynamicCollider.Space.RemoveSingle(dynamicCollider)
}
ret.Id = nextRenderFrameId ret.Id = nextRenderFrameId
ret.BulletLocalIdCounter = bulletLocalId ret.BulletLocalIdCounter = bulletLocalId
@ -1222,6 +1234,15 @@ func generateRectColliderInCollisionSpace(blX, blY, w, h float64, data interface
return collider return collider
} }
func UpdateRectCollider(collider *resolv.Object, wx, wy, w, h, topPadding, bottomPadding, leftPadding, rightPadding, spaceOffsetX, spaceOffsetY float64, data interface{}, tag string) {
blX, blY := WorldToPolygonColliderBLPos(wx, wy, w*0.5, h*0.5, topPadding, bottomPadding, leftPadding, rightPadding, spaceOffsetX, spaceOffsetY)
collider.X, collider.Y, collider.W, collider.H = blX, blY, w, h
rectShape := collider.Shape.(*resolv.ConvexPolygon)
rectShape.UpdateAsRectangle(0, 0, w, h)
collider.Data = data
// Ignore "tag" for now
}
func GenerateConvexPolygonCollider(unalignedSrc *Polygon2D, spaceOffsetX, spaceOffsetY float64, data interface{}, tag string) *resolv.Object { func GenerateConvexPolygonCollider(unalignedSrc *Polygon2D, spaceOffsetX, spaceOffsetY float64, data interface{}, tag string) *resolv.Object {
aligned := AlignPolygon2DToBoundingBox(unalignedSrc) aligned := AlignPolygon2DToBoundingBox(unalignedSrc)
var w, h float64 = 0, 0 var w, h float64 = 0, 0

View File

@ -9,6 +9,14 @@ import (
/* /*
[WARNING] Should avoid using "MakeFullWrapper" as much as possible, and completely remove its usage in 60fps calls like "update(dt)" on frontend! [WARNING] Should avoid using "MakeFullWrapper" as much as possible, and completely remove its usage in 60fps calls like "update(dt)" on frontend!
*/ */
func NewDynamicRectangleColliders(cnt int) []*js.Object {
ret := make([]*js.Object, cnt)
for i := 0; i < cnt; i++ {
ret[i] = js.MakeWrapper(GenerateRectCollider(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, nil, ""))
}
return ret
}
func NewCollisionHolder() *js.Object { func NewCollisionHolder() *js.Object {
return js.MakeWrapper(resolv.NewCollision()) return js.MakeWrapper(resolv.NewCollision())
} }
@ -103,9 +111,9 @@ func GetCharacterConfigsOrderedByJoinIndex(speciesIdList []int) []*js.Object {
return ret return ret
} }
func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrameJs(inputsBuffer *resolv.RingBuffer, currRenderFrameId int32, collisionSys *resolv.Space, collisionSysMap map[int32]*resolv.Object, collisionSpaceOffsetX, collisionSpaceOffsetY float64, chConfigsOrderedByJoinIndex []*CharacterConfig, renderFrameBuffer *resolv.RingBuffer, collision *resolv.Collision, effPushbacks []*Vec2D, hardPushbackNormsArr [][]*Vec2D, jumpedOrNotList []bool) bool { func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrameJs(inputsBuffer *resolv.RingBuffer, currRenderFrameId int32, collisionSys *resolv.Space, collisionSysMap map[int32]*resolv.Object, collisionSpaceOffsetX, collisionSpaceOffsetY float64, chConfigsOrderedByJoinIndex []*CharacterConfig, renderFrameBuffer *resolv.RingBuffer, collision *resolv.Collision, effPushbacks []*Vec2D, hardPushbackNormsArr [][]*Vec2D, jumpedOrNotList []bool, dynamicRectangleColliders []*resolv.Object) bool {
// We need access to all fields of RoomDownsyncFrame for displaying in frontend // We need access to all fields of RoomDownsyncFrame for displaying in frontend
return ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer, currRenderFrameId, collisionSys, collisionSysMap, collisionSpaceOffsetX, collisionSpaceOffsetY, chConfigsOrderedByJoinIndex, renderFrameBuffer, collision, effPushbacks, hardPushbackNormsArr, jumpedOrNotList) return ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer, currRenderFrameId, collisionSys, collisionSysMap, collisionSpaceOffsetX, collisionSpaceOffsetY, chConfigsOrderedByJoinIndex, renderFrameBuffer, collision, effPushbacks, hardPushbackNormsArr, jumpedOrNotList, dynamicRectangleColliders)
} }
func GetRoomDownsyncFrame(renderFrameBuffer *resolv.RingBuffer, frameId int32) *js.Object { func GetRoomDownsyncFrame(renderFrameBuffer *resolv.RingBuffer, frameId int32) *js.Object {
@ -183,5 +191,6 @@ func main() {
"GetMeleeBullet": GetMeleeBullet, "GetMeleeBullet": GetMeleeBullet,
"GetFireballBullet": GetFireballBullet, "GetFireballBullet": GetFireballBullet,
"GetInput": GetInput, "GetInput": GetInput,
"NewDynamicRectangleColliders": NewDynamicRectangleColliders,
}) })
} }

View File

@ -24,7 +24,7 @@ func (cc *Collision) Clear() {
cc.Cells.Clear() cc.Cells.Clear()
} }
func (cc *Collision) FirstCollidedObject() *Object { func (cc *Collision) PopFirstCollidedObject() *Object {
if 0 >= cc.Objects.Cnt { if 0 >= cc.Objects.Cnt {
return nil return nil
} }

View File

@ -254,7 +254,7 @@ func (obj *Object) CheckAllWithHolder(dx, dy float64, cc *Collision) bool {
if obj.Space == nil { if obj.Space == nil {
return false return false
} }
cc.Clear()
cc.checkingObject = obj cc.checkingObject = obj
if dx < 0 { if dx < 0 {

View File

@ -125,7 +125,7 @@ func (line *Line) IntersectionPointsCircle(circle *Circle) []Vector {
} }
type ConvexPolygon struct { type ConvexPolygon struct {
Points []Vector Points *RingBuffer
X, Y float64 X, Y float64
Closed bool Closed bool
} }
@ -135,46 +135,72 @@ type ConvexPolygon struct {
// polygon square, with the vertices at {0,0}, {10,0}, {10, 10}, and {0, 10}. // polygon square, with the vertices at {0,0}, {10,0}, {10, 10}, and {0, 10}.
func NewConvexPolygon(points ...float64) *ConvexPolygon { func NewConvexPolygon(points ...float64) *ConvexPolygon {
// if len(points)/2 < 2 { cp := &ConvexPolygon{
// return nil Points: NewRingBuffer(6), // I don't expected more points to be coped with in this particular game
// } Closed: true,
}
cp := &ConvexPolygon{Points: []Vector{}, Closed: true}
cp.AddPoints(points...) cp.AddPoints(points...)
return cp return cp
} }
func (cp *ConvexPolygon) Clone() Shape { func (cp *ConvexPolygon) GetPointByOffset(offset int32) Vector {
if cp.Points.Cnt <= offset {
points := []Vector{} return nil
for _, point := range cp.Points {
points = append(points, point.Clone())
} }
return cp.Points.GetByFrameId(cp.Points.StFrameId + offset).(Vector)
}
func (cp *ConvexPolygon) Clone() Shape {
newPoly := NewConvexPolygon() newPoly := NewConvexPolygon()
newPoly.X = cp.X newPoly.X = cp.X
newPoly.Y = cp.Y newPoly.Y = cp.Y
newPoly.AddPointsVec(points...) for i := int32(0); i < cp.Points.Cnt; i++ {
newPoly.Points.Put(cp.GetPointByOffset(i))
}
newPoly.Closed = cp.Closed newPoly.Closed = cp.Closed
return newPoly return newPoly
} }
// AddPointsVec allows you to add points to the ConvexPolygon with a slice of Vectors, each indicating a point / vertex.
func (cp *ConvexPolygon) AddPointsVec(points ...Vector) {
cp.Points = append(cp.Points, points...)
}
// AddPoints allows you to add points to the ConvexPolygon with a slice or selection of float64s, with each pair indicating an X or Y value for // AddPoints allows you to add points to the ConvexPolygon with a slice or selection of float64s, with each pair indicating an X or Y value for
// a point / vertex (i.e. AddPoints(0, 1, 2, 3) would add two points - one at {0, 1}, and another at {2, 3}). // a point / vertex (i.e. AddPoints(0, 1, 2, 3) would add two points - one at {0, 1}, and another at {2, 3}).
func (cp *ConvexPolygon) AddPoints(vertexPositions ...float64) { func (cp *ConvexPolygon) AddPoints(vertexPositions ...float64) {
for v := 0; v < len(vertexPositions); v += 2 { for v := 0; v < len(vertexPositions); v += 2 {
cp.Points = append(cp.Points, Vector{vertexPositions[v], vertexPositions[v+1]}) // "resolv.Vector" is an alias of "[]float64", thus already a pointer type
cp.Points.Put(Vector{vertexPositions[v], vertexPositions[v+1]})
} }
} }
func (cp *ConvexPolygon) UpdateAsRectangle(x, y, w, h float64) bool {
// This function might look ugly but it's a fast in-place update!
if 4 != cp.Points.Cnt {
panic("ConvexPolygon not having exactly 4 vertices to form a rectangle#1!")
}
for i := int32(0); i < cp.Points.Cnt; i++ {
thatVec := cp.GetPointByOffset(i)
if nil == thatVec {
panic("ConvexPolygon not having exactly 4 vertices to form a rectangle#2!")
}
switch i {
case 0:
thatVec[0] = x
thatVec[1] = y
case 1:
thatVec[0] = x + w
thatVec[1] = y
case 2:
thatVec[0] = x + w
thatVec[1] = y + h
case 3:
thatVec[0] = x
thatVec[1] = y + h
}
}
return true
}
// Lines returns a slice of transformed Lines composing the ConvexPolygon. // Lines returns a slice of transformed Lines composing the ConvexPolygon.
func (cp *ConvexPolygon) Lines() []*Line { func (cp *ConvexPolygon) Lines() []*Line {
@ -200,8 +226,9 @@ func (cp *ConvexPolygon) Lines() []*Line {
// Transformed returns the ConvexPolygon's points / vertices, transformed according to the ConvexPolygon's position. // Transformed returns the ConvexPolygon's points / vertices, transformed according to the ConvexPolygon's position.
func (cp *ConvexPolygon) Transformed() []Vector { func (cp *ConvexPolygon) Transformed() []Vector {
transformed := make([]Vector, len(cp.Points)) transformed := make([]Vector, cp.Points.Cnt)
for i, point := range cp.Points { for i := int32(0); i < cp.Points.Cnt; i++ {
point := cp.GetPointByOffset(i)
transformed[i] = Vector{point[0] + cp.X, point[1] + cp.Y} transformed[i] = Vector{point[0] + cp.X, point[1] + cp.Y}
} }
return transformed return transformed
@ -331,10 +358,6 @@ func (polygon *ConvexPolygon) PointInside(point Vector) bool {
return contactCount == 1 return contactCount == 1
} }
func (polygon *ConvexPolygon) GetPoints() []Vector {
return polygon.Points
}
type ContactSet struct { type ContactSet struct {
Points []Vector // Slice of Points indicating contact between the two Shapes. Points []Vector // Slice of Points indicating contact between the two Shapes.
MTV Vector // Minimum Translation Vector; this is the vector to move a Shape on to move it outside of its contacting Shape. MTV Vector // Minimum Translation Vector; this is the vector to move a Shape on to move it outside of its contacting Shape.
@ -553,42 +576,6 @@ func (cp *ConvexPolygon) ContainedBy(otherShape Shape) bool {
return true return true
} }
// FlipH flips the ConvexPolygon's vertices horizontally according to their initial offset when adding the points.
func (cp *ConvexPolygon) FlipH() {
for _, v := range cp.Points {
v[0] = -v[0]
}
// We have to reverse vertex order after flipping the vertices to ensure the winding order is consistent between Objects (so that the normals are consistently outside or inside, which is important
// when doing Intersection tests). If we assume that the normal of a line, going from vertex A to vertex B, is one direction, then the normal would be inverted if the vertices were flipped in position,
// but not in order. This would make Intersection tests drive objects into each other, instead of giving the delta to move away.
cp.ReverseVertexOrder()
}
// FlipV flips the ConvexPolygon's vertices vertically according to their initial offset when adding the points.
func (cp *ConvexPolygon) FlipV() {
for _, v := range cp.Points {
v[1] = -v[1]
}
cp.ReverseVertexOrder()
}
// ReverseVertexOrder reverses the vertex ordering of the ConvexPolygon.
func (cp *ConvexPolygon) ReverseVertexOrder() {
verts := []Vector{cp.Points[0]}
for i := len(cp.Points) - 1; i >= 1; i-- {
verts = append(verts, cp.Points[i])
}
cp.Points = verts
}
// NewRectangle returns a rectangular ConvexPolygon with the vertices in clockwise order. In actuality, an AABBRectangle should be its own // NewRectangle returns a rectangular ConvexPolygon with the vertices in clockwise order. In actuality, an AABBRectangle should be its own
// "thing" with its own optimized Intersection code check. // "thing" with its own optimized Intersection code check.
func NewRectangle(x, y, w, h float64) *ConvexPolygon { func NewRectangle(x, y, w, h float64) *ConvexPolygon {