The Perfect Jump

Hier könnt Ihr gute, von Euch geschriebene Codes posten. Sie müssen auf jeden Fall funktionieren und sollten möglichst effizient, elegant und beispielhaft oder einfach nur cool sein.
Benutzeravatar
diceman
Beiträge: 347
Registriert: 06.07.2017 12:24
Kontaktdaten:

The Perfect Jump

Beitrag von diceman »

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: 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
Zuletzt geändert von diceman am 05.10.2018 16:56, insgesamt 9-mal geändert.
Now these points of data make a beautiful line,
And we're out of Beta, we're releasing on time.
Benutzeravatar
RSBasic
Admin
Beiträge: 8022
Registriert: 05.10.2006 18:55
Wohnort: Gernsbach
Kontaktdaten:

Re: The Perfect Jump

Beitrag von RSBasic »

:allright:
Aus privaten Gründen habe ich leider nicht mehr so viel Zeit wie früher. Bitte habt Verständnis dafür.
Bild
Bild
Benutzeravatar
diceman
Beiträge: 347
Registriert: 06.07.2017 12:24
Kontaktdaten:

Re: The Perfect Jump

Beitrag von diceman »

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.
Benutzeravatar
RSBasic
Admin
Beiträge: 8022
Registriert: 05.10.2006 18:55
Wohnort: Gernsbach
Kontaktdaten:

Re: The Perfect Jump

Beitrag von RSBasic »

:allright:
Aus privaten Gründen habe ich leider nicht mehr so viel Zeit wie früher. Bitte habt Verständnis dafür.
Bild
Bild
Benutzeravatar
NicTheQuick
Ein Admin
Beiträge: 8675
Registriert: 29.08.2004 20:20
Computerausstattung: Ryzen 7 5800X, 32 GB DDR4-3200
Ubuntu 22.04.3 LTS
GeForce RTX 3080 Ti
Wohnort: Saarbrücken
Kontaktdaten:

Re: The Perfect Jump

Beitrag von NicTheQuick »

Cool. Hab übrigens vor kurzem ein Video darüber gesehen. Vielleicht interessiert dich das auch: Math for Game Programmers: Building a Better Jump
Bild
Benutzeravatar
diceman
Beiträge: 347
Registriert: 06.07.2017 12:24
Kontaktdaten:

Re: The Perfect Jump

Beitrag von diceman »

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.
Benutzeravatar
DrShrek
Beiträge: 1970
Registriert: 08.09.2004 00:59

Re: The Perfect Jump

Beitrag von DrShrek »

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, ...
Benutzeravatar
diceman
Beiträge: 347
Registriert: 06.07.2017 12:24
Kontaktdaten:

Re: The Perfect Jump

Beitrag von diceman »

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