Creator Guide
Korean
Korean
  • 🚩OVERDARE 소개
  • 🐤시작하기
    • OVERDARE App
    • OVERDARE Studio
  • 📌운영 정책
    • 커뮤니티 가이드라인
    • UGC 콘텐츠 제작 가이드라인
    • UGC의 외부 활용 가이드라인
    • LOGO 활용 가이드라인
    • 지식 재산권 정책
    • 신고 안내
    • 콘텐츠 제재 이의 신청 안내
    • 크리에이터 정산 정책
    • OVERDARE 수익화 가이드라인
  • 🏰스튜디오 메뉴얼
    • 스튜디오 인터페이스
    • 에셋 임포트
    • 좌표계
    • 게임 설정
    • 스튜디오 테스트 플레이
    • 월드 퍼블리시
    • Collaboration
    • 스크립트 에디터
    • 정렬
    • 애니메이션 에디터
    • Material Manager
    • Collision Groups
    • Tag Editor
    • Payout Guideline
    • Object
      • 파트
      • 모델
      • 캐릭터
        • Humanoid Description
      • 카메라
      • 물리
      • 조명
      • Tool
      • VFX
      • Sound
      • GUI
  • 📝스크립트 메뉴얼
    • 스크립트 개요
    • 루아 기초 가이드
    • 코딩 스타일
    • 오브젝트 참조
    • 이벤트
    • 서버-클라 통신
    • BindableEvent
    • Value Objects
    • 모바일 조작 처리
    • 트윈
    • 중단점
    • 모듈 스크립트
    • TPS Strafing System
    • Saving & Loading Data
    • 유니티 개발자용 가이드
    • 스크립트 최적화 실전 가이드
  • 📚API Reference
    • Enums
      • ActuatorRelativeTo
      • AnimationPriority
      • AspectType
      • AssetTypeVerification
      • BorderMode
      • CameraMode
      • CameraType
      • ContextActionResult
      • CoreGuiType
      • DominantAxis
      • EasingDirection
      • EasingStyle
      • ForceLimitMode
      • HttpCompression
      • HttpContentType
      • HumanoidDisplayDistanceType
      • HumanoidStateType
      • KeyCode
      • Material
      • MaterialPattern
      • NormalId
      • ParticleEmitterShape
      • ParticleEmitterShapeInOut
      • ParticleEmitterShapeStyle
      • ParticleFlipbookLayout
      • ParticleFlipbookMode
      • ParticleOrientation
      • PartType
      • PlaybackState
      • RaycastFilterType
      • RollOffMode
      • RotationType
      • UserInputState
      • UserInputType
      • VelocityConstraintMode
    • DataTypes
      • BlendSpaceSampleSata
      • BrickColor
      • CFrame
      • Color3
      • ColorSequence
      • ColorSequenceKeypoint
      • Content
      • Enum
      • EnumItem
      • NumberRange
      • NumberSequence
      • NumberSequenceKeypoint
      • OverlapParams
      • PhysicalProperties
      • Ray
      • RaycastParams
      • RaycastResult
      • ScriptConnection
      • ScriptSignal
      • TweenInfo
      • Udim
      • Udim2
      • Vector2
      • Vector3
    • Classes
      • Animation
      • AngularVelocity
      • AnimationTrack
      • Animator
      • Atmosphere
      • Attachment
      • Backpack
      • BackpackItem
      • BasePart
      • BaseScript
      • Beam
      • BindableEvent
      • BlendSpace
      • BoolValue
      • Bone
      • Camera
      • CharacterMesh
      • CollectionService
      • Constraint
      • ContextActionService
      • CoreGui
      • DataStore
      • DataModel
      • DataStoreGetOptions
      • DataStoreIncrementOptions
      • DataStoreInfo
      • DataStoreKeyPages
      • DataStoreKeyInfo
      • DataStoreService
      • DataStoreListingPages
      • DataStoreSetOptions
      • FormFactorPart
      • Frame
      • Folder
      • GlobalDataStore
      • GuiBase2d
      • GuiButton
      • GuiObject
      • HttpService
      • Humanoid
      • HumanoidDescription
      • ImageButton
      • ImageLabel
      • InputObject
      • IntValue
      • LayerCollector
      • Instance
      • Light
      • Lighting
      • LinearVelocity
      • LocalScript
      • LuaSourceContainer
      • MaterialService
      • MaterialVariant
      • MeshPart
      • Model
      • ModuleScript
      • Mouse
      • OrderedDataStore
      • Pages
      • Part
      • ParticleEmitter
      • PhysicsService
      • Player
      • PlayerGui
      • Players
      • PlayerScripts
      • PointLight
      • PVInstance
      • ReplicatedStorage
      • RemoteEvent
      • ScreenGui
      • RunService
      • Script
      • ServerStorage
      • ServiceProvider
      • Skeleton
      • ServerScriptService
      • Sound
      • SoundService
      • SoundGroup
      • SpotLight
      • SpawnLocation
      • StarterCharacterScripts
      • StarterPack
      • StarterGui
      • StarterPlayer
      • StarterPlayerScripts
      • StringValue
      • SurfaceGui
      • SurfaceGuiBase
      • Team
      • Teams
      • TextLabel
      • TextButton
      • Tool
      • Trail
      • Tween
      • TweenService
      • TweenBase
      • UIAspectRatioConstraint
      • UserGameSettings
      • UserInputService
      • UserSettings
      • VectorForce
      • Workspace
      • WrapLayer
      • WorldRoot
      • WrapTarget
  • 🅰️OVERDARE Glossary
  • 📰Release Note
