Aktuelle Zeit: 22.04.2019 22:22

Alle Zeiten sind UTC + 1 Stunde [ Sommerzeit ]




Ein neues Thema erstellen Auf das Thema antworten  [ 9 Beiträge ] 
Autor Nachricht
 Betreff des Beitrags: The Perfect Jump
BeitragVerfasst: 04.10.2018 14:18 
Offline
Benutzeravatar

Registriert: 06.07.2017 12:24
Ich habe mich gestern im Nachtdienst hingesetzt und einen rudimentären Jump 'n Run Prototypen gebastelt ...
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:
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

_________________
Now these points of data make a beautiful line,
And we're out of Beta, we're releasing on time.


Zuletzt geändert von diceman am 05.10.2018 16:56, insgesamt 9-mal geändert.

Nach oben
 Profil  
Mit Zitat antworten  
 Betreff des Beitrags: Re: The Perfect Jump
BeitragVerfasst: 04.10.2018 14:20 
Offline
Moderator
Benutzeravatar

Registriert: 05.10.2006 18:55
Wohnort: Rupture Farms
:allright:

_________________
BildBildBildBildBildBild


Nach oben
 Profil  
Mit Zitat antworten  
 Betreff des Beitrags: Re: The Perfect Jump
BeitragVerfasst: 04.10.2018 15:39 
Offline
Benutzeravatar

Registriert: 25.09.2016 01:42
Sehr schön danke fürs posten :allright:


Nach oben
 Profil  
Mit Zitat antworten  
 Betreff des Beitrags: Re: The Perfect Jump
BeitragVerfasst: 04.10.2018 17:23 
Offline
Benutzeravatar

Registriert: 06.07.2017 12:24
Kleiner Tweak oben im Code ...
Durchlässige Plattformen sind auch möglich, deren Kollision ausschließlich von herabfallenden Objekten berücksichtigt wird.
So kann man die aus den "Mario"-Spielen bekannten Kletter-Plattformen erstellen:
Und eine Coyote Time von 1 Frame ist absolut ausreichend (und trotzdem spürbar): mit normalem Anlauf, ohne Booster, landet man ohne zu Springen von der mittleren Plattform auf den rechten grünen Hügel. Ohne Coyote Time fällt man kurz vorher runter, bzw. muß die Boost-Taste benutzen.

Bild

Hmm ... ich kriege gerade echt Lust, da etwas draus zu machen. Nächster Schritt, eine scrollende Map, zum Beispiel?
Challenge accepted!
Bis demnächst im WIP-Thread. :D

_________________
Now these points of data make a beautiful line,
And we're out of Beta, we're releasing on time.


Nach oben
 Profil  
Mit Zitat antworten  
 Betreff des Beitrags: Re: The Perfect Jump
BeitragVerfasst: 04.10.2018 18:27 
Offline
Moderator
Benutzeravatar

Registriert: 05.10.2006 18:55
Wohnort: Rupture Farms
:allright:

_________________
BildBildBildBildBildBild


Nach oben
 Profil  
Mit Zitat antworten  
 Betreff des Beitrags: Re: The Perfect Jump
BeitragVerfasst: 04.10.2018 21:52 
Offline
Ein Admin
Benutzeravatar

Registriert: 29.08.2004 20:20
Wohnort: Saarbrücken
Cool. Hab übrigens vor kurzem ein Video darüber gesehen. Vielleicht interessiert dich das auch: Math for Game Programmers: Building a Better Jump

_________________
Freakscorner.de - Der Bastelkeller | Neustes Video: Neje DK - 1 Watt Laser Engraver
Ubuntu Gnome 18.04.1 LTS x64, PureBasic 5.60 x64 (außerdem 4.41, 4.50, 4.61, 5.00, 5.10, 5.11, 5.21, 5.22, 5.30, 5.31, 5.40, 5.50)
"Die deutsche Rechtschreibung ist Freeware, du darfst sie kostenlos nutzen – Aber sie ist nicht Open Source, d. h. du darfst sie nicht verändern oder in veränderter Form veröffentlichen."


