Scripts & Nodes
⚠️ Experimental - Pre-Release
Scripts in Perro are attached to nodes to add behavior and logic. Each script extends a specific node type, giving you access to that node's fields and methods through the self keyword.
Script Definition
Scripts are defined using the @script directive, followed by a name and the node type they extend:
@script Player extends Sprite2D var speed = 7.5 fn init() { Console.print("Player script initialized") self.texture = Texture.load("res://player.png") } fn update() { var delta = Time.get_delta() self.transform.position.x += speed * delta }The script name (Player) is used for identification, and the node type (Sprite2D) determines what fields and methods are available through self.
Extending Node Types
Scripts can extend any node type in the engine. When you extend a node type, you inherit all of its fields and methods:
Extending Node2D
@script MyNode extends Node2D fn update() { // Access Node2D fields self.transform.position.x = 100 self.transform.position.y = 50 self.visible = true self.z_index = 1 }Extending Sprite2D
@script MySprite extends Sprite2D fn init() { // Access Sprite2D fields (inherits from Node2D) self.texture = Texture.load("res://sprite.png") self.transform.position.x = 0 self.transform.position.y = 0 }Extending Node (Base)
@script MyNode extends Node fn init() { // Access Node fields self.name = "MyNode" var child = self.get_node("ChildNode") }The self Keyword
Use self to access the node that the script is attached to. Through self, you can access all fields and methods of the node type you extended:
@script Player extends Sprite2D fn init() { // Access node fields self.name = "Player" self.transform.position.x = 0 self.transform.position.y = 0 self.visible = true // Access node methods var child = self.get_node("Weapon") var parent = self.get_parent() // Add child nodes var bullet = new Sprite2D() self.add_child(bullet) }Lifecycle Methods
Scripts can define lifecycle methods that are called automatically by the engine:
init()
Called once when the script is first attached to the node. Use this for initialization.
fn init() { Console.print("Script initialized") self.texture = Texture.load("res://sprite.png")}fixed_update()
Called at a fixed rate of updates per second (XPS) based on your target XPS (default 30 XPS). Use this for physics and game logic that needs consistent timing.
fn fixed_update() { // Physics, collisions, game logic // Called at fixed rate (e.g., 30 updates per second)}update()
Called every time the update loop runs, which can be thousands of times per second. Use this for high-frequency updates, input polling, or logic that needs to run as fast as possible.
fn update() { // High-frequency updates // Can be called thousands of times per second if Input.is_key_pressed("Space") { // Handle input }}Speeds have been achieved up to 40,000 updates per second on Windows and 200,000 updates per second on Linux VM
draw()
Called at the current FPS for rendering. Use this for drawing operations and visual updates that should match the display refresh rate.
fn draw() { // Rendering and visual updates // Called at current FPS (e.g., 60 FPS)}Script Attachment
Scripts are attached to nodes through the script_path field. This field contains the path to the script file, or null if no script is attached.
@script MyNode extends Node fn init() { // Check if this node has a script attached if self.script_path != null { Console.print("Script path: " + self.script_path) } }Scripts are typically attached in scene files or through the editor. The engine automatically loads and executes scripts based on the script_path.
Related
- Pup Language Guide- Learn the Pup scripting language syntax
- Node Hierarchy- See all available node types you can extend
- Node API- Base node class documentation
- Signals- Connect scripts with signals