Powered by GitBook
On this page
  • 개요
  • 주의 사항
  • 기본 동작 이해
  • 변수 타입
  • 메모리 구조
  • GC (Garbage Collection)
  • GC 이해의 중요성
  • 최적화 기초 가이드라인
  • 성능 최적화
  • 통신 및 이벤트 최적화
  • 구조 설계
  • 도입 우선순위가 높은 성능 개선 전략
  • Destroy + nil 정리
  • 이벤트 Disconnect + nil 정리
  • 오브젝트 풀링 (Object Pooling)
  • 활용 예시
  1. 스크립트 메뉴얼

스크립트 최적화 실전 가이드

개요

대규모 RPG나 액션 게임처럼 복잡한 구조의 게임에서는, 스크립트의 설계와 동작 방식이 게임의 전반적인 퍼포먼스에 중대한 영향을 미칩니다. 최적화를 고려하지 않은 스크립트는 서버 다운, 클라이언트 프레임 드롭, 메모리 누수, 예기치 않은 크래시 등 치명적인 문제를 유발할 수 있으며, 이는 곧 게임의 리텐션 저하와 수익 감소로 직결될 수 있습니다.

이 문서는 스크립트의 기본 동작 원리를 이해하고, 실전에서 적용할 수 있는 다양한 성능 최적화 기법들을 소개합니다. 이를 통해 복잡한 로직, 대량의 오브젝트, 빈번한 통신 처리 상황에서도 안정적인 성능을 유지하며 쾌적한 플레이 환경을 제공하는 데 도움이 될 것입니다.

주의 사항

스크립트는 최적화와 코드 가독성 사이의 균형을 고려하여 작성해야 합니다. 지나치게 성능만을 의식한 코드는 이해하기 어려워지고, 반대로 과도한 추상화나 장황한 구조는 성능에 악영향을 줄 수 있습니다.

기본 동작 이해

최적화된 코드를 작성하려면 Lua의 기본 동작 방식을 깊이 이해하는 것이 중요합니다. 코드 실행, 메모리 관리, 데이터 처리 방식에 대한 이해는 자원 낭비를 줄이고 성능 저하를 방지하는 데 도움이 됩니다.

변수 타입

Lua에서는 변수에 저장되는 값이 값 자체(Value Type)인지, 객체 참조(Reference Type)인지에 따라 동작이 달라집니다.

유형
예시
특징

값 타입 (Value)

number string boolean nil

  • 대입 시 복사됨

참조 타입 (Reference)

table function coroutine Instance

  • 대입 시 참조 공유

  • 복사가 아닌 참조이므로, 하나에서 값을 변경하면 다른 쪽에도 영향을 줌

참조 타입은 변수에 값 자체가 아닌 데이터의 참조(주소)가 저장되기 때문에, 하나의 참조를 여러 변수에서 공유할 수 있습니다. 이로 인해 한 변수에서 값을 변경하면 동일한 참조를 가진 다른 변수에도 영향을 미치게 됩니다. 이러한 특성은 데이터 구조를 유연하게 다룰 수 있게 해주지만, 의도하지 않은 변경이나 버그의 원인이 될 수 있으므로 주의가 필요합니다.

local t1 = { score = 100 }
local t2 = t1     -- 참조가 공유됨

t2.score = 200    -- t1에도 영향을 줌

print(t1.score)   -- 출력: 200
print(t2.score)   -- 출력: 200

메모리 구조

