점푸 점푸!

로블록스 스튜디오와 친해지기

상단 화면

  • 조작

우측 화면

  • Workspace
    • 실제 게임 월드에 존재하는 파트, 모델, 지형, 캐릭터 등이 들어가는 공간
  • Players
    • 게임에 접속한 플레이어 객체들이 관리되는 서비스
  • Lighting
    • 하늘, 조명, 그림자, 분위기, 시간대 같은 시각 효과를 설정하는 서비스
  • MaterialService
    • 파트의 재질(Material) 외형을 커스터마이징하거나 관리하는 서비스
  • NetworkClient
    • 클라이언트의 네트워크 연결을 담당하는 내부 서비스로, 보통 직접 수정하지 않음
  • ReplicatedFirst
    • 게임 시작 시 클라이언트에게 가장 먼저 복제되는 객체를 넣는 공간
  • ReplicatedStorage
    • 서버와 클라이언트가 함께 접근할 수 있는 공용 저장 공간
  • ServerScriptService
    • 서버에서 실행되는 Script를 넣는 공간
  • ServerStorage
    • 서버만 접근할 수 있는 비공개 저장 공간
  • StarterGui
    • 플레이어에게 기본으로 복사될 GUI 화면을 넣는 공간
  • StarterPack
    • 플레이어가 시작할 때 받는 Tool, 아이템 등을 넣는 공간
  • StarterPlayer
    • 플레이어 캐릭터와 카메라, 이동 관련 기본 설정을 관리하는 공간
  • Teams
    • 팀전 게임에서 사용할 팀 정보를 관리하는 서비스
  • SoundService
    • 게임 전체의 사운드, 배경음악, 효과음 설정을 관리하는 서비스
  • TextChatService
    • Roblox의 텍스트 채팅 시스템을 관리하는 서비스
  • VoiceChatService
    • Roblox의 음성 채팅 기능을 관리하는 서비스

shortcuts

  • select 1
  • move 2
  • scale 3
  • rotate 4
  • transform 5
  • focus f
  • test f5
  • test exit shift + f5
  • wasd, qe
  • copy & paste
    • ctrl + c / ctrl + d
    • ctrl + v

추천 플러그인

  • click to teleport camera
    • shift + mb3

3차원 사물과 지형 만들기

3차원 사물

  • 책상 만들어보기
    • union
    • align
  • 데칼코마니 꾸며보기
    • decal 추가
      • face - 방향 설정
      • 도구상자에서 ColorMapContent 복사 후, 붙여 넣기

지형

  • 지형 생성
  • 지형 복제
    • cmd + d

루아 스크립트

변수

local number1 = 10
local number2 = 10.123
local string = "Hello"
local boolean = true
 
print(number1)
print(number2)
print(string)
print(boolean)

문자열

