mirror of
https://github.com/genxium/DelayNoMore
synced 2024-12-26 03:39:00 +00:00
Added necessary js type exposure to jsexport.
This commit is contained in:
parent
df5c9fda30
commit
8a9d449d83
@ -1401,8 +1401,7 @@ func (pR *Room) applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputF
|
|||||||
}
|
}
|
||||||
offenderWx, offenderWy := VirtualGridToWorldPos(offender.VirtualGridX, offender.VirtualGridY, pR.VirtualGridToWorldRatio)
|
offenderWx, offenderWy := VirtualGridToWorldPos(offender.VirtualGridX, offender.VirtualGridY, pR.VirtualGridToWorldRatio)
|
||||||
bulletWx, bulletWy := offenderWx+xfac*meleeBullet.HitboxOffset, offenderWy
|
bulletWx, bulletWy := offenderWx+xfac*meleeBullet.HitboxOffset, offenderWy
|
||||||
newBulletCollider := GenerateRectCollider(bulletWx, bulletWy, meleeBullet.HitboxSize.X, meleeBullet.HitboxSize.Y, topPadding, bottomPadding, leftPadding, rightPadding, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY, "MeleeBullet")
|
newBulletCollider := GenerateRectCollider(bulletWx, bulletWy, meleeBullet.HitboxSize.X, meleeBullet.HitboxSize.Y, topPadding, bottomPadding, leftPadding, rightPadding, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY, meleeBullet, "MeleeBullet")
|
||||||
newBulletCollider.Data = meleeBullet
|
|
||||||
pR.Space.Add(newBulletCollider)
|
pR.Space.Add(newBulletCollider)
|
||||||
collisionSysMap[collisionBulletIndex] = newBulletCollider
|
collisionSysMap[collisionBulletIndex] = newBulletCollider
|
||||||
bulletColliders[collisionBulletIndex] = newBulletCollider
|
bulletColliders[collisionBulletIndex] = newBulletCollider
|
||||||
@ -1625,8 +1624,7 @@ func (pR *Room) refreshColliders(spaceW, spaceH int32) {
|
|||||||
for _, player := range pR.Players {
|
for _, player := range pR.Players {
|
||||||
wx, wy := VirtualGridToWorldPos(player.VirtualGridX, player.VirtualGridY, pR.VirtualGridToWorldRatio)
|
wx, wy := VirtualGridToWorldPos(player.VirtualGridX, player.VirtualGridY, pR.VirtualGridToWorldRatio)
|
||||||
colliderWidth, colliderHeight := player.ColliderRadius*2, player.ColliderRadius*4
|
colliderWidth, colliderHeight := player.ColliderRadius*2, player.ColliderRadius*4
|
||||||
playerCollider := GenerateRectCollider(wx, wy, colliderWidth, colliderHeight, topPadding, bottomPadding, leftPadding, rightPadding, 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 := GenerateRectCollider(wx, wy, colliderWidth, colliderHeight, topPadding, bottomPadding, leftPadding, rightPadding, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY, player, "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)
|
pR.Space.Add(playerCollider)
|
||||||
// Keep track of the collider in "pR.CollisionSysMap"
|
// Keep track of the collider in "pR.CollisionSysMap"
|
||||||
joinIndex := player.JoinIndex
|
joinIndex := player.JoinIndex
|
||||||
@ -1637,8 +1635,7 @@ func (pR *Room) refreshColliders(spaceW, spaceH int32) {
|
|||||||
|
|
||||||
for _, barrier := range pR.Barriers {
|
for _, barrier := range pR.Barriers {
|
||||||
boundaryUnaligned := barrier.Boundary
|
boundaryUnaligned := barrier.Boundary
|
||||||
barrierCollider := GenerateConvexPolygonCollider(boundaryUnaligned, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY, "Barrier")
|
barrierCollider := GenerateConvexPolygonCollider(boundaryUnaligned, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY, barrier, "Barrier")
|
||||||
barrierCollider.Data = barrier
|
|
||||||
pR.Space.Add(barrierCollider)
|
pR.Space.Add(barrierCollider)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,7 @@ func NewWorldColliderDisplay(game *Game, stageDiscreteW, stageDiscreteH, stageTi
|
|||||||
topPadding, bottomPadding, leftPadding, rightPadding := snapIntoPlatformOverlap, snapIntoPlatformOverlap, snapIntoPlatformOverlap, snapIntoPlatformOverlap
|
topPadding, bottomPadding, leftPadding, rightPadding := snapIntoPlatformOverlap, snapIntoPlatformOverlap, snapIntoPlatformOverlap, snapIntoPlatformOverlap
|
||||||
for i, playerPos := range playerPosList.Eles {
|
for i, playerPos := range playerPosList.Eles {
|
||||||
colliderWidth, colliderHeight := playerColliderRadius*2, playerColliderRadius*4
|
colliderWidth, colliderHeight := playerColliderRadius*2, playerColliderRadius*4
|
||||||
playerCollider := GenerateRectCollider(playerPos.X, playerPos.Y, colliderWidth, colliderHeight, topPadding, bottomPadding, leftPadding, rightPadding, 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"
|
playerCollider := GenerateRectCollider(playerPos.X, playerPos.Y, colliderWidth, colliderHeight, topPadding, bottomPadding, leftPadding, rightPadding, spaceOffsetX, spaceOffsetY, nil, "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))))
|
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
|
playerColliders[i] = playerCollider
|
||||||
space.Add(playerCollider)
|
space.Add(playerCollider)
|
||||||
@ -53,7 +53,7 @@ func NewWorldColliderDisplay(game *Game, stageDiscreteW, stageDiscreteH, stageTi
|
|||||||
|
|
||||||
barrierLocalId := 0
|
barrierLocalId := 0
|
||||||
for _, barrierUnaligned := range barrierList.Eles {
|
for _, barrierUnaligned := range barrierList.Eles {
|
||||||
barrierCollider := GenerateConvexPolygonCollider(barrierUnaligned, spaceOffsetX, spaceOffsetY, "Barrier")
|
barrierCollider := GenerateConvexPolygonCollider(barrierUnaligned, spaceOffsetX, spaceOffsetY, nil, "Barrier")
|
||||||
Logger.Debug(fmt.Sprintf("Added barrier: shape=%v", ConvexPolygonStr(barrierCollider.Shape.(*resolv.ConvexPolygon))))
|
Logger.Debug(fmt.Sprintf("Added barrier: shape=%v", ConvexPolygonStr(barrierCollider.Shape.(*resolv.ConvexPolygon))))
|
||||||
space.Add(barrierCollider)
|
space.Add(barrierCollider)
|
||||||
barrierLocalId++
|
barrierLocalId++
|
||||||
@ -122,7 +122,7 @@ func NewWorldColliderDisplay(game *Game, stageDiscreteW, stageDiscreteH, stageTi
|
|||||||
xfac := float64(1.0)
|
xfac := float64(1.0)
|
||||||
offenderWx, offenderWy := playerPosList.Eles[0].X, playerPosList.Eles[0].Y
|
offenderWx, offenderWy := playerPosList.Eles[0].X, playerPosList.Eles[0].Y
|
||||||
bulletWx, bulletWy := offenderWx+xfac*meleeBullet.HitboxOffset, offenderWy
|
bulletWx, bulletWy := offenderWx+xfac*meleeBullet.HitboxOffset, offenderWy
|
||||||
newBulletCollider := GenerateRectCollider(bulletWx, bulletWy, meleeBullet.HitboxSize.X, meleeBullet.HitboxSize.Y, topPadding, bottomPadding, leftPadding, rightPadding, spaceOffsetX, spaceOffsetY, "MeleeBullet")
|
newBulletCollider := GenerateRectCollider(bulletWx, bulletWy, meleeBullet.HitboxSize.X, meleeBullet.HitboxSize.Y, topPadding, bottomPadding, leftPadding, rightPadding, spaceOffsetX, spaceOffsetY, nil, "MeleeBullet")
|
||||||
space.Add(newBulletCollider)
|
space.Add(newBulletCollider)
|
||||||
bulletShape := newBulletCollider.Shape.(*resolv.ConvexPolygon)
|
bulletShape := newBulletCollider.Shape.(*resolv.ConvexPolygon)
|
||||||
Logger.Warn(fmt.Sprintf("bullet ->: Added bullet collider to space: a=%v", ConvexPolygonStr(bulletShape)))
|
Logger.Warn(fmt.Sprintf("bullet ->: Added bullet collider to space: a=%v", ConvexPolygonStr(bulletShape)))
|
||||||
@ -145,7 +145,7 @@ func NewWorldColliderDisplay(game *Game, stageDiscreteW, stageDiscreteH, stageTi
|
|||||||
offenderWx, offenderWy := playerPosList.Eles[1].X, playerPosList.Eles[1].Y
|
offenderWx, offenderWy := playerPosList.Eles[1].X, playerPosList.Eles[1].Y
|
||||||
bulletWx, bulletWy := offenderWx+xfac*meleeBullet.HitboxOffset, offenderWy
|
bulletWx, bulletWy := offenderWx+xfac*meleeBullet.HitboxOffset, offenderWy
|
||||||
|
|
||||||
newBulletCollider := GenerateRectCollider(bulletWx, bulletWy, meleeBullet.HitboxSize.X, meleeBullet.HitboxSize.Y, topPadding, bottomPadding, leftPadding, rightPadding, spaceOffsetX, spaceOffsetY, "MeleeBullet")
|
newBulletCollider := GenerateRectCollider(bulletWx, bulletWy, meleeBullet.HitboxSize.X, meleeBullet.HitboxSize.Y, topPadding, bottomPadding, leftPadding, rightPadding, spaceOffsetX, spaceOffsetY, nil, "MeleeBullet")
|
||||||
space.Add(newBulletCollider)
|
space.Add(newBulletCollider)
|
||||||
bulletShape := newBulletCollider.Shape.(*resolv.ConvexPolygon)
|
bulletShape := newBulletCollider.Shape.(*resolv.ConvexPolygon)
|
||||||
Logger.Warn(fmt.Sprintf("bullet <-: Added bullet collider to space: a=%v", ConvexPolygonStr(bulletShape)))
|
Logger.Warn(fmt.Sprintf("bullet <-: Added bullet collider to space: a=%v", ConvexPolygonStr(bulletShape)))
|
||||||
|
@ -22,19 +22,20 @@ func RectCenterStr(body *resolv.Object, halfBoundingW, halfBoundingH, topPadding
|
|||||||
return fmt.Sprintf("{%.2f, %.2f}", body.X+leftPadding+halfBoundingW-spaceOffsetX, body.Y+bottomPadding+halfBoundingH-spaceOffsetY)
|
return fmt.Sprintf("{%.2f, %.2f}", body.X+leftPadding+halfBoundingW-spaceOffsetX, body.Y+bottomPadding+halfBoundingH-spaceOffsetY)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GenerateRectCollider(wx, wy, w, h, topPadding, bottomPadding, leftPadding, rightPadding, spaceOffsetX, spaceOffsetY float64, tag string) *resolv.Object {
|
func GenerateRectCollider(wx, wy, w, h, topPadding, bottomPadding, leftPadding, rightPadding, spaceOffsetX, spaceOffsetY float64, data interface{}, tag string) *resolv.Object {
|
||||||
blX, blY := WorldToPolygonColliderBLPos(wx, wy, w*0.5, h*0.5, topPadding, bottomPadding, leftPadding, rightPadding, spaceOffsetX, spaceOffsetY)
|
blX, blY := WorldToPolygonColliderBLPos(wx, wy, w*0.5, h*0.5, topPadding, bottomPadding, leftPadding, rightPadding, spaceOffsetX, spaceOffsetY)
|
||||||
return generateRectColliderInCollisionSpace(blX, blY, leftPadding+w+rightPadding, bottomPadding+h+topPadding, tag)
|
return generateRectColliderInCollisionSpace(blX, blY, leftPadding+w+rightPadding, bottomPadding+h+topPadding, data, tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateRectColliderInCollisionSpace(blX, blY, w, h float64, tag string) *resolv.Object {
|
func generateRectColliderInCollisionSpace(blX, blY, w, h float64, data interface{}, 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
|
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)
|
shape := resolv.NewRectangle(0, 0, w, h)
|
||||||
collider.SetShape(shape)
|
collider.SetShape(shape)
|
||||||
|
collider.Data = data
|
||||||
return collider
|
return collider
|
||||||
}
|
}
|
||||||
|
|
||||||
func GenerateConvexPolygonCollider(unalignedSrc *Polygon2D, spaceOffsetX, spaceOffsetY float64, 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
|
||||||
|
|
||||||
@ -60,6 +61,7 @@ func GenerateConvexPolygonCollider(unalignedSrc *Polygon2D, spaceOffsetX, spaceO
|
|||||||
|
|
||||||
collider := resolv.NewObject(aligned.Anchor.X+spaceOffsetX, aligned.Anchor.Y+spaceOffsetY, w, h, tag)
|
collider := resolv.NewObject(aligned.Anchor.X+spaceOffsetX, aligned.Anchor.Y+spaceOffsetY, w, h, tag)
|
||||||
collider.SetShape(shape)
|
collider.SetShape(shape)
|
||||||
|
collider.Data = data
|
||||||
|
|
||||||
return collider
|
return collider
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,11 @@
|
|||||||
package dnmshared
|
package dnmshared
|
||||||
|
|
||||||
|
const (
|
||||||
|
RING_BUFF_CONSECUTIVE_SET = int32(0)
|
||||||
|
RING_BUFF_NON_CONSECUTIVE_SET = int32(1)
|
||||||
|
RING_BUFF_FAILED_TO_SET = int32(2)
|
||||||
|
)
|
||||||
|
|
||||||
type RingBuffer struct {
|
type RingBuffer struct {
|
||||||
Ed int32 // write index, open index
|
Ed int32 // write index, open index
|
||||||
St int32 // read index, closed index
|
St int32 // read index, closed index
|
||||||
@ -48,15 +54,15 @@ func (rb *RingBuffer) Pop() interface{} {
|
|||||||
return pItem
|
return pItem
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rb *RingBuffer) GetByOffset(offsetFromSt int32) interface{} {
|
func (rb *RingBuffer) GetArrIdxByOffset(offsetFromSt int32) int32 {
|
||||||
if 0 == rb.Cnt {
|
if 0 == rb.Cnt || 0 > offsetFromSt {
|
||||||
return nil
|
return -1
|
||||||
}
|
}
|
||||||
arrIdx := rb.St + offsetFromSt
|
arrIdx := rb.St + offsetFromSt
|
||||||
if rb.St < rb.Ed {
|
if rb.St < rb.Ed {
|
||||||
// case#1: 0...st...ed...N-1
|
// case#1: 0...st...ed...N-1
|
||||||
if rb.St <= arrIdx && arrIdx < rb.Ed {
|
if rb.St <= arrIdx && arrIdx < rb.Ed {
|
||||||
return rb.Eles[arrIdx]
|
return arrIdx
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// if rb.St >= rb.Ed
|
// if rb.St >= rb.Ed
|
||||||
@ -65,11 +71,19 @@ func (rb *RingBuffer) GetByOffset(offsetFromSt int32) interface{} {
|
|||||||
arrIdx -= rb.N
|
arrIdx -= rb.N
|
||||||
}
|
}
|
||||||
if arrIdx >= rb.St || arrIdx < rb.Ed {
|
if arrIdx >= rb.St || arrIdx < rb.Ed {
|
||||||
return rb.Eles[arrIdx]
|
return arrIdx
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rb *RingBuffer) GetByOffset(offsetFromSt int32) interface{} {
|
||||||
|
arrIdx := rb.GetArrIdxByOffset(offsetFromSt)
|
||||||
|
if -1 == arrIdx {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return rb.Eles[arrIdx]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rb *RingBuffer) GetByFrameId(frameId int32) interface{} {
|
func (rb *RingBuffer) GetByFrameId(frameId int32) interface{} {
|
||||||
@ -78,3 +92,33 @@ func (rb *RingBuffer) GetByFrameId(frameId int32) interface{} {
|
|||||||
}
|
}
|
||||||
return rb.GetByOffset(frameId - rb.StFrameId)
|
return rb.GetByOffset(frameId - rb.StFrameId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// [WARNING] During a battle, frontend could receive non-consecutive frames (either renderFrame or inputFrame) due to resync, the buffer should handle these frames properly.
|
||||||
|
func (rb *RingBuffer) SetByFrameId(pItem interface{}, frameId int32) (int32, int32, int32) {
|
||||||
|
oldStFrameId, oldEdFrameId := rb.StFrameId, rb.EdFrameId
|
||||||
|
if frameId < oldStFrameId {
|
||||||
|
return RING_BUFF_FAILED_TO_SET, oldStFrameId, oldEdFrameId
|
||||||
|
}
|
||||||
|
// By now "rb.StFrameId <= frameId"
|
||||||
|
if oldEdFrameId > frameId {
|
||||||
|
arrIdx := rb.GetArrIdxByOffset(frameId - rb.StFrameId)
|
||||||
|
if -1 != arrIdx {
|
||||||
|
rb.Eles[arrIdx] = pItem
|
||||||
|
return RING_BUFF_CONSECUTIVE_SET, oldStFrameId, oldEdFrameId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// By now "rb.EdFrameId <= frameId"
|
||||||
|
ret := RING_BUFF_CONSECUTIVE_SET
|
||||||
|
if oldEdFrameId < frameId {
|
||||||
|
rb.St, rb.Ed = 0, 0
|
||||||
|
rb.StFrameId, rb.EdFrameId = frameId, frameId
|
||||||
|
rb.Cnt = 0
|
||||||
|
ret = RING_BUFF_NON_CONSECUTIVE_SET
|
||||||
|
}
|
||||||
|
|
||||||
|
// By now "rb.EdFrameId == frameId"
|
||||||
|
rb.Put(pItem)
|
||||||
|
|
||||||
|
return ret, oldStFrameId, oldEdFrameId
|
||||||
|
}
|
||||||
|
@ -440,7 +440,7 @@
|
|||||||
"array": [
|
"array": [
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
209.73151519075364,
|
215.95961841836203,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
|
1361
frontend/assets/scenes/offline_map_2.fire
Normal file
1361
frontend/assets/scenes/offline_map_2.fire
Normal file
File diff suppressed because it is too large
Load Diff
7
frontend/assets/scenes/offline_map_2.fire.meta
Normal file
7
frontend/assets/scenes/offline_map_2.fire.meta
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"ver": "1.2.5",
|
||||||
|
"uuid": "8491a86c-bec9-4813-968a-128ca01639e0",
|
||||||
|
"asyncLoadAssets": false,
|
||||||
|
"autoReleaseAssets": false,
|
||||||
|
"subMetas": {}
|
||||||
|
}
|
238
frontend/assets/scripts/OfflineMapBackend.js
Normal file
238
frontend/assets/scripts/OfflineMapBackend.js
Normal file
@ -0,0 +1,238 @@
|
|||||||
|
const i18n = require('LanguageData');
|
||||||
|
i18n.init(window.language); // languageID should be equal to the one we input in New Language ID input field
|
||||||
|
|
||||||
|
const OnlineMap = require('./Map');
|
||||||
|
|
||||||
|
cc.Class({
|
||||||
|
extends: OnlineMap,
|
||||||
|
|
||||||
|
onDestroy() {
|
||||||
|
console.warn("+++++++ Map onDestroy()");
|
||||||
|
},
|
||||||
|
|
||||||
|
onLoad() {
|
||||||
|
const self = this;
|
||||||
|
window.mapIns = self;
|
||||||
|
self.showCriticalCoordinateLabels = true;
|
||||||
|
|
||||||
|
const mapNode = self.node;
|
||||||
|
const canvasNode = mapNode.parent;
|
||||||
|
|
||||||
|
self.mainCameraNode = self.canvasNode.getChildByName("Main Camera");
|
||||||
|
self.mainCamera = self.mainCameraNode.getComponent(cc.Camera);
|
||||||
|
for (let child of self.mainCameraNode.children) {
|
||||||
|
child.setScale(1 / self.mainCamera.zoomRatio);
|
||||||
|
}
|
||||||
|
self.widgetsAboveAllNode = self.mainCameraNode.getChildByName("WidgetsAboveAll");
|
||||||
|
self.mainCameraNode.setPosition(cc.v2());
|
||||||
|
|
||||||
|
/** Init required prefab ended. */
|
||||||
|
|
||||||
|
self.inputDelayFrames = 8;
|
||||||
|
self.inputScaleFrames = 2;
|
||||||
|
self.inputFrameUpsyncDelayTolerance = 2;
|
||||||
|
|
||||||
|
self.renderCacheSize = 1024;
|
||||||
|
self.serverFps = 60;
|
||||||
|
self.rollbackEstimatedDt = 0.016667;
|
||||||
|
self.rollbackEstimatedDtMillis = 16.667;
|
||||||
|
self.rollbackEstimatedDtNanos = 16666666;
|
||||||
|
self.tooFastDtIntervalMillis = 0.5 * self.rollbackEstimatedDtMillis;
|
||||||
|
|
||||||
|
self.worldToVirtualGridRatio = 1000;
|
||||||
|
self.virtualGridToWorldRatio = 1.0 / self.worldToVirtualGridRatio;
|
||||||
|
self.meleeSkillConfig = {
|
||||||
|
1: {
|
||||||
|
// for offender
|
||||||
|
startupFrames: 10,
|
||||||
|
activeFrames: 20,
|
||||||
|
recoveryFrames: 34, // usually but not always "startupFrames+activeFrames", I hereby set it to be 1 frame more than the actual animation to avoid critical transition, i.e. when the animation is 1 frame from ending but "rdfPlayer.framesToRecover" is already counted 0 and the player triggers an other same attack, making an effective bullet trigger but no animation is played due to same animName is still playing
|
||||||
|
recoveryFramesOnBlock: 34,
|
||||||
|
recoveryFramesOnHit: 34,
|
||||||
|
moveforward: {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
},
|
||||||
|
hitboxOffset: 12.0, // should be about the radius of the PlayerCollider
|
||||||
|
hitboxSize: {
|
||||||
|
x: 23.0,
|
||||||
|
y: 32.0,
|
||||||
|
},
|
||||||
|
|
||||||
|
// for defender
|
||||||
|
hitStunFrames: 18,
|
||||||
|
blockStunFrames: 9,
|
||||||
|
pushback: 8.0,
|
||||||
|
releaseTriggerType: 1, // 1: rising-edge, 2: falling-edge
|
||||||
|
damage: 5
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
[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 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.snapIntoPlatformThreshold = 0.5; // a platform must be "horizontal enough" for a character to "stand on"
|
||||||
|
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);
|
||||||
|
|
||||||
|
const fullPathOfTmxFile = cc.js.formatStr("map/%s/map", "dungeon");
|
||||||
|
cc.loader.loadRes(fullPathOfTmxFile, cc.TiledMapAsset, (err, tmxAsset) => {
|
||||||
|
if (null != err) {
|
||||||
|
console.error(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
tiledMapIns.tmxAsset = null;
|
||||||
|
mapNode.removeAllChildren();
|
||||||
|
self._resetCurrentMatch();
|
||||||
|
|
||||||
|
if (self.showCriticalCoordinateLabels) {
|
||||||
|
const drawer = new cc.Node();
|
||||||
|
drawer.setPosition(cc.v2(0, 0))
|
||||||
|
safelyAddChild(self.node, drawer);
|
||||||
|
setLocalZOrder(drawer, 999);
|
||||||
|
const g = drawer.addComponent(cc.Graphics);
|
||||||
|
g.lineWidth = 2;
|
||||||
|
self.g = g;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
tiledMapIns.tmxAsset = tmxAsset;
|
||||||
|
const newMapSize = tiledMapIns.getMapSize();
|
||||||
|
const newTileSize = tiledMapIns.getTileSize();
|
||||||
|
self.node.setContentSize(newMapSize.width * newTileSize.width, newMapSize.height * newTileSize.height);
|
||||||
|
self.node.setPosition(cc.v2(0, 0));
|
||||||
|
|
||||||
|
let barrierIdCounter = 0;
|
||||||
|
const boundaryObjs = tileCollisionManager.extractBoundaryObjects(self.node);
|
||||||
|
for (let boundaryObj of boundaryObjs.barriers) {
|
||||||
|
const x0 = boundaryObj.anchor.x,
|
||||||
|
y0 = boundaryObj.anchor.y;
|
||||||
|
|
||||||
|
const newBarrier = self.collisionSys.createPolygon(x0, y0, Array.from(boundaryObj, p => {
|
||||||
|
return [p.x, p.y];
|
||||||
|
}));
|
||||||
|
newBarrier.data = {
|
||||||
|
hardPushback: true
|
||||||
|
};
|
||||||
|
|
||||||
|
if (false && self.showCriticalCoordinateLabels) {
|
||||||
|
for (let i = 0; i < boundaryObj.length; ++i) {
|
||||||
|
const barrierVertLabelNode = new cc.Node();
|
||||||
|
switch (i % 4) {
|
||||||
|
case 0:
|
||||||
|
barrierVertLabelNode.color = cc.Color.RED;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
barrierVertLabelNode.color = cc.Color.GRAY;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
barrierVertLabelNode.color = cc.Color.BLACK;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
barrierVertLabelNode.color = cc.Color.MAGENTA;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const wx = boundaryObj.anchor.x + boundaryObj[i].x,
|
||||||
|
wy = boundaryObj.anchor.y + boundaryObj[i].y;
|
||||||
|
barrierVertLabelNode.setPosition(cc.v2(wx, wy));
|
||||||
|
const barrierVertLabel = barrierVertLabelNode.addComponent(cc.Label);
|
||||||
|
barrierVertLabel.fontSize = 12;
|
||||||
|
barrierVertLabel.lineHeight = barrierVertLabel.fontSize + 1;
|
||||||
|
barrierVertLabel.string = `(${wx.toFixed(1)}, ${wy.toFixed(1)})`;
|
||||||
|
safelyAddChild(self.node, barrierVertLabelNode);
|
||||||
|
setLocalZOrder(barrierVertLabelNode, 5);
|
||||||
|
|
||||||
|
barrierVertLabelNode.active = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
// console.log("Created barrier: ", newBarrier);
|
||||||
|
++barrierIdCounter;
|
||||||
|
const collisionBarrierIndex = (self.collisionBarrierIndexPrefix + barrierIdCounter);
|
||||||
|
self.collisionSysMap.set(collisionBarrierIndex, newBarrier);
|
||||||
|
}
|
||||||
|
|
||||||
|
const startRdf = window.pb.protos.RoomDownsyncFrame.create({
|
||||||
|
id: window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START,
|
||||||
|
players: {
|
||||||
|
10: window.pb.protos.PlayerDownsync.create({
|
||||||
|
id: 10,
|
||||||
|
joinIndex: 1,
|
||||||
|
virtualGridX: self.worldToVirtualGridPos(boundaryObjs.playerStartingPositions[0].x, boundaryObjs.playerStartingPositions[0].y)[0],
|
||||||
|
virtualGridY: self.worldToVirtualGridPos(boundaryObjs.playerStartingPositions[0].x, boundaryObjs.playerStartingPositions[0].y)[1],
|
||||||
|
speed: 1 * self.worldToVirtualGridRatio,
|
||||||
|
colliderRadius: 12,
|
||||||
|
characterState: window.ATK_CHARACTER_STATE.InAirIdle1[0],
|
||||||
|
framesToRecover: 0,
|
||||||
|
dirX: 0,
|
||||||
|
dirY: 0,
|
||||||
|
velX: 0,
|
||||||
|
velY: 0,
|
||||||
|
inAir: true,
|
||||||
|
}),
|
||||||
|
11: window.pb.protos.PlayerDownsync.create({
|
||||||
|
id: 11,
|
||||||
|
joinIndex: 2,
|
||||||
|
virtualGridX: self.worldToVirtualGridPos(boundaryObjs.playerStartingPositions[1].x, boundaryObjs.playerStartingPositions[1].y)[0],
|
||||||
|
virtualGridY: self.worldToVirtualGridPos(boundaryObjs.playerStartingPositions[1].x, boundaryObjs.playerStartingPositions[1].y)[1],
|
||||||
|
speed: 1 * self.worldToVirtualGridRatio,
|
||||||
|
colliderRadius: 12,
|
||||||
|
characterState: window.ATK_CHARACTER_STATE.InAirIdle1[0],
|
||||||
|
framesToRecover: 0,
|
||||||
|
dirX: 0,
|
||||||
|
dirY: 0,
|
||||||
|
velX: 0,
|
||||||
|
velY: 0,
|
||||||
|
inAir: true,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
self.selfPlayerInfo = {
|
||||||
|
id: 11
|
||||||
|
};
|
||||||
|
self._initPlayerRichInfoDict(startRdf.players);
|
||||||
|
self.onRoomDownsyncFrame(startRdf);
|
||||||
|
|
||||||
|
self.battleState = ALL_BATTLE_STATES.IN_BATTLE;
|
||||||
|
});
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
update(dt) {
|
||||||
|
const self = this;
|
||||||
|
if (ALL_BATTLE_STATES.IN_BATTLE == self.battleState) {
|
||||||
|
const elapsedMillisSinceLastFrameIdTriggered = performance.now() - self.lastRenderFrameIdTriggeredAt;
|
||||||
|
if (elapsedMillisSinceLastFrameIdTriggered < self.tooFastDtIntervalMillis) {
|
||||||
|
// [WARNING] We should avoid a frontend ticking too fast to prevent cheating, as well as ticking too slow to cause a "resync avalanche" that impacts user experience!
|
||||||
|
// console.debug("Avoiding too fast frame@renderFrameId=", self.renderFrameId, ": elapsedMillisSinceLastFrameIdTriggered=", elapsedMillisSinceLastFrameIdTriggered);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
let st = performance.now();
|
||||||
|
let prevSelfInput = null,
|
||||||
|
currSelfInput = null;
|
||||||
|
const noDelayInputFrameId = self._convertToInputFrameId(self.renderFrameId, 0); // It's important that "inputDelayFrames == 0" here
|
||||||
|
if (self.shouldGenerateInputFrameUpsync(self.renderFrameId)) {
|
||||||
|
const prevAndCurrInputs = self._generateInputFrameUpsync(noDelayInputFrameId);
|
||||||
|
prevSelfInput = prevAndCurrInputs[0];
|
||||||
|
currSelfInput = prevAndCurrInputs[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
const [prevRdf, rdf] = self.rollbackAndChase(self.renderFrameId, self.renderFrameId + 1, self.collisionSys, self.collisionSysMap, false);
|
||||||
|
self.applyRoomDownsyncFrameDynamics(rdf, prevRdf);
|
||||||
|
self.showDebugBoundaries(rdf);
|
||||||
|
++self.renderFrameId;
|
||||||
|
self.lastRenderFrameIdTriggeredAt = performance.now();
|
||||||
|
let t3 = performance.now();
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error during Map.update", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
9
frontend/assets/scripts/OfflineMapBackend.js.meta
Normal file
9
frontend/assets/scripts/OfflineMapBackend.js.meta
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"ver": "1.0.5",
|
||||||
|
"uuid": "b3810903-496b-43d7-8461-898cee958548",
|
||||||
|
"isPlugin": false,
|
||||||
|
"loadPluginInWeb": true,
|
||||||
|
"loadPluginInNative": true,
|
||||||
|
"loadPluginInEditor": false,
|
||||||
|
"subMetas": {}
|
||||||
|
}
|
109
jsexport/main.go
109
jsexport/main.go
@ -1,51 +1,100 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gopherjs/gopherjs/js"
|
. "dnmshared"
|
||||||
|
. "dnmshared/sharedprotos"
|
||||||
|
"github.com/gopherjs/gopherjs/js"
|
||||||
"github.com/solarlune/resolv"
|
"github.com/solarlune/resolv"
|
||||||
. "jsexport/protos"
|
"jsexport/models"
|
||||||
"jsexport/models"
|
. "jsexport/protos"
|
||||||
. "dnmshared"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewRingBufferJs(n int32) *js.Object {
|
|
||||||
return js.MakeWrapper(NewRingBuffer(n));
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewCollisionSpaceJs(spaceW, spaceH, minStepW, minStepH int) *js.Object {
|
func NewCollisionSpaceJs(spaceW, spaceH, minStepW, minStepH int) *js.Object {
|
||||||
return js.MakeWrapper(resolv.NewSpace(spaceW, spaceH, minStepW, minStepH))
|
return js.MakeWrapper(resolv.NewSpace(spaceW, spaceH, minStepW, minStepH))
|
||||||
}
|
}
|
||||||
|
|
||||||
func GenerateRectColliderJs(wx, wy, w, h, topPadding, bottomPadding, leftPadding, rightPadding, spaceOffsetX, spaceOffsetY float64, tag string) *js.Object {
|
func NewVec2DJs(x, y float64) *js.Object {
|
||||||
/*
|
return js.MakeFullWrapper(&Vec2D{
|
||||||
[WARNING] It's important to note that we don't need "js.MakeFullWrapper" for a call sequence as follows.
|
X: x,
|
||||||
```
|
Y: y,
|
||||||
var space = gopkgs.NewCollisionSpaceJs(2048, 2048, 8, 8);
|
})
|
||||||
var a = gopkgs.GenerateRectColliderJs(189, 497, 48, 48, snapIntoPlatformOverlap, snapIntoPlatformOverlap, snapIntoPlatformOverlap, snapIntoPlatformOverlap, spaceOffsetX, spaceOffsetY, "Player");
|
}
|
||||||
space.Add(a);
|
|
||||||
```
|
func NewPolygon2DJs(anchor *Vec2D, points []*Vec2D) *js.Object {
|
||||||
The "space" variable doesn't need access to the field of "a" in JavaScript level to run "space.Add(...)" method, which is good.
|
return js.MakeFullWrapper(&Polygon2D{
|
||||||
*/
|
Anchor: anchor,
|
||||||
return js.MakeWrapper(GenerateRectCollider(wx, wy, w, h, topPadding, bottomPadding, leftPadding, rightPadding, spaceOffsetX, spaceOffsetY, tag));
|
Points: points,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBarrierJs(boundary *Polygon2D) *js.Object {
|
||||||
|
return js.MakeWrapper(&Barrier{
|
||||||
|
Boundary: boundary,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPlayerDownsyncJs(id, virtualGridX, virtualGridY, dirX, dirY, velX, velY, speed, battleState, characterState, joinIndex, hp, maxHp int32, inAir bool, colliderRadius float64) *js.Object {
|
||||||
|
return js.MakeWrapper(&PlayerDownsync{
|
||||||
|
Id: id,
|
||||||
|
VirtualGridX: virtualGridX,
|
||||||
|
VirtualGridY: virtualGridY,
|
||||||
|
DirX: dirX,
|
||||||
|
DirY: dirY,
|
||||||
|
VelX: velX,
|
||||||
|
VelY: velY,
|
||||||
|
Speed: speed,
|
||||||
|
BattleState: battleState,
|
||||||
|
JoinIndex: joinIndex,
|
||||||
|
ColliderRadius: colliderRadius,
|
||||||
|
Hp: hp,
|
||||||
|
MaxHp: maxHp,
|
||||||
|
CharacterState: characterState,
|
||||||
|
InAir: inAir,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRoomDownsyncFrameJs(id int32, playersArr []*PlayerDownsync, meleeBullets []*MeleeBullet) *js.Object {
|
||||||
|
return js.MakeFullWrapper(&RoomDownsyncFrame{
|
||||||
|
Id: id,
|
||||||
|
PlayersArr: playersArr,
|
||||||
|
MeleeBullets: meleeBullets,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenerateRectColliderJs(wx, wy, w, h, topPadding, bottomPadding, leftPadding, rightPadding, spaceOffsetX, spaceOffsetY float64, data interface{}, tag string) *js.Object {
|
||||||
|
/*
|
||||||
|
[WARNING] It's important to note that we don't need "js.MakeFullWrapper" for a call sequence as follows.
|
||||||
|
```
|
||||||
|
var space = gopkgs.NewCollisionSpaceJs(2048, 2048, 8, 8);
|
||||||
|
var a = gopkgs.GenerateRectColliderJs(189, 497, 48, 48, snapIntoPlatformOverlap, snapIntoPlatformOverlap, snapIntoPlatformOverlap, snapIntoPlatformOverlap, spaceOffsetX, spaceOffsetY, "Player");
|
||||||
|
space.Add(a);
|
||||||
|
```
|
||||||
|
The "space" variable doesn't need access to the field of "a" in JavaScript level to run "space.Add(...)" method, which is good.
|
||||||
|
*/
|
||||||
|
return js.MakeWrapper(GenerateRectCollider(wx, wy, w, h, topPadding, bottomPadding, leftPadding, rightPadding, spaceOffsetX, spaceOffsetY, data, tag))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func CheckCollisionJs(obj *resolv.Object, dx, dy float64) *js.Object {
|
func CheckCollisionJs(obj *resolv.Object, dx, dy float64) *js.Object {
|
||||||
// TODO: Support multiple tags in the future
|
// TODO: Support multiple tags in the future
|
||||||
// Unfortunately I couldn't find a way to just call "var a = GenerateRectColliderJs(...); space.Add(a); a.Check(...)" to get the collision result, the unwrapped method will result in stack overflow. Need a better solution later.
|
// Unfortunately I couldn't find a way to just call "var a = GenerateRectColliderJs(...); space.Add(a); a.Check(...)" to get the collision result, the unwrapped method will result in stack overflow. Need a better solution later.
|
||||||
return js.MakeFullWrapper(obj.Check(dx, dy));
|
return js.MakeFullWrapper(obj.Check(dx, dy))
|
||||||
}
|
}
|
||||||
|
|
||||||
func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrameJs(delayedInputFrame *InputFrameDownsync, currRenderFrame *RoomDownsyncFrame, collisionSys *resolv.Space, collisionSysMap map[int32]*resolv.Object, gravityX, gravityY, jumpingInitVelY, inputDelayFrames, inputScaleFrames int32, inputsBuffer *RingBuffer, collisionSpaceOffsetX, collisionSpaceOffsetY, snapIntoPlatformOverlap, snapIntoPlatformThreshold, worldToVirtualGridRatio, virtualGridToWorldRatio float64) *js.Object {
|
func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrameJs(delayedInputFrame, delayedInputFrameForPrevRenderFrame *InputFrameDownsync, currRenderFrame *RoomDownsyncFrame, collisionSys *resolv.Space, collisionSysMap map[int32]*resolv.Object, gravityX, gravityY, jumpingInitVelY, inputDelayFrames, inputScaleFrames int32, collisionSpaceOffsetX, collisionSpaceOffsetY, snapIntoPlatformOverlap, snapIntoPlatformThreshold, worldToVirtualGridRatio, virtualGridToWorldRatio float64) *js.Object {
|
||||||
// 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 js.MakeFullWrapper(models.ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputFrame, currRenderFrame, collisionSys, collisionSysMap, gravityX, gravityY, jumpingInitVelY, inputDelayFrames, inputScaleFrames, inputsBuffer, collisionSpaceOffsetX, collisionSpaceOffsetY, snapIntoPlatformOverlap, snapIntoPlatformThreshold, worldToVirtualGridRatio, virtualGridToWorldRatio))
|
return js.MakeFullWrapper(models.ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputFrame, delayedInputFrameForPrevRenderFrame, currRenderFrame, collisionSys, collisionSysMap, gravityX, gravityY, jumpingInitVelY, inputDelayFrames, inputScaleFrames, collisionSpaceOffsetX, collisionSpaceOffsetY, snapIntoPlatformOverlap, snapIntoPlatformThreshold, worldToVirtualGridRatio, virtualGridToWorldRatio))
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
js.Global.Set("gopkgs", map[string]interface{}{
|
js.Global.Set("gopkgs", map[string]interface{}{
|
||||||
"NewRingBufferJs": NewRingBufferJs,
|
"NewVec2DJs": NewVec2DJs,
|
||||||
"NewCollisionSpaceJs": NewCollisionSpaceJs,
|
"NewPolygon2DJs": NewPolygon2DJs,
|
||||||
"GenerateRectColliderJs": GenerateRectColliderJs,
|
"NewBarrierJs": NewBarrierJs,
|
||||||
"CheckCollisionJs": CheckCollisionJs,
|
"NewPlayerDownsyncJs": NewPlayerDownsyncJs,
|
||||||
|
"NewRoomDownsyncFrameJs": NewRoomDownsyncFrameJs,
|
||||||
|
"NewCollisionSpaceJs": NewCollisionSpaceJs,
|
||||||
|
"GenerateRectColliderJs": GenerateRectColliderJs,
|
||||||
|
"CheckCollisionJs": CheckCollisionJs,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
. "dnmshared"
|
||||||
|
. "dnmshared/sharedprotos"
|
||||||
"github.com/solarlune/resolv"
|
"github.com/solarlune/resolv"
|
||||||
. "dnmshared/sharedprotos"
|
. "jsexport/protos"
|
||||||
. "jsexport/protos"
|
|
||||||
. "dnmshared"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -81,10 +81,10 @@ func CalcHardPushbacksNorms(playerCollider *resolv.Object, playerShape *resolv.C
|
|||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputFrame *InputFrameDownsync, currRenderFrame *RoomDownsyncFrame, collisionSys *resolv.Space, collisionSysMap map[int32]*resolv.Object, gravityX, gravityY, jumpingInitVelY, inputDelayFrames, inputScaleFrames int32, inputsBuffer *RingBuffer, collisionSpaceOffsetX, collisionSpaceOffsetY, snapIntoPlatformOverlap, snapIntoPlatformThreshold, worldToVirtualGridRatio, virtualGridToWorldRatio float64) *RoomDownsyncFrame {
|
func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputFrame, delayedInputFrameForPrevRenderFrame *InputFrameDownsync, currRenderFrame *RoomDownsyncFrame, collisionSys *resolv.Space, collisionSysMap map[int32]*resolv.Object, gravityX, gravityY, jumpingInitVelY, inputDelayFrames, inputScaleFrames int32, collisionSpaceOffsetX, collisionSpaceOffsetY, snapIntoPlatformOverlap, snapIntoPlatformThreshold, worldToVirtualGridRatio, virtualGridToWorldRatio float64) *RoomDownsyncFrame {
|
||||||
topPadding, bottomPadding, leftPadding, rightPadding := snapIntoPlatformOverlap, snapIntoPlatformOverlap, snapIntoPlatformOverlap, snapIntoPlatformOverlap
|
topPadding, bottomPadding, leftPadding, rightPadding := snapIntoPlatformOverlap, snapIntoPlatformOverlap, snapIntoPlatformOverlap, snapIntoPlatformOverlap
|
||||||
// [WARNING] This function MUST BE called while "InputsBufferLock" is locked!
|
// [WARNING] This function MUST BE called while "InputsBufferLock" is locked!
|
||||||
roomCapacity := len(currRenderFrame.PlayersArr)
|
roomCapacity := len(currRenderFrame.PlayersArr)
|
||||||
nextRenderFramePlayers := make([]*PlayerDownsync, roomCapacity)
|
nextRenderFramePlayers := make([]*PlayerDownsync, roomCapacity)
|
||||||
// Make a copy first
|
// Make a copy first
|
||||||
for i, currPlayerDownsync := range currRenderFrame.PlayersArr {
|
for i, currPlayerDownsync := range currRenderFrame.PlayersArr {
|
||||||
@ -117,11 +117,6 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputFrame *Input
|
|||||||
|
|
||||||
// 1. Process player inputs
|
// 1. Process player inputs
|
||||||
if nil != delayedInputFrame {
|
if nil != delayedInputFrame {
|
||||||
var delayedInputFrameForPrevRenderFrame *InputFrameDownsync = nil
|
|
||||||
tmp := inputsBuffer.GetByFrameId(ConvertToInputFrameId(currRenderFrame.Id-1, inputDelayFrames, inputScaleFrames))
|
|
||||||
if nil != tmp {
|
|
||||||
delayedInputFrameForPrevRenderFrame = tmp.(*InputFrameDownsync)
|
|
||||||
}
|
|
||||||
inputList := delayedInputFrame.InputList
|
inputList := delayedInputFrame.InputList
|
||||||
for i, currPlayerDownsync := range currRenderFrame.PlayersArr {
|
for i, currPlayerDownsync := range currRenderFrame.PlayersArr {
|
||||||
joinIndex := currPlayerDownsync.JoinIndex
|
joinIndex := currPlayerDownsync.JoinIndex
|
||||||
@ -150,16 +145,16 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputFrame *Input
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note that by now "0 == thatPlayerInNextFrame.FramesToRecover", we should change "CharacterState" to "WALKING" or "IDLE" depending on player inputs
|
// Note that by now "0 == thatPlayerInNextFrame.FramesToRecover", we should change "CharacterState" to "WALKING" or "IDLE" depending on player inputs
|
||||||
if 0 != decodedInput.Dx || 0 != decodedInput.Dy {
|
if 0 != decodedInput.Dx || 0 != decodedInput.Dy {
|
||||||
thatPlayerInNextFrame.DirX = decodedInput.Dx
|
thatPlayerInNextFrame.DirX = decodedInput.Dx
|
||||||
thatPlayerInNextFrame.DirY = decodedInput.Dy
|
thatPlayerInNextFrame.DirY = decodedInput.Dy
|
||||||
thatPlayerInNextFrame.VelX = decodedInput.Dx * currPlayerDownsync.Speed
|
thatPlayerInNextFrame.VelX = decodedInput.Dx * currPlayerDownsync.Speed
|
||||||
thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_WALKING
|
thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_WALKING
|
||||||
} else {
|
} else {
|
||||||
thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_IDLE1
|
thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_IDLE1
|
||||||
thatPlayerInNextFrame.VelX = 0
|
thatPlayerInNextFrame.VelX = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -187,9 +182,7 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputFrame *Input
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Invoke collision system stepping (no-op for backend collision lib)
|
// 3. 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
|
||||||
collisionPlayerIndex := COLLISION_PLAYER_INDEX_PREFIX + joinIndex
|
collisionPlayerIndex := COLLISION_PLAYER_INDEX_PREFIX + joinIndex
|
||||||
@ -201,6 +194,7 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputFrame *Input
|
|||||||
if collision := playerCollider.Check(0, 0); nil != collision {
|
if collision := playerCollider.Check(0, 0); nil != collision {
|
||||||
for _, obj := range collision.Objects {
|
for _, obj := range collision.Objects {
|
||||||
isBarrier, isAnotherPlayer, isBullet := false, false, false
|
isBarrier, isAnotherPlayer, isBullet := false, false, false
|
||||||
|
// TODO: Make this part work in JavaScript without having to expose all types Barrier/PlayerDownsync/MeleeBullet by js.MakeWrapper.
|
||||||
switch obj.Data.(type) {
|
switch obj.Data.(type) {
|
||||||
case *Barrier:
|
case *Barrier:
|
||||||
isBarrier = true
|
isBarrier = true
|
||||||
@ -226,7 +220,7 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputFrame *Input
|
|||||||
thatPlayerInNextFrame.InAir = false
|
thatPlayerInNextFrame.InAir = false
|
||||||
}
|
}
|
||||||
if isAnotherPlayer {
|
if isAnotherPlayer {
|
||||||
// [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-snapIntoPlatformOverlap*2)*overlapResult.OverlapX, (overlapResult.Overlap-snapIntoPlatformOverlap*2)*overlapResult.OverlapY
|
pushbackX, pushbackY = (overlapResult.Overlap-snapIntoPlatformOverlap*2)*overlapResult.OverlapX, (overlapResult.Overlap-snapIntoPlatformOverlap*2)*overlapResult.OverlapY
|
||||||
}
|
}
|
||||||
for _, hardPushbackNorm := range hardPushbackNorms[joinIndex-1] {
|
for _, hardPushbackNorm := range hardPushbackNorms[joinIndex-1] {
|
||||||
@ -262,7 +256,7 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputFrame *Input
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 7. Get players out of stuck barriers if there's any
|
// 4. 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
|
||||||
collisionPlayerIndex := COLLISION_PLAYER_INDEX_PREFIX + joinIndex
|
collisionPlayerIndex := COLLISION_PLAYER_INDEX_PREFIX + joinIndex
|
||||||
@ -274,7 +268,7 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputFrame *Input
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &RoomDownsyncFrame{
|
return &RoomDownsyncFrame{
|
||||||
Id: currRenderFrame.Id + 1,
|
Id: currRenderFrame.Id + 1,
|
||||||
PlayersArr: nextRenderFramePlayers,
|
PlayersArr: nextRenderFramePlayers,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user