Lua는 내부적으로 스택(stack)과 힙(heap) 메모리 구조를 사용하여 변수를 관리합니다. 변수의 타입에 따라 값이 저장되는 위치, 생명 주기, 메모리 해제 방식 등 동작 방식이 달라집니다.

유형
저장 공간
특징

값 타입 (Value)

스택(Stack)

  • 생명 주기 : 함수나 블록이 실행되는 동안 유효하며, 해당 범위를 벗어나면 자동으로 소멸

  • 메모리 해제 : 명시적인 정리가 필요 없으며, 실행 흐름에 따라 자동으로 제거

참조 타입 (Reference)

힙(Heap)

  • 생명 주기 : 객체에 대한 참조가 존재하는 동안 유지되며, 참조가 하나라도 남아 있으면 메모리에 유지

  • 메모리 해제 : 모든 참조가 끊기면 GC에 의해 메모리에서 제거됨

  • 특징 : 하나의 객체를 여러 변수에서 참조할 수 있어 구조가 유연하지만, 메모리 관리에 주의 필요

값 타입은 스코프가 끝나면 메모리에서 자동으로 해제되지만, 참조 타입은 GC에 의해 참조 유무에 따라 주기적으로 정리됩니다. 이 차이로 인해 참조 타입은 불필요한 참조를 해제하지 않으면 메모리 누수, 성능 저하가 발생할 수 있습니다.

GC (Garbage Collection)

Lua는 자동 메모리 관리를 위해 Mark-and-Sweep(표시-삭제) 방식의 Garbage Collection(GC)을 사용합니다. GC는 더 이상 사용되지 않는 객체를 자동으로 감지하고 메모리를 해제해주므로, C++ 같은 언어처럼 개발자가 직접 메모리를 할당하거나 해제할 필요가 없습니다.

즉, 메모리 관리의 부담을 Lua가 대부분 대신 처리해주지만, GC가 효율적으로 동작하려면 불필요한 참조를 남기지 않는 구조와 접근 방식이 중요합니다. 참조가 남아 있는 한 객체는 "사용 중"으로 간주되어 메모리에서 제거되지 않기 때문에, 개발자가 참조 해제 타이밍을 신중하게 설계하지 않으면 메모리 누수가 발생할 수 있습니다.

Mark-and-Sweep(표시-삭제)

  1. Mark 단계 : 전역 변수, 지역 변수, 실행 중 함수 스택 등 루트 객체에서 출발해, 연결된 객체들을 따라가며 참조 중인 객체를 표시합니다.

  2. Sweep 단계 : 표시되지 않은, 즉 어디에서도 참조되지 않는 객체는 메모리에서 해제(수거) 됩니다.

메모리가 해제되는 조건

  • 참조하는 곳이 전혀 없는 객체

  • Destroy() 후에 참조도 nil로 변경된 객체

  • Disconnect() 후에 참조도 nil로 정리된 이벤트 연결 객체

GC 이해의 중요성

GC 동작을 이해해야 하는 이유는 단순히 “메모리를 알아서 정리해주니까 편하다”는 수준을 넘어, 성능, 안정성, 유지보수성 측면에서 실제로 큰 차이를 만들기 때문입니다.

  • GC는 자동으로 메모리를 정리해주지만, 참조가 남아 있으면 절대 수거하지 않습니다.

  • GC 대상 객체를 명확히 제거하면, 불필요한 메모리 점유를 줄이고 메모리 사용량을 예측 가능하게 유지할 수 있습니다.

  • GC는 힙 객체가 많을수록 자주 실행되며, 실행 시 프레임 드롭이 발생할 수 있습니다. 반복적인 객체 생성과 삭제를 피하고, 풀링 같은 재사용으로 GC 발생 자체를 줄이는 것이 중요합니다.

  • “왜 Destroy했는데도 메모리가 안 줄어들지?”, “왜 게임이 점점 느려지지?” 와 같은 문제 원인을 빠르게 파악하고 구조를 개선할 수 있습니다.

최적화 기초 가이드라인

