Advertisement
Guest User

Godot 3.1 Authoritative Server

a guest
Apr 26th, 2019
946
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 8.19 KB | None | 0 0
  1. extends KinematicBody
  2.  
  3. #############
  4. # VARIABLES #
  5. #############
  6.  
  7. # PLAYER ATTRIBUTES #
  8. const SPEED = 6
  9. const ACC = 5
  10. const DEC = 5
  11. const GRAVITY = 20
  12. const JUMP_SPEED = 8
  13. const JUMP_DAMP = 0.25 # currently defunct, used for variable jump heights
  14.  
  15. # ENGINE VARIABLES #
  16. var velocity = Vector3(0,0,0)
  17. var movedir = Vector3()
  18. var animation = "shoot"
  19. var can_jump = false
  20.  
  21. # CAMERA STUFF #
  22. onready var camera = $Camera
  23. var sensitivity = 0.001
  24.  
  25. # CLIENT #
  26. var client_timestamp = 0 # keeps track of when inputs take place
  27. var pending_inputs = [] # client-only queue of previous inputs used for server reconciliation
  28. var queued_inputs = [] # similar to above, but is sent to server and cleared once sent
  29. var server_state = [] # state received from server to sync position & velocity
  30.  
  31. # SERVER #
  32. var server_timestamp = 0 # used for tracking the latest received input
  33. var client_input = [] # queue of inputs received from client
  34. var queued_states = [] # queue of states to send to client
  35.  
  36. # PUPPETS #
  37. var packet_elapsed_time = 0 # how long it's been since the last packet
  38. var tick_time = 0 # basically the tick rate. synced in game.gd
  39. var old_position = global_transform.origin # position to interpolate from
  40. var new_position = global_transform.origin # position to interpolate to
  41.  
  42. #############
  43. # FUNCTIONS #
  44. #############
  45.  
  46. func _ready():
  47.     add_to_group("player")
  48.  
  49. func _physics_process(delta):
  50.     set_animation()
  51.    
  52.     can_jump = is_on_floor() # !!this line is dangerous and should eventually be checked by the server!!
  53.    
  54.     # currently playing player
  55.     if is_network_master():
  56.         camera.current = true
  57.         allow_mouse_capture()
  58.         get_inputs()
  59.         move()
  60.        
  61.         # if not the server, prepare packets for server
  62.         if !get_tree().is_network_server():
  63.             create_client_input()
  64.             server_reconciliation()
  65.    
  66.     # the server
  67.     if get_tree().is_network_server():
  68.         # if not the host, process client movements
  69.         if !is_network_master():
  70.             process_client_movement()
  71.        
  72.         create_server_state() # prepare packets for client
  73.    
  74.     # puppets
  75.     if !is_network_master() && !get_tree().is_network_server():
  76.         sync_entities()
  77.  
  78. func update_tick(): # called by game.gd; sends packets
  79.     # server updates to clients
  80.     if get_tree().is_network_server():
  81.         # send all of the states that occured since the last tick
  82.         while queued_states.size() > 0:
  83.             rpc_unreliable("receive_server_state", queued_states[0])
  84.             queued_states.pop_front()
  85.    
  86.     # client updates to server
  87.     elif is_network_master():
  88.         # send all of the inputs that occured since the last tick
  89.         while queued_inputs.size() > 0:
  90.             rpc_unreliable_id(1, "receive_client_input", queued_inputs[0])
  91.             queued_inputs.pop_front()
  92.  
  93. # CLIENT FUNCTIONS #
  94. # stuff that happens on your screen (and other players' screens)
  95.  
  96. remote func receive_server_state(state): # takes received state of server & adds it to a queue (used in reconciliation)
  97.     server_state.append(state)
  98.     packet_elapsed_time = 0
  99.  
  100. func create_client_input(): # takes inputs and adds it to two queues
  101.     var new_input = {
  102.         movedir = movedir,
  103.         rot = rotation.y,
  104.         timestamp = client_timestamp
  105.     }
  106.    
  107.     pending_inputs.append(new_input) # used in server_reconciliation
  108.     queued_inputs.append(new_input) # used to send to server
  109.     client_timestamp += 1
  110.  
  111. func server_reconciliation(): # compares latest received server state & replays unreceived movements
  112.     while server_state.size() > 0:
  113.         var msg = server_state.front() # the earliest unplayed state
  114.         server_state.pop_front()
  115.        
  116.         # sync player to server state
  117.         global_transform.origin = msg.position
  118.         velocity = msg.velocity
  119.        
  120.         # replay movements that the state didn't receive at the time
  121.         var current_input = 0
  122.         while current_input < pending_inputs.size():
  123.             var input = pending_inputs[current_input]
  124.             if input.timestamp <= msg.timestamp: # if the server already processed this input, remove it
  125.                 pending_inputs.remove(current_input)
  126.             else: # otherwise, reprocess the input on the client
  127.                 move(input.movedir)
  128.                 current_input += 1
  129.  
  130. func get_inputs(): # gets currently pressed movement & jump keys
  131.     movedir = Vector3(0,0,0)
  132.  
  133.     movedir += camera.global_transform.basis.z * (-int(Input.is_action_pressed("up")) + int(Input.is_action_pressed("down")))
  134.     movedir += camera.global_transform.basis.x * (-int(Input.is_action_pressed("left")) + int(Input.is_action_pressed("right")))
  135.    
  136.     movedir.y = 0
  137.     movedir = movedir.normalized()
  138.    
  139.     # we just use movedir.y for jumps to save some bandwidth on a jump variable
  140.     if Input.is_action_pressed("jump") && can_jump:
  141.         movedir.y = 1
  142.  
  143. func allow_mouse_capture(): # lock mouse to screen if clicked
  144.     if Input.is_mouse_button_pressed(BUTTON_LEFT):
  145.         Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
  146.  
  147. func _input(event): # rotate camera to mouse && press TAB to unlock mouse
  148.     if !is_network_master():
  149.         return
  150.     if event is InputEventMouseMotion && Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED:
  151.         rotation.y -= event.relative.x * sensitivity # left right
  152.         camera.rotation.x = clamp(camera.rotation.x - event.relative.y * sensitivity, deg2rad(-85), deg2rad(85)) # up down
  153.        
  154.     if event is InputEventKey && event.pressed:
  155.         if event.scancode == KEY_TAB:
  156.             Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
  157.  
  158. # SERVER FUNCTIONS #
  159. # processes inputs received by the client and creates the state every peer will adhere to
  160.  
  161. remote func receive_client_input(input): # takes received client movement & adds it to a queue
  162.     client_input.append(input)
  163.  
  164. func process_client_movement(): # iterate through and process every input received by the client
  165.     server_timestamp = 0
  166.    
  167.     while client_input.size() > 0:
  168.         var msg = client_input[0]
  169.        
  170.         server_timestamp = max(server_timestamp, msg.timestamp)
  171.        
  172.         # make sure the client isn't trying to punk us with bogus movedirs (speedhacks)
  173.         msg.movedir.x = clamp(msg.movedir.x, -1, 1)
  174.         msg.movedir.z = clamp(msg.movedir.z, -1, 1)
  175.        
  176.         rotation.y = msg.rot
  177.        
  178.         move(msg.movedir)
  179.         client_input.pop_front()
  180.  
  181. func create_server_state(): # saves the player's current state to be sent to every other player
  182.     var new_state = {
  183.         position = global_transform.origin,
  184.         velocity = velocity,
  185.         rot = rotation.y,
  186.         timestamp = server_timestamp,
  187.         }
  188.        
  189.     queued_states.append(new_state)
  190.  
  191. # PUPPET FUNCTIONS #
  192. # these are player objects that are not controlled by the client i.e. other players
  193.  
  194. func sync_entities(): # sync position & velocity to server state
  195.     packet_elapsed_time += get_physics_process_delta_time() # time since last packet (reset in update_tick)
  196.        
  197.     if server_state:
  198.         var msg = server_state.back()
  199.        
  200.         old_position = server_state.front().position # the oldest known position that will be interpolated from
  201.         new_position = msg.position # the last position that will be interpolated to
  202.        
  203.         velocity = msg.velocity
  204.         rotation.y = msg.rot
  205.        
  206.         server_state.clear() # we don't care about this state anymore
  207.    
  208.     ### entity interpolation
  209.     #!# currently the host doesn't interpolate the other players on his screen
  210.     global_transform.origin = old_position.linear_interpolate(new_position, packet_elapsed_time / tick_time)
  211.  
  212. # GENERIC FUNCTIONS #
  213.  
  214. func move(dir = movedir): # process movement based off of direction & velocity
  215.     var delta = get_physics_process_delta_time()
  216.     var hv = Vector3(velocity.x, 0, velocity.z) # horizontal velocity
  217.     var new_pos = dir * SPEED
  218.    
  219.     var accel = DEC
  220.    
  221.     if dir.dot(hv) > 0:
  222.         accel = ACC
  223.    
  224.     hv = hv.linear_interpolate(new_pos, accel * delta) # I know lerping by delta doesn't work like this but I forgot the other way
  225.    
  226.     velocity.x = hv.x
  227.     velocity.z = hv.z
  228.    
  229.     if !is_on_floor():
  230.         velocity.y -= GRAVITY * delta
  231.    
  232.     if dir.y != 0: # on jump key press (refer to get_input)
  233.         velocity.y = JUMP_SPEED
  234.    
  235.     velocity = move_and_slide(velocity, Vector3(0,1,0))
  236.  
  237. func set_animation(): # set Sprite3D animation (also refer to that script for billboard sprites)
  238.     var hspeed = Vector3(velocity.x, 0, velocity.z).length() # horizontal speed
  239.    
  240.     if !is_grounded():
  241.         animation = "jump"
  242.     elif hspeed < SPEED * 0.2:
  243.         animation = "idle"
  244.     else:
  245.         animation = "walk"
  246.  
  247. func is_grounded(): # basically is_on_floor combined with a longer RayCast3D to give a little leeway
  248.     if is_on_floor() || $OnGround.is_colliding():
  249.         return true
  250.     return false
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement