Skip to main content

ARController

Like a TouchScreen, but for your actual screen. Has the capability to display augmented 3D objects and visuals in a virtualized world. Click to equip/unequip. Output may only be displayed from one ARController at a time.

It is a craftable and spawnable non-flammable solid.

Here is a list of possible sizes that reach the maximum malleability (400) that have integer components: 1x400x1, 1x200x2, 1x100x4, 1x80x5, 1x50x8, 1x40x10, 1x25x16, 1x20x20, 2x100x2, 2x50x4, 2x40x5, 2x25x8, 2x20x10, 4x25x4, 4x20x5, 4x10x10, 5x16x5, 5x10x8, 10x8x5, 16x5x5, 20x10x2, 20x5x4

At its default size (8x1x8) it has a durability of 1, at its maximum size it has a durability of 2.

By default, its colour is # 0 0 0.

It requires 1 ARGlasses, 2 Antenna, 4 Iron, and 4 Quartz to be crafted.

Methods


ClearElements(context)

Clears all objects that are descendants of the screen.

The context parameter is a CanvasContext.


ClearElements3D()

Clears 3D elements.


CreateElement(className, properties, context) → element

Creates an element of the specified class name with the specified properties.

The parameters for CreateElement are as follows:

  • The className parameter is a string.
  • The properties parameter is a dictionary with keys that are strings and values that are anys.
  • The context parameter is a CanvasContext.

The element return is an Instance.

-- Try and get the ar controller, throw an error if we don't find it using `assert`.
local screen = assert(GetPart("ARController"), "no ar controller connected")

-- Clear the left over screen elements from the last time GUIs were loaded onto it.
-- Note: If you utilise the `Instance.new` API instead, you do not need to do this,
-- as objects created through said API are *automatically* deleted when the
-- microcontroller stops running.
screen:ClearElements()

-- We're going to spawn 32 randomly sized/coloured/positioned cubes.
for index = 1, 32 do
-- Generate a random `Vector3` in a -16 to 16 cube region.
local position = Vector3.new(
math.random(-16, 16),
math.random(-16, 16),
math.random(-16, 16)
)

-- This may be confusing if you've never worked with `CFrame`s, 'why is pi here?'
-- the reason is that pi is 180 degrees when working in 'radians' (the angle system
-- used by `CFrame`s). `math.random` with *no* parameters returns a random decimal
-- number between 0 and 1, so each parameter is just a number between 0 and pi.
local orientation = CFrame.Angles(
math.random() * math.pi,
math.random() * math.pi,
math.random() * math.pi
)

-- Select a size between 0 and 4 for all axes, `Vector3.one` is shorthand for
-- `Vector3.new(1, 1, 1)`, we multiply it by a number between 1 and 4 and this
-- gives us a vector with a side length of 1 to 4 on all axes.
local size = Vector3.one * (1 + math.random() * 3)

-- Pick a random colour with 3 `math.random` calls
local color = Color3.new(math.random(), math.random(), math.random())

-- Call the `CreateElement` method with the `ClassName` of the object you want to create,
-- and a dictionary of the properties to apply (you cannot specify the `Parent` property).
-- Note the `"3D"` context parameter, this says we want to use the instance viewport, you
-- could alternatively use `CreateElement3D` to avoid this third parameter.
local object = screen:CreateElement("Part", {
CFrame = orientation + position + Microcontroller.Position,
Size = size,
Color = color,
TopSurface = Enum.SurfaceType.Smooth, -- The `Top` and `Bottom` surfaces
BottomSurface = Enum.SurfaceType.Smooth, -- have a stud pattern by default.
}, "3D")
end

-- Keep the microcontroller on by permanently 'yielding' the code (making it wait).
coroutine.yield()

CreateElement3D(shape, properties) → part

Creates a 3D part instance in the player's viewport.

The parameters for CreateElement3D are as follows:

  • The shape parameter is a string that can be Ball, Block, Cylinder, Wedge, or CornerWedge
  • The properties parameter is a dictionary with keys that are strings and values that are anys.