성능 최적화

  • 오브젝트는 Instance.new, Clone 같은 런타임 생성/파괴 대신 오브젝트 풀링 기반으로 처리 (예: 총알을 Instance.new로 매번 생성하지 말고, 미리 만들어둔 오브젝트를 꺼내 사용하고 사용 완료시 다시 보관 처리)

  • 한 번에 너무 많은 오브젝트를 생성/파괴하는 구조 지양 (예: 스폰 시 몬스터 50마리를 동시에 생성하는 대신, 순차적 생성으로 렉 분산)

  • 미사용 객체는 반드시 Destroy() 후 nil 처리

  • 미사용 이벤트는 반드시 Disconnect() 후 nil 처리

    • 특히, 플레이어와 캐릭터에 연결된 이벤트는 반드시 명시적으로 해제

  • 사용하지 않는 UI는 화면에서 숨김 처리

  • 오브젝트, 객체, 서비스는 참조를 캐싱해서 재사용 (예: game:GetService("Players")를 매번 호출하지 말고, 변수에 저장해서 사용)

  • 모듈 스크립트는 require() 결과를 캐싱해서 재사용

  • 글로벌 변수의 사용은 지양하고, 로컬 범위에서 처리 (예: 스크립트 전체에 영향을 줄 수 있는 myData 같은 전역 변수 사용 지양)

  • 사용되지 않는 변수는 반드시 제거

  • Vector3, CFrame과 같은 객체는 미리 계산하여 캐싱 (예: 발사체 오프셋과 같은 고정 값은 매번 계산하지 않고, 초기화 시 미리 계산해두고 재사용지양)

  • 복잡한 참조 관계(서로를 참조하는 구조) 대신 단방향 참조로 설계 (예: 캐릭터와 총이 서로 참조하는 구조보다는 총이 캐릭터를 참조)

  • 테이블은 가능하면 재사용하는 구조로 설계 (예: 루프마다 {}를 새로 만들기보다, 외부에서 재사용하는 방식이 메모리 절약에 유리)

  • 익명 함수는 남용하지 않고, 불필요한 클로저 사용을 줄일 것 (특히 for 루프 안에서 이벤트 연결을 위한 새로운 함수를 매번 정의하지 않기)

  • 단순한 테이블은 key보다 index 기반으로 처리 ({ "a", "b", "c" } 형태는 pairs보다 ipairs가 더 빠르게 순회됨)

  • 나눗셈은 곱셈으로 대체 가능한 경우 * 연산자 사용 (예: x * 0.5는 x / 2보다 빠름)

  • 긴 문자열 병합은 "a" .. "b" 대신 table.concat()으로 처리하여 성능 향상 (.. 연산자는 문자열마다 새로운 메모리를 할당하여 병합하기 때문에, 반복이 많을수록 GC 부하 발생)

  • for, while 등 루프는 무조건 실행하지 말고 조건부 또는 간헐적으로 동작하도록 설계 (특히 루프 간격 조절)

  • 많은 양의 반복 처리는 한 번에 몰아서 하지 말고, 코루틴이나 분할 로직으로 분산 처리

  • 과도한 코루틴 생성을 피하고, 사용한 코루틴은 재사용하거나 종료 후 nil 처리

  • Heartbeat와 같은 프레임 단위 이벤트는 꼭 필요한 경우에만 사용하고 사용 완료시 해제 처리

통신 및 이벤트 최적화

  • 클라이언트에서 가능한 처리는 클라이언트에서 수행하여 서버 부하 최소화

  • RemoteEvent 통신은 꼭 필요한 경우에만 사용하고, 유사한 동작은 하나의 RemoteEvent로 통합 처리 (자주 호출되는 처리는 묶어서 전송)

  • 데이터 전송량은 가능한 최소화하여 설계 (예: 높이와 관련된 데이터는 Vector3 전체 대신 number 전송)

  • 속성 전달은 Attribute 사용 (부하 순위: RemoteEvent > ValueInstance > Attribute)

  • 통신 처리에 우선순위 큐를 도입하여, 필수 업데이트와 선택 업데이트를 구분해서 효율적으로 처리 (이펙트, 사운드 같은 연출은 후순위 처리)

  • 이벤트 기반(Event-Driven) 구조로 필요한 시점에만 데이터가 처리되도록 설계

    • 이벤트가 너무 자주 호출되는 상황에서는 반대로 get 함수 기반으로 설계

  • DataStore와 같은 서버 통신 기반 API는 호출 제한이 존재하므로, 분당 호출 수 제한하여 설계

구조 설계

  • 여러개의 KillPart처럼 같은 기능을 처리하는 경우, 오브젝트마다 각각 스크립트를 구성하기보다는 하나의 매니저 스크립트에서 여러 오브젝트를 통합 관리

  • MVC와 같은 구조 설계를 적용하여 코드 간 의존성을 줄이고 중복 코드를 최소화 (예: UI는 필요한 시점에만 갱신되도록 책임을 명확히 분리하여 불필요한 연산 부하 감소)

  • 게임 로직, 몬스터 등에 상태머신(State Machine)을 도입하여 필요한 로직만 실행되고 계산되게 설계

  • 데이터 처리와 시각화의 역할을 서버와 클라이언트로 명확히 분리하여, 부하 집중 분산

