물리 엔진
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