Mein primärer Antrieb war es, das fluffige, kinetische, präzise Feeling der Mario Games einzufangen.
Was zeichnet einen "Mario Jump" aus?
- Beschleunigung: die Figur ist nicht sofort beim Drücken der Richtungstaste auf Maximalgeschwindigkeit, sondern hat einen Beschleunigungs-Intervall
- Acceleration-Decay: Beim Loslassen der Richtungstaste schlittert die Figur noch ein Stück weiter
- Durch Drücken einer Boost-Taste kann man die Maximalgeschwindigkeit erhöhen (um dadurch weiter zu Springen)
- Wenn die Sprungtaste länger gedrückt wird, springt die Figur höher
- Die Beschleunigung in vertikaler Richtung nimmt ab, bis sie sich am Peak wieder umdreht.
- Während die Figur in der Luft ist, kann man sie weiter steuern
Am kniffligsten war die Kollisionsabfrage:
Bei hoher Beschleunigung konnte es passieren, daß die Figur durch schmale Objekte "hindurch" sprang, da es kein Frame gab, in dem die Kollisionsabfrage greifen konnte. Das habe ich letztendlich so gelöst, indem ich zusätzlich zur regulären Kollisionsabfrage alle worldObjects(), welche sich in der Richtung des gegenwärtigen Impulses befinden, einmal durchlaufe, und gucke ob mit der gegenwärtigen Beschleunigung in x und y Achse sich die Spielfigur beim nächsten Frame dahinter befinden würde. Wenn ja, wird noch im selben Frame ein Connect forciert, und die Spielfigur auf den minimal möglichen Abstand gesetzt.
Last but not least habe ich einen "Coyote-Timer" implementiert: Sobald der Spieler eine Plattform verlässt, beginnt ein Timer herunterzuzählen (4 Frames), bevor die Schwerkraft anfängt zu wirken. Spaziert man einfach so herunter, merkt man diese 4 Frames nicht. Allerdings ist es so besser möglich, direkt an der Kante mit Anlauf exakt getimte Super-Jumps auszuführen.
Wer Lust hat, kann ein bißchen damit rumspielen, (oder sogar als Basis für ein eigenes Spiel verwenden) ...
• Pfeiltasten links und rechts: laufen
• Pfeiltaste hoch: Springen
• Pfeiltaste runter: Ducken
• Leertaste: Boost
• ESC: Spiel beenden
Code: Alles auswählen
EnableExplicit
Declare loop()
Declare display()
Declare processInput()
Declare verticalCollision()
Declare horizontalCollision()
Declare standUp()
Declare createScreen(windowName.s)
Declare spriteFactory()
Declare buildSprite(objectIndex,width,height,x,y,red,green,blue,collision)
Declare limit(val,min,max)
Structure WORLD_OBJECT
index.i
x.i
y.i
width.i
height.i
collision.i
EndStructure
Global NewList worldObject.WORLD_OBJECT()
#xRes = 800
#yRes = 600
Global frameDuration, frameFinished
Global perSecond.f
Global screen
Global acceleration, velocity
Global coyoteTimer
#iniSpeed = 100
#regSpeed = 500
#maxSpeed = 750
#accelerationInc = 750
#accelerationDecay = 2000
#jumpHeight = 150
#iniVelocity = 600
#velocityDecay = 7000
#iniFallVelocity = 300
#velocityInc = 3750
#maxFallSpeed = 1500
Enumeration
#nullSpeed
#leftDir
#rightDir
EndEnumeration
Global xPlayer = 160
Global yPlayer = 0
Global isStanding = 1
Global Dim playerHeight(1)
playerHeight(0) = 20
playerHeight(1) = 40
Global playerWidth = 30
Global impulse = -1
Global direction
Enumeration
#nullCollision
#climbCollision
#fullCollision
EndEnumeration
Global Dim playerSPR(1) ;0 = Ducken, 1 = Stehen
#maxWorldObjects = 8
Global Dim worldObjectSPR(#maxWorldObjects)
createScreen("The Perfect Jump")
spriteFactory()
loop()
Procedure loop()
Repeat
display()
processInput()
WaitWindowEvent(1)
;Delta Time
perSecond.f = frameDuration/1000
frameDuration = ElapsedMilliseconds() - frameFinished
frameFinished = ElapsedMilliseconds()
Until KeyboardPushed(#PB_Key_Escape)
EndProcedure
Procedure display()
ClearScreen(RGB(150,230,255))
ForEach worldObject()
DisplaySprite(worldObjectSPR(worldObject()\index),worldObject()\x,worldObject()\y)
Next
DisplaySprite(playerSPR(isStanding),xPlayer,yPlayer)
FlipBuffers()
EndProcedure
Procedure processInput()
Define leftKey, rightKey, jumpKey, boostKey, duckKey
Static jumpKeyPressed, groundCoord, duckKeyPressed
ExamineKeyboard()
leftKey = KeyboardPushed(#PB_Key_Left)
rightKey = KeyboardPushed(#PB_Key_Right)
duckKey = KeyboardPushed(#PB_Key_Down)
jumpKey = KeyboardPushed(#PB_Key_Up)
boostKey = KeyboardPushed(#PB_Key_Space)
If leftKey And (Not rightKey) And direction <> #leftDir
acceleration = #iniSpeed
direction = #leftDir
EndIf
If rightKey And (Not leftKey) And direction <> #rightDir
acceleration = #iniSpeed
direction = #rightDir
EndIf
If rightKey Or leftKey
If Not isStanding
standUp()
EndIf
If acceleration < #regSpeed Or (acceleration < #maxSpeed And boostKey)
acceleration = limit(acceleration+(#accelerationInc*perSecond),#iniSpeed,#maxSpeed)
If boostKey And acceleration < #maxSpeed ;Doppelte Beschleunigung bei Drücken der Boost-Taste
acceleration = limit(acceleration+(#accelerationInc*perSecond),#iniSpeed,#maxSpeed)
EndIf
EndIf
Else
If acceleration
acceleration = limit(acceleration-(#accelerationDecay*perSecond),0,#maxSpeed) ;Bei Richtungsänderung oder Loslassen der Richtungstaste --> Acceleration-Decay
EndIf
EndIf
If direction = #leftDir
xPlayer-(acceleration*perSecond)
EndIf
If direction = #rightDir
xPlayer+(acceleration*perSecond)
EndIf
If acceleration
If acceleration > #regSpeed And (Not boostKey)
acceleration = #regSpeed ;Geschwindigkeit drosseln bei Loslassen der Boost-Taste
EndIf
horizontalCollision()
Else
direction = #nullSpeed
EndIf
If Not jumpKey And impulse = 0 ;Input wird nur registriert, wenn Spieler am Boden ist (kein Festhalten der Sprungtaste)
jumpKeyPressed = #False
EndIf
If (Not jumpKeyPressed) And jumpKey And impulse = 0
If Not isStanding
standUp()
EndIf
coyoteTimer = 0
impulse = 1
velocity = #iniVelocity
jumpKeyPressed = #True
groundCoord = yPlayer ;maximale Sprunghöhe von gegenwärtiger Position aus speichern
EndIf
If impulse = 1
yPlayer = yPlayer-(velocity*perSecond)
If velocity = #iniVelocity And Not jumpKey ;Wenn Sprungtaste vor maximaler Höhe losgelassen wird, Velocity-Decay initialisieren
velocity -1
EndIf
If yPlayer <= groundCoord - #jumpHeight Or velocity < #iniVelocity ;Velocity Decay
velocity-(#velocityDecay*perSecond)
If velocity <= 0 ;Wenn Peak erreicht, Impuls umdrehen (Fallen)
velocity = 1
impulse = -1
EndIf
EndIf
EndIf
If impulse = -1
yPlayer = yPlayer+(velocity*perSecond)
velocity = limit(velocity+(#velocityInc*perSecond),1,#maxFallSpeed)
jumpKeyPressed = #True ;Beim Fallen Sprungtaste blockieren
EndIf
If Not duckKey ;DuckTaste muß nach jedem Input, welcher Ducken cancelt, neu gedrückt werden (kein Festhalten der DuckTaste)
duckKeyPressed = #False
EndIf
If Not duckKeyPressed And duckKey And impulse = 0 And Not (leftKey Or rightKey)
If isStanding = 1
duckKeyPressed = #True
isStanding = 0
yPlayer + (SpriteHeight(playerSPR(1))-SpriteHeight(playerSPR(0)))
EndIf
EndIf
If Not duckKey
standUp()
EndIf
verticalCollision()
EndProcedure
Procedure standUp() ;Ducken canceln
If isStanding = 0
isStanding = 1
yPlayer - (SpriteHeight(playerSPR(1))-SpriteHeight(playerSPR(0)))
EndIf
EndProcedure
Procedure horizontalCollision()
ForEach worldObject()
If worldObject()\collision <> #fullCollision
Continue
EndIf
If worldObject()\y < yPlayer+playerHeight(isStanding) And worldObject()\y+worldObject()\height > yPlayer
If direction = #leftDir
If SpriteCollision(playerSPR(isStanding),xPlayer,yPlayer,worldObjectSPR(worldObject()\index),worldObject()\x,worldObject()\y) Or
(worldObject()\x+worldObject()\width < xPlayer And xPlayer-(acceleration*perSecond) < worldObject()\x+worldObject()\width) Or
worldObject()\x+worldObject()\width = xPlayer
xPlayer = worldObject()\x+worldObject()\width
acceleration = 0
ProcedureReturn #True
EndIf
EndIf
If direction = #rightDir
If SpriteCollision(playerSPR(isStanding),xPlayer,yPlayer,worldObjectSPR(worldObject()\index),worldObject()\x,worldObject()\y) Or
(worldObject()\x > xPlayer+playerWidth And xPlayer+playerWidth+(acceleration*perSecond) > worldObject()\x) Or
worldObject()\x = xPlayer+playerWidth
xPlayer = worldObject()\x-playerWidth
acceleration = 0
ProcedureReturn #True
EndIf
EndIf
EndIf
Next
EndProcedure
Procedure verticalCollision()
ForEach worldObject()
If worldObject()\x <= xPlayer+playerWidth And worldObject()\x+worldObject()\width >= xPlayer
If impulse = 1
If worldObject()\collision = #fullCollision
If SpriteCollision(playerSPR(isStanding),xPlayer,yPlayer,worldObjectSPR(worldObject()\index),worldObject()\x,worldObject()\y) Or
(worldObject()\y+worldObject()\height < yPlayer And yPlayer-(velocity*perSecond) < worldObject()\y+worldObject()\height) Or
worldObject()\y+worldObject()\height = yPlayer
yPlayer = worldObject()\y+worldObject()\height
impulse = -1
velocity = 1
ProcedureReturn #True
EndIf
EndIf
Else
If (SpriteCollision(playerSPR(isStanding),xPlayer,yPlayer,worldObjectSPR(worldObject()\index),worldObject()\x,worldObject()\y) And worldObject()\collision = #fullCollision) Or
(worldObject()\y > yPlayer+playerHeight(isStanding) And yPlayer+playerHeight(isStanding)+(velocity*perSecond) > worldObject()\y) Or
worldObject()\y = yPlayer+playerHeight(isStanding)
yPlayer = worldObject()\y-playerHeight(isStanding)
impulse = 0
velocity = 0
coyoteTimer = 0
ProcedureReturn #True
EndIf
EndIf
EndIf
Next
If impulse = 0 And Not coyoteTimer
coyoteTimer = ElapsedMilliseconds()
EndIf
If coyoteTimer And ElapsedMilliseconds() - coyoteTimer > frameDuration*0;1
impulse = -1
velocity = #iniFallVelocity
coyoteTimer = 0
EndIf
EndProcedure
Procedure createScreen(windowName.s)
If InitSprite()
screen = OpenWindow(#PB_Any,0,0,#xRes,#yRes,windowName.s,#PB_Window_ScreenCentered)
If OpenWindowedScreen(WindowID(screen),0,0,#xRes,#yRes)
InitKeyboard()
InitMouse()
Else
Debug "OpenWindowedScreen() failed!"
EndIf
Else
Debug "InitSprite() failed!"
EndIf
EndProcedure
Procedure spriteFactory()
playerSPR(0) = CreateSprite(#PB_Any,playerWidth,playerHeight(0)) ;Ducken
If StartDrawing(SpriteOutput(playerSPR(0)))
Box(0,0,playerWidth,playerHeight(0),RGB(255,125,0))
StopDrawing()
EndIf
playerSPR(1) = CreateSprite(#PB_Any,playerWidth,playerHeight(1)) ;Stehen
If StartDrawing(SpriteOutput(playerSPR(1)))
Box(0,0,playerWidth,playerHeight(1),RGB(255,125,0))
StopDrawing()
EndIf
buildSprite(0,800,25,0,575,0,150,0,#fullCollision) ;Boden
buildSprite(1,25,575,0,0,50,50,50,#fullCollision) ;linke Mauer
buildSprite(2,25,575,775,0,50,50,50,#fullCollision) ;rechte Mauer
buildSprite(3,200,10,75,500,100,100,100,#fullCollision) ;untere Plattform
buildSprite(4,100,10,600,350,100,100,100,#fullCollision) ;mittlere Plattform
buildSprite(5,100,10,225,190,100,100,100,#fullCollision) ;hohe Plattform
buildSprite(6,50,10,500,150,100,100,100,#fullCollision) ;oberste Plattform
buildSprite(7,100,200,400,375,50,200,50,#climbCollision) ;hintere durchlässige Plattform
buildSprite(8,100,100,350,475,0,255,150,#climbCollision) ;vordere durchlässige Plattform
EndProcedure
Procedure buildSprite(objectIndex,width,height,x,y,red,green,blue,collision)
worldObjectSPR(objectIndex) = CreateSprite(#PB_Any,width,height)
If StartDrawing(SpriteOutput(worldObjectSPR(objectIndex)))
Box(0,0,width,height,RGB(red,green,blue))
StopDrawing()
EndIf
AddElement(worldObject())
worldObject()\index = objectIndex
worldObject()\x = x
worldObject()\y = y
worldObject()\width = width
worldObject()\height = height
worldObject()\collision = collision
EndProcedure
Procedure limit(val,min,max)
If val < min
ProcedureReturn min
EndIf
If val > max
ProcedureReturn max
EndIf
ProcedureReturn val
EndProcedure