도입 우선순위가 높은 성능 개선 전략

이 항목들은 비교적 간단한 적용만으로 렉, 메모리 누수, 성능 불안정성과 같은 주요 문제를 효과적으로 줄이는 데 실질적인 도움이 됩니다.

Destroy + nil 정리

미사용 객체는 GC에 의해 수거되도록 명시적으로 참조를 제거합니다.

local Effect = Instance.new("ParticleEmitter")
...
Effect.Parent = Part

wait(3)
Effect:Destroy()  -- 객체 제거
Effect = nil      -- 참조 제거 (GC 가능)

이벤트 Disconnect + nil 정리

미사용 이벤트 역시 명시적으로 참조를 제거해야 하며, 플레이어/캐릭터 관련 이벤트는 특히 명확한 해제가 중요합니다.

local Connection = nil

local function OnDied()
    if Connection then
        Connection:Disconnect()  -- 이벤트 연결 해제
        Connection = nil         -- 참조 제거 (GC 가능)
    end
end
Connection = Humanoid.Died:Connect(OnDied)

오브젝트 풀링 (Object Pooling)

오브젝트를 반복적으로 생성/삭제하는 대신, 재사용 가능한 구조를 구축하여 총알, 이펙트, UI 슬롯 등의 오브젝트를 효율적으로 재활용합니다.

이를 통해 불필요한 메모리 할당과 해제를 줄이고, GC의 부담을 최소화하여 프레임 드랍이나 일시적인 랙 현상을 방지할 수 있습니다.

local BulletManager = {}

-- 총알 오브젝트를 저장하는 풀
local PoolList = {}

-- 총알 템플릿 오브젝트 생성
local template = Instance.new("Part")
template.Size = Vector3.new(0.2, 0.2, 2)
...
template.Name = "Bullet"

-- 지정된 수만큼 총알을 생성해 풀에 저장
function BulletManager:Init(count)
    for i = 1, count do
        local bullet = template:Clone()
        ...
        bullet:SetAttribute("Active", false)

        table.insert(PoolList, bullet)
    end
end

-- 풀에서 사용 가능한 총알 하나를 반환
function BulletManager:GetFromPool()
    for _, bullet in ipairs(PoolList) do
        if not bullet:GetAttribute("Active") then
            bullet:SetAttribute("Active", true)
            ...

            return bullet
        end
    end
    return nil
end

-- 사용이 끝난 총알을 풀로 되돌림
function BulletManager:Release(bullet)
    bullet:SetAttribute("Active", false)
    ...
end

return BulletManager
local PoolCount = 30 -- 재사용 오브젝트 최대 갯수
BulletManager:Init(PoolCount) -- 총알 풀을 초기화하고 오브젝트를 미리 생성합니다.

local Bullet = BulletManager:GetFromPool() -- 사용 가능한 오브젝트를 가져옵니다.

if Bullet then
    Bullet.Position = startPos
    ...

    wait(1)
    BulletManager:Release(Bullet) --- 사용이 끝난 총알을 풀에 반환하여 재사용할 수 있도록 합니다.
end

활용 예시

이 매뉴얼은 단순히 외워야 할 규칙을 나열한 문서가 아닙니다. 프로젝트 성격, 구조 복잡도, 퍼포먼스 요구 수준에 따라 유연하게 참고하고 적용할 수 있는 ‘기준점’ 역할을 합니다.

  • 새로운 기능을 구현할 때는 구조와 처리 흐름이 성능에 어떤 영향을 줄 수 있는지 미리 고려해보는 체크리스트로 활용하세요.

  • 디버깅 중 성능 문제가 의심될 때는, 해당 섹션을 빠르게 훑어보며 개선 지점을 찾는 기준으로 삼을 수 있습니다.

  • 협업 시에는 팀원 간 코드 스타일과 최적화 수준을 맞추는 데 기반 문서로 활용할 수 있습니다.

꼭 모든 항목을 강제로 적용할 필요는 없습니다. 다만, 이 문서에 담긴 기준들은 프로젝트가 커질수록 반드시 마주치게 될 문제를 미리 방지하기 위한 지침임을 기억해 주세요.

Previous유니티 개발자용 가이드NextAPI Reference

Last updated 11 hours ago

📝