본문으로 건너뛰기

물리 엔진

Connect Base 게임 서버는 서버 측 물리 엔진을 제공하여 정확하고 공정한 물리 시뮬레이션을 수행합니다.

기본 개념

┌─────────────────────────────────────┐
│           물리 월드                  │
│                                     │
│  ┌─────┐   ┌─────┐   ┌─────────┐   │
│  │ 강체 │   │ 강체 │   │ 트리거  │   │
│  │(Rigid)│  │(Rigid)│  │(Trigger)│   │
│  └─────┘   └─────┘   └─────────┘   │
│                                     │
│  ────────────────────────────────   │
│            바닥 (Static)             │
└─────────────────────────────────────┘

콜라이더 타입

lua
-- 박스 콜라이더
function createBoxCollider(entity, width, height)
    return {
        type = "box",
        width = width,
        height = height,
        offset = { x = 0, y = 0 }
    }
end

-- 원형 콜라이더
function createCircleCollider(entity, radius)
    return {
        type = "circle",
        radius = radius,
        offset = { x = 0, y = 0 }
    }
end

-- 캡슐 콜라이더 (캐릭터용)
function createCapsuleCollider(entity, width, height)
    return {
        type = "capsule",
        width = width,
        height = height
    }
end

충돌 감지

lua
-- 충돌 콜백 등록
function onCollisionEnter(entityA, entityB, contactPoint)
    local tagA = getTag(entityA)
    local tagB = getTag(entityB)

    -- 플레이어가 아이템에 닿음
    if tagA == "player" and tagB == "item" then
        collectItem(entityA, entityB)
    end

    -- 투사체가 적에게 명중
    if tagA == "projectile" and tagB == "enemy" then
        local damage = getDamage(entityA)
        applyDamage(entityB, damage)
        destroyEntity(entityA)
    end
end

function onCollisionExit(entityA, entityB)
    -- 충돌 종료 처리
end

-- 트리거 (통과 가능한 영역)
function onTriggerEnter(trigger, entity)
    if trigger.id == "checkpoint" then
        saveCheckpoint(entity)
    elseif trigger.id == "damage_zone" then
        startDamageOverTime(entity, 10)  -- 초당 10 데미지
    end
end

레이캐스트

레이캐스트는 직선을 발사하여 충돌하는 객체를 찾습니다:

lua
-- 기본 레이캐스트
function performRaycast(origin, direction, maxDistance)
    local hit = raycast(origin, direction, maxDistance)

    if hit then
        return {
            entity = hit.entity,
            point = hit.point,
            normal = hit.normal,
            distance = hit.distance
        }
    end

    return nil
end

-- 시야선 확인
function hasLineOfSight(from, to)
    local direction = normalize({
        x = to.x - from.x,
        y = to.y - from.y
    })
    local distance = getDistance(from, to)

    local hit = raycast(from, direction, distance, {"wall", "obstacle"})

    -- 중간에 장애물이 없으면 시야선 있음
    return hit == nil
end

-- 바닥 체크
function isGrounded(entity)
    local origin = {
        x = entity.position.x,
        y = entity.position.y + entity.height / 2
    }
    local direction = { x = 0, y = 1 }  -- 아래 방향

    local hit = raycast(origin, direction, 0.1, {"ground"})
    return hit ~= nil
end

영역 쿼리

특정 영역 내의 객체를 찾습니다:

lua
-- 원형 영역 쿼리
function getEntitiesInRadius(center, radius, tags)
    local entities = overlapCircle(center, radius, tags)
    return entities
end

-- 폭발 범위 내 적 찾기
function explosionDamage(center, radius, damage)
    local enemies = getEntitiesInRadius(center, radius, {"enemy"})

    for _, enemy in ipairs(enemies) do
        local distance = getDistance(center, enemy.position)
        local falloff = 1 - (distance / radius)  -- 거리에 따른 감쇠
        local actualDamage = damage * falloff

        applyDamage(enemy, actualDamage)
    end
end

