FirstPerson Tutorial von GameDev.net

Fragen zu Grafik- & Soundproblemen und zur Spieleprogrammierung haben hier ihren Platz.
Benutzeravatar
Makke
Beiträge: 156
Registriert: 24.08.2011 18:00
Computerausstattung: AMD Ryzen 7 5700X - AMD Radeon RX 6800 XT - 32 GB DDR4 SDRAM
Wohnort: Ruhrpott
Kontaktdaten:

FirstPerson Tutorial von GameDev.net

Beitrag von Makke »

Hi,

ich habe bei Gamedev.net dieses Tutorial gefunden: LINK. Es ist eigentlich für die Leadwerks Engine, aber ich wollte mal schauen ob man das umsetzen kann. In dem Tutorial kann man sich alle Grafik und Sonddateien, sowie den Sourcecode (für Leadwerks) herunterladen.

Hier ist der Code und ich denke er funktioniert ganz gut in Purebasic mit der OGRE Engine.

Code: Alles auswählen

EnableExplicit

;--- includes
UsePNGImageDecoder()
UseOGGSoundDecoder()
InitEngine3D()
InitSprite()
InitKeyboard()
InitMouse()
InitSound()
Define.b FullScreen = #False
OpenWindow(0,0,0,1200,768,"3D-Test",#PB_Window_SystemMenu|#PB_Window_ScreenCentered)
OpenWindowedScreen(WindowID(0), 0, 0, 1200, 768, 0, 0, 0, #PB_Screen_NoSynchronization)

;--- constants
#SLASH = "\"
#STANDARDFPS = 60
#Mask_Entity_Level = 1 << 31
Enumeration 1
  #Entity_Level
  #Light_Sun_Level
EndEnumeration

Enumeration
  #PlayerCollide_Nothing
  #PlayerCollide_Bottom
  #PlayerCollide_Knee
  #PlayerCollide_Crouch
  #PlayerCollide_BottomKnee
  #PlayerCollide_BottomCrouch
  #PlayerCollide_KneeCrouch
  #PlayerCollide_BottomKneeCrouch
EndEnumeration

;--- structures
Structure iVector2
  x.i
  y.i
EndStructure

Structure fVector3
  x.f
  y.f
  z.f
EndStructure

Structure iKeys
  forward.i
  backward.i
  strafe_left.i
  strafe_right.i
  run.i
  jump.i
  crouch.i
EndStructure

Structure structPlayer
  camera.i
  mesh.i
  entity.i
  entityScaleFactor.f
  entityRotationCorrector.i
  crosshair.i
  elapsedTime.i
  elapsedTimeLastFrame.f
  standHeight.f
  crouchHeight.f
  kneeHeight.f
  cameraHeight.f
  move.f
  strafe.f
  movementSpeed.f
  currentSpeed.f
  maxAcceleration.f
  sensitivity.f
  smoothedCameraPositionY.f
  cameraYPositionSmoothing.f
  cameraLookSmoothing.f
  runBoost.f
  jump.f
  jumpForce.f
  jumpBoost.f
  lastYPos.f
  currentJump.f
  footstepWalkFrequency.i
  footstepRunFrequency.i
  footstepTimer.l
  running.b
  crouched.b
  jumping.b
  landing.b
  walking.b
  MouseSpeed.iVector2
  NormalizedMovement.fVector3
  CameraPosition.fVector3
  PlayerRotation.fVector3
  Keys.iKeys
  Array FootstepSound.i(4)
  Array LandSound.i(4)
  Array JumpSound.i(1)
EndStructure

;--- variables
Define.structPlayer Player
Define.i            WindowEvt, TimeSinceLastFrame
Define.b            DoLoop

;--- declarations
Declare Player_Create()
Declare Player_Destroy()
Declare Player_Update()

;--- player
Player_Create()

;--- main loop
MouseLocate(ScreenWidth()/2, ScreenHeight()/2)
DoLoop = #True
Repeat
  
  ;--- catch all window events
  If FullScreen = #False
    Repeat
      WindowEvt = WindowEvent()
      Select WindowEvt
        Case #PB_Event_CloseWindow
          DoLoop = #False
      EndSelect
      SetWindowTitle(0, "3D-Test" + " - " + StrF(Engine3DFrameRate(#PB_Engine3D_Current),1))
    Until WindowEvt = 0
  EndIf
  
  ;--- player
  Player_Update()
  
  
  ;--- render
  Player\elapsedTimeLastFrame = RenderWorld()
  If IsSprite(Player\crosshair)
    DisplayTransparentSprite(Player\crosshair, ScreenWidth()/2-3, ScreenHeight()/2-3)
  EndIf
  FlipBuffers() 
  
Until DoLoop = #False

;--- end
End

;--- macros
Macro PrintV3(V)
  CompilerIf #PB_Compiler_Debugger
    Debug "x:"+StrF(V\x,6)+"; y:"+StrF(V\y,6)+"; z:"+StrF(V\z,6)
  CompilerEndIf
EndMacro

;--- procedures
Procedure.f Clamp(value.f, min.f=0.0, max.f=1.0)
  If value < min
    value = min
  Else
    If value > max
      value = max
    EndIf
  EndIf
  ProcedureReturn value
EndProcedure

Procedure.f Curve(newvalue.f, oldvalue.f, increments.f)
  If increments > 1
    oldvalue = oldvalue-(oldvalue-newvalue)/increments
  Else
    oldvalue = newvalue
  EndIf
  ProcedureReturn oldvalue
EndProcedure

Procedure.f Inc(targetvalue.f, currentvalue.f, increments.f)
  If targetvalue > currentvalue
    ProcedureReturn currentvalue + increments
  ElseIf targetvalue < currentvalue
    ProcedureReturn currentvalue - increments
  Else
    ProcedureReturn targetvalue
  EndIf
EndProcedure

Procedure.f fMin(value1.f, value2.f)
  If value1 > value2
    ProcedureReturn value2
  ElseIf value1 < value2
    ProcedureReturn value1
  ElseIf value1 = value2
    ProcedureReturn value1
  EndIf
EndProcedure

Procedure.f fMax(value1.f, value2.f)
  If value1 > value2
    ProcedureReturn value1
  ElseIf value1 < value2
    ProcedureReturn value2
  ElseIf value1 = value2
    ProcedureReturn value1
  EndIf
EndProcedure

Procedure PlayerCamera_SetFOV(FOV.i)
  Shared Player
  CameraFOV(Player\camera, FOV)
EndProcedure

Procedure PlayerCamera_SetPosition(x.f, y.f, z.f)
  Shared Player
  MoveCamera(Player\camera, x, y, z, #PB_Absolute)
EndProcedure

Procedure PlayerCamera_GetPosition(*V.fVector3)
  Shared Player
  *V\x = CameraX(Player\camera)
  *V\y = CameraY(Player\camera)
  *V\z = CameraZ(Player\camera)
EndProcedure

Procedure Player_SetPhysicsMode(PhysicsMode.i, Mass.f=1.0)
  Shared Player
  EntityPhysicBody(Player\entity, PhysicsMode, Mass, 0.45, 0) 
EndProcedure

Procedure Player_SetPosition(x.f, y.f, z.f)
  Shared Player
  MoveEntity(Player\entity, x, y, z, #PB_Absolute)
EndProcedure

Procedure Player_GetPosition(*V.fVector3)
  Shared Player
  *V\x = EntityX(Player\entity)
  *V\y = EntityY(Player\entity)
  *V\z = EntityZ(Player\entity)
EndProcedure

Procedure Player_GetVelocity(*V.fVector3)
  Shared Player
  *V\x = GetEntityAttribute(Player\entity, #PB_Entity_VelocityX)
  *V\y = GetEntityAttribute(Player\entity, #PB_Entity_VelocityY)
  *V\z = GetEntityAttribute(Player\entity, #PB_Entity_VelocityZ)
EndProcedure

Procedure.i Player_OnGround()
  Shared Player
  ProcedureReturn RayCollide(EntityX(Player\entity), EntityY(Player\entity), EntityZ(Player\entity), EntityX(Player\entity), EntityY(Player\entity)-0.2, EntityZ(Player\entity)) 
  ;ProcedureReturn RayCast(EntityX(Player\entity), EntityY(Player\entity), EntityZ(Player\entity), EntityX(Player\entity), EntityY(Player\entity)-0.2, EntityZ(Player\entity), #Mask_Entity_Level) 
EndProcedure

Procedure.i Player_CollideForward(DirectionX.f, DirectionZ.f)
  Shared      Player
  Static.f    newx, newz
  Protected.f x, y, z
  Protected.i raybottom, rayknee, raycrouch, result
  If DirectionX <> 0 : newx = DirectionX * #STANDARDFPS/10 : EndIf
  If DirectionZ <> 0 : newz = DirectionZ * #STANDARDFPS/10 : EndIf
  x = EntityX(Player\entity) + DirectionX
  y = EntityY(Player\entity)
  z = EntityZ(Player\entity) + DirectionZ
  raybottom = RayCollide(x, y + 0.1, z, x + newx, y + 0.1, z + newz)
  rayknee   = RayCollide(x, y + Player\kneeHeight, z, x + newx, y + Player\kneeHeight, z + newz)
  raycrouch = RayCollide(x, y + Player\crouchHeight, z, x + newx, y + Player\crouchHeight, z + newz)
  CreateLine3D(99, x, y+0.1, z, RGB(255,0,0), x + newx, y+0.1, z + newz, RGB(255,0,0))
  CreateLine3D(98, x, y + Player\kneeHeight, z, RGB(255,0,0), x + newx, y + Player\kneeHeight, z + newz, RGB(255,0,0))
  CreateLine3D(97, x, y + Player\crouchHeight, z, RGB(255,0,0), x + newx, y + Player\crouchHeight, z + newz, RGB(255,0,0))
  If raybottom = 0 And rayknee = 0 And raycrouch = 0
    result = #PlayerCollide_Nothing
  ElseIf raybottom <> 0 And rayknee <> 0 And raycrouch <> 0
    result = #PlayerCollide_BottomKneeCrouch
  ElseIf raybottom = 0 And rayknee <> 0 And raycrouch <> 0
    result = #PlayerCollide_KneeCrouch
  ElseIf raybottom <> 0 And rayknee = 0 And raycrouch <> 0
    result = #PlayerCollide_BottomCrouch
  ElseIf raybottom <> 0 And rayknee <> 0 And raycrouch = 0
    result = #PlayerCollide_BottomKnee
  ElseIf raybottom = 0 And rayknee = 0 And raycrouch <> 0
    result = #PlayerCollide_Crouch
  ElseIf raybottom = 0 And rayknee <> 0 And raycrouch = 0
    result = #PlayerCollide_Knee
  ElseIf raybottom <> 0 And rayknee = 0 And raycrouch = 0
    result = #PlayerCollide_Bottom  
  EndIf
  ProcedureReturn result
EndProcedure

Procedure Player_CreateHitbox()
  ; EntityBoneX() delivers equal values every time and with all different bones ... ?
EndProcedure

Procedure Player_Create()
  Shared      Player
  Protected.i tempTexture, tempMaterial, i
  
  ; create temp material for help entity
  tempTexture = CreateTexture(#PB_Any, 1, 1)
  StartDrawing(TextureOutput(tempTexture))
  Plot(0, 0, RGB(0, 0, 255))
  StopDrawing()
  tempMaterial = CreateMaterial(#PB_Any, TextureID(tempTexture))
  
  With Player
    
    ; initialize values
    \standheight  = 1.8
    \crouchheight = 1.2
    \kneeHeight   = \crouchheight / 2
    \cameraheight = \standheight
    
    \move                     = 0.0
    \strafe                   = 0.0
    \cameraYPositionSmoothing = 3.0
    \maxAcceleration          = 0.5
    \movementSpeed            = 3.0
    \walking                  = #False
    
    \sensitivity              = 1.0
    \cameraLookSmoothing      = 2.0
    \cameraYPositionSmoothing = 3.0
    \smoothedCameraPositionY  = 0
    
    \running  = #False
    \runBoost = 2.0
    
    \jump      = 0.0
    \jumpforce = 6.0
    \jumpboost = 2.0
    \jumping   = #False
    \crouched  = #False
    
    \landing               = #False
    \footstepTimer         = 0
    \footstepWalkFrequency = 400
    \footstepRunFrequency  = 320
    
    ; keyboard settings
    \Keys\forward      = #PB_Key_W
    \Keys\backward     = #PB_Key_S
    \Keys\strafe_left  = #PB_Key_A
    \Keys\strafe_right = #PB_Key_D
    \Keys\run          = #PB_Key_LeftShift
    \Keys\jump         = #PB_Key_Space
    \Keys\crouch       = #PB_Key_C
    
    ; create the player camera
    \camera = CreateCamera(#PB_Any, 0, 0, 100, 100)
    PlayerCamera_SetFOV(70)
    PlayerCamera_SetPosition(0, \cameraHeight, 0)
    
    ; set up player model and physics
    \entityScaleFactor       = 0.01
    \entityRotationCorrector = 90
    \mesh   = CreateCylinder(#PB_Any, \standHeight/4, \standHeight)
    \entity = CreateEntity(#PB_Any, MeshID(\mesh), MaterialID(tempMaterial))
    Player_SetPhysicsMode(#PB_Entity_ConvexHullBody, 10.0)
    Player_SetPosition(0, 1, -5)
    
    ; load sound files
    For i = 0 To 3
      \FootstepSound(i) = LoadSound(#PB_Any, "sounds" + #SLASH + "footstep_shoe_sand_0"+Str(i+1)+".ogg")
    Next
    For i = 0 To 3
      ;\LandSound(i) = LoadSound(#PB_Any, "sounds" + #SLASH + "footstep_shoe-softsole_metal_jump_0"+Str(i+1)+".ogg")
    Next
    For i = 0 To 0
      \JumpSound(i) = LoadSound(#PB_Any, "sounds" + #SLASH + "jump_shoe_sand_0"+Str(i+1)+".ogg")
    Next
    
    \crosshair = CreateSprite(#PB_Any, 6, 6)
    StartDrawing(SpriteOutput(\crosshair))
    Box(0,0,6,6,RGB(0,0,0))
    Box(1,1,4,4,RGB(192,192,0))
    Box(2,0,2,6,RGB(255,255,0))
    Box(0,2,6,2,RGB(255,255,0))
    StopDrawing()
    
  EndWith
  
  ; create hitbox for entity
  ;Player_CreateHitbox()
  
  ; destroy the temp material
  FreeMaterial(tempMaterial)
  FreeTexture(tempTexture)
  
EndProcedure

Procedure Player_Destroy()
  Shared Player
  FreeSprite(Player\crosshair)
  FreeEntity(Player\entity)
  FreeMesh(Player\mesh)
  FreeCamera(Player\camera)
  Player\crosshair = #Null
  Player\entity    = #Null
  Player\mesh      = #Null
  Player\camera    = #Null
EndProcedure

Procedure.b Player_UpdateControls()
  Shared             Player
  Shared             DoLoop
  Protected.f        sx, sy, dx, dy
  Protected.b        result = #True
  Protected.fVector3 mouseposition
  
  ; get inputs from the keyboard
  If ExamineKeyboard()
    
    If KeyboardReleased(#PB_Key_Escape)
      DoLoop = #False
    EndIf
    
    With Player
    
      If KeyboardPushed(\Keys\forward)
        \move = -1
      ElseIf KeyboardPushed(\Keys\backward)
        \move = 1
      Else
        \move = 0
      EndIf
      
      If KeyboardPushed(\Keys\strafe_left)
        \strafe = -1
      ElseIf KeyboardPushed(\Keys\strafe_right)
        \strafe = 1
      Else
        \strafe = 0
      EndIf
      
      If KeyboardPushed(\Keys\run)
        \running = #True
      Else
        \running = #False
      EndIf
      
      If Player_OnGround()
        
        If KeyboardReleased(\Keys\jump)
          \jump    = 1 * \jumpForce
          \jumping = #True
        EndIf
        
        If KeyboardPushed(\Keys\crouch) And \jumping = #False
          \crouched = #True
        Else
          \crouched = #False
        EndIf
        
      EndIf
      
    EndWith
    
  Else
    result = #False
  EndIf
  
  ; get inputs from the mouse
  If ExamineMouse()
    
    ; get screen center
    sx = ScreenWidth() / 2
    sy = ScreenHeight() / 2
    
    ; get mouse position
    mouseposition\x = MouseX()
    mouseposition\y = MouseY()
    
    ; move mouse to the center of the screen
    MouseLocate(sx, sy)
    
    ; get change in mouse position
    dx = mouseposition\x - sx
    dy = mouseposition\y - sy
    
    With Player
      
      ; mouse smoothing
      \MouseSpeed\x = Curve(dx, \MouseSpeed\x, \cameraLookSmoothing)
      \MouseSpeed\y = Curve(dy, \MouseSpeed\y, \cameraLookSmoothing)
      
      ; adjust and set the camera rotation
      \PlayerRotation\x - \MouseSpeed\y * \sensitivity / 10.0
      \PlayerRotation\y - \MouseSpeed\x * \sensitivity / 10.0
      
      ; prevent inhuman looking angles
      \Playerrotation\x = Clamp(\PlayerRotation\x, -90, 90)
      
    EndWith
    
  Else
    result = #False
  EndIf
  
  ProcedureReturn result
  
EndProcedure

Procedure Player_Update()
  Shared             Player
  Static.b           playerblocked
  Protected.fVector3 velocity
  Protected.f        maxaccel   = Player\maxAcceleration
  Protected.f        frametimer = Player\elapsedTimeLastFrame / 1000 * #STANDARDFPS
  Protected.f        groundspeed, movespeed, length, radianangle, newx, newz, newy
  Protected.i        playercollide, onground = Player_OnGround()
  
  With Player
    \elapsedTime + \elapsedTimeLastFrame
  EndWith
  
  Player_UpdateControls()
  
;   ; get players velocity, doesn't work in purebasic ?
;   Player_GetVelocity(velocity)
;   With velocity
;     groundspeed = Sqr((\x * \x) + (\z * \z))
;   EndWith

  ; check if player is crouching
  With Player
    If \crouched
      \cameraHeight = Inc(\crouchHeight, \cameraHeight, 0.1 * frametimer)
    Else
      \cameraHeight = Inc(\standHeight, \cameraHeight, 0.1 * frametimer)
    EndIf
  EndWith
  
  ; normalize movement
  With Player\NormalizedMovement
    \x = Player\strafe
    \z = Player\move  
    length = Sqr((\x * \x) + (\y * \y) + (\z * \z))
    If length > 0
      \x = \x / length
      \y = \y / length
      \z = \z / length
    EndIf
  EndWith
  
  ; check if player is jumping
  With Player
    If \landing
      newy = -maxaccel * frametimer
      If onground
        \landing     = #False
        \currentJump = 0
        newy + maxaccel
        ;PlaySound(\LandSound(Random(3,0)))
      EndIf
    EndIf
    If \jumping
      If onground
        PlaySound(\JumpSound(0))
        StartEntityAnimation(\entity, "Jump")
      EndIf
      \currentJump + maxaccel / 1.5 * frametimer
      newy         = maxaccel / 1.5 * frametimer
      If \currentJump > \jump
        \jump = 0
        \jumping = #False
        \landing = #True
      EndIf
    EndIf
  EndWith
    
  ; run if shift key is pressed
  If Player\running
    maxaccel  * 2.0
  EndIf
  
  ; accelerate player and play animation
  With Player
    If \move <> 0 Or \strafe <> 0 Or \jump <> 0
      If \running And \jumping
        movespeed = fMin(\movementSpeed * \runBoost, \movementSpeed * \jumpBoost)
        \currentSpeed = Inc(movespeed, \currentSpeed, maxaccel * frametimer)
      ElseIf \running And Not \jumping
        \currentSpeed = Inc(\movementSpeed * \runBoost, \currentSpeed, maxaccel * frametimer)
      ElseIf Not \running And \jumping
        \currentSpeed = Inc(\movementSpeed * \jumpBoost, \currentSpeed, maxaccel * frametimer)
      ElseIf Not \running And Not \jumping
        If \crouched
          \currentSpeed = Inc(\movementSpeed / 3, \currentSpeed, maxaccel * frametimer)
        Else
          \currentSpeed = Inc(\movementSpeed, \currentSpeed, maxaccel * frametimer)
        EndIf
      EndIf
    Else
      \currentSpeed = Inc(0, \currentSpeed, maxaccel * frametimer)
    EndIf
    If \currentSpeed > 0
      If \walking = #False
        StartEntityAnimation(\entity, "Walk")
        \walking = #True
      EndIf
    Else
      If \walking = #True
        StopEntityAnimation(\entity, "Walk")
        \walking = #False
      EndIf
    EndIf
  EndWith

  ; play footsteps
  With Player
    If \elapsedTime > \footstepTimer And length > 0.0 And onground
      PlaySound(\FootstepSound(Random(3,0)))
      If \currentSpeed > \movementSpeed
        \footstepTimer = \elapsedTime + \footstepRunFrequency
      Else
        \footstepTimer = \elapsedTime + \footstepWalkFrequency
      EndIf
    EndIf
  EndWith
  
  ; rotate player camera
  With Player\PlayerRotation
    RotateCamera(Player\camera, \x, \y, 0, #PB_Absolute)
  EndWith
  
  ; check if player still on ground or must walk up
  With Player
    If Not \jumping And Not \landing And Not onground
      If \walking Or \running
        newy-0.05:Debug"DOWN"
      EndIf
    EndIf
  EndWith

  ; move and rotate player entity
  With Player
    radianangle   = Radian(CameraYaw(\camera))
    ;\currentSpeed * frametimer
    If \move <> 0 And \strafe = 0
      newx = Sin(Radian(CameraYaw(\camera))) * \move * \currentSpeed
      newz = Cos(Radian(CameraYaw(\camera))) * \move * \currentSpeed
    ElseIf \move = 0 And \strafe <> 0
      newx = Sin(Radian(CameraYaw(\camera) + \strafe * 90)) * \currentSpeed
      newz = Cos(Radian(CameraYaw(\camera) + \strafe * 90)) * \currentSpeed
    ElseIf \move <> 0 And \strafe <> 0
      newx = Sin(Radian(CameraYaw(\camera) + \strafe * -45)) * \NormalizedMovement\z * \currentSpeed
      newz = Cos(Radian(CameraYaw(\camera) + \strafe * -45)) * \NormalizedMovement\z * \currentSpeed
    EndIf
    newx * frametimer / 2
    ;newy * frametimer
    newz * frametimer / 2
    playercollide = Player_CollideForward(newx, newz)
    If playercollide = #PlayerCollide_Bottom ; for height differences
      newy+0.15 : Debug "UP:bottom"
    ElseIf playercollide = #PlayerCollide_BottomKnee Or playercollide = #PlayerCollide_Knee ; for stairs
      newy+0.40 : Debug "UP:bottom_knee"
    ElseIf playercollide = #PlayerCollide_BottomKneeCrouch ; collide with map buildings, so don't move
      newy+0.15 : Debug "UP:bottom_knee_crouch"
      playerblocked = #True
    ElseIf playercollide = #PlayerCollide_KneeCrouch Or playercollide = #PlayerCollide_Crouch
      playerblocked = #True : Debug "UP:knee_crouch"
    ElseIf playercollide = #PlayerCollide_Nothing
      playerblocked = #False
    EndIf
    If playerblocked = #False
      MoveEntity(\entity, EntityX(\entity) + newx, EntityY(\entity) + newy, EntityZ(\entity) + newz, #PB_Absolute)
    EndIf
  EndWith
  With Player\PlayerRotation
    RotateEntity(Player\entity, 0, \y + Player\entityRotationCorrector, 0, #PB_Absolute)
  EndWith
  
  ; move and smooth player camera
  With Player
    Player_GetPosition(\CameraPosition)
    If \CameraPosition\y > \smoothedCameraPositionY
      \smoothedCameraPositionY = Curve(\CameraPosition\y, \smoothedCameraPositionY, \cameraYPositionSmoothing)
      \CameraPosition\y        = \smoothedCameraPositionY
    Else
      \smoothedCameraPositionY = \CameraPosition\y
    EndIf
    MoveCamera(Player\camera, \CameraPosition\x, \CameraPosition\y + \cameraHeight, \CameraPosition\z, #PB_Absolute)
  EndWith
  
EndProcedure
Da ich leider keinen Webspace habe, konnte ich jetzt ein Minilevel nicht hochladen, aber ihr werdet bestimmt irgendwo einen kleinen Raum zum rumlaufen haben. Ich habe auch noch eine Include die ich immer für die Hardwareinitialisierung benutze, die habe ich jetzt erstmal nicht hineinkopiert. Ihr müsstet Euch also noch das Fenster Eurer Wahl öffnen. Ich hoffe der Ein oder Andere hat noch ordntlich Optimierungbedarf entdeckt und teilt diesen hier mit.

Viel Spass beim Testen.
---
Windows 11 (64 bit)