SHARE
TWEET

Godot 3.1 Authoritative Server

a guest Apr 26th, 2019 210 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  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
RAW Paste Data
We use cookies for various purposes including analytics. By continuing to use Pastebin, you agree to our use of cookies as described in the Cookies Policy. OK, I Understand
 
Top