mirror of
https://github.com/genxium/DelayNoMore
synced 2024-12-26 11:48:56 +00:00
Temp broken commit, but it's working for OfflineMap.
This commit is contained in:
parent
5c06cfdbac
commit
5a463239bb
@ -61,10 +61,13 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ATK_CHARACTER_STATE_IDLE1 = 0
|
ATK_CHARACTER_STATE_IDLE1 = 0
|
||||||
ATK_CHARACTER_STATE_WALKING = 1
|
ATK_CHARACTER_STATE_WALKING = 1
|
||||||
ATK_CHARACTER_STATE_ATK1 = 2
|
ATK_CHARACTER_STATE_ATK1 = 2
|
||||||
ATK_CHARACTER_STATE_ATKED1 = 3
|
ATK_CHARACTER_STATE_ATKED1 = 3
|
||||||
|
ATK_CHARACTER_STATE_INAIR_IDLE1 = 4
|
||||||
|
ATK_CHARACTER_STATE_INAIR_ATK1 = 5
|
||||||
|
ATK_CHARACTER_STATE_INAIR_ATKED1 = 6
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -1305,20 +1308,37 @@ func (pR *Room) applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputF
|
|||||||
MeleeBullets: make([]*MeleeBullet, 0), // Is there any better way to reduce malloc/free impact, e.g. smart prediction for fixed memory allocation?
|
MeleeBullets: make([]*MeleeBullet, 0), // Is there any better way to reduce malloc/free impact, e.g. smart prediction for fixed memory allocation?
|
||||||
}
|
}
|
||||||
|
|
||||||
bulletPushbacks := make([]Vec2D, pR.Capacity) // Guaranteed determinism regardless of traversal order
|
// Guaranteed determinism regardless of traversal order
|
||||||
effPushbacks := make([]Vec2D, pR.Capacity) // Guaranteed determinism regardless of traversal order
|
jumpTriggered := make([]bool, pR.Capacity)
|
||||||
|
movements := make([]Vec2D, pR.Capacity)
|
||||||
|
bulletPushbacks := make([]Vec2D, pR.Capacity)
|
||||||
|
effPushbacks := make([]Vec2D, pR.Capacity)
|
||||||
|
|
||||||
// Reset playerCollider position from the "virtual grid position"
|
// Reset playerCollider position from the "virtual grid position"
|
||||||
for _, player := range pR.PlayersArr {
|
for _, player := range pR.PlayersArr {
|
||||||
playerId := player.Id
|
playerId := player.Id
|
||||||
joinIndex := player.JoinIndex
|
joinIndex := player.JoinIndex
|
||||||
|
jumpTriggered[joinIndex-1] = false
|
||||||
|
movements[joinIndex-1].X, movements[joinIndex-1].Y = float64(0), float64(0)
|
||||||
bulletPushbacks[joinIndex-1].X, bulletPushbacks[joinIndex-1].Y = float64(0), float64(0)
|
bulletPushbacks[joinIndex-1].X, bulletPushbacks[joinIndex-1].Y = float64(0), float64(0)
|
||||||
effPushbacks[joinIndex-1].X, effPushbacks[joinIndex-1].Y = float64(0), float64(0)
|
effPushbacks[joinIndex-1].X, effPushbacks[joinIndex-1].Y = float64(0), float64(0)
|
||||||
currPlayerDownsync := currRenderFrame.Players[playerId]
|
currPlayerDownsync := currRenderFrame.Players[playerId]
|
||||||
|
thatPlayerInNextFrame := nextRenderFramePlayers[playerId]
|
||||||
newVx, newVy := currPlayerDownsync.VirtualGridX, currPlayerDownsync.VirtualGridY
|
newVx, newVy := currPlayerDownsync.VirtualGridX, currPlayerDownsync.VirtualGridY
|
||||||
collisionPlayerIndex := COLLISION_PLAYER_INDEX_PREFIX + joinIndex
|
collisionPlayerIndex := COLLISION_PLAYER_INDEX_PREFIX + joinIndex
|
||||||
playerCollider := collisionSysMap[collisionPlayerIndex]
|
playerCollider := collisionSysMap[collisionPlayerIndex]
|
||||||
playerCollider.X, playerCollider.Y = VirtualGridToPolygonColliderAnchorPos(newVx, newVy, player.ColliderRadius, player.ColliderRadius, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY, pR.VirtualGridToWorldRatio)
|
playerCollider.X, playerCollider.Y = VirtualGridToPolygonColliderAnchorPos(newVx, newVy, player.ColliderRadius, player.ColliderRadius, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY, pR.VirtualGridToWorldRatio)
|
||||||
|
|
||||||
|
movements[joinIndex-1].X, movements[joinIndex-1].Y = VirtualGridToWorldPos(currPlayerDownsync.VelX, currPlayerDownsync.VelY, pR.VirtualGridToWorldRatio)
|
||||||
|
playerCollider.X += movements[joinIndex-1].X
|
||||||
|
playerCollider.Y += movements[joinIndex-1].Y
|
||||||
|
if currPlayerDownsync.InAir {
|
||||||
|
thatPlayerInNextFrame.VelX += pR.GravityX
|
||||||
|
thatPlayerInNextFrame.VelY += pR.GravityY
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update in the collision system
|
||||||
|
playerCollider.Update()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check bullet-anything collisions first, because the pushbacks caused by bullets might later be reverted by player-barrier collision
|
// Check bullet-anything collisions first, because the pushbacks caused by bullets might later be reverted by player-barrier collision
|
||||||
@ -1366,7 +1386,12 @@ func (pR *Room) applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputF
|
|||||||
xfac = float64(-1.0)
|
xfac = float64(-1.0)
|
||||||
}
|
}
|
||||||
bulletPushbacks[t.JoinIndex-1].X += xfac * meleeBullet.Pushback
|
bulletPushbacks[t.JoinIndex-1].X += xfac * meleeBullet.Pushback
|
||||||
nextRenderFramePlayers[t.Id].CharacterState = ATK_CHARACTER_STATE_ATKED1
|
thatAckedPlayerInCurFrame := currRenderFrame.Players[t.Id]
|
||||||
|
thatAckedPlayerInNextFrame := nextRenderFramePlayers[t.Id]
|
||||||
|
thatAckedPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_ATKED1
|
||||||
|
if thatAckedPlayerInCurFrame.InAir {
|
||||||
|
thatAckedPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_INAIR_ATKED1
|
||||||
|
}
|
||||||
oldFramesToRecover := nextRenderFramePlayers[t.Id].FramesToRecover
|
oldFramesToRecover := nextRenderFramePlayers[t.Id].FramesToRecover
|
||||||
if meleeBullet.HitStunFrames > oldFramesToRecover {
|
if meleeBullet.HitStunFrames > oldFramesToRecover {
|
||||||
nextRenderFramePlayers[t.Id].FramesToRecover = meleeBullet.HitStunFrames
|
nextRenderFramePlayers[t.Id].FramesToRecover = meleeBullet.HitStunFrames
|
||||||
@ -1425,10 +1450,27 @@ func (pR *Room) applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputF
|
|||||||
}
|
}
|
||||||
currPlayerDownsync := currRenderFrame.Players[playerId]
|
currPlayerDownsync := currRenderFrame.Players[playerId]
|
||||||
decodedInput := pR.decodeInput(inputList[joinIndex-1])
|
decodedInput := pR.decodeInput(inputList[joinIndex-1])
|
||||||
prevBtnALevel := int32(0)
|
prevBtnALevel, prevBtnBLevel := int32(0), int32(0)
|
||||||
if nil != delayedInputFrameForPrevRenderFrame {
|
if nil != delayedInputFrameForPrevRenderFrame {
|
||||||
prevDecodedInput := pR.decodeInput(delayedInputFrameForPrevRenderFrame.InputList[joinIndex-1])
|
prevDecodedInput := pR.decodeInput(delayedInputFrameForPrevRenderFrame.InputList[joinIndex-1])
|
||||||
prevBtnALevel = prevDecodedInput.BtnALevel
|
prevBtnALevel = prevDecodedInput.BtnALevel
|
||||||
|
prevBtnBLevel = prevDecodedInput.BtnBLevel
|
||||||
|
}
|
||||||
|
|
||||||
|
if decodedInput.BtnBLevel > prevBtnBLevel {
|
||||||
|
characStateAlreadyInAir := false
|
||||||
|
if ATK_CHARACTER_STATE_INAIR_IDLE1 == thatPlayerInNextFrame.CharacterState || ATK_CHARACTER_STATE_INAIR_ATK1 == thatPlayerInNextFrame.CharacterState || ATK_CHARACTER_STATE_INAIR_ATKED1 == thatPlayerInNextFrame.CharacterState {
|
||||||
|
characStateAlreadyInAir = true
|
||||||
|
}
|
||||||
|
characStateIsInterruptWaivable := false
|
||||||
|
if ATK_CHARACTER_STATE_IDLE1 == thatPlayerInNextFrame.CharacterState || ATK_CHARACTER_STATE_WALKING == thatPlayerInNextFrame.CharacterState || ATK_CHARACTER_STATE_INAIR_IDLE1 == thatPlayerInNextFrame.CharacterState {
|
||||||
|
characStateIsInterruptWaivable = true
|
||||||
|
}
|
||||||
|
if !characStateAlreadyInAir && characStateIsInterruptWaivable {
|
||||||
|
thatPlayerInNextFrame.VelY = pR.JumpingInitVelY
|
||||||
|
jumpTriggered[joinIndex-1] = true
|
||||||
|
Logger.Info(fmt.Sprintf("playerId=%v, joinIndex=%v triggered a rising-edge of btnB at renderFrame.id=%v, delayedInputFrame.id=%v, nextVelY=%v, characStateAlreadyInAir=%v, characStateIsInterruptWaivable=%v", playerId, joinIndex, currRenderFrame.Id, delayedInputFrame.InputFrameId, thatPlayerInNextFrame.VelY, characStateAlreadyInAir, characStateIsInterruptWaivable))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if decodedInput.BtnALevel > prevBtnALevel {
|
if decodedInput.BtnALevel > prevBtnALevel {
|
||||||
@ -1443,6 +1485,9 @@ func (pR *Room) applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputF
|
|||||||
toRet.MeleeBullets = append(toRet.MeleeBullets, &newMeleeBullet)
|
toRet.MeleeBullets = append(toRet.MeleeBullets, &newMeleeBullet)
|
||||||
thatPlayerInNextFrame.FramesToRecover = newMeleeBullet.RecoveryFrames
|
thatPlayerInNextFrame.FramesToRecover = newMeleeBullet.RecoveryFrames
|
||||||
thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_ATK1
|
thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_ATK1
|
||||||
|
if false == currPlayerDownsync.InAir {
|
||||||
|
thatPlayerInNextFrame.VelX = 0
|
||||||
|
}
|
||||||
Logger.Debug(fmt.Sprintf("roomId=%v, playerId=%v triggered a rising-edge of btnA at currRenderFrame.id=%v, delayedInputFrame.id=%v", pR.Id, playerId, currRenderFrame.Id, delayedInputFrame.InputFrameId))
|
Logger.Debug(fmt.Sprintf("roomId=%v, playerId=%v triggered a rising-edge of btnA at currRenderFrame.id=%v, delayedInputFrame.id=%v", pR.Id, playerId, currRenderFrame.Id, delayedInputFrame.InputFrameId))
|
||||||
|
|
||||||
} else if decodedInput.BtnALevel < prevBtnALevel {
|
} else if decodedInput.BtnALevel < prevBtnALevel {
|
||||||
@ -1454,17 +1499,24 @@ func (pR *Room) applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputF
|
|||||||
thatPlayerInNextFrame.DirX = decodedInput.Dx
|
thatPlayerInNextFrame.DirX = decodedInput.Dx
|
||||||
thatPlayerInNextFrame.DirY = decodedInput.Dy
|
thatPlayerInNextFrame.DirY = decodedInput.Dy
|
||||||
thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_WALKING
|
thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_WALKING
|
||||||
|
thatPlayerInNextFrame.VelX = decodedInput.Dx * currPlayerDownsync.Speed
|
||||||
} else {
|
} else {
|
||||||
thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_IDLE1
|
thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_IDLE1
|
||||||
|
thatPlayerInNextFrame.VelX = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if currPlayerDownsync.InAir {
|
||||||
|
switch thatPlayerInNextFrame.CharacterState {
|
||||||
|
case ATK_CHARACTER_STATE_IDLE1:
|
||||||
|
case ATK_CHARACTER_STATE_WALKING:
|
||||||
|
thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_INAIR_IDLE1
|
||||||
|
case ATK_CHARACTER_STATE_ATK1:
|
||||||
|
thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_INAIR_ATK1
|
||||||
|
case ATK_CHARACTER_STATE_ATKED1:
|
||||||
|
thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_INAIR_ATKED1
|
||||||
|
default:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
movementX, movementY := VirtualGridToWorldPos(decodedInput.Dx+decodedInput.Dx*currPlayerDownsync.Speed, decodedInput.Dy+decodedInput.Dy*currPlayerDownsync.Speed, pR.VirtualGridToWorldRatio)
|
|
||||||
playerCollider.X += movementX
|
|
||||||
playerCollider.Y += movementY
|
|
||||||
|
|
||||||
// Update in the collision system
|
|
||||||
playerCollider.Update()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle pushbacks upon collision after all movements treated as simultaneous
|
// handle pushbacks upon collision after all movements treated as simultaneous
|
||||||
@ -1472,11 +1524,30 @@ func (pR *Room) applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputF
|
|||||||
joinIndex := player.JoinIndex
|
joinIndex := player.JoinIndex
|
||||||
collisionPlayerIndex := COLLISION_PLAYER_INDEX_PREFIX + joinIndex
|
collisionPlayerIndex := COLLISION_PLAYER_INDEX_PREFIX + joinIndex
|
||||||
playerCollider := collisionSysMap[collisionPlayerIndex]
|
playerCollider := collisionSysMap[collisionPlayerIndex]
|
||||||
|
currPlayerDownsync := currRenderFrame.Players[playerId]
|
||||||
|
thatPlayerInNextFrame := nextRenderFramePlayers[playerId]
|
||||||
|
fallStopping := false
|
||||||
|
snappedIntoPlatformEx, snappedIntoPlatformEy := float64(0), float64(0)
|
||||||
if collision := playerCollider.Check(0, 0); collision != nil {
|
if collision := playerCollider.Check(0, 0); collision != nil {
|
||||||
playerShape := playerCollider.Shape.(*resolv.ConvexPolygon)
|
playerShape := playerCollider.Shape.(*resolv.ConvexPolygon)
|
||||||
for _, obj := range collision.Objects {
|
for _, obj := range collision.Objects {
|
||||||
barrierShape := obj.Shape.(*resolv.ConvexPolygon)
|
barrierShape := obj.Shape.(*resolv.ConvexPolygon)
|
||||||
if overlapped, pushbackX, pushbackY, overlapResult := CalcPushbacks(0, 0, playerShape, barrierShape); overlapped {
|
if overlapped, pushbackX, pushbackY, overlapResult := CalcPushbacks(0, 0, playerShape, barrierShape); overlapped {
|
||||||
|
if nil == obj.Data {
|
||||||
|
// "nil == obj.Data" implies a barrier
|
||||||
|
const flatEnough = (self.snapIntoPlatformThreshold < normAlignmentWithGravity); // prevents false snapping on the lateral sides
|
||||||
|
const remainsNotInAir = (!currPlayerDownsync.inAir && flatEnough);
|
||||||
|
const localFallStopping = (currPlayerDownsync.inAir && flatEnough);
|
||||||
|
if (remainsNotInAir || localFallStopping) {
|
||||||
|
fallStopping |= localFallStopping;
|
||||||
|
[pushbackX, pushbackY] = [(result2.overlap - self.snapIntoPlatformOverlap) * result2.overlap_x, (result2.overlap - self.snapIntoPlatformOverlap) * result2.overlap_y]
|
||||||
|
snappedIntoPlatformEx = -result2.overlap_y;
|
||||||
|
snappedIntoPlatformEy = result2.overlap_x;
|
||||||
|
if (snappedIntoPlatformEx * currPlayerDownsync.dirX + snappedIntoPlatformEy * currPlayerDownsync.dirY) {
|
||||||
|
[snappedIntoPlatformEx, snappedIntoPlatformEy] = [-snappedIntoPlatformEx, -snappedIntoPlatformEy];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Logger.Debug(fmt.Sprintf("Overlapped: a=%v, b=%v, pushbackX=%v, pushbackY=%v", ConvexPolygonStr(playerShape), ConvexPolygonStr(barrierShape), pushbackX, pushbackY))
|
Logger.Debug(fmt.Sprintf("Overlapped: a=%v, b=%v, pushbackX=%v, pushbackY=%v", ConvexPolygonStr(playerShape), ConvexPolygonStr(barrierShape), pushbackX, pushbackY))
|
||||||
effPushbacks[joinIndex-1].X += pushbackX
|
effPushbacks[joinIndex-1].X += pushbackX
|
||||||
effPushbacks[joinIndex-1].Y += pushbackY
|
effPushbacks[joinIndex-1].Y += pushbackY
|
||||||
|
@ -60,6 +60,43 @@ func GenerateConvexPolygonCollider(unalignedSrc *Polygon2D, spaceOffsetX, spaceO
|
|||||||
return collider
|
return collider
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func CalcPushbacksWithGravitySnapping(oldDx, oldDy float64, playerShape, barrierShape *resolv.ConvexPolygon, currentInAir bool, snapIntoPlatformOverlap, snapIntoPlatformThreshold float64) (bool, float64, float64, *SatResult, float64, float64, bool) {
|
||||||
|
origX, origY := playerShape.Position()
|
||||||
|
snappedIntoPlatformEx, snappedIntoPlatformEy := float64(0), float64(0)
|
||||||
|
localFallStopping := false
|
||||||
|
defer func() {
|
||||||
|
playerShape.SetPosition(origX, origY)
|
||||||
|
}()
|
||||||
|
playerShape.SetPosition(origX+oldDx, origY+oldDy)
|
||||||
|
overlapResult := &SatResult{
|
||||||
|
Overlap: 0,
|
||||||
|
OverlapX: 0,
|
||||||
|
OverlapY: 0,
|
||||||
|
AContainedInB: true,
|
||||||
|
BContainedInA: true,
|
||||||
|
Axis: vector.Vector{0, 0},
|
||||||
|
}
|
||||||
|
if overlapped := IsPolygonPairOverlapped(playerShape, barrierShape, overlapResult); overlapped {
|
||||||
|
pushbackX, pushbackY := overlapResult.Overlap*overlapResult.OverlapX, overlapResult.Overlap*overlapResult.OverlapY
|
||||||
|
normAlignmentWithGravity := (overlapResult.OverlapX * 0 + overlapResult.OverlapX * (-1.0))
|
||||||
|
flatEnough := (snapIntoPlatformThreshold < normAlignmentWithGravity) // prevents false snapping on the lateral sides
|
||||||
|
remainsNotInAir := (!currentInAir && flatEnough)
|
||||||
|
localFallStopping = (currentInAir && flatEnough)
|
||||||
|
if remainsNotInAir || localFallStopping {
|
||||||
|
// [OverlayX, OverlapY] is the unit vector that points into the platform; FIXME: Should only assign to [snappedIntoPlatformEx, snappedIntoPlatformEy] at most once!
|
||||||
|
snappedIntoPlatformEx, snappedIntoPlatformEy = -overlapResult.OverlapY, overlapResult.OverlapX
|
||||||
|
pushbackX, pushbackY = (overlapResult.Overlap - snapIntoPlatformOverlap) * overlapResult.OverlapX, (overlapResult.Overlap - snapIntoPlatformOverlap) * overlapResult.OverlapY
|
||||||
|
if (snappedIntoPlatformEx * currPlayerDownsync.dirX + snappedIntoPlatformEy * currPlayerDownsync.dirY < 0) {
|
||||||
|
// snapped dir should have a positive projection from player facing dir
|
||||||
|
snappedIntoPlatformEx, snappedIntoPlatformEy = -snappedIntoPlatformEx, -snappedIntoPlatformEy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true, pushbackX, pushbackY, overlapResult, snappedIntoPlatformEx, snappedIntoPlatformEy, localFallStopping
|
||||||
|
} else {
|
||||||
|
return false, 0, 0, overlapResult, 0, 0, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func CalcPushbacks(oldDx, oldDy float64, playerShape, barrierShape *resolv.ConvexPolygon) (bool, float64, float64, *SatResult) {
|
func CalcPushbacks(oldDx, oldDy float64, playerShape, barrierShape *resolv.ConvexPolygon) (bool, float64, float64, *SatResult) {
|
||||||
origX, origY := playerShape.Position()
|
origX, origY := playerShape.Position()
|
||||||
defer func() {
|
defer func() {
|
||||||
|
@ -277,7 +277,7 @@
|
|||||||
"__uuid__": "472df5d3-35e7-4184-9e6c-7f41bee65ee3"
|
"__uuid__": "472df5d3-35e7-4184-9e6c-7f41bee65ee3"
|
||||||
},
|
},
|
||||||
"_texture": null,
|
"_texture": null,
|
||||||
"_stopped": false,
|
"_stopped": true,
|
||||||
"playOnLoad": true,
|
"playOnLoad": true,
|
||||||
"autoRemoveOnFinish": false,
|
"autoRemoveOnFinish": false,
|
||||||
"totalParticles": 200,
|
"totalParticles": 200,
|
||||||
@ -408,7 +408,7 @@
|
|||||||
"ctor": "Float64Array",
|
"ctor": "Float64Array",
|
||||||
"array": [
|
"array": [
|
||||||
3,
|
3,
|
||||||
40,
|
60,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
|
@ -454,7 +454,7 @@
|
|||||||
"array": [
|
"array": [
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
215.64032554232523,
|
209.57814771583418,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
|
@ -451,6 +451,9 @@ cc.Class({
|
|||||||
const newBarrierCollider = self.collisionSys.createPolygon(x0, y0, Array.from(boundaryObj, p => {
|
const newBarrierCollider = self.collisionSys.createPolygon(x0, y0, Array.from(boundaryObj, p => {
|
||||||
return [p.x, p.y];
|
return [p.x, p.y];
|
||||||
}));
|
}));
|
||||||
|
newBarrierCollider.data = {
|
||||||
|
hardPushback: true
|
||||||
|
};
|
||||||
|
|
||||||
if (self.showCriticalCoordinateLabels) {
|
if (self.showCriticalCoordinateLabels) {
|
||||||
for (let i = 0; i < boundaryObj.length; ++i) {
|
for (let i = 0; i < boundaryObj.length; ++i) {
|
||||||
@ -835,7 +838,7 @@ cc.Class({
|
|||||||
*/
|
*/
|
||||||
// [WARNING] Don't try to get "prevRdf(i.e. renderFrameId == latest-1)" by "self.recentRenderCache.getByFrameId(...)" here, as the cache might have been updated by asynchronous "onRoomDownsyncFrame(...)" calls!
|
// [WARNING] Don't try to get "prevRdf(i.e. renderFrameId == latest-1)" by "self.recentRenderCache.getByFrameId(...)" here, as the cache might have been updated by asynchronous "onRoomDownsyncFrame(...)" calls!
|
||||||
self.applyRoomDownsyncFrameDynamics(rdf, prevRdf);
|
self.applyRoomDownsyncFrameDynamics(rdf, prevRdf);
|
||||||
self.showDebugBoundaries();
|
self.showDebugBoundaries(rdf);
|
||||||
++self.renderFrameId; // [WARNING] It's important to increment the renderFrameId AFTER all the operations above!!!
|
++self.renderFrameId; // [WARNING] It's important to increment the renderFrameId AFTER all the operations above!!!
|
||||||
self.lastRenderFrameIdTriggeredAt = performance.now();
|
self.lastRenderFrameIdTriggeredAt = performance.now();
|
||||||
let t3 = performance.now();
|
let t3 = performance.now();
|
||||||
@ -980,7 +983,7 @@ cc.Class({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
showDebugBoundaries() {
|
showDebugBoundaries(rdf) {
|
||||||
const self = this;
|
const self = this;
|
||||||
if (self.showCriticalCoordinateLabels) {
|
if (self.showCriticalCoordinateLabels) {
|
||||||
let g = self.g;
|
let g = self.g;
|
||||||
@ -1089,42 +1092,102 @@ cc.Class({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const nextRenderFrameMeleeBullets = [];
|
const nextRenderFrameMeleeBullets = [];
|
||||||
|
|
||||||
// Guaranteed determinism regardless of traversal order
|
|
||||||
const jumpTriggered = new Array(self.playerRichInfoArr.length);
|
|
||||||
const movements = new Array(self.playerRichInfoArr.length);
|
|
||||||
const bulletPushbacks = new Array(self.playerRichInfoArr.length);
|
|
||||||
const effPushbacks = new Array(self.playerRichInfoArr.length);
|
const effPushbacks = new Array(self.playerRichInfoArr.length);
|
||||||
|
const hardPushbackNorms = new Array(self.playerRichInfoArr.length);
|
||||||
|
|
||||||
// Reset playerCollider position from the "virtual grid position"
|
// 1. Process player inputs
|
||||||
|
/*
|
||||||
|
[WARNING] Player input alone WOULD NOT take "characterState" into any "ATK_CHARACTER_STATE_IN_AIR_SET", only after the calculation of "effPushbacks" do we know exactly whether or not a player is "inAir", the finalize the transition of "thatPlayerInNextFrame.characterState".
|
||||||
|
*/
|
||||||
|
if (null != delayedInputFrame) {
|
||||||
|
const delayedInputFrameForPrevRenderFrame = self.getCachedInputFrameDownsyncWithPrediction(self._convertToInputFrameId(currRenderFrame.id - 1, self.inputDelayFrames));
|
||||||
|
const inputList = delayedInputFrame.inputList;
|
||||||
|
for (let j in self.playerRichInfoArr) {
|
||||||
|
const joinIndex = parseInt(j) + 1;
|
||||||
|
const playerRichInfo = self.playerRichInfoArr[j];
|
||||||
|
const playerId = playerRichInfo.id;
|
||||||
|
const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex;
|
||||||
|
const [currPlayerDownsync, thatPlayerInNextFrame] = [currRenderFrame.players[playerId], nextRenderFramePlayers[playerId]];
|
||||||
|
if (0 < thatPlayerInNextFrame.framesToRecover) {
|
||||||
|
// No need to process inputs for this player, but there might be bullet pushbacks on this player
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const decodedInput = self.ctrl.decodeInput(inputList[joinIndex - 1]);
|
||||||
|
const prevDecodedInput = (null == delayedInputFrameForPrevRenderFrame ? null : self.ctrl.decodeInput(delayedInputFrameForPrevRenderFrame.inputList[joinIndex - 1]));
|
||||||
|
const prevBtnALevel = (null == prevDecodedInput ? 0 : prevDecodedInput.btnALevel);
|
||||||
|
const prevBtnBLevel = (null == prevDecodedInput ? 0 : prevDecodedInput.btnBLevel);
|
||||||
|
if (1 == decodedInput.btnBLevel && 0 == prevBtnBLevel) {
|
||||||
|
const characStateAlreadyInAir = window.ATK_CHARACTER_STATE_IN_AIR_SET.has(thatPlayerInNextFrame.characterState);
|
||||||
|
const characStateIsInterruptWaivable = window.ATK_CHARACTER_STATE_INTERRUPT_WAIVE_SET.has(thatPlayerInNextFrame.characterState);
|
||||||
|
if (
|
||||||
|
!characStateAlreadyInAir
|
||||||
|
&&
|
||||||
|
characStateIsInterruptWaivable
|
||||||
|
) {
|
||||||
|
thatPlayerInNextFrame.velY = self.jumpingInitVelY;
|
||||||
|
console.log(`playerId=${playerId}, joinIndex=${joinIndex} triggered a rising-edge of btnB at renderFrame.id=${currRenderFrame.id}, delayedInputFrame.id=${delayedInputFrame.inputFrameId}, nextVelY=${thatPlayerInNextFrame.velY}, characStateAlreadyInAir=${characStateAlreadyInAir}, characStateIsInterruptWaivable=${characStateIsInterruptWaivable}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (1 == decodedInput.btnALevel && 0 == prevBtnALevel) {
|
||||||
|
// console.log(`playerId=${playerId} triggered a rising-edge of btnA at renderFrame.id=${currRenderFrame.id}, delayedInputFrame.id=${delayedInputFrame.inputFrameId}`);
|
||||||
|
if (self.bulletTriggerEnabled) {
|
||||||
|
const punchSkillId = 1;
|
||||||
|
const punch = window.pb.protos.MeleeBullet.create(self.meleeSkillConfig[punchSkillId]);
|
||||||
|
thatPlayerInNextFrame.framesToRecover = punch.recoveryFrames;
|
||||||
|
punch.battleLocalId = self.bulletBattleLocalIdCounter++;
|
||||||
|
punch.offenderJoinIndex = joinIndex;
|
||||||
|
punch.offenderPlayerId = playerId;
|
||||||
|
punch.originatedRenderFrameId = currRenderFrame.id;
|
||||||
|
nextRenderFrameMeleeBullets.push(punch);
|
||||||
|
// console.log(`A rising-edge of meleeBullet is created at renderFrame.id=${currRenderFrame.id}, delayedInputFrame.id=${delayedInputFrame.inputFrameId}: ${self._stringifyRecentInputCache(true)}`);
|
||||||
|
console.log(`A rising-edge of meleeBullet is created at renderFrame.id=${currRenderFrame.id}, delayedInputFrame.id=${delayedInputFrame.inputFrameId}`);
|
||||||
|
|
||||||
|
thatPlayerInNextFrame.characterState = window.ATK_CHARACTER_STATE.Atk1[0];
|
||||||
|
if (false == currPlayerDownsync.inAir) {
|
||||||
|
thatPlayerInNextFrame.velX = 0; // prohibits simultaneous movement with Atk1 on the ground
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (0 == decodedInput.btnALevel && 1 == prevBtnALevel) {
|
||||||
|
// console.log(`playerId=${playerId} triggered a falling-edge of btnA at renderFrame.id=${currRenderFrame.id}, delayedInputFrame.id=${delayedInputFrame.inputFrameId}`);
|
||||||
|
} else {
|
||||||
|
// No bullet trigger, process joystick movement inputs.
|
||||||
|
if (0 != decodedInput.dx || 0 != decodedInput.dy) {
|
||||||
|
// Update directions and thus would eventually update moving animation accordingly
|
||||||
|
thatPlayerInNextFrame.dirX = decodedInput.dx;
|
||||||
|
thatPlayerInNextFrame.dirY = decodedInput.dy;
|
||||||
|
thatPlayerInNextFrame.velX = decodedInput.dx * currPlayerDownsync.speed;
|
||||||
|
thatPlayerInNextFrame.characterState = window.ATK_CHARACTER_STATE.Walking[0];
|
||||||
|
} else {
|
||||||
|
thatPlayerInNextFrame.characterState = window.ATK_CHARACTER_STATE.Idle1[0];
|
||||||
|
thatPlayerInNextFrame.velX = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Process player movement
|
||||||
for (let j in self.playerRichInfoArr) {
|
for (let j in self.playerRichInfoArr) {
|
||||||
const joinIndex = parseInt(j) + 1;
|
const joinIndex = parseInt(j) + 1;
|
||||||
jumpTriggered[joinIndex - 1] = false;
|
|
||||||
movements[joinIndex - 1] = [0.0, 0.0];
|
|
||||||
bulletPushbacks[joinIndex - 1] = [0.0, 0.0];
|
|
||||||
effPushbacks[joinIndex - 1] = [0.0, 0.0];
|
effPushbacks[joinIndex - 1] = [0.0, 0.0];
|
||||||
const playerRichInfo = self.playerRichInfoArr[j];
|
const playerRichInfo = self.playerRichInfoArr[j];
|
||||||
const playerId = playerRichInfo.id;
|
const playerId = playerRichInfo.id;
|
||||||
const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex;
|
const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex;
|
||||||
const playerCollider = collisionSysMap.get(collisionPlayerIndex);
|
const playerCollider = collisionSysMap.get(collisionPlayerIndex);
|
||||||
const currPlayerDownsync = currRenderFrame.players[playerId];
|
const [currPlayerDownsync, thatPlayerInNextFrame] = [currRenderFrame.players[playerId], nextRenderFramePlayers[playerId]];
|
||||||
const thatPlayerInNextFrame = nextRenderFramePlayers[playerId];
|
|
||||||
|
|
||||||
const newVx = currPlayerDownsync.virtualGridX;
|
// Reset playerCollider position from the "virtual grid position"
|
||||||
const newVy = currPlayerDownsync.virtualGridY;
|
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);
|
[playerCollider.x, playerCollider.y] = self.virtualGridToPolygonColliderAnchorPos(newVx, newVy, self.playerRichInfoArr[joinIndex - 1].colliderRadius, self.playerRichInfoArr[joinIndex - 1].colliderRadius);
|
||||||
|
|
||||||
// Process gravity before anyother interaction, by now "currPlayerDownsync.velX & velY" are properly snapped to be parallel to the edge of its standing platform if necessary
|
|
||||||
[movements[joinIndex - 1][0], movements[joinIndex - 1][1]] = self.virtualGridToWorldPos(currPlayerDownsync.velX, currPlayerDownsync.velY);
|
|
||||||
playerCollider.x += movements[joinIndex - 1][0];
|
|
||||||
playerCollider.y += movements[joinIndex - 1][1];
|
|
||||||
if (currPlayerDownsync.inAir) {
|
if (currPlayerDownsync.inAir) {
|
||||||
thatPlayerInNextFrame.velX += self.gravityX;
|
thatPlayerInNextFrame.velX += self.gravityX;
|
||||||
thatPlayerInNextFrame.velY += self.gravityY;
|
thatPlayerInNextFrame.velY += self.gravityY;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check bullet-anything collisions first, because the pushbacks caused by bullets might later be reverted by player-barrier collision
|
// 3. Add bullet colliders into collision system
|
||||||
const bulletColliders = new Map(); // Will all be removed at the end of `applyInputFrameDownsyncDynamicsOnSingleRenderFrame` due to the need for being rollback-compatible
|
const bulletColliders = new Map(); // Will all be removed at the end of `applyInputFrameDownsyncDynamicsOnSingleRenderFrame` due to the need for being rollback-compatible
|
||||||
const removedBulletsAtCurrFrame = new Set();
|
const removedBulletsAtCurrFrame = new Set();
|
||||||
for (let k in currRenderFrame.meleeBullets) {
|
for (let k in currRenderFrame.meleeBullets) {
|
||||||
@ -1156,32 +1219,91 @@ cc.Class({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 4. Invoke collision system stepping
|
||||||
collisionSys.update();
|
collisionSys.update();
|
||||||
const result1 = collisionSys.createResult(); // Can I reuse a "self.collisionSysResult" object throughout the whole battle?
|
const result = collisionSys.createResult(); // Can I reuse a "self.collisionSysResult" object throughout the whole battle?
|
||||||
|
|
||||||
|
// 5. Calc pushbacks for each player (after its movement) w/o bullets
|
||||||
|
for (let j in self.playerRichInfoArr) {
|
||||||
|
const joinIndex = parseInt(j) + 1;
|
||||||
|
const playerRichInfo = self.playerRichInfoArr[j];
|
||||||
|
const playerId = playerRichInfo.id;
|
||||||
|
const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex;
|
||||||
|
const playerCollider = collisionSysMap.get(collisionPlayerIndex);
|
||||||
|
const potentials = playerCollider.potentials();
|
||||||
|
hardPushbackNorms[joinIndex - 1] = self.calcHardPushbacksNorms(playerCollider, potentials, result, self.snapIntoPlatformThreshold, self.snapIntoPlatformOverlap, joinIndex, effPushbacks);
|
||||||
|
|
||||||
|
const [currPlayerDownsync, thatPlayerInNextFrame] = [currRenderFrame.players[playerId], nextRenderFramePlayers[playerId]];
|
||||||
|
let fallStopping = false;
|
||||||
|
for (const potential of potentials) {
|
||||||
|
// ignore bullets for this step
|
||||||
|
if (null != potential.data && null != potential.data.offenderJoinIndex) continue;
|
||||||
|
// Test if the player collides with the wall/another player
|
||||||
|
if (!playerCollider.collides(potential, result)) continue;
|
||||||
|
|
||||||
|
const normAlignmentWithGravity = (result.overlap_x * 0 + result.overlap_y * (-1.0));
|
||||||
|
const landedOnGravityPushback = (self.snapIntoPlatformThreshold < normAlignmentWithGravity); // prevents false snapping on the lateral sides
|
||||||
|
// Push the player out of the wall/another player
|
||||||
|
let [pushbackX, pushbackY] = [result.overlap * result.overlap_x, result.overlap * result.overlap_y];
|
||||||
|
if (landedOnGravityPushback) {
|
||||||
|
// kindly note that one player might land on top of another player, and snapping is also required in such case
|
||||||
|
[pushbackX, pushbackY] = [(result.overlap - self.snapIntoPlatformOverlap) * result.overlap_x, (result.overlap - self.snapIntoPlatformOverlap) * result.overlap_y];
|
||||||
|
}
|
||||||
|
for (let hardPushbackNorm of hardPushbackNorms[joinIndex - 1]) {
|
||||||
|
// remove pushback component on the directions of "hardPushbackNorms[joinIndex-1]" (by now those hardPushbacks are already accounted in "effPushbacks[joinIndex-1]")
|
||||||
|
const projectedMagnitude = pushbackX * hardPushbackNorm[0] + pushbackY * hardPushbackNorm[1];
|
||||||
|
pushbackX -= projectedMagnitude * hardPushbackNorm[0];
|
||||||
|
pushbackY -= projectedMagnitude * hardPushbackNorm[1];
|
||||||
|
}
|
||||||
|
thatPlayerInNextFrame.inAir &= !landedOnGravityPushback;
|
||||||
|
fallStopping |= (currPlayerDownsync.inAir && landedOnGravityPushback);
|
||||||
|
|
||||||
|
effPushbacks[joinIndex - 1][0] += pushbackX;
|
||||||
|
effPushbacks[joinIndex - 1][1] += pushbackY;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fallStopping) {
|
||||||
|
thatPlayerInNextFrame.velX = 0;
|
||||||
|
thatPlayerInNextFrame.velY = 0;
|
||||||
|
thatPlayerInNextFrame.characterState = window.ATK_CHARACTER_STATE.Idle1[0];
|
||||||
|
thatPlayerInNextFrame.framesToRecover = 0;
|
||||||
|
}
|
||||||
|
if (currPlayerDownsync.inAir) {
|
||||||
|
thatPlayerInNextFrame.characterState = window.toInAirConjugate(thatPlayerInNextFrame.characterState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. Check bullet-anything collisions
|
||||||
bulletColliders.forEach((bulletCollider, collisionBulletIndex) => {
|
bulletColliders.forEach((bulletCollider, collisionBulletIndex) => {
|
||||||
const potentials = bulletCollider.potentials();
|
const potentials = bulletCollider.potentials();
|
||||||
const offender = currRenderFrame.players[bulletCollider.data.offenderPlayerId];
|
const offender = currRenderFrame.players[bulletCollider.data.offenderPlayerId];
|
||||||
let shouldRemove = false;
|
let shouldRemove = false;
|
||||||
for (const potential of potentials) {
|
for (const potential of potentials) {
|
||||||
if (null != potential.data && potential.data.joinIndex == bulletCollider.data.offenderJoinIndex) continue;
|
if (null != potential.data && potential.data.joinIndex == bulletCollider.data.offenderJoinIndex) continue;
|
||||||
if (!bulletCollider.collides(potential, result1)) continue;
|
if (!bulletCollider.collides(potential, result)) continue;
|
||||||
if (null != potential.data && null !== potential.data.joinIndex) {
|
if (null != potential.data && null != potential.data.joinIndex) {
|
||||||
const joinIndex = potential.data.joinIndex;
|
const joinIndex = potential.data.joinIndex;
|
||||||
let xfac = 1;
|
let xfac = 1;
|
||||||
if (0 > offender.dirX) {
|
if (0 > offender.dirX) {
|
||||||
xfac = -1;
|
xfac = -1;
|
||||||
}
|
}
|
||||||
bulletPushbacks[joinIndex - 1][0] += xfac * bulletCollider.data.pushback; // Only for straight punch, there's no y-pushback
|
let [pushbackX, pushbackY] = [-xfac * bulletCollider.data.pushback, 0]; // Only for straight punch, there's no y-pushback
|
||||||
bulletPushbacks[joinIndex - 1][1] += 0;
|
for (let hardPushbackNorm of hardPushbackNorms[joinIndex - 1]) {
|
||||||
const thatAckedPlayerInCurFrame = currRenderFrame.players[potential.data.id];
|
// remove pushback component on the directions of "hardPushbackNorms[joinIndex-1]" (by now those hardPushbacks are already accounted in "effPushbacks[joinIndex-1]")
|
||||||
const thatAckedPlayerInNextFrame = nextRenderFramePlayers[potential.data.id];
|
const projectedMagnitude = pushbackX * hardPushbackNorm[0] + pushbackY * hardPushbackNorm[1];
|
||||||
thatAckedPlayerInNextFrame.characterState = window.ATK_CHARACTER_STATE.Atked1[0];
|
pushbackX -= projectedMagnitude * hardPushbackNorm[0];
|
||||||
if (thatAckedPlayerInCurFrame.inAir) {
|
pushbackY -= projectedMagnitude * hardPushbackNorm[1];
|
||||||
thatAckedPlayerInNextFrame.characterState = window.toInAirConjugate(thatAckedPlayerInNextFrame.characterState);
|
// TODO: What if a bullet knocks down the attacked player into ground?
|
||||||
}
|
}
|
||||||
const oldFramesToRecover = thatAckedPlayerInNextFrame.framesToRecover;
|
effPushbacks[joinIndex - 1][0] += pushbackX;
|
||||||
thatAckedPlayerInNextFrame.framesToRecover = (oldFramesToRecover > bulletCollider.data.hitStunFrames ? oldFramesToRecover : bulletCollider.data.hitStunFrames); // In case the hit player is already stun, we extend it
|
effPushbacks[joinIndex - 1][1] += pushbackY;
|
||||||
|
const [atkedPlayerInCurFrame, atkedPlayerInNextFrame] = [currRenderFrame.players[potential.data.id], nextRenderFramePlayers[potential.data.id]];
|
||||||
|
atkedPlayerInNextFrame.characterState = window.ATK_CHARACTER_STATE.Atked1[0];
|
||||||
|
if (atkedPlayerInCurFrame.inAir) {
|
||||||
|
atkedPlayerInNextFrame.characterState = window.toInAirConjugate(atkedPlayerInNextFrame.characterState);
|
||||||
|
}
|
||||||
|
const oldFramesToRecover = atkedPlayerInNextFrame.framesToRecover;
|
||||||
|
atkedPlayerInNextFrame.framesToRecover = (oldFramesToRecover > bulletCollider.data.hitStunFrames ? oldFramesToRecover : bulletCollider.data.hitStunFrames); // In case the hit player is already stun, we extend it
|
||||||
}
|
}
|
||||||
shouldRemove = true;
|
shouldRemove = true;
|
||||||
}
|
}
|
||||||
@ -1203,143 +1325,7 @@ cc.Class({
|
|||||||
nextRenderFrameMeleeBullets.push(meleeBullet);
|
nextRenderFrameMeleeBullets.push(meleeBullet);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process player inputs
|
// 7. Get players out of stuck barriers if there's any
|
||||||
if (null != delayedInputFrame) {
|
|
||||||
const delayedInputFrameForPrevRenderFrame = self.getCachedInputFrameDownsyncWithPrediction(self._convertToInputFrameId(currRenderFrame.id - 1, self.inputDelayFrames));
|
|
||||||
const inputList = delayedInputFrame.inputList;
|
|
||||||
for (let j in self.playerRichInfoArr) {
|
|
||||||
const joinIndex = parseInt(j) + 1;
|
|
||||||
effPushbacks[joinIndex - 1] = [0.0, 0.0];
|
|
||||||
const playerRichInfo = self.playerRichInfoArr[j];
|
|
||||||
const playerId = playerRichInfo.id;
|
|
||||||
const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex;
|
|
||||||
const playerCollider = collisionSysMap.get(collisionPlayerIndex);
|
|
||||||
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
|
|
||||||
playerCollider.x += bulletPushbacks[joinIndex - 1][0];
|
|
||||||
playerCollider.y += bulletPushbacks[joinIndex - 1][1];
|
|
||||||
if (0 != bulletPushbacks[joinIndex - 1][0] || 0 != bulletPushbacks[joinIndex - 1][1]) {
|
|
||||||
console.log(`playerId=${playerId}, joinIndex=${joinIndex} is pushbacked back by ${bulletPushbacks[joinIndex - 1]} by bullet impacts, now its framesToRecover is ${thatPlayerInNextFrame.framesToRecover}`);
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const decodedInput = self.ctrl.decodeInput(inputList[joinIndex - 1]);
|
|
||||||
const prevDecodedInput = (null == delayedInputFrameForPrevRenderFrame ? null : self.ctrl.decodeInput(delayedInputFrameForPrevRenderFrame.inputList[joinIndex - 1]));
|
|
||||||
const prevBtnALevel = (null == prevDecodedInput ? 0 : prevDecodedInput.btnALevel);
|
|
||||||
const prevBtnBLevel = (null == prevDecodedInput ? 0 : prevDecodedInput.btnBLevel);
|
|
||||||
/*
|
|
||||||
[WARNING] Player input alone WOULD NOT take "characterState" into any "ATK_CHARACTER_STATE_IN_AIR_SET", only after the calculation of "effPushbacks" do we know exactly whether or not a player is "inAir", the finalize the transition of "thatPlayerInNextFrame.characterState".
|
|
||||||
*/
|
|
||||||
if (1 == decodedInput.btnBLevel && 0 == prevBtnBLevel) {
|
|
||||||
const characStateAlreadyInAir = window.ATK_CHARACTER_STATE_IN_AIR_SET.has(thatPlayerInNextFrame.characterState);
|
|
||||||
const characStateIsInterruptWaivable = window.ATK_CHARACTER_STATE_INTERRUPT_WAIVE_SET.has(thatPlayerInNextFrame.characterState);
|
|
||||||
if (
|
|
||||||
!characStateAlreadyInAir
|
|
||||||
&&
|
|
||||||
characStateIsInterruptWaivable
|
|
||||||
) {
|
|
||||||
thatPlayerInNextFrame.velY = self.jumpingInitVelY;
|
|
||||||
jumpTriggered[joinIndex - 1] = true;
|
|
||||||
console.log(`playerId=${playerId}, joinIndex=${joinIndex} triggered a rising-edge of btnB at renderFrame.id=${currRenderFrame.id}, delayedInputFrame.id=${delayedInputFrame.inputFrameId}, nextVelY=${thatPlayerInNextFrame.velY}, characStateAlreadyInAir=${characStateAlreadyInAir}, characStateIsInterruptWaivable=${characStateIsInterruptWaivable}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (1 == decodedInput.btnALevel && 0 == prevBtnALevel) {
|
|
||||||
// console.log(`playerId=${playerId} triggered a rising-edge of btnA at renderFrame.id=${currRenderFrame.id}, delayedInputFrame.id=${delayedInputFrame.inputFrameId}`);
|
|
||||||
if (self.bulletTriggerEnabled) {
|
|
||||||
const punchSkillId = 1;
|
|
||||||
const punch = window.pb.protos.MeleeBullet.create(self.meleeSkillConfig[punchSkillId]);
|
|
||||||
thatPlayerInNextFrame.framesToRecover = punch.recoveryFrames;
|
|
||||||
punch.battleLocalId = self.bulletBattleLocalIdCounter++;
|
|
||||||
punch.offenderJoinIndex = joinIndex;
|
|
||||||
punch.offenderPlayerId = playerId;
|
|
||||||
punch.originatedRenderFrameId = currRenderFrame.id;
|
|
||||||
nextRenderFrameMeleeBullets.push(punch);
|
|
||||||
// console.log(`A rising-edge of meleeBullet is created at renderFrame.id=${currRenderFrame.id}, delayedInputFrame.id=${delayedInputFrame.inputFrameId}: ${self._stringifyRecentInputCache(true)}`);
|
|
||||||
// console.log(`A rising-edge of meleeBullet is created at renderFrame.id=${currRenderFrame.id}, delayedInputFrame.id=${delayedInputFrame.inputFrameId}`);
|
|
||||||
|
|
||||||
thatPlayerInNextFrame.characterState = window.ATK_CHARACTER_STATE.Atk1[0];
|
|
||||||
if (false == currPlayerDownsync.inAir) {
|
|
||||||
thatPlayerInNextFrame.velX = 0; // prohibits simultaneous movement with Atk1 on the ground
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (0 == decodedInput.btnALevel && 1 == prevBtnALevel) {
|
|
||||||
// console.log(`playerId=${playerId} triggered a falling-edge of btnA at renderFrame.id=${currRenderFrame.id}, delayedInputFrame.id=${delayedInputFrame.inputFrameId}`);
|
|
||||||
} else {
|
|
||||||
// No bullet trigger, process joystick movement inputs (except for jumping).
|
|
||||||
if (0 != decodedInput.dx || 0 != decodedInput.dy) {
|
|
||||||
// Update directions and thus would eventually update moving animation accordingly
|
|
||||||
thatPlayerInNextFrame.dirX = decodedInput.dx;
|
|
||||||
thatPlayerInNextFrame.dirY = decodedInput.dy;
|
|
||||||
thatPlayerInNextFrame.velX = decodedInput.dx * currPlayerDownsync.speed;
|
|
||||||
thatPlayerInNextFrame.characterState = window.ATK_CHARACTER_STATE.Walking[0];
|
|
||||||
} else {
|
|
||||||
thatPlayerInNextFrame.characterState = window.ATK_CHARACTER_STATE.Idle1[0];
|
|
||||||
thatPlayerInNextFrame.velX = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (currPlayerDownsync.inAir) {
|
|
||||||
thatPlayerInNextFrame.characterState = window.toInAirConjugate(thatPlayerInNextFrame.characterState);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
collisionSys.update(); // by now all "bulletCollider"s are removed
|
|
||||||
const result2 = collisionSys.createResult(); // Can I reuse a "self.collisionSysResult" object throughout the whole battle?
|
|
||||||
|
|
||||||
for (let j in self.playerRichInfoArr) {
|
|
||||||
const joinIndex = parseInt(j) + 1;
|
|
||||||
const playerId = self.playerRichInfoArr[j].id;
|
|
||||||
const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex;
|
|
||||||
const playerCollider = collisionSysMap.get(collisionPlayerIndex);
|
|
||||||
const potentials = playerCollider.potentials();
|
|
||||||
const currPlayerDownsync = currRenderFrame.players[playerId];
|
|
||||||
const thatPlayerInNextFrame = nextRenderFramePlayers[playerId];
|
|
||||||
let fallStopping = false;
|
|
||||||
let [snappedIntoPlatformEx, snappedIntoPlatformEy] = [null, null];
|
|
||||||
for (const potential of potentials) {
|
|
||||||
// Test if the player collides with the wall
|
|
||||||
if (!playerCollider.collides(potential, result2)) continue;
|
|
||||||
// Push the player out of the wall
|
|
||||||
let [pushbackX, pushbackY] = [result2.overlap * result2.overlap_x, result2.overlap * result2.overlap_y];
|
|
||||||
if (null == potential.data) {
|
|
||||||
// "null == potential.data" implies a barrier
|
|
||||||
const normAlignmentWithGravity = (result2.overlap_x * 0 + result2.overlap_y * (-1.0));
|
|
||||||
const flatEnough = (self.snapIntoPlatformThreshold < normAlignmentWithGravity); // prevents false snapping on the lateral sides
|
|
||||||
const remainsNotInAir = (!currPlayerDownsync.inAir && flatEnough);
|
|
||||||
const localFallStopping = (currPlayerDownsync.inAir && flatEnough);
|
|
||||||
if (remainsNotInAir || localFallStopping) {
|
|
||||||
fallStopping |= localFallStopping;
|
|
||||||
[pushbackX, pushbackY] = [(result2.overlap - self.snapIntoPlatformOverlap) * result2.overlap_x, (result2.overlap - self.snapIntoPlatformOverlap) * result2.overlap_y]
|
|
||||||
// [overlay_x, overlap_y] is the unit vector that points into the platform; FIXME: Should only assign to [snappedIntoPlatformEx, snappedIntoPlatformEy] at most once!
|
|
||||||
snappedIntoPlatformEx = -result2.overlap_y;
|
|
||||||
snappedIntoPlatformEy = result2.overlap_x;
|
|
||||||
if (snappedIntoPlatformEx * currPlayerDownsync.dirX + snappedIntoPlatformEy * currPlayerDownsync.dirY) {
|
|
||||||
[snappedIntoPlatformEx, snappedIntoPlatformEy] = [-snappedIntoPlatformEx, -snappedIntoPlatformEy];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// What if we're on the edge of 2 barriers? Would adding up make an unexpected bounce?
|
|
||||||
effPushbacks[joinIndex - 1][0] += pushbackX;
|
|
||||||
effPushbacks[joinIndex - 1][1] += pushbackY;
|
|
||||||
}
|
|
||||||
if (false == jumpTriggered[joinIndex - 1] && null != snappedIntoPlatformEx && null != snappedIntoPlatformEy) {
|
|
||||||
thatPlayerInNextFrame.inAir = false;
|
|
||||||
if (fallStopping) {
|
|
||||||
thatPlayerInNextFrame.velY = 0;
|
|
||||||
thatPlayerInNextFrame.velX = 0;
|
|
||||||
thatPlayerInNextFrame.characterState = window.ATK_CHARACTER_STATE.Idle1[0];
|
|
||||||
thatPlayerInNextFrame.framesToRecover = 0;
|
|
||||||
}
|
|
||||||
const dotProd = thatPlayerInNextFrame.velX * snappedIntoPlatformEx + thatPlayerInNextFrame.velY * snappedIntoPlatformEy;
|
|
||||||
[thatPlayerInNextFrame.velX, thatPlayerInNextFrame.velY] = [dotProd * snappedIntoPlatformEx, dotProd * snappedIntoPlatformEy];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get players out of stuck barriers if there's any
|
|
||||||
for (let j in self.playerRichInfoArr) {
|
for (let j in self.playerRichInfoArr) {
|
||||||
const joinIndex = parseInt(j) + 1;
|
const joinIndex = parseInt(j) + 1;
|
||||||
const playerId = self.playerRichInfoArr[j].id;
|
const playerId = self.playerRichInfoArr[j].id;
|
||||||
@ -1491,4 +1477,22 @@ cc.Class({
|
|||||||
const [wx, wy] = self.virtualGridToWorldPos(vx, vy);
|
const [wx, wy] = self.virtualGridToWorldPos(vx, vy);
|
||||||
return self.worldToPolygonColliderAnchorPos(wx, wy, halfBoundingW, halfBoundingH)
|
return self.worldToPolygonColliderAnchorPos(wx, wy, halfBoundingW, halfBoundingH)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
calcHardPushbacksNorms(collider, potentials, result, snapIntoPlatformThreshold, snapIntoPlatformOverlap, joinIndex, effPushbacks) {
|
||||||
|
let hardPushbackNorms = [];
|
||||||
|
let fallStopping = false;
|
||||||
|
for (const potential of potentials) {
|
||||||
|
if (null == potential.data || !(true == potential.data.hardPushback)) continue;
|
||||||
|
if (!collider.collides(potential, result)) continue;
|
||||||
|
// allow snapping into all colliders with {hardPushback: true}
|
||||||
|
const [pushbackX, pushbackY] = [(result.overlap - snapIntoPlatformOverlap) * result.overlap_x, (result.overlap - snapIntoPlatformOverlap) * result.overlap_y];
|
||||||
|
|
||||||
|
// [overlay_x, overlap_y] is the unit vector that points into the platform; FIXME: Should only assign to [snappedIntoPlatformEx, snappedIntoPlatformEy] at most once!
|
||||||
|
hardPushbackNorms.push([result.overlap_x, result.overlap_y]);
|
||||||
|
effPushbacks[joinIndex - 1][0] += pushbackX;
|
||||||
|
effPushbacks[joinIndex - 1][1] += pushbackY;
|
||||||
|
}
|
||||||
|
|
||||||
|
return hardPushbackNorms;
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
@ -13,7 +13,7 @@ cc.Class({
|
|||||||
onLoad() {
|
onLoad() {
|
||||||
const self = this;
|
const self = this;
|
||||||
window.mapIns = self;
|
window.mapIns = self;
|
||||||
self.showCriticalCoordinateLabels = false;
|
self.showCriticalCoordinateLabels = true;
|
||||||
|
|
||||||
cc.director.getCollisionManager().enabled = false;
|
cc.director.getCollisionManager().enabled = false;
|
||||||
|
|
||||||
@ -73,7 +73,7 @@ cc.Class({
|
|||||||
/*
|
/*
|
||||||
[WARNING] As when a character is standing on a barrier, if not carefully curated there MIGHT BE a bouncing sequence of "[(inAir -> dropIntoBarrier ->), (notInAir -> pushedOutOfBarrier ->)], [(inAir -> ..."
|
[WARNING] As when a character is standing on a barrier, if not carefully curated there MIGHT BE a bouncing sequence of "[(inAir -> dropIntoBarrier ->), (notInAir -> pushedOutOfBarrier ->)], [(inAir -> ..."
|
||||||
|
|
||||||
Moreover, "snapIntoPlatformOverlap" should be small enough such that the jumping initial "velY" can escape from it by 1 renderFrame (when jumping is triggered, the character is waived from snappig for 1 renderFrame).
|
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.1;
|
self.snapIntoPlatformOverlap = 0.1;
|
||||||
self.snapIntoPlatformThreshold = 0.5; // a platform must be "horizontal enough" for a character to "stand on"
|
self.snapIntoPlatformThreshold = 0.5; // a platform must be "horizontal enough" for a character to "stand on"
|
||||||
@ -119,6 +119,9 @@ cc.Class({
|
|||||||
const newBarrier = self.collisionSys.createPolygon(x0, y0, Array.from(boundaryObj, p => {
|
const newBarrier = self.collisionSys.createPolygon(x0, y0, Array.from(boundaryObj, p => {
|
||||||
return [p.x, p.y];
|
return [p.x, p.y];
|
||||||
}));
|
}));
|
||||||
|
newBarrier.data = {
|
||||||
|
hardPushback: true
|
||||||
|
};
|
||||||
|
|
||||||
if (false && self.showCriticalCoordinateLabels) {
|
if (false && self.showCriticalCoordinateLabels) {
|
||||||
for (let i = 0; i < boundaryObj.length; ++i) {
|
for (let i = 0; i < boundaryObj.length; ++i) {
|
||||||
@ -225,7 +228,7 @@ cc.Class({
|
|||||||
|
|
||||||
const [prevRdf, rdf] = self.rollbackAndChase(self.renderFrameId, self.renderFrameId + 1, self.collisionSys, self.collisionSysMap, false);
|
const [prevRdf, rdf] = self.rollbackAndChase(self.renderFrameId, self.renderFrameId + 1, self.collisionSys, self.collisionSysMap, false);
|
||||||
self.applyRoomDownsyncFrameDynamics(rdf, prevRdf);
|
self.applyRoomDownsyncFrameDynamics(rdf, prevRdf);
|
||||||
self.showDebugBoundaries();
|
self.showDebugBoundaries(rdf);
|
||||||
++self.renderFrameId;
|
++self.renderFrameId;
|
||||||
self.lastRenderFrameIdTriggeredAt = performance.now();
|
self.lastRenderFrameIdTriggeredAt = performance.now();
|
||||||
let t3 = performance.now();
|
let t3 = performance.now();
|
||||||
|
Loading…
Reference in New Issue
Block a user