The part return is a Part.


GetCanvas

Overload A – (context) → canvas

Returns the parent container of all the AR controller's 2D content.

The context parameter is the string 2D. It can also be nil.

The canvas return is a Folder.

Overload B – (context) → canvas

Returns the parent container of all the AR controller's 3D content.

The context parameter is the string 3D.

The canvas return is a WorldModel.


GetCursor() → cursor

Returns the part owner's cursor, or, alternatively, any other player's cursor if the owner does not have a cursor (this is considered the primary user).

The cursor return is an ARCursor.


GetCursors() → cursors

Gets a dictionary of player usernames to their cursors.

The cursors return is a dictionary with keys that are strings and values that are ARCursors.

Configurables


Transparency

The transparency of the AR display. It is a number. It ranges between 0 and 1.

Events


Configured(configurerId)

Fires when the object is configured.

The configurerId parameter is the UserId of the player who configured the object. It is a number.


CursorMoved(cursor)

Fired when a player's cursor updates.

The cursor parameter is an ARCursor.


CursorPressed(cursor)

Fired when a player left clicks.

The cursor parameter is an ARCursor.


CursorReleased(cursor)

Fired when a player releases the left click button.

The cursor parameter is an ARCursor.


KeyPressed(key, keyName, userId)

Fires when the user presses a key.

The parameters for KeyPressed are as follows:

  • The key parameter is the Enum.KeyCode of the key that was pressed. It is an Enum.KeyCode.
  • The keyName parameter is the letter of the key that was pressed. If the key is non-printable (i.e. shift or backspace) keyString will be an empty string. If Shift is held, it will be capitalised. It is a string.
  • The userId parameter is the UserId of the player who pressed the key. It is a number.
-- Get all of the necessary parts and throw a useful error if something isn't found.
local anchor = assert(GetPart("Anchor"), "no anchor connected")
local thrusterSwitch = assert(GetPart("Switch"), "no switch connected")
local keyboard = assert(GetPart("Keyboard"), "no keyboard connected")

-- Define the `UserId`s of people who are allowed.
local WHITELIST = {
[1178125707] = true,
}

-- Create a dictionary of what key to press to what code to run
local BINDS = {
-- Toggle the anchor state when `R` is pressed.
[Enum.KeyCode.R] = function()
anchor.Anchor = not anchor.Anchor
end,
-- Toggle the switch state when `X` is pressed.
[Enum.KeyCode.X] = function()
thrusterSwitch.SwitchValue = not thrusterSwitch.SwitchValue
end,
}

-- Connect to the `keyboard.KeyPressed` event, we don't need the `keyName` variable,
-- so we'll call it `_` as to say "we're not going to use this" to anyone reading the code.
keyboard.KeyPressed:Connect(function(key, _, userId)
-- If the user *isn't* in the whitelist, *cancel* this function using `return`.
if not WHITELIST[userId] then return end

-- Try and find the piece of code to run for the key that was pressed, if we don't find
-- it, cancel, like it's done when the user isn't in the whitelist.
local callback = BINDS[key]
if not callback then return end

-- Run the piece of code!
callback()
end)

OnClick(clickerId)

Fires when the object is clicked.

The clickerId parameter is a number.


UserInput(inputObject, userId)

Will fire when a user presses a key, it is already filtered to gameProcessedEvent being false.

The parameters for UserInput are as follows:

  • The inputObject parameter is the UserInputObject produced by the player, is not whitelisted to any Enum.UserInputState. It is a UserInputObject.
  • The userId parameter is the UserId of the player who pressed the input. It is a number.
local UPDATES_PER_TICK = 16 -- How many times to rotate per tick, higher is smoother.
local ANGLE_INCREMENT = 4 -- Every 1/UPDATES_PER_TICK seconds increment the servo angle by this.
local SHIFT_HELD_MULTIPLIER = 0.25 -- When shift is held, multiply the angle increment by this.

