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
string
s and values that areany
s. - 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
, orCornerWedge
- The properties parameter is a dictionary with keys that are
string
s and values that areany
s.
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 string
s and values that are ARCursor
s.
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 anEnum.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. IfShift
is held, it will be capitalised. It is astring
. - The userId parameter is the
UserId
of the player who pressed the key. It is anumber
.
-- 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 anyEnum.UserInputState
. It is aUserInputObject
. - The userId parameter is the
UserId
of the player who pressed the input. It is anumber
.
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)