¶
Introduction to Wren scripting¶
Scripting in luxe is done with the Wren programming language.
Wren is a class based language, which should be familiar if you're used to other languages.
This page isn't intended as a programming tutorial,
but rather a quick start guide for getting started with Wren in luxe.
String interpolation¶
A useful thing to start with, is that within a string,
you can put Wren expressions inside of a %( )
section to combine the values.
var value = "luxe"
Log.print("hello %(value)") //prints "hello luxe"
Log.print("hello %(5 + 5 - 2)") //prints "hello 8"
Annotations¶
Classes and variables use #annotations
to signal information to the engine or language.
The most common useful one would be #doc = "A documentation string"
.
#doc = "Whether or not to display the thing"
var visible : Bool = true
You should add documentation to methods, variables and classes whenever you can. These show up in code completion as well as other places. You can also use the raw string (longer string) syntax for documentation. Here's a full example:
#doc="""
Attach a `Sprite` modifier to `entity`, drawn using `image`,
with a given size of `width`x`height`.
```js
var entity = Entity.create(world)
var image = Assets.image("luxe: image/logo")
Sprite.create(entity, material, 128, 128)
```
"""
#args(
entity = "The entity to attach the `Sprite` to",
image = "The image to display on the sprite",
width = "The width in world units for the sprite",
height = "The height in world units for the sprite"
)
foreign static create(entity: Entity, image: Image, width: Num, height: Num) : None
A note on typing¶
The version of Wren in luxe allows optional typing, and the completion handles inferring types.
var num: Num = 5
can often be written as var num = 5
and still have a known type.
Types allow error checking when calling methods, and accidental typos on variables. While the typing isn't fully whole just yet, it continues to improve. Aim to always type your code, especially at the API edges.
A variable is typed like this:
var name: Type
And a method is typed like this:
method(param: Type) : Return { }
A basic class¶
class Hello {
construct new() {
Log.print("hello")
}
}
//prints:
// 'hello'
var hello = Hello.new()
Some useful notes:
construct
is the keyword for a constructor- The
new
is on the right hand side, instead ofnew Hello()
, it'sHello.new()
- That's because
new
is just a function name!construct create
is fine, or as you'll see in the luxe game class,construct ready
- You call a constructor on the class itself, via the class name
Basic functions¶
class Hello {
construct new() { Log.print("hello") }
//simple method
luxe() { Log.print("luxe") }
}
//prints:
// 'hello'
// 'luxe'
var hello = Hello.new()
//call simple method
hello.luxe()
- That
simple method
is calledluxe
, it requires no keywords - It is a class method, and is called on an instance of the class, not the class itself
Variables¶
Variables in luxe will be familiar as well. Scope works as you'd expect, with local variables and class variables.
class Hello {
//explicit class fields must come first in the class
//and must be initialized. can use any expression!
var value = 3
construct new() {
//a class field that is private
_private = 0
}
print() {
var local = "cannot be seen outside this scope"
Log.print(local)
//we can access our value variable from here,
//because it belongs to this class
value = value + 2
//prints 5
Log.print(value)
//also prints 5, as _value is a private field declared by `var value`
Log.print(_value)
//prints 0
Log.print(_private)
//prints null, prefer declaring explicit fields instead for errors
Log.print(_not_defined)
}
}
- local variables work as expected!
- class variables are typically explicitly defined
- class fields can be local only using the regular wren _field syntax
- explicit class fields also declare an underscore getter
Getters and setters¶
Private fields in a class cannot be seen from outside.
In other languages terms: all private variables (non explicit var) are private to this class only.
In order to make our values accessible from outside, we make them available first. We do that with getters and setters, which gives us read/write access control as well.
class Hello {
//automatic form
//generates `auto { _auto }`
//and `auto=(v) { _auto=v }`
var auto = true
//manual short form
value { _value }
value=(new_value) { _value = new_value }
//long form, read only
other_value {
return _other_value
}
construct new() {
_value = 4
_other_value = 5
}
}
var hello = Hello.new()
hello.value = 6 //update value
Log.print(hello.value) //prints 6
Log.print(hello.other_value) //prints 5
- Explicit class var fields declare a getter/setter for you, making them public
- A getter is a method without arguments, i.e no
()
after the name - A setter is a method with
=(one_arg)
, where the incoming value is passed in - Short form is a convenient way to say "return whatever I put in a single line", like these:
number { 3 }
,string { "string" }
,list { [] }
- (This comes in really handy later as you'll see)
- If you need to use more than one line/one expression, you must use
return
- note that
other_value
doesn't have a setter, it can't be set from outside
Static methods and variables¶
Static methods work largely the same as the class instance ones,
except for the static keyword up front. Like in other languages,
you can't access class variables or methods from static methods.
class Hello {
//static getter
static get { 5 }
//static setter
static set=(value) { Log.print(value) }
//static method
static method() {
Log.print("static method!")
}
}
Log.print(Hello.get) //prints 5
Hello.set = "hello" //prints hello
Hello.method() //prints "static method!"
- You call the methods directly on the class itself
Static variables work the same as class variables, except for an extra underscore.
__static_value
instead of _value
class Hello {
//static getter
static get { __value }
//static setter
static set=(value) { __value = value }
//static method
static init() {
__value = 5
}
}
Hello.init()
Log.print(Hello.get) //prints 5
Hello.set = 6 //prints hello
Log.print(Hello.get) //prints 6
Functions¶
Wren can store functions in variables and pass them to other functions and methods. The distinction between functions and methods is important.
var notify = Fn.new() {
Log.print("you have been notified!")
}
notify.call()
- note that we use
notify.call()
and notnotify()
Arguments
Arguments in a function using the |arg1, arg2, arg3|
syntax.
var notify = Fn.new() {|message|
Log.print("you have been notified, message: %(message)")
}
notify.call("the message!")
Passing functions to methods¶
You can pass functions to other functions or methods. Note that you cannot pass a method this way.
class Hello {
construct new() {
_function = null
_name = null
}
set_function(name, fn) {
_name = name
_function = fn
}
call_function(value) {
if(_function != null) _function.call(value)
}
}
var hello = Hello.new()
//we can store a function in a variable, and pass it into the method
var variable = Fn.new() {|value| Log.print(value) }
hello.set_function("name", variable)
hello.call_function(5) //print 5
//We can also pass a function directly
hello.set_function("name", Fn.new() {|value| Log.print(value + 8) })
hello.call_function(8) //print 16
Function short form syntax¶
Wren has some special syntax for functions to make them more succinct.
This relates to the "short form: where a single expression is a return value" mentioned before as well.
Using the same example as before, but with the short form for a function:
hello.set_function("name") {|value|
Log.print("hello %(value)")
}
How does this work?
Fn.new {|value| }
becomes{|value| }
When does this work?
- It works only when the last argument accepts a function
- It works when calling
Fn.new
which we saw before! - The below example doesn't work, we have to use a variable or a longer form
set_function(fn, name) {
_name = name
_function = fn
}
...
//requires the long form!
hello.set_function(Fn.new(){|value| }, "name")
Why short form is nice¶
In Wren, a lot of methods accept a function. A good example is the Sequence class.
Notice how compact the usage of the where
function is.
It uses both short forms: A single expression returns true
or false
, inside a short form function.
The where
function is defined as where(predicate)
, since it's only one argument, the last argument, it's valid to use the short form.
var list = [1,2,3,4]
var even = list.where {|value| value % 2 == 0 }
Log.print(even.toList) //prints [2, 4]
Many many functions are much nicer this way
["one", "two", "three"].each {|word| Log.print(word) }
Try Wren online¶
You can try Wren in your browser or on your phone, just in case you didn't know.