
Godot Genre Battle Royale
- 133 installs
- 454 repo stars
- Updated July 28, 2026
- thedivergentai/gd-agentic-skills
Use godot-genre-battle-royale for development tasks
About
godot-genre-battle-royale: A skill for development. This provides functionality for development workflows.
- godot-genre-battle-royale
Godot Genre Battle Royale by the numbers
- 133 all-time installs (skills.sh)
- +9 installs in the week ending Jul 27, 2026 (Skillselion tracking)
- Ranked #2,684 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/thedivergentai/gd-agentic-skills --skill godot-genre-battle-royaleAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 133 |
|---|---|
| repo stars | ★ 454 |
| Last updated | July 28, 2026 |
| Repository | thedivergentai/gd-agentic-skills ↗ |
What it does
Use godot-genre-battle-royale for development tasks
Files
Genre: Battle Royale
Expert blueprint for Battle Royale games with zone mechanics, large-scale networking, and survival gameplay.
NEVER Do (Expert Anti-Patterns)
Networking & Scale
- NEVER sync all 100 players every frame; strictly use a Relevancy System to sync high-freq data only for players within ~100m. Far players sync at ~5Hz.
- NEVER use
TRANSFER_MODE_RELIABLEfor movement data; strictly use Unreliable to prevent packet backup and network congestion. - NEVER focus on client-side hit detection; strictly use Authoritative Server Validation where the server confirms "Did it hit?" based on state history.
- NEVER trust the client for game state; strictly validate all movement, looting, and inventory changes exclusively on the authoritative server.
- NEVER run a dedicated server with visuals; strictly use Headless Mode (
--headless) or dummy drivers to save massive CPU/GPU resources. - NEVER call RPCs before connection; strictly wait for the
connected_to_serversignal before attempting synchronization logic.
Mechanics & Performance
- NEVER pick a fully random center for the Safe Zone; strictly target centers that ensure the new circle is completely contained within the current one.
- NEVER allow "Storm Tunneling"; strictly use a Distance-to-Center calculation rather than a simple collision perimeter to prevent skips at low tick rates.
- NEVER spawn loot without Object Pooling; strictly pre-instantiate and toggle visibility/collision to avoid GC spikes during dense spawns.
- NEVER ignore
VisibilityNotifier3D; strictly disableAnimationPlayer,_process(), and heavy AI logic for players that are not visible to the observer. - NEVER print in tight server loops; strictly avoid
print()as console I/O is blocking and will tank server performance in high-player-count matches.
---
Available Scripts
MANDATORY: Read the appropriate script before implementing the corresponding pattern.
Networking & Multiplayer
kill_feed_bus.gd
Global elimination signal bus with match stat tracking.
headless_branch_logic.gd
Expert dedicated server initialization that branches logic based on headless execution and server-specific feature flags.
enet_br_server.gd
High-player-capacity ENet server setup optimized for 100+ concurrent peers over UDP.
state_replication_unreliable.gd
Pattern for synchronizing player transforms via TRANSFER_MODE_UNRELIABLE to minimize network congestion in large matches.
authoritative_looting.gd
Authoritative server-side validation logic for preventing cheat-based item collection and infinite looting.
targeted_rpc_relay.gd
Optimized communication pattern using rpc_id() to target specific peers and reduce wasted packet broadcasts.
server_state_buffer.gd
Handling network jitter and out-of-order UDP packets via sequential state buffering and tick-based sorting.
Performance & Optimization
rid_loot_spawner.gd
Bypassing the node hierarchy for massive loot density. Uses RenderingServer directly to eliminate CPU overhead for item drops.
async_map_loader.gd
Non-blocking map sector streaming using ResourceLoader background threads for seamless open-world exploration.
multimesh_vegetation.gd
Drawing dense foliage and environment assets (100k+ instances) via MultiMeshInstance3D to maximize rendering performance.
threaded_ai_manager.gd
Offloading server-side bot behavior and pathfinding logic to the WorkerThreadPool to prevent main-thread stalling.
NEVER Do in Battle Royale
- NEVER export mobile clients without the INTERNET permission — Communication will silently fail on Android/iOS if the manifest is missing the networking permission [37].
- NEVER use `get_var(true)` on untrusted data — Deserializing arbitrary objects allows attackers to execute remote code on the server or other clients [31].
- NEVER synchronize `Object` or `Resource` types over network — Use the
MultiplayerSynchronizerstrictly for base types (int, float, vec) [39]. - NEVER assume `UNRELIABLE` packets arrive in order — Design state interpolation carefully to handle missing or out-of-order ticks [28].
- NEVER leave `multiplayer_poll` false without manual calling — If using custom threads, failing to call
multiplayer.poll()freezes all traffic [40].
---
Core Loop
1. Deploy: Player chooses a landing spot from an air vehicle. 2. Loot: Player scavenges weapons and armor. 3. Move: Player runs to the safe zone to avoid taking damage. 4. Engage: Player fights others they encounter. 5. Survive: Player attempts to be the last one standing.
Skill Chain
| Phase | Skills | Purpose |
|---|---|---|
| 1. Net | godot-multiplayer-networking | Authoritative server, lag compensation |
| 2. Map | godot-3d-world-building, level-of-detail | Large terrain, chunking, distant trees |
| 3. Items | godot-inventory-system | Managing backpack, attachments, armor |
| 4. Combat | shooter-mechanics, ballistics | Projectile physics, damage calculation |
| 5. Logic | game-manager | Managing the Storm/Zone state |
Architecture Overview
1. The Zone Manager (The Storm)
Manages the shrinking safe area.
# zone_manager.gd
extends Node
@export var phases: Array[ZonePhase]
var current_phase_index: int = 0
var current_radius: float = 2000.0
var target_radius: float = 2000.0
var center: Vector2 = Vector2.ZERO
var target_center: Vector2 = Vector2.ZERO
var shrink_speed: float = 0.0
func start_next_phase() -> void:
var phase = phases[current_phase_index]
target_radius = phase.end_radius
# Pick new center WITHIN current circle but respecting new radius
var random_angle = randf() * TAU
var max_offset = current_radius - target_radius
var offset = Vector2.RIGHT.rotated(random_angle) * (randf() * max_offset)
target_center = center + offset
shrink_speed = (current_radius - target_radius) / phase.shrink_time
func _process(delta: float) -> void:
if current_radius > target_radius:
current_radius -= shrink_speed * delta
center = center.move_toward(target_center, (shrink_speed * delta) * (center.distance_to(target_center) / (current_radius - target_radius)))2. Loot Spawner
Efficiently populating the world.
# loot_manager.gd
func spawn_loot() -> void:
for spawn_point in get_tree().get_nodes_in_group("loot_spawns"):
if randf() < spawn_point.spawn_chance:
var item_id = loot_table.roll_item()
var loot_instance = loot_scene.instantiate()
loot_instance.setup(item_id)
add_child(loot_instance)3. Deployment System
Transitioning from plane to ground.
# player_controller.gd
enum State { IN_PLANE, FREEFALL, PARACHUTE, GROUNDED }
func _physics_process(delta: float) -> void:
match current_state:
State.FREEFALL:
velocity.y = move_toward(velocity.y, -50.0, gravity * delta)
move_and_slide()
if position.y < auto_deploy_height:
deploy_parachute()Key Mechanics Implementation
Zone Damage
Checking if player is outside the circle.
func check_zone_damage() -> void:
var dist = Vector2(global_position.x, global_position.z).distance_to(ZoneManager.center)
if dist > ZoneManager.current_radius:
take_damage(ZoneManager.dps * delta)Networking Optimization
You cannot sync 100 players every frame.
- Relevancy: Only send updates for players within visual range.
- Frequency: Update far-away players at 4Hz, nearby at 20Hz+ (Server Tick).
- Snapshot Interpolation: Client buffers headers to play them back smoothly.
Godot-Specific Tips
- MultiplayerSynchronizer: Use
replication_intervalto lower bandwidth for distant objects. - VisibilityNotifier3D: Critical. Disable
_processand AnimationPlayer for players behind you or far away. - Occlusion Culling: Essential for large maps with buildings. Bake occlusion data.
- HLOD: Use Hierarchical Level of Detail for terrain and large structures.
Common Pitfalls
1. Too Main Loot: Too much loot causes lag. Fix: Use object pooling for loot pickups. 2. Camping: Players hide forever. Fix: The Zone forces movement. Also, anti-camping mechanics like "scan reveals" (optional). 3. Cheating: Client-side hit detection. Fix: Authoritative server logic. Client says "I shot at direction X", Server calculates "Did it hit?".
Advanced Battle Royale Systems
Elite patterns for handling massive scale, low-latency networking, and high-performance visuals.
1. Lag Compensation (Hit Validation Backtracking)
The server maintains a history of entity transforms. When a client reports a hit with a timestamp, the server "rewinds" the target to that moment for authoritative validation.
class_name LagCompensator extends Node
var _position_history: Dictionary = {} # Timestamp -> Transform3D
const MAX_HISTORY_MS: int = 1000 # Keep 1 second of history
func _physics_process(_delta: float) -> void:
if multiplayer.is_server():
var current_time := Time.get_ticks_msec()
_position_history[current_time] = owner.global_transform
# Cleanup old entries
for t in _position_history.keys():
if t < current_time - MAX_HISTORY_MS:
_position_history.erase(t)
@rpc("any_peer", "call_remote", "reliable")
func server_validate_hit(client_hit_pos: Vector3, client_timestamp: int) -> void:
if not multiplayer.is_server(): return
# 1. Backtrack to find the transform at the time the client saw it
var historical_transform := _get_closest_transform(client_timestamp)
# 2. Validate hit against historical state
var distance := historical_transform.origin.distance_to(client_hit_pos)
if distance < 2.0: # Tolerance
apply_damage_authoritative()
func _get_closest_transform(timestamp: int) -> Transform3D:
# Logic to find exact or interpolated historical transform
return _position_history.get(timestamp, owner.global_transform)2. Delta-Patching (MultiplayerSynchronizer Optimization)
Godot 4.x natively supports delta-patching via MultiplayerSynchronizer. Set replication to ON_CHANGE to strictly transmit modified data.
class_name MonsterSynchronizer extends Node
func setup_replication(monster: CharacterBody3D) -> void:
var sync := MultiplayerSynchronizer.new()
add_child(sync)
var config := SceneReplicationConfig.new()
# Position: High frequency sync
var pos_path := NodePath(str(monster.get_path()) + ":position")
config.add_property(pos_path)
config.property_set_replication_mode(pos_path, SceneReplicationConfig.REPLICATION_MODE_ALWAYS)
# Health: Delta-patch (only sync when changed)
var health_path := NodePath(str(monster.get_path()) + ":current_health")
config.add_property(health_path)
config.property_set_replication_mode(health_path, SceneReplicationConfig.REPLICATION_MODE_ON_CHANGE)
sync.replication_config = config
sync.delta_interval = 0.05 # Limit sync to 20Hz for bandwidth efficiency3. Zone Visualizer (Storm Perimeter Shader)
Use a custom spatial shader with unshaded and cull_disabled render modes for high-performance, massive-scale zone effects.
zone_shield.gdshader
shader_type spatial;
render_mode unshaded, cull_disabled;
uniform vec4 storm_color : source_color = vec4(0.6, 0.1, 1.0, 1.0);
uniform float storm_opacity : hint_range(0.0, 1.0) = 0.5;
void fragment() {
ALBEDO = storm_color.rgb;
ALPHA = storm_opacity;
EMISSION = storm_color.rgb * 2.0; # Glow visibility at distance
}Expert Tip: For the "Zone Wall", use an inverted SphereMesh with its scale controlled by the ZoneManager. This ensures the player is always "inside" the mesh, rendering the back-faces (cull_disabled) correctly.
Reference
- Master Skill: godot-master
# async_map_loader.gd
# Non-blocking map chunk streaming
extends Node
# EXPERT NOTE: BR maps are huge. Load sectors in background threads
# to prevent frame drops during exploration.
func load_sector(path: String):
ResourceLoader.load_threaded_request(path)
func _process(_delta):
var progress = []
var status = ResourceLoader.load_threaded_get_status("res://levels/sector_a.tscn", progress)
if status == ResourceLoader.THREAD_LOAD_LOADED:
var scene = ResourceLoader.load_threaded_get("res://levels/sector_a.tscn")
_attach_sector(scene)
func _attach_sector(_s): pass
# authoritative_looting.gd
# Server-side validation for item collection
extends Node
# EXPERT NOTE: Trust the Server. If a client "loots" an item,
# the server must verify proximity and item existence.
@rpc("any_peer", "call_local", "reliable")
func request_loot(loot_id: String):
if not multiplayer.is_server(): return
var sender_id = multiplayer.get_remote_sender_id()
if _verify_looting(sender_id, loot_id):
_distribute_loot(sender_id, loot_id)
func _verify_looting(_id, _item): return true
func _distribute_loot(_id, _item): pass
# enet_br_server.gd
# Low-latency UDP server setup for high-player-count games
extends Node
# EXPERT NOTE: ENet is mandatory for Battle Royale games
# to avoid TCP's head-of-line blocking and latency spikes.
func start_match_server(port: int = 7000):
var peer := ENetMultiplayerPeer.new()
# Unlimited bandwidth, 0 channels (max performance)
var err = peer.create_server(port, 100) # 100 players
if err == OK:
multiplayer.multiplayer_peer = peer
print("Match server spawned on port ", port)
# headless_branch_logic.gd
# Dedicated server pathing for battle royale hosts
extends Node
# EXPERT NOTE: Dedicated servers must skip UI and input
# and use optimized physics drivers (Dummy).
func _ready():
if DisplayServer.get_name() == "headless":
_server_init()
else:
_client_init()
func _server_init():
# Configure server-only timers or state update rates
multiplayer.peer_connected.connect(_on_peer_connected)
print("Dedicated Server active for Battle Royale session.")
func _on_peer_connected(id): pass
func _client_init(): pass
# skills/genre-battle-royale/code/kill_feed_bus.gd
extends Node
## Kill-Feed Signal Bus Expert Pattern
## Global hub for tracking eliminations and weapon stats.
# player_id, killer_id, weapon_type
signal elimination_occurred(victim: String, killer: String, weapon: String)
var _match_stats = {}
func log_elimination(victim: String, killer: String, weapon: String) -> void:
# 1. Broadly Emit
# Every UI component and logging system listens to this single point.
elimination_occurred.emit(victim, killer, weapon)
# 2. Persist Match State
# Record for post-game summary.
if not _match_stats.has(killer):
_match_stats[killer] = 0
_match_stats[killer] += 1
func get_killer_rankings() -> Array:
# 3. Data Transformation
# Returns a sorted array of killer names for the match summary.
var rankings = _match_stats.keys()
rankings.sort_custom(func(a, b): return _match_stats[a] > _match_stats[b])
return rankings
## EXPERT NOTE:
## For true Battle Royale scale (100+ players), use 'Area Interest'
## networking to only send kill-feed data to players whom it concerns,
## unless it's a 'Major Event' like the top 10 players remaining.
# multimesh_vegetation.gd
# Drawing thousands of environment assets in one draw call
extends MultiMeshInstance3D
# EXPERT NOTE: Battle Royale terrain requires dense foliage.
# MultiMeshInstance3D is essential for 100k+ instances.
func populate_grass(count: int, area: Rect2):
multimesh.instance_count = count
for i in range(count):
var pos = Transform3D(Basis(), Vector3(randf_range(area.position.x, area.end.x), 0, randf_range(area.position.y, area.end.y)))
multimesh.set_instance_transform(i, pos)
# rid_loot_spawner.gd
# Bypassing Nodes for massive loot quantity
extends Node
# EXPERT NOTE: Rendering thousands of loot items as nodes
# is slow. Use RenderingServer directly for CPU efficiency.
func spawn_loot_render_only(pos: Vector3, mesh_rid: RID):
var instance = RenderingServer.instance_create()
RenderingServer.instance_set_base(instance, mesh_rid)
RenderingServer.instance_set_scenario(instance, get_world_3d().scenario)
RenderingServer.instance_set_transform(instance, Transform3D(Basis(), pos))
return instance
# server_state_buffer.gd
# Handling UDP packet jitter on the server
extends Node
# EXPERT NOTE: UDP packets lack sequence guarantees.
# Buffer and sort state chunks using IDs to prevent stutter.
var buffer: Dictionary = {}
func push_state(peer_id: int, state_id: int, data: Dictionary):
if !buffer.has(peer_id): buffer[peer_id] = []
buffer[peer_id].append({"id": state_id, "data": data})
# Sort by ID to ensure sequential processing
buffer[peer_id].sort_custom(func(a, b): return a.id < b.id)
# state_replication_unreliable.gd
# Synchronizing player transforms via unreliable streams
extends Node
# EXPERT NOTE: For 100 players, Reliable mode causes congestion.
# ALWAYS use Unreliable/Unreliable Ordered for movement.
@rpc("authority", "call_remote", "unreliable")
func update_player_transform(p_id: int, pos: Vector3, rot: float):
# Interpolate state on clients
_on_peer_transform_sync(p_id, pos, rot)
func _on_peer_transform_sync(_id, _p, _r): pass
# skills/genre-battle-royale/code/storm_system.gd
extends Node
## Storm System Expert Pattern
## Features Dynamic Zone Shrinking and Damage Interpolation.
signal zone_shrunk(new_safe_center: Vector2, new_safe_radius: float)
@export var initial_radius: float = 2000.0
@export var damage_per_tick: float = 5.0
@export var tick_rate: float = 1.0
var current_center: Vector2 = Vector2.ZERO
var current_radius: float = initial_radius
var _time_since_last_tick: float = 0.0
func _process(delta: float) -> void:
_time_since_last_tick += delta
if _time_since_last_tick >= tick_rate:
_time_since_last_tick = 0
_check_players_in_storm()
func shrink_zone(new_center: Vector2, new_radius: float, duration: float) -> void:
# 1. Smooth Interpolation
# Uses a Tween to shrink the visual and logical safe zone over time.
var tween = create_tween().set_parallel(true).set_trans(Tween.TRANS_SINE)
tween.tween_property(self, "current_center", new_center, duration)
tween.tween_property(self, "current_radius", new_radius, duration)
tween.finished.connect(func(): zone_shrunk.emit(new_center, new_radius))
func _check_players_in_storm() -> void:
var players = get_tree().get_nodes_in_group("players")
for player in players:
var dist = player.global_position.distance_to(current_center)
if dist > current_radius:
_apply_storm_damage(player)
func _apply_storm_damage(player: Node) -> void:
# 2. Threshold Scaling
# Increase damage as the radius gets smaller (End-game intensity).
var scale_factor = (initial_radius / current_radius)
var final_damage = damage_per_tick * scale_factor
if player.has_method("take_damage"):
player.take_damage(final_damage, "Storm")
## EXPERT NOTE:
## Use a Global Shader to visualize the storm boundary.
## Pass 'current_center' and 'current_radius' as Uniforms for perfect sync.
# targeted_rpc_relay.gd
# Communicating with specific peers to reduce traffic
extends Node
# EXPERT NOTE: Global broadcasts are wasteful. Use rpc_id(1)
# for client->server and rpc_id(peer_id) for server->specific_client.
@rpc("any_peer", "call_local", "reliable")
func talk_to_server(msg: String):
if multiplayer.is_server():
print("Client says: ", msg)
func send_private_message(peer_id: int, secret: String):
_receive_private.rpc_id(peer_id, secret)
@rpc("authority", "call_remote", "reliable")
func _receive_private(_s): pass
# threaded_ai_manager.gd
# Offloading server-side AI to worker threads
extends Node
# EXPERT NOTE: Server CPUs are often the bottleneck.
# Move bot behavior logic to the WorkerThreadPool.
func process_bots():
var bot_ids = range(bots.size())
WorkerThreadPool.add_group_task(_tick_bot, bot_ids.size())
func _tick_bot(index: int):
# Bot logic running on secondary thread
# CAUTION: Physics access must be synchronized!
pass
var bots: Array = []