Intro to GRAB Assembly
Recently, GRAB has added code blocks, which allow users to build programs with a block coding interface for writing assembly. This post introduces GRAB Assembly from a more technical point of view: writing code to copy into the game, and using the JSON Editor to write complex programs.
I first explain the structure of GRAB assembly, and explain a simple program before going into detail on the different parts of the language: registers, connections, operators, some commonly used patterns, and using macros.
Assembly is a low level programming language just above machine code. Instead of complex logic, each line is a very small, specific instruction.
Structure of a program
In GRAB Assembly, like most assembly languages, a program is a list of instructions.
Each line represents one instruction. Instructions are executed from top to bottom
unless control flow instructions (GOTO, IF, END) jump the ProgramCounter.
An instruction generally has the following form:
OPERATOR OPERAND OPERAND ...
For example:
SET R0 10
This instruction uses the SET operator to assign the constant value 10 to the register
R0.
The operator defines what action is performed (for example, SET, ADD, MUL, or GOTO).
The operands are the values the operator works on. These can be:
- Working registers (
R0-R7) - Object connections (
Obj.Pos.X,Trg.Act, etc.) - Constant values (
10,0.5,-3) - Labels (
loop,any string)
The number of operands depends on the operator. Some instructions take no operands, while others take one, two, or three. Most commonly, the first operand is the destination.
Comments are highly recommended to explain longer programs, they help make the program
more readable and easier to update at a later date.
Comments start with a semicolon (;). Everything on a line after the semicolon is removed
when pasting code into GRAB.
SET R0 10 ; this text will be removed
A simple program
Before we dive into writing programs with GRAB Assembly, let’s break down a simple program.
Below is a program written in GRAB Assembly.
; infinitely move 'Obj' upwards
SET R0 10 ; 3) speed to move
LABEL loop ; 5) label to jump back to
ADD Obj.Pos.Y Obj.Pos.Y R0 ; 6) add the speed to the position
SLEEP 0 ; 7) sleep till next frame
GOTO loop ; 8) loop forever
Line 1, as well as the text after the semicolon (;) on each line, is a comment. It will be removed when pasting into GRAB, but is handy to explain what code is doing.
Line 2 is blank.
Line 3 sets the R0 register to the constant value 10.
Line 4 is blank.
Line 5 creates a LABEL called loop, which will be jumped to later
to create an infinite loop.
Line 6 adds R0 (which is 10) to Obj.Pos.Y and puts the result into
Obj.Pos.Y. Y is up and down; adding to it means Obj will move upwards.
Line 7 uses a very common trick of SLEEP 0, which halts the program until
the next frame, which means that the loop will run only once per frame.
Line 8 uses GOTO to jump back to loop. This creates an infinite loop.
By using comments and indenting, this program becomes more easily readable, but in GRAB, is equivalent to the following:
SET R0 10
LABEL loop
ADD Obj.Pos.Y Obj.Pos.Y R0
SLEEP 0
GOTO loop
Working Registers
In GRAB Assembly the main way to store and manipulate data is through
working registers. GRAB Assembly provides eight working registers,
named R0 through R7.
These registers can be read from and written to.
Registers have no fixed meaning and their purpose is entirely defined by how you use them.
; set R0 to a constant value
SET R0 10 ; R0 = 10
; perform addition using registers
ADD R0 R0 2 ; R0 = R0 + 2 -> 12
; use another register
SET R1 6 ; R1 = 6
; divide R0 by R1
DIV R0 R0 R1 ; R0 = R0 / R1 -> 2
Commonly, programs will:
- Load values from connections into registers.
- Perform calculations using registers.
- Write the final result back to a connection.
Because registers are limited (8), it’s good practice to reuse them carefully and to add comments that describe how each register is currently being used.
If you need more than 8 registers, you can use connections as extra places to store data. However, you should be aware that the object exists physically.
Connections
Connections can be added in GRAB’s selection menu. Select a code block, click
Edit Connections, and select another object to add a connection to it.
When you add a connection to an object, you can connect the position, rotation, or active state, and can name the connection.
This creates a register such as:
Obj.Pos.X ; object position
Obj.Rot.Y ; object rotation
Trg.Act ; trigger is active
Position and rotation are readable and writable. Active is read-only and only available for connections to triggers.
Connections behave just like working registers, but are connected to an object; when you modify the connection’s register, the object is modified; when the object is modified, the register is modified.
Connections can be used anywhere a register can, except for Act
connections which cannot be modified in code.
NOTE: Rotations are calculated in Y -> X -> Z order. This means that, when the game computes how an object should be rotated based on the rotation values: first, the object is rotated around the Y axis; then the result of that rotation is rotated around the X axis; and finally the result of that rotation is rotated around the Z axis. So changing one rotation axis can affect how subsequent rotations behave.
Operators
| Instruction | Op 1 | Op 2 | Op 3 | Description |
|---|---|---|---|---|
| SET | DST | SRC | DST = SRC | |
| SWAP | A | B | swap A and B | |
| ADD | DST | A | B | DST = A + B |
| SUB | DST | A | B | DST = A - B |
| MUL | DST | A | B | DST = A * B |
| DIV | DST | A | B | DST = A / B |
| MOD | DST | A | B | DST = A % B |
| FLOOR | DST | SRC | DST = ⌊SRC⌋ | |
| SQRT | DST | SRC | DST = √SRC | |
| EQUAL | DST | A | B | DST = A == B |
| LESS | DST | A | B | DST = A < B |
| GREATER | DST | A | B | DST = A > B |
| AND | DST | A | B | DST = A && B |
| OR | DST | A | B | DST = A || B |
| NOT | DST | SRC | DST = !SRC | |
| SIN | DST | SRC | DST = sin(SRC) | |
| COS | DST | SRC | DST = cos(SRC) | |
| ATAN2 | DST | A | B | DST = angle to (B,A) |
| LABEL | LABEL | define a label | ||
| GOTO | LABEL | jump to a label | ||
| IF | REG | LABEL | jump to label if REG != 0 | |
| END | halt the program | |||
| RAND | DST | N | DST = random 0 to N - 1 | |
| SLEEP | MS | sleep for MS |
Data Operators
Data operators are simple operators that move data in registers.
SET
The SET operator takes two operands: a destination register (DST), and a source value (SRC).
It copies the source’s value into the destination.
DST- A writable registerSRC- A register or constant value
SET R0 10 ; set R0 to 10
SET R1 R0 ; set R1 to R0
SWAP
The SWAP operator takes two register operands (A, and B), and swaps their values.
A- A writable registerB- A writable register
SWAP R0 R1 ; swap the values of R0 and R1
Arithmetic Operators
Arithmetic operators take three operands: a destination register (DST), and two register or value operands (A, and B).
They set the destination to the result of A and B.
DST- A writable registerA- A register or constant valueB- A register or constant value
The operators work as follows:
ADDwill add two values (A+B)SUBwill subtract two values (A-B)MULwill multiply two values (A*B)DIVwill divide two values (A/B)MODwill modulo two values (remainder) (A%B)
ADD R0 10 3 ; R0 = 10 + 3 -> 13
SUB R0 10 3 ; R0 = 10 - 3 -> 7
MUL R0 10 3 ; R0 = 10 * 3 -> 30
DIV R0 10 3 ; R0 = 10 / 3 -> 3.333...
MOD R0 10 3 ; R0 = 10 % 3 -> 1
ADD R0 R0 10 ; R0 = R0 + 10
ADD R0 R1 R2 ; R0 = R1 + R2
NOTE: When using
DIV, be careful not to divide by zero, which will error the program.
Other Math Operators
FLOOR
The FLOOR operator takes two operands: a destination register (DST), and a source value (SRC).
It rounds down the source’s value into the destination.
DST- A writable registerSRC- A register or constant value
; R1 is 2.6
FLOOR R0 R1 ; round R1 down into R0
; R0 is 2
SQRT
The SQRT operator takes two operands: a destination register (DST), and a source value (SRC).
It puts the square root of the source’s value into the destination.
DST- A writable registerSRC- A register or constant value
SQRT R0 25 ; set R0 to the square root of 25
; R0 is 5
SIN
The SIN operator takes two operands: a destination register (DST), and a source angle (SRC).
It puts the sine of the source’s value as an angle in degrees into the destination.
DST- A writable registerSRC- A register or constant value
SIN R0 90 ; set R0 to the sine of 90
; R0 is 1
COS
The COS operator takes two operands: a destination register (DST), and a source angle (SRC).
It puts the cosine of the source’s value as an angle in degrees into the destination.
DST- A writable registerSRC- A register or constant value
COS R0 90 ; set R0 to the cosine of 90
; R0 is 0
Trigonometry basics
If you are unfamiliar with sine and cosine, I’ll go over
the fundamentals of what they are in the context of the
SIN and COS instructions.
Sine and cosine are ways of turning an angle into numbers
that describe direction. Imagine standing at the centre
of a circle and pointing in some direction: COS tells you
how far in the X direction that angle points, while SIN tells
you how far in the Y direction it points.
If the point always stays on the circle, these values
smoothly range between -1 and 1 as the angle changes.
Using the diagram below, drag the white dot around the circle.
The position of the dot directly matches the yellow line, which
represents the angle SRC. The resulting SIN is the length
of the red line from the dot down to 0 on the Y axis, and COS is the length
of the blue line from the dot across to 0 on the X axis.
NOTE: All trigonometric operators in GRAB Assembly use degrees, not radians.
ATAN2
The ATAN2 operator takes two operands: a destination register (DST),
a source y (A), and a source x (B).
It sets the destination to the angle from (0, 0) to (B, A).
You can think of it as reversing SIN and COS to get the original angle.
NOTE: ATAN2 uses (y, x), not (x, y).
DST- A writable registerA- A register or constant valueB- A register or constant value
ATAN2 R0 1 0 ; set R0 to atan2 of 1 and 0
; R0 is 90
Comparison Operators
Comparison operators take three operands and set the first to the result of the second and the third.
DST- A writable registerA- A register or constant valueB- A register or constant value
Comparison operators will always set the destination register to either
0 or 1, where 1 (and any other non 0 value) means the expression is true, and 0 means it is false.
This means they work directly with IF, which checks if a register is not 0.
EQUALChecks if A equals BLESSChecks if A is less than BGREATERChecks if A is greater than BANDTrue only if both are trueORTrue if either is trueNOTFlips true/false
EQUAL R0 1 2 ; R0 = (1 == 2) -> 0
LESS R0 1 2 ; R0 = (1 < 2) -> 1
GREATER R0 1 2 ; R0 = (1 > 2) -> 0
AND R0 1 0 ; R0 = (1 != 0 and 0 != 0) -> 0
OR R0 1 2 ; R0 = (1 != 0 or 0 != 0) -> 1
NOT R0 1 ; R0 = (!1) -> 0
NOTE: All values in GRAB Assembly are stored as floating point numbers. Because of this, arithmetic can introduce small precision errors (e.g.,
0.1 + 0.2 = 0.30000000000000004), meaning results are often not exact. When comparing values, keep this in mind, especially withEQUAL. Only rely on equality when you know the values are set exactly.For example, if you want to check whether an object has moved from 0 to 0.1, instead of checking whether the value is
EQUALto 0.1, check whether it isGREATERthan 0.05.
Control Flow Operators
Control flow operators allow for jumping around the code to create loops, conditional code, or functions.
For example, a loop can be created that makes an object unable to pass below 0 height.
LABEL loop ; infinite loop
GREATER R0 Obj.Pos.Y 0 ; if Y > 0
IF R0 dont_limit ; dont limit
; set Y to 0 ; else ( <= 0 )
SET Obj.Pos.Y 0
LABEL dont_limit
GOTO loop
While I prefer this kind of pattern of having reversed conditions, (if not less than 0), and jumping over logic, others may prefer to have the conditional code separated more like a function.
This code is equivalent to the previous.
LABEL loop ; infinite loop
LESS R0 Obj.Pos.Y 0 ; if Y < 0
IF R0 limit ; limit
LABEL after_limit
GOTO loop
; limit function
LABEL limit
; set Y to 0
SET Obj.Pos.Y 0
GOTO after_limit
LABEL
Labels a line in the code with a name.
LABEL loop
GOTO
GOTO jumps to a label.
GOTO loop
IF
IF will jump to the label only if REG is not zero.
SET R0 1
IF R0 func ; jump to 'func' if R0 is 1
END
Halts the code.
END
Misc Operators
RAND
RAND sets a register to a random number from 0 to N-1.
RAND R0 10
; R0 could be anything from 0 to 9
SLEEP
SLEEP pauses execution for a given amount of time in milliseconds.
TIP: SLEEP 0 will delay execution until the next frame.
SLEEP 1000 ; sleep for 1 second
Special Registers
Special registers are managed by the runtime and expose certain information about the current state of the program.
ProgramCounter
ProgramCounter is the index of the current instruction being read. The line number - 1.
You write to it to jump to a specific line number, which, coupled with reading and saving it,
can allow the implementation of callable functions.
NOTE: At the time of writing this, ProgramCounter is not yet accessible, but should be in the near future.
DeltaTime
DeltaTime stores the time since the last frame. DeltaTime should be used to make moving objects
smooth and consistent by multiplying movement by the DeltaTime to ensure the frame rate doesn’t
affect the speed.
Others
Haltwhether the program is haltedHaltFramewhether execution is halted for this frame (SLEEP 0)SleepTimerremaining time until sleep completes
Common Patterns
Infinite loop with sleep 0
Commonly, you will want to have something run every frame.
You can achieve this by creating a LABEL and a GOTO that wrap around code to run infinitely.
It is also important to include a SLEEP 0 at the end of the loop to ensure the loop only runs once per frame.
Without SLEEP 0, the program will run up to 500 instructions each frame, which is usually not what you want.
LABEL loop
; code here
SLEEP 0
GOTO loop
DeltaTime
As explained above, DeltaTime stores the time since the last frame.
Generally, when moving something, you want it to be at a constant rate.
If you create a simple movement loop:
LABEL loop
ADD Obj.Pos.X Obj.Pos.X 0.1
SLEEP 0
GOTO loop
The object will move 0.1m per frame.
If you have a stable frame rate, that will be 75 fps * 0.1m = 7.5m/s.
However, if you are lagging, then the object will move slower because there
are less frames.
The way to counteract this to make the movement appear smooth, and be consistent locally and across multiplayer, is to multiply the movement by DeltaTime.
LABEL loop
MUL R0 0.1 DeltaTime
ADD Obj.Pos.X Obj.Pos.X R0
SLEEP 0
GOTO loop
Macros
The JSON Editor has a simple but very powerful extra tool on top of GRAB Assembly: preprocessor macros that allow for generating far more complex programs.
| Macro | Arg 1 | Arg 2 | Arg 3 | Arg 4 | Description |
|---|---|---|---|---|---|
| #FOR | var | start | stop | step | for (i = start; i <= stop; i += step) |
| #IF | A | op | B | if (A op B) | |
| #END | end scope |
FOR Macro
#FOR takes a variable name, and a start, stop, and step.
It loops from start to stop inclusively, incrementing by step each loop.
step can be omitted and defaults to 1.
IF Macro
#IF takes two values with an operator in the middle.
Valid operators are:
==A equals B!=A does not equal B>A is greater than B>=A is greater than or equal to B<A is less than B<=A is less than or equal to B
; is a 10
#IF a == 10
#END
; is x less than y
#IF x < y
#END
Macro variables
#FOR macros will create variables with the first argument.
To use these variables, you can write #name anywhere inside
the loops scope and it will be substituted.
The # is not needed when using it in subsequent macros. Only when
in GRAB Assembly code.
; loop 0 to 9
#FOR n 0 9
; sets all objects named Obj0 to Obj9 X positions to 0 to 9
SET Obj#n.Pos.X #n
#END
Macro Arguments
Any value operands can be either a number (1, 1.1), a
variable (a, x, i), or a simple expression (a+b, 1*2, a/2).
Valid ‘simple expressions’ are 2 values with one of +, -, *, /,
or %, and cannot include a space.
; note: real code would need #END
#FOR a 0 10 1
#FOR a b c-1 d+1
#IF 1*10 == a*b
Example
SET R7 21 ; to get 0 to 20
SET R6 10 ; then - 10 to get -10 to 10
#FOR n 1 3
; 0 - 20
RAND R0 R7
RAND R1 R7
RAND R2 R7
#IF n == 3
SET R0 10 ; 10 - 10 means always 0
#END
; -10 - 10
SUB Obj#n.Pos.X R0 R6
SUB Obj#n.Pos.Y R1 R6
SUB Obj#n.Pos.Z R2 R6
#END
This code uses #FOR to duplicate code that sets the
X, Y, and Z of an object to a random value -10
through 10. The for loop creates a variable n at
1 and loops up to 3. Step is omitted, so it is 1.
For each loop it sets Obj#n.Pos.X where #n will
be replaced with the current value of n. e.g., Obj1.Pos.X.
There is also an #IF that runs when n == 3. This
means each loop will check that condition, and if it
is true, will add the SET R0 10 line, which forces
the object’s X position to be 0.
The resulting GRAB Assembly code will set 3 objects to random -10 to 10 positions, but object 3’s X will always be 0:
SET R7 21 ; the constants from the top
SET R6 10
RAND R0 R7 ; loop where n is 1
RAND R1 R7
RAND R2 R7
SUB Obj1.Pos.X R0 R6
SUB Obj1.Pos.Y R1 R6
SUB Obj1.Pos.Z R2 R6
RAND R0 R7 ; loop where n is 2
RAND R1 R7
RAND R2 R7
SUB Obj2.Pos.X R0 R6
SUB Obj2.Pos.Y R1 R6
SUB Obj2.Pos.Z R2 R6
RAND R0 R7 ; loop where n is 3
RAND R1 R7
RAND R2 R7
SET R0 10 ; n is 3 so this part is here
SUB Obj3.Pos.X R0 R6
SUB Obj3.Pos.Y R1 R6
SUB Obj3.Pos.Z R2 R6
You are now enlightened
Thanks for reading! If you found this guide useful, share it around! If you have some feedback, let me know! I appreciate it :)
Also, if you are here at the time of publishing this, Merry christler or whatever.
index