Nach oben
 Profil  
Mit Zitat antworten  
 Betreff des Beitrags: Re: The Perfect Jump
BeitragVerfasst: 04.10.2018 23:01 
Offline
Benutzeravatar

Registriert: 06.07.2017 12:24
Ein Kumpel hat mir soeben von einem kleinen Bug erzählt, daß man wegclippen kann, wenn man ganz links steht und dann nach rechts tippt. Ich konnte das auf meinem Computer nicht reproduzieren, aber mit seiner Hilfe konnte ich den Fehler finden und ausmerzen. Habe die aktuelle Fassung oben in den ersten Post eingepflegt ... der Grund, warum der Fehler auf manchen Computern nicht auftritt, scheint mit der Delta-Time zusammenzuhängen ... auf schnelleren Rechnern hat man smoothere Bewegungen und dadurch genauere Abfragen. Ist zumindest meine Vermutung.
:coderselixir: :coderselixir: :coderselixir:
Der Fehler selbst war übrigens ein ganz doofer Subtraktionsfehler, der sich durch eine copy/paste-Aktion eingeschlichen hat.

NicTheQuick hat geschrieben:
Cool. Hab übrigens vor kurzem ein Video darüber gesehen. Vielleicht interessiert dich das auch: Math for Game Programmers: Building a Better Jump

Das Video habe ich schonmal im Schnelldurchlauf "überflogen". :)
Aber danke für die Erinnerung, ist schon etwas her, und ich könnte diesmal noch etwas genauer hinsehen und mir etwas abgucken.

//EDIT:
Und noch ein kleiner Tweak in oben gepostetem Code ...
Eine zusätzliche Konstante #iniFallVelocity sorgt dafür, daß das "Abstürzen" von einer Kante etwas unmittelbarer geschieht, sich so realistischer anfühlt, und signifikant von der Impuls-Umkehr bei Erreichen des Jump Peaks unterscheidet.

_________________
Now these points of data make a beautiful line,
And we're out of Beta, we're releasing on time.


Nach oben
 Profil  
Mit Zitat antworten  
 Betreff des Beitrags: Re: The Perfect Jump
BeitragVerfasst: 05.10.2018 10:27 
Offline
Benutzeravatar

Registriert: 08.09.2004 00:59
diceman hat geschrieben:
Re: Roguelike-Diary (WIP ...)
... Die größte Falle ist tatsächlich, sich im early development mit Graphics/Polish-Aspekten und Feature-Creation zu verzetteln; das ist der Fortschritts-Killer No. 1!
Nicht vergessen!

_________________
Siehste! Geht doch....?!
PB*, *4PB, PetriDish, Movie2Image, PictureManager, TrainYourBrain, ...


Nach oben
 Profil  
Mit Zitat antworten  
 Betreff des Beitrags: Re: The Perfect Jump
BeitragVerfasst: 05.10.2018 11:27 
Offline
Benutzeravatar

Registriert: 06.07.2017 12:24
:allright: :allright:

_________________
Now these points of data make a beautiful line,
And we're out of Beta, we're releasing on time.


Nach oben
 Profil  
Mit Zitat antworten  
Beiträge der letzten Zeit anzeigen:  Sortiere nach  
Ein neues Thema erstellen Auf das Thema antworten  [ 9 Beiträge ] 

Alle Zeiten sind UTC + 1 Stunde [ Sommerzeit ]


Wer ist online?

Mitglieder in diesem Forum: 0 Mitglieder und 1 Gast


Sie dürfen keine neuen Themen in diesem Forum erstellen.
Sie dürfen keine Antworten zu Themen in diesem Forum erstellen.
Sie dürfen Ihre Beiträge in diesem Forum nicht ändern.
Sie dürfen Ihre Beiträge in diesem Forum nicht löschen.

Suche nach:
Gehe zu:  

 


Powered by phpBB © 2008 phpBB Group | Deutsche Übersetzung durch phpBB.de
subSilver+ theme by Canver Software, sponsor Sanal Modifiye