I’m a beginner programmer in godot 4. I need help for my first person horror game. I was working with a tutorial. How do i make the mouse not visible and how do i make so that the mouse when it reaches the side of the window won’t stop the camera from rotating. Also my character doesn’t fall for some reason. Here’s my code:
extends CharacterBody3D
const GRAVITY = 200
const SPEED = 20
@export var jump_velocity = 4.5
var look_sensitivity = 0.001
@onready var camera = $CameraControl
func _physics_process(delta: float) -> void:
if not is_on_floor():
velocity.y = -GRAVITY * delta
var horizontal_velocity = Input.get_vector("Move_Left", "Move_Right", "Move_Forward", "Move_Backwards").normalized() * SPEED
velocity = horizontal_velocity.x * global_transform.basis.x + horizontal_velocity.y * global_transform.basis.z
move_and_slide()
if Input.is_action_just_pressed("ui_cancel"):
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED if Input.mouse_mode == Input.MOUSE_MODE_VISIBLE else Input.MOUSE_MODE_VISIBLE
func _input(event):
if event is InputEventMouseMotion:
rotate_y(-event.relative.x * look_sensitivity)
camera.rotate_x(-event.relative.y * look_sensitivity)
camera.rotation.x = clamp(camera.rotation.x, -PI/2, PI/2)
Seems like your mouse lock is toggled based on “ui_cancel”, I recommend changing this action from “ui_cancel” to a custom one, and split this from a toggled action to separate lock and un-lock
Moving into _unhandled_input allows you to detect any mouse click not handled by UI
func _unhandled_input(event: InputEvent) -> void:
if event is InputEventMouseButton:
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
elif event.is_action_pressed("UnlockMouse"):
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
Your gravity is failing to work because your overriding the xyz components of velocity all at once with this line
If you want to separately modify the horizontal and vertical components you must keep two variables for velocity and combine them just before move_and_slide().
# split velocity
var vertical_velocity := velocity.project(up_direction)
var horizontal_velocity := velocity - vertical_velocity
# vertical-only gravity
if not is_on_floor():
vertical_velocity += get_gravity() * delta
# horizontal-only movement
var input_dir := Input.get_vector("left", "right", "forward", "backward")
var direction := basis * Vector3(input_dir.x, 0, input_dir.y)
horizontal_velocity = horizontal_velocity.move_toward(direction * SPEED, ACCELERATION * delta)
# re-combine velocity components
velocity = horizontal_velocity + vertical_velocity
move_and_slide()
A mouse lock/unlock button is useful in development when you’re debugging. I prefer for games however to just trigger that when you pause/unpause the game. So I put something like this in an autoload or static class:
The print statements aren’t necessary, but I like to know things are working. Also the save game I just left in there as an example of how you can save on exit automatically. So the simplified version would be:
And you can literally put it in any file, so you could just put it in your CharacterBody3D script and it’ll work. Of course, you’ll also need to implement a pause button that uses get_tree().paused. I just do this in my Game autoload:
## Pauses or unpauses the game based on the boolean sent. Defaults to pausing the game.
## [br]Convenience method.
func pause(pause: bool = true) -> void:
get_tree().paused = pause
## Unpauses the game.
## [br]Convenience method.
func unpause() -> void:
pause(false)
## Returns whether or not the game is paused.
## [br]Convenience method.
func is_paused() -> bool:
return get_tree().paused
Then I hook the pause key up to call Game.pause(). If I need to know if the game is paused, I can evaluate Game.is_paused().
(Sorry for late reply) So this actually works except one thing, the acceleration variable is pretty hard for me to understand. I know what it does but when i put it on 1 it feels like my character moves on ice, how can i fix this issue, do i just play with the value untill it works how i want to? Also how do i stop the acceleration when it reaches a certain point, and how do i make so that it will take faster for the character to stop on higher acceleration?
You should try using higher values for your acceleration. The acceleration value is the amount of units per second your velocity can change. So, with an acceleration of 1, it would take 20 seconds to reach your previous default speed of 20.
Since your horizontal velocity always moves towards direction * SPEED, the speed value already limits your maximum horizontal velocity.
And the acceleration is also used for slowing down, so increasing its value will make you stop faster too.
No, but it’s not a lot of code. That was an example function. Sorry for the confusion.
However this is all my save game code:
#TODO: Add unit tests.
extends Node
const SETTINGS_PATH = "user://configuration.settings"
const SAVE_GAME_PATH = "user://game.save"
## If this value is On, save_game() will be called when the player quits the game.
@export var save_on_quit: bool = false
var configuration_settings: Dictionary
var game_information: Dictionary
var is_ready = false
func _ready() -> void:
if FileAccess.file_exists(SETTINGS_PATH):
configuration_settings = _load_file(SETTINGS_PATH)
ready.connect(func(): is_ready = true)
func _notification(what) -> void:
match what:
NOTIFICATION_WM_CLOSE_REQUEST: #Called when the application quits.
if save_on_quit:
save_game()
## Returns true if the save was successful, otherwise false.
## Calls every node added to the Persist Global Group to save data. Works by
## calling every node in the group and running its `save_node()` function, then
## storing everything in the save file. If a node is in the group, but didn't
## implement the `save_node()` function, it is skipped.
func save_game() -> bool:
var saved_nodes = get_tree().get_nodes_in_group("Persist")
for node in saved_nodes:
# Check the node has a save function.
if not node.has_method("save_node"):
print("Setting node '%s' is missing a save_node() function, skipped" % node.name)
continue
game_information[node.name] = node.save_node()
print("Saving Info for %s: %s" % [node.name, game_information[node.name]])
return _save_file(game_information, SAVE_GAME_PATH)
## Call this to call the `load_node()` function for every node in the Persist
## Global Group. The save game, if it exists, will be loaded from disk and the
## values propagated to the game objects.
func load_game() -> void:
game_information = _load_file(SAVE_GAME_PATH)
if game_information.is_empty():
return
var saved_nodes = get_tree().get_nodes_in_group("Persist")
for node in saved_nodes:
# Check the node has a load function.
if not node.has_method("load_node"):
print("Setting node '%s' is missing a load_node() function, skipped" % node.name)
continue
# Check if we have information to load for the value
if game_information.has(node.name):
print("Loading Info for %s: %s" % [node.name, game_information[node.name]])
node.load_node(game_information[node.name])
## Stores the passed data under the indicated setting catergory.
func save_setting(data: Variant, category: String) -> void:
configuration_settings[category] = data
_save_file(configuration_settings, SETTINGS_PATH)
## Returns the stored data for the passed setting category.
func load_setting(category: String) -> Variant:
if !is_ready:
if FileAccess.file_exists(SETTINGS_PATH):
configuration_settings = _load_file(SETTINGS_PATH)
if configuration_settings.has(category):
return configuration_settings[category]
return ERR_DOES_NOT_EXIST
## Takes data and serializes it for saving.
func _serialize_data(data: Variant) -> String:
return JSON.stringify(data)
## Takes serialized data and deserializes it for loading.
func _deserialize_data(data: String) -> Variant:
var json = JSON.new()
var error = json.parse(data)
if error == OK:
return json.data
else:
print("JSON Parse Error: ", json.get_error_message(), " in ", data, " at line ", json.get_error_line())
return null
func _save_file(save_information: Dictionary, path: String) -> bool:
var file = FileAccess.open(path, FileAccess.WRITE)
if file == null:
print("File '%s' could not be opened. File not saved." % path)
return false
file.store_var(save_information)
return true
func _load_file(path: String) -> Variant:
if not FileAccess.file_exists(path):
print("File '%s' does not exist. File not loaded." % path)
var return_value: Dictionary = {}
return return_value
var file = FileAccess.open(path, FileAccess.READ)
return file.get_var()
I turned it into an autoload you can just use if you like.
Oh, thanks! that was really helpful to know, since i’m not really good with code i didn’t know that it was an acceleration to the speed, i thought it was just accelerating it infinitely
Wait you’re a person who makes things on github for godot devs!? that’s so cool and i’m really thankful, i will use this in my future projects (or the one i’m working on rn if i’ll think that this will be necessary)!