-- 박스 영역 쿼리
function getEntitiesInBox(center, width, height, tags)
    local halfWidth = width / 2
    local halfHeight = height / 2

    local min = { x = center.x - halfWidth, y = center.y - halfHeight }
    local max = { x = center.x + halfWidth, y = center.y + halfHeight }

    return overlapBox(min, max, tags)
end

물리 속성

lua
-- 강체 생성
function createRigidBody(entity, bodyType)
    return {
        type = bodyType,  -- "dynamic", "static", "kinematic"
        mass = 1.0,
        friction = 0.5,
        restitution = 0.3,  -- 탄성 (0-1)
        linearDamping = 0.1,
        angularDamping = 0.1,
        gravityScale = 1.0
    }
end

-- 힘 적용
function applyForce(entity, force)
    local rb = getRigidBody(entity)
    rb.velocity.x = rb.velocity.x + force.x / rb.mass
    rb.velocity.y = rb.velocity.y + force.y / rb.mass
end

-- 충격 적용 (즉시)
function applyImpulse(entity, impulse)
    local rb = getRigidBody(entity)
    rb.velocity.x = rb.velocity.x + impulse.x
    rb.velocity.y = rb.velocity.y + impulse.y
end

-- 넉백 효과
function knockback(target, source, force)
    local direction = normalize({
        x = target.position.x - source.position.x,
        y = target.position.y - source.position.y
    })

    applyImpulse(target, {
        x = direction.x * force,
        y = direction.y * force
    })
end

이동 및 점프

lua
-- 캐릭터 컨트롤러
local CHARACTER_SPEED = 5
local JUMP_FORCE = 10
local GRAVITY = -20

function updateCharacter(entity, input, deltaTime)
    local rb = getRigidBody(entity)

    -- 수평 이동
    local moveX = 0
    if input.left then moveX = moveX - 1 end
    if input.right then moveX = moveX + 1 end

    rb.velocity.x = moveX * CHARACTER_SPEED

    -- 점프
    if input.jump and isGrounded(entity) then
        rb.velocity.y = JUMP_FORCE
    end

    -- 중력 적용
    rb.velocity.y = rb.velocity.y + GRAVITY * deltaTime

    -- 위치 업데이트
    entity.position.x = entity.position.x + rb.velocity.x * deltaTime
    entity.position.y = entity.position.y + rb.velocity.y * deltaTime

    -- 바닥 충돌 처리
    if entity.position.y < 0 then
        entity.position.y = 0
        rb.velocity.y = 0
    end
end

투사체 물리

lua
-- 투사체 생성
function createProjectile(owner, direction, speed)
    local projectile = {
        ownerId = owner.id,
        position = { x = owner.position.x, y = owner.position.y },
        velocity = {
            x = direction.x * speed,
            y = direction.y * speed
        },
        lifetime = 5.0,  -- 5초 후 자동 제거
        damage = 25
    }

    return projectile
end

-- 포물선 운동 (수류탄 등)
function createGrenadeProjectile(owner, direction, speed, throwAngle)
    local radians = math.rad(throwAngle)

    return {
        ownerId = owner.id,
        position = { x = owner.position.x, y = owner.position.y },
        velocity = {
            x = direction.x * speed * math.cos(radians),
            y = speed * math.sin(radians)
        },
        gravity = -15,
        fuseTime = 3.0,
        explosionRadius = 5,
        damage = 100
    }
end

유틸리티 함수

lua
-- 거리 계산
function getDistance(a, b)
    local dx = b.x - a.x
    local dy = b.y - a.y
    return math.sqrt(dx * dx + dy * dy)
end

-- 방향 정규화
function normalize(vector)
    local length = math.sqrt(vector.x * vector.x + vector.y * vector.y)
    if length == 0 then return { x = 0, y = 0 } end
    return {
        x = vector.x / length,
        y = vector.y / length
    }
end

-- 각도 계산
function getAngle(from, to)
    return math.atan2(to.y - from.y, to.x - from.x)
end

-- AABB 충돌 체크
function checkAABBCollision(a, b)
    return a.min.x < b.max.x and a.max.x > b.min.x and
           a.min.y < b.max.y and a.max.y > b.min.y
end