-- Who can rotate the servos.
local WHITELIST = {
[1178125707] = true,
}

-- Get references to all the hardware, everything is *required* except for the
-- seat, which is optional.
local yawServo = assert(GetPartFromPort(1, "Servo"), "no yaw servo connected")
local pitchServo = assert(GetPartFromPort(2, "Servo"), "no pitch servo connected")
local keyboard = assert(GetPart("Keyboard"), "no keyboard connected")
local seat = GetPart("Seat")

-- Make sure the servos are wired properly!
assert(yawServo ~= pitchServo, "there is only one servo connected")

local servoAngles = {}
local heldKeys = {}

local function incrementAngle(servo, direction)
-- Make the `ANGLE_INCREMENT` negative if the direction is specified as `-1`
-- with a bit of multiplication.
local baseIncrement = ANGLE_INCREMENT * direction

-- If we need to initialise the target angle of the servo:
if not servoAngles[servo] then
-- Rotate it back to 0 degrees and initialise the current state.
servo:SetAngle(0)
servoAngles[servo] = 0
end

-- Here we return a new function, that when called, applies the angle increment
-- operation to the servo, this is a trick called a "closure".
return function()
-- Calculate the new angle, check if we're holding shift, if we are multiply
-- the increment by the `SHIFT_HELD_MULTIPLIER`
local multiplier = if heldKeys[Enum.KeyCode.LeftShift] then SHIFT_HELD_MULTIPLIER else 1
local currentAngle = baseIncrement * multiplier

-- Update both the real and internal servo angle.
servo:SetAngle(currentAngle)
servoAngles[servo] = currentAngle
end
end

-- Register all of the angle incrementing functions that should keep running
-- whilst the user keeps holding the bind.
local HOLD_BINDS = {
[Enum.KeyCode.W] = incrementAngle(pitchServo, 1),
[Enum.KeyCode.A] = incrementAngle(yawServo, 1),
[Enum.KeyCode.S] = incrementAngle(pitchServo, -1),
[Enum.KeyCode.D] = incrementAngle(yawServo, -1),
}

-- Register all of the general binds that only run on key down.
local BINDS = {
[Enum.KeyCode.F] = function()
TriggerPort(3) -- Assume port 3 has something like guns!
end,
}

-- If we have a seat, we can detect when the user gets up and clear all the
-- held keys as if they stopped inputting.
if seat then
seat.OccupantChanged:Connect(function(occupant)
if occupant then return end -- If someone just sat down, cancel
table.clear(heldKeys) -- Whereas if someone got up, clear the held keys
end)
else
-- Provide a warning about issues that might occur when no seat is connected.
warn([[There is no seat connected to the microcontroller!
If a player jumps whilst holding an input, the input will get stuck!]])
end

keyboard.UserInput:Connect(function(input, userId)
-- Check if the user is whitelisted, if they're not, cancel.
if not WHITELIST[userId] then return end

-- If the bind pressed has a function to run on key down, run it.
if BINDS[input.KeyCode] then
local callback = BINDS[input.KeyCode]
callback()
end

-- If the user *started* pressing the key, register it as held, otherwise
-- deregister it.
if input.UserInputState == Enum.UserInputState.Begin then
heldKeys[input.KeyCode] = true
elseif input.UserInputState == Enum.UserInputState.End then
heldKeys[input.KeyCode] = nil
end
end)

-- For each tick we want to call all the functions that should run for held keys.
-- You could alternatively use a simple `while true do` loop, but this keeps it
-- tick aligned, and tick alignment may provide benefits in some cases.
Microcontroller.Loop:Connect(function(tickDuration)
for index = 1, UPDATES_PER_TICK do
for key, callback in HOLD_BINDS do
-- If we're not holding this bind, look at the next bind.
if not heldKeys[key] then continue end
callback()
end

-- Wait a little for for the next time we should update.
task.wait(tickDuration / UPDATES_PER_TICK)
end
end)