local A = "Hello"
local B = " World!"
print(A .. B)
print(#A)

조건문

-- if 조건 then
--    실행할 내용
-- else if     then
-- else
-- end
if a == 1 then
	print("hi")
end

반복문

-- for 변수 = 초기값, 최종값, 증감값 do
--    반복할 내용
-- end
for num = 1, 10, 1 do
	print(num)
end

함수

function name(arg1, arg2, ...)
	-- some code
end

적용해보기

Kill block

-- kill block
 
local lava = script.Parent -- 이 스크립트의 부모 파트를 lava 변수에 저장한다
 
local function killPlayer(otherPart) -- lava에 닿은 파트를 otherPart로 받아 실행되는 함수
    local partParent = otherPart.Parent -- 닿은 파트의 부모 객체를 가져온다
    local humanoid = partParent:FindFirstChild("Humanoid") -- 부모 객체 안에서 Humanoid를 찾는다
 
    if humanoid then -- Humanoid가 존재한다면, 즉 플레이어 캐릭터라면
        humanoid.Health = 0 -- 체력을 0으로 만들어 캐릭터를 죽인다
    end
end
 
lava.Touched:Connect(killPlayer) -- lava에 무언가 닿으면 killPlayer 함수를 실행한다
 

Faded Block

-- fade block
local platform = script.Parent
 
local function fade()
    for count = 1, 10 do
        platform.Transparency = count / 10
        wait(0.1)
    end
    platform.CanCollide = false
    wait(5)
    platform.CanCollide = true
    platform.Transparency = 0
end
 
platform.Touched:Connect(fade)
 

리더보드 설정

  • ServerScriptService/SetupPoints
-- server leader board
local Players = game:GetService("Players")
 
local function onPlayerAdded(player)
	local leaderstats = Instance.new("Folder")
	leaderstats.Name = "leaderstats"
	leaderstats.Parent = player
 
	local points = Instance.new("IntValue")
	points.Name = "Points"
	points.Value = 0
	points.Parent = leaderstats
end
 
Players.PlayerAdded:Connect(onPlayerAdded)
 
 

점수 얻기

-- get point
local part = script.Parent
local canGet = true
 
local function onTouch(otherPart)
    local humanoid = otherPart.Parent:FindFirstChild('Humanoid')
    if humanoid then
        local player = game.Players:FindFirstChild(otherPart.Parent.Name)
        if player and canGet then
            print("Get Point")
            canGet = false
            player.leaderstats.Points.Value = player.leaderstats.Points.Value + 1
            -- 만약 다시 점수를 얻어야하는 경우라면
            -- wait(10)
            -- canGet = true
        end
    end
end
 
part.Touched:Connect(onTouch)
 

BGM 추가

local music = game.Workspace.Script["Happy Song"]
music:Play()
-- attribute -> looped -> true

무빙 플랫폼

Structure

ServerScriptService
├─ MovingPlatformService
└─ Modules
   └─ MovingPlatform   ← ModuleScript
 
Workspace
└─ Green
   ├─ Part
   ├─ Part
   └─ MovingPlatformConfig   ← Script

MovingPlatformService

local CollectionService = game:GetService("CollectionService")
local ServerScriptService = game:GetService("ServerScriptService")
 
local MovingPlatform = require(ServerScriptService.Modules.MovingPlatform)
 
local TAG_NAME = "MovingPlatform"
 
local function applyToModel(instance: Instance)
	if not instance:IsA("Model") then
		warn("[MovingPlatform] 태그는 Model에 붙여야 합니다:", instance:GetFullName())
		return
	end
 
	local direction = instance:GetAttribute("MoveDirection") or Vector3.new(1, 0, 0)
	local distance = instance:GetAttribute("MoveDistance") or 20
	local speed = instance:GetAttribute("MoveSpeed") or 6
	local friction = instance:GetAttribute("Friction") or 1
 
	MovingPlatform.apply(instance, direction, distance, speed, friction)
end
 
local taggedModels = CollectionService:GetTagged(TAG_NAME)
 
print("[MovingPlatform] 찾은 모델 개수:", #taggedModels)
 
for _, instance in ipairs(taggedModels) do
	applyToModel(instance)
end
 
CollectionService:GetInstanceAddedSignal(TAG_NAME):Connect(function(instance)
	applyToModel(instance)
end)

MovingPlatform

local RunService = game:GetService("RunService")
 
local MovingPlatform = {}
 
local activeConnections = {}
 
local function getParts(model: Model): { BasePart }
	local parts = {}
 
	for _, descendant in ipairs(model:GetDescendants()) do
		if descendant:IsA("BasePart") then
			table.insert(parts, descendant)
		end
	end
 
	return parts
end
 
function MovingPlatform.apply(
	model: Model,
	direction: Vector3,
	distance: number,
	speed: number,
	friction: number?
)
	if activeConnections[model] then
		activeConnections[model]:Disconnect()
		activeConnections[model] = nil
	end
 
	local parts = getParts(model)
 
	if #parts == 0 then
		warn("[MovingPlatform] Model 안에 BasePart가 없습니다:", model:GetFullName())
		return
	end
 
	if direction.Magnitude == 0 then
		warn("[MovingPlatform] direction이 Vector3.zero입니다:", model:GetFullName())
		return
	end
 
	if distance <= 0 then
		warn("[MovingPlatform] distance는 0보다 커야 합니다:", model:GetFullName())
		return
	end
 
	if speed <= 0 then
		warn("[MovingPlatform] speed는 0보다 커야 합니다:", model:GetFullName())
		return
	end
 
	friction = friction or 1
 
	for _, part in ipairs(parts) do
		part.Anchored = true
		part.CanCollide = true
		part.CanTouch = true
		part.CustomPhysicalProperties = PhysicalProperties.new(
			1,        -- Density
			friction, -- Friction
			0,        -- Elasticity
			1,        -- FrictionWeight
			1         -- ElasticityWeight
		)
	end
 
	local startPivot = model:GetPivot()
	local goalPivot = startPivot + direction.Unit * distance
 
	local travelTime = distance / speed
	local elapsed = 0
	local lastPivot = startPivot
 
	local connection
	connection = RunService.Heartbeat:Connect(function(deltaTime)
		if not model.Parent then
			connection:Disconnect()
			activeConnections[model] = nil
			return
		end
 
		elapsed += deltaTime
 
		local cyclePosition = (elapsed / travelTime) % 2
		local linearAlpha
 
		if cyclePosition <= 1 then
			linearAlpha = cyclePosition
		else
			linearAlpha = 2 - cyclePosition
		end
 
		local easedAlpha = 0.5 - math.cos(linearAlpha * math.pi) * 0.5
 
		local nextPivot = startPivot:Lerp(goalPivot, easedAlpha)
		local deltaPosition = nextPivot.Position - lastPivot.Position
 
		if deltaTime > 0 then
			local velocity = deltaPosition / deltaTime
 
			for _, part in ipairs(parts) do
				part.AssemblyLinearVelocity = velocity
			end
		end
 
		model:PivotTo(nextPivot)
		lastPivot = nextPivot
	end)
 
	activeConnections[model] = connection
 
	print("[MovingPlatform] 적용됨:", model:GetFullName())
 
	return connection
end
 
return MovingPlatform

움직일 파트의 스크립트

local CollectionService = game:GetService("CollectionService")
 
local model = script.Parent
 
CollectionService:AddTag(model, "MovingPlatform")
 
model:SetAttribute("MoveDirection", Vector3.new(1, 0, 0))
model:SetAttribute("MoveDistance", 10)
model:SetAttribute("MoveSpeed", 6)
model:SetAttribute("Friction", 1)

더블 점프

Structure

ReplicatedStorage
└─ Modules
   └─ CharacterController   ← ModuleScript
 
StarterPlayer
└─ StarterPlayerScripts
   └─ ClientMain.client     ← LocalScript

ClientMain.client

 
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
 
local CharacterController = require(ReplicatedStorage.Modules.CharacterController)
 
CharacterController.start(Players.LocalPlayer)

CharacterController

--!strict
-- Client character actions: lower extra jump while airborne.
 
local UserInputService = game:GetService("UserInputService")
local Workspace = game:GetService("Workspace")
 
local CharacterController = {}
 
local DOUBLE_JUMP_FALLBACK_UP_VELOCITY = 50
local DOUBLE_JUMP_HEIGHT_RATIO = 0.72
local DOUBLE_JUMP_FORWARD_BOOST = 4
local DOUBLE_JUMP_MIN_AIR_TIME = 0.12
 
local function isJumpInput(input: InputObject)
	return input.KeyCode == Enum.KeyCode.Space
		or input.KeyCode == Enum.KeyCode.ButtonA
end
 
function CharacterController.start(player: Player)
	local currentHumanoid: Humanoid? = nil
	local currentRoot: BasePart? = nil
 
	local becameAirborneAt = 0
	local canDoubleJump = false
	local usedDoubleJump = false
	local nextDoubleJumpAt = 0
 
	-- 추가: 점프 입력을 계속 누르고 있는지 확인
	local jumpInputHeld = false
 
	-- 추가: 첫 번째 점프 이후 점프 키를 뗐는지 확인
	local releasedJumpAfterAirborne = false
 
	local function resetDoubleJump()
		becameAirborneAt = 0
		canDoubleJump = false
		usedDoubleJump = false
		releasedJumpAfterAirborne = false
	end
 
	local function getNormalJumpVelocity(humanoid: Humanoid)
		local normalJumpVelocity = DOUBLE_JUMP_FALLBACK_UP_VELOCITY
 
		if humanoid.UseJumpPower then
			normalJumpVelocity = humanoid.JumpPower
		else
			normalJumpVelocity = math.sqrt(2 * Workspace.Gravity * humanoid.JumpHeight)
		end
 
		if normalJumpVelocity <= 0 then
			return DOUBLE_JUMP_FALLBACK_UP_VELOCITY
		end
 
		return normalJumpVelocity
	end
 
	local function doExtraJump()
		local humanoid = currentHumanoid
		local root = currentRoot
 
		if not humanoid or not root or humanoid.Health <= 0 then
			return
		end
 
		if usedDoubleJump or not canDoubleJump then
			return
		end
 
		if os.clock() < nextDoubleJumpAt then
			return
		end
 
		if os.clock() - becameAirborneAt < DOUBLE_JUMP_MIN_AIR_TIME then
			return
		end
 
		if humanoid.FloorMaterial ~= Enum.Material.Air then
			return
		end
 
		-- 핵심:
		-- 첫 번째 점프 후 스페이스바를 한 번 떼야만 더블 점프 허용
		if not releasedJumpAfterAirborne then
			return
		end
 
		usedDoubleJump = true
		canDoubleJump = false
		nextDoubleJumpAt = os.clock() + 0.25
 
		local currentVelocity = root.AssemblyLinearVelocity
		local forwardBoost = root.CFrame.LookVector * DOUBLE_JUMP_FORWARD_BOOST
		local upVelocity = getNormalJumpVelocity(humanoid) * DOUBLE_JUMP_HEIGHT_RATIO
 
		root.AssemblyLinearVelocity = Vector3.new(
			currentVelocity.X + forwardBoost.X,
			upVelocity,
			currentVelocity.Z + forwardBoost.Z
		)
 
		humanoid:ChangeState(Enum.HumanoidStateType.Jumping)
	end
 
	local function bindCharacter(character: Model)
		currentHumanoid = character:WaitForChild("Humanoid") :: Humanoid
		currentRoot = character:WaitForChild("HumanoidRootPart") :: BasePart
 
		resetDoubleJump()
 
		currentHumanoid.StateChanged:Connect(function(_, newState: Enum.HumanoidStateType)
			if newState == Enum.HumanoidStateType.Jumping
				or newState == Enum.HumanoidStateType.Freefall then
 
				if becameAirborneAt == 0 then
					becameAirborneAt = os.clock()
 
					-- 공중에 진입했을 때 점프 키를 이미 누르고 있다면,
					-- 아직 더블 점프 불가
					releasedJumpAfterAirborne = not jumpInputHeld
				end
 
				if not usedDoubleJump then
					canDoubleJump = true
				end
 
			elseif newState == Enum.HumanoidStateType.Landed
				or newState == Enum.HumanoidStateType.Running
				or newState == Enum.HumanoidStateType.RunningNoPhysics then
 
				resetDoubleJump()
			end
		end)
	end
 
	-- 점프 키를 누르고 있는지 추적
	UserInputService.InputBegan:Connect(function(input: InputObject, gameProcessed: boolean)
		if gameProcessed then
			return
		end
 
		if isJumpInput(input) then
			jumpInputHeld = true
		end
	end)
 
	-- 첫 번째 점프 이후 점프 키를 떼면 더블 점프 가능 상태로 전환
	UserInputService.InputEnded:Connect(function(input: InputObject, gameProcessed: boolean)
		if isJumpInput(input) then
			jumpInputHeld = false
 
			if becameAirborneAt ~= 0 and not usedDoubleJump then
				releasedJumpAfterAirborne = true
			end
		end
	end)
 
	UserInputService.JumpRequest:Connect(function()
		doExtraJump()
	end)
 
	if player.Character then
		bindCharacter(player.Character)
	end
 
	player.CharacterAdded:Connect(bindCharacter)
end
 
return CharacterController