Snake Game
This is a complete, playable version of the classic Snake game implemented in Godot 4. The core innovation of this project is using GDScript to manage all the game logic (movement, collision, growth, and food placement) while offloading the entire rendering of the grid, snake, and food to a single, optimized CanvasItem Shader.
The GDScript efficiently calculates the game state (a list of grid coordinates) and passes this data as uniform arrays to the shader every frame. The shader then determines the color of each pixel based on whether it falls on a food position, a snake segment, or the background.
Adjustable Uniforms (Shader Parameters):
| Parameter | Type | Controlled By | Description |
| COLOR_SERPIENTE | vec4 |
@export var in GDScript |
The color used to render the snake’s body segments. |
| COLOR_COMIDA | vec4 |
@export var in GDScript |
The color used to render the food pellet. |
| COLOR_FONDO | vec4 |
@export var in GDScript |
The color of the game board background. |
| board_size | vec2 |
const TAMANO_TABLERO |
The size of the game grid (e.g., 30×25 cells). |
| snake_body | vec2[] |
GDScript Array | The array of grid coordinates that define the snake’s current position and length. |
| food_pos | vec2 |
GDScript Variable | The current grid position of the food pellet. |
| game_over | bool |
GDScript State | A flag that triggers the semi-transparent overlay to indicate the game has ended. |
Script move and logic snake:
extends ColorRect
@export var color_serpiente: Color = Color("lime")
@export var color_comida: Color = Color.RED
@export var color_fondo: Color = Color.BLACK
const TAMANO_TABLERO = Vector2i(30, 25)
var serpiente: Array[Vector2i]
var direccion: Vector2i
var comida_pos: Vector2i
var game_over: bool = false
var moviendo: bool = false
var timer: Timer
func _ready():
get_window().size = TAMANO_TABLERO * 20
timer = Timer.new()
add_child(timer)
timer.timeout.connect(_mover_serpiente)
iniciar_juego()
func _input(event):
if game_over and event.is_action_pressed("ui_accept"):
iniciar_juego()
return
if not moviendo:
var nueva_direccion: Vector2i
if event.is_action_pressed("ui_up"): nueva_direccion = Vector2i.UP
elif event.is_action_pressed("ui_down"): nueva_direccion = Vector2i.DOWN
elif event.is_action_pressed("ui_left"): nueva_direccion = Vector2i.LEFT
elif event.is_action_pressed("ui_right"): nueva_direccion = Vector2i.RIGHT
if nueva_direccion != Vector2i.ZERO and nueva_direccion + direccion != Vector2i.ZERO:
direccion = nueva_direccion
moviendo = true
func iniciar_juego():
game_over = false
serpiente.clear()
var cabeza = TAMANO_TABLERO / 2
serpiente.push_back(cabeza)
serpiente.push_back(cabeza - Vector2i.RIGHT)
direccion = Vector2i.RIGHT
_colocar_comida()
timer.wait_time = 0.15
timer.start()
_actualizar_shader()
func _mover_serpiente():
if game_over:
return
var cabeza_actual = serpiente.front()
var nueva_cabeza = cabeza_actual + direccion
if (nueva_cabeza.x < 0 or nueva_cabeza.x >= TAMANO_TABLERO.x or
nueva_cabeza.y < 0 or nueva_cabeza.y >= TAMANO_TABLERO.y or
serpiente.has(nueva_cabeza)):
_terminar_juego()
return
serpiente.insert(0, nueva_cabeza)
if nueva_cabeza == comida_pos:
_colocar_comida()
else:
serpiente.pop_back()
moviendo = false
_actualizar_shader()
func _colocar_comida():
var nueva_pos: Vector2i
while true:
nueva_pos = Vector2i(randi_range(0, TAMANO_TABLERO.x - 1), randi_range(0, TAMANO_TABLERO.y - 1))
if not serpiente.has(nueva_pos):
comida_pos = nueva_pos
return
func _terminar_juego():
game_over = true
timer.stop()
_actualizar_shader()
func _actualizar_shader():
material.set_shader_parameter("snake_body", serpiente)
material.set_shader_parameter("snake_length", serpiente.size())
material.set_shader_parameter("food_pos", comida_pos)
material.set_shader_parameter("game_over", game_over)
material.set_shader_parameter("COLOR_SERPIENTE", color_serpiente)
material.set_shader_parameter("COLOR_COMIDA", color_comida)
material.set_shader_parameter("COLOR_FONDO", color_fondo)
Shader code
shader_type canvas_item;
uniform vec2 board_size = vec2(30.0, 25.0);
uniform vec2 snake_body[512];
uniform int snake_length;
uniform vec2 food_pos;
uniform bool game_over;
uniform vec4 COLOR_FONDO;
uniform vec4 COLOR_SERPIENTE;
uniform vec4 COLOR_COMIDA;
const vec4 COLOR_GAME_OVER = vec4(1.0, 1.0, 1.0, 0.5);
void fragment() {
ivec2 grid_uv = ivec2(UV * board_size);
vec4 final_color = COLOR_FONDO;
for (int i = 0; i < snake_length; i++) {
if (grid_uv == ivec2(snake_body[i])) {
final_color = COLOR_SERPIENTE;
break;
}
}
if (grid_uv == ivec2(food_pos)) {
final_color = COLOR_COMIDA;
}
if (game_over) {
final_color = mix(final_color, COLOR_GAME_OVER, COLOR_GAME_OVER.a);
}
COLOR = final_color;
}

Comments 0
No comments yet.