섬 내 거래를 사용하면 Verse를 사용하여 섬에서 아이템, 오퍼, 번들 오퍼를 홍보할 수 있습니다.
이 가이드에서는 고유한 아이템, 오퍼, 번들 오퍼를 구성하는 방법을 알아보고, Verse API의 마켓플레이스 모듈을 사용하면 게임 내 아이템의 판매를 처리하게 됩니다.
아이템
아이템은 Verse에서 권한으로 정의되며 두 가지 카테고리로 나뉩니다. 사용 시 플레이어 인벤토리에서 제거되는 소모성 아이템과, 인벤토리에서 제거되지 않아 플레이어가 계속 사용할 수 있는 내구성 아이템입니다.
모든 Verse 권한은 다음 프로퍼티를 갖고 있습니다.
Name: 50자까지 입력 가능한 권한 이름입니다.
Description: 500자까지 입력 가능하며 권한과 함께 표시되는 긴 설명입니다.
ShortDescription: 작은 대화 상자에서 최대 100자까지 권한을 요약하는 간략한 설명입니다.
Icon: 권한의 이미지입니다.
권한이 유료 랜덤 아이템일 경우, 설명에 플레이어가 아이템을 획득할 정확한 확률 수치를 포함해야 합니다. 자세한 정보는 유료 랜덤 아이템을 참고하세요.
Verse 권한에는 다음과 같은 선택적 프로퍼티도 있습니다.
MaxCount: 플레이어가 아무 때나 보유 가능한 최대 권한 개수입니다.
Consumable: true로 설정하면 권한을 소모할 수 있으며, 총 사용 횟수가 줄어듭니다. false로 설정하면 권한이 영구 아이템이 되어 사용해도 소모되지 않습니다.
PaidArea: true로 설정할 경우 권한이 지불 시 이용 가능한 영역에 대한 액세스를 제공합니다.
PaidRandomItem: true로 설정할 경우, 랜덤 보상을 얻으려면 권한을 구매하거나 콘텐츠로 교환해야 합니다.
ConsequentialToGameplay: true로 설정할 경우, 아이템이 섬에서 실질적인 이점을 제공합니다. 자세한 내용은 게임플레이 결과에 영향 있음을 참고하세요.
사용하지 않는 활성화된 권한이 있지만 IARC 설문지에서 인앱 구매를 확인하지 않은 경우, 섬이 검토를 통과하지 못합니다.
이 문제를 해결하려면 해당 권한을 라이브 게임에서 사용할 준비가 될 때까지 Verse에서 권한을 주석 처리할 수 있습니다. 권한을 주석 처리하면 IARC 설문지에서 인앱 구매가 있다고 확인하지 않아도 됩니다.
Verse에서 소모품 권한 생성하기
권한은 Verse에서 정의되며, 권한 베이스 클래스에서 파생됩니다. 다음 스니펫은 소모성 아이템을 만드는 과정을 보여주며, 이 예시에서는 소모성 아이템으로 옥수수 씨앗을 만들어 보겠습니다. 아래에 사용할 수 있도록 씨앗 아이콘이 포함되어 있습니다.
# The base entitlement you should define for ALL of your entitlements in your experience.
my_island_entitlement := class<abstract><castable>(entitlement){}
CornSeedPacket<public> := module:
Name<public><localizes> : message = "Corn seed pack"
Description<public><localizes> : message = "A pack of corn seeds. Opening a pack yields 10 corn seeds for planting."
ShortDescription<public><localizes> : message = "Contains 10 corn seeds for planting."
cornseedpacket<public> := class<concrete>(my_island_entitlement):
var Name<override>:message = CornSeedPacket.Name
권한을 만들 때는 컴파일 성공을 위해 Verse 코드에 유효한 아이콘 텍스처 경로를 포함해야 합니다. 본 가이드에 포함된 옥수수 씨앗 꾸러미와 다른 아이콘을 자유롭게 사용해 주세요!
권한 아이콘에 2의 거듭제곱 텍스처를 사용해 상점에서 최고 품질의 이미지를 구현하세요. UEFN에서 아이콘으로 사용할 텍스처를 임포트하는 방법은 에셋 임포트하기를 참고하세요. Verse에서 텍스처와 같은 에셋을 노출하는 방법에 대한 자세한 정보는 에셋 노출하기를 참고하세요.
Verse에서 영구 권한 만들기
Verse에서 내구성 권한은 소모성 권한과 동일한 형식을 따르지만, 한 가지 중요한 차이점이 있는데, Consumable이 true가 아닌 false로 설정된다는 것입니다. 영구 권한은 플레이어가 한 번만 구매할 수 있으며, 플레이어는 특정 영구 권한을 하나만 소유할 수 있습니다.
이 예시에서는 내구성 권한으로 삽을 만들어 봅니다. 삽 텍스처는 아래 스니펫에 이어서 준비되어 있습니다.
Shovel<public> := module:
Name<public><localizes>: message = "Shovel"
Description<public><localizes>: message = "An unbreakable shovel used to dig holes for planting."
ShortDescription<public><localizes>: message = "Digs holes."
shovel<public> := class<concrete>(my_island_entitlement):
var Name<override>:message = Shovel.Name
var Description<override>:message = Shovel.Description
var ShortDescription<override>:message = Shovel.ShortDescription
var Icon<override>:texture = # path to your texture here
기본적으로 아이템은 Consumable이 아니며 MaxCount가 1입니다. 아이템이 유료 영역이거나, 유료 랜덤 아이템이거나, 게임플레이 결과에 영향이 있는 실질적 이점을 제공하는 경우, 코드에서 관련 필드를 정의해야 합니다.
권한 유효성 검사 규칙
Verse에서 유효한 권한은 아래 가이드라인을 따라야 합니다. 가이드라인을 충족하지 않는 권한 구매는 실패합니다.
유효한 권한을 정의하는 규칙은 다음과 같습니다.
Name의 글자수 제한은 50자입니다.
Description의 글자수 제한은 500자입니다.
ShortDescription의 글자수 제한은 100자입니다.
MaxCount는 Consumable=false일 경우 1이어야 합니다.
MaxCount의 최댓값은 10,000,000입니다.
MaxCount < 1로 설정하는 것이 금지되지는 않지만, 플레이어에게 아이템을 한 개보다 적게 줄 수는 없으므로 실패하게 됩니다.
권한 카탈로그
권한 카탈로그를 사용해 플레이어에게 제공하는 모든 권한을 볼 수 있습니다.
UEFN에서 툴(Tools) > 권한 카탈로그(Entitlement Catalog)를 클릭하거나, 섬의 크리에이터 포털에서 카탈로그를 바로 클릭하여 권한이 나열된 보고서를 볼 수 있습니다.
오퍼
오퍼는 아이템 또는 에셋의 가격을 V-Bucks로 지정합니다. 오퍼에는 각각 자체적인 이름, 설명, 아이콘이 있으며, 이는 권한 사양과는 별개입니다. 오퍼는 Verse에서 정의합니다.
모든 오퍼에는 다음 프로퍼티가 있습니다.
Name: 오퍼의 이름입니다.
Description: 오퍼와 함께 표시되는 긴 설명입니다.
ShortDescription: 작은 대화 상자에 오퍼를 요약하기 위해 사용하는 간략한 설명입니다.
Icon: 오퍼의 이미지입니다.
EntitlementType: 오퍼에 포함된 권한 선언입니다.
Price: V-Bucks 기준 가격입니다. 50 V-Bucks보다 작거나 5000 V-Bucks보다 클 수 없으며, 가격은 50 단위로 설정되어야 합니다.
간단한 오퍼 만들기
이 스니펫은 단순 오퍼, 옥수수 씨앗 꾸러미를 위한 기본 오퍼를 정의해 줍니다. 권한 예시의 옥수수 씨앗 아이콘을 이 오퍼 아이콘으로 재사용할 수 있습니다.
CornSeedPacket<public> := module:
Name<public><localizes> : message = "Corn seed pack"
Description<public><localizes> : message = "A pack of corn seeds. Opening a pack yields 10 corn seeds for planting."
ShortDescription<public><localizes> : message = "Contains 10 corn seeds for planting."
corn_seed_pack<public> := class(entitlement_offer):
var Name<override> : message = CornSeedPacket.Name
var Description<override> : message = CornSeedPacket.Description
var ShortDescription<override> : message = CornSeedPacket.ShortDescription
V-Bucks 가격은 50~5000 V-Bucks 사이여야 하며, 50의 배수로 지정해야 합니다.
플레이어가 구매에 앞서 각 유료 랜덤 아이템의 정확한 획득 확률 수치를 볼 수 있어야 하며, 그렇게 하지 못할 경우 포트나이트 개발자 규정 위반으로 간주되며, 개발자와 개발자의 섬에 해당하는 제재가 취해집니다.
자세한 정보는 섬 내 거래 제한을 참고하세요.
고정 오퍼와 대체 오퍼 만들기 및 수정하기
한 권한에서 대체 오퍼를 만들어 홀리데이 기념 특별가나 출시 기념 보너스를 제공할 수 있으며, 지역별로 가격을 다르게 설정할 수 있습니다. 또한 동일한 오퍼를 만들지만 서로 다른 아이콘을 사용해 어느 권한이 플레이어에게 더 매력적인지 테스트하는 데 사용할 수도 있습니다. 이 아이콘을 예시로 사용해 보겠습니다.
CornSeedPacketAlternate<public> := module:
Name<public><localizes> : message = "Corn seed pack"
Description<public><localizes> : message = "Special price! Only today!"
ShortDescription<public><localizes> : message = "Special offer half price!"
corn_seed_pack_alternate<public> := class(entitlement_offer):
var Name<override> : message = CornSeedPacketAlternate.Name
var Description<override> : message = CornSeedPacketAlternate.Description
var ShortDescription<override> : message = CornSeedPacketAlternate.ShortDescription
var Icon<override> : texture = # Your texture here
번들 오퍼
번들은 Verse에서 정의하며, 여러 오퍼를 조합하거나, 같은 오퍼를 스택으로 제공하거나, 두 가지 방식을 함께 활용할 수도 있습니다. 단순 오퍼와 마찬가지로 번들 오퍼에는 자체적인 가격, 이름, 설명이 지정되어야 하고, 권한과 오퍼와는 구별되는 아이콘이 있어야 합니다. 번들 오퍼 안에 번들을 포함시켜 오퍼를 중첩할 수도 있는데, 삽과 옥수수 씨앗 꾸러미 번들이 포함된 기간 한정 번들을 예로 들 수 있습니다. 이렇게 하면 더 작은 번들을 구성 요소로 사용해 합쳐서 더 큰 번들을 만들 수 있습니다.
일반적인 번들 유형은 다음과 같습니다.
스택 번들: 같은 권한의 여러 오퍼가 포함된 번들로, 대개 할인가로 판매됩니다.
복수 오퍼 번들: 복수 권한의 오퍼가 합쳐진 번들로, 중첩 오퍼와 일반 오퍼를 조합해 포함할 수 있습니다.
중첩 오퍼의 중첩 단계는 5단계를 넘을 수 없으며, 넘을 경우 거래 시도 시 실패합니다. 가능하면 오퍼 중첩을 최소화하세요.
스택 번들 만들기
이 스니펫에서는 옥수수 씨앗의 스택 번들을 정의합니다. 번들에는 오퍼의 튜플 배열이 포함됩니다. 여기에는 정의된 오퍼 및 오퍼 개수를 나타내는 int가 포함됩니다. 여기에서는 번들에 두 개의 corn_seed_pack 오퍼가 있으며, 예시에 사용할 아이콘이 준비되어 있습니다.
CornSeedPacketBundle<public> := module:
Name<public><localizes> : message = "Corn seed pack bundle"
Description<public><localizes> : message = "Two packs of corn seeds. Opening a pack yields 10 corn seeds for planting."
ShortDescription<public><localizes> : message = "Two packs of corn seeds containing 10 corn seeds for planting."
corn_seed_pack_bundle<public> := class(bundle_offer):
var Name<override> : message = CornSeedPacketBundle.Name
var Description<override> : message = CornSeedPacketBundle.Description
var ShortDescription<override> : message = CornSeedPacketBundle.ShortDescription
var Icon<override> : texture = # your texture here
복수 오퍼 번들 만들기
플레이어는 여러 번의 거래를 피하려 할 것입니다. 이를 위해 여러 권한이 든 복수 오퍼가 포함된 번들을 만들 수 있습니다. 이 스니펫은 플레이어에게 최대 개수의 옥수수 씨앗 꾸러미와 삽 하나를 제공하는 복수 오퍼 번들을 생성합니다.
StarterBundle<public> := module:
Name<public><localizes> : message = "Starter bundle"
Description<public><localizes> : message = "Everything a new player needs. Get fully stocked to start quickly! A shovel that digs holes, and ten packs of corn seeds each containing 10 corn seeds for planting."
ShortDescription<public><localizes> : message = "A shovel that digs holes, and ten packs of corn seeds each containing 10 corn seeds for planting."
starter_bundle<public> := class(bundle_offer):
var Name<override> : message = StarterBundle.Name
var Description<override> : message = StarterBundle.Description
var ShortDescription<override> : message = StarterBundle.ShortDescription
var Icon<override> : texture = # your texture here
Verse의 경우 번들에 권한이 직접적으로 포함되지 않습니다. 대신, 권한 정의가 있는 오퍼가 포함됩니다.
동적으로 생성되는 오퍼
동적으로 생성되는 오퍼는 Verse에서 런타임 시 생성되는 오퍼 또는 번들 오퍼입니다. 동적 오퍼의 보통 다음과 같은 경우에 사용됩니다.
제작 게임에서 플레이어가 보유할 수 있는 최대 개수 목재를 제공하는 오퍼
던전 크롤러 게임의 던전 입구에서 체력 및 마나 물약을 최대로 채우는 번들
단순하게 하기 위해 버튼이나 표지판 등 간단한 무언가에 연결하세요. Verse 코드에서 이렇게 하면 상호작용 시 > BuyOffer 호출 흐름을 따르게 됩니다.
이 스니펫은 옥수수 씨앗 꾸러미 번들을 플레이어가 보유 가능한 최대 개수만큼 판매하는 방법을 보여줍니다. 구매 실패 시 오류 메시지가 출력됩니다.
TryBuyOffer(Player : player)<suspends>:void =
Purchases := GetPurchasedEntitlements(Player, Entitlements.corn_seed_pack)
var NumPlayerCornSeedPacks : int = 0
if (Purchase := Purchases[0]):
set NumPlayerCornSeedPacks = Purchase(1)
# Limit to at least 1 packet.
# If the player has the maximum amount, the offer displays with a disabled purchase button.
NumCornSeedPacks := Max(1, Entitlements.corn_seed_pack{}.MaxCount - NumPlayerCornSeedPacks)
오퍼 유효성 검사 규칙
유효한 오퍼 또는 번들 오퍼는 아래 가이드라인을 따라야 합니다. 가이드라인을 충족하지 않는 오퍼의 구매는 검토에 실패하게 됩니다. 유효한 오퍼를 정의하는 규칙은 다음과 같습니다.
중첩 오퍼의 중첩 단계는 5단계를 넘을 수 없습니다.
권한 식별자 총 개수는 오퍼당 100개를 초과할 수 없습니다. 즉, 한 번에 판매되는 여러 권한의 총 개수는 최대 100개라는 뜻입니다.
오퍼의 가격은 50 - 5000 V-Bucks 사이여야 하고, 50의 배수로만 지정해야 합니다.
오퍼
Name의 기본 텍스트는 50자를 초과할 수 없습니다.오퍼
Description의 기본 텍스트는 500자를 초과할 수 없습니다.오퍼
ShortDescription의 기본 텍스트는 100자를 초과할 수 없습니다.오퍼에는 권한이 1개 이상 포함되어야 합니다.
오퍼에는 권한의
MaxCount보다 큰 권한 수량이 포함되지 않습니다.오퍼에는
MaxCount> 1인 내구성 권한이 포함되지 않습니다.
오퍼를 어디에 표시하고 누가 볼 수 있을지 설정할 때 제한을 적용할 수도 있습니다. 자세한 내용은 섬 내 거래 제한을 참고하세요.
상점 만들기
권한과 권한의 오퍼 및 번들을 구성했으니 이제 이를 판매할 장소가 필요합니다!
기본 UI
기본 상점 UI는 추가한 모든 권한 및 오퍼가 등록된 목록과 함께 열립니다. 목록에서 첫 권한이 강조 표시되어 있으며, 목록 옆의 창에는 권한 개요가 나와 있습니다.
상점에는 여러 페이지가 있을 수 있습니다.
아이템이 5개가 넘는 목록은 스크롤이 가능합니다.
플레이어는 BuyOffer 메서드를 호출하거나 ShowOffersDialog 메서드로 기본 상점을 사용함으로써 구매 과정을 트리거합니다. 아래는 상점 구매 과정을 게임 디자인과 통합하는 데 사용할 수 있는 몇 가지 장치 예시입니다.
볼륨 장치
씬 그래프 타이머 장치
NPC
대화 장치
상점 디자인 시 플레이어가 직접 구매 절차를 시작하도록 선택하게 하는 것이 가장 바람직합니다. 이 선택 과정을 건너뛰고 강제로 구매 절차를 시작하면 플레이어의 선택권이 사라져 불만을 초래할 수 있습니다.
모든 오퍼에는 아이템을 구매하거나 오퍼 구성품을 살펴볼 수 있도록 마켓플레이스 창을 여는 구매(Purchase) 및 살펴보기(Inspect) 버튼이 있습니다. 번들의 경우 유일하게 번들 살펴보기(Inspect Bundle) 버튼이 있습니다.
닫기(Close) 버튼을 선택하면 마켓플레이스가 닫힙니다.
다음과 같이 개발자가 상점 경험을 완전히 제어할 수 있습니다.
개발자가 플레이어에게 제공할 아이템이나 게임플레이 프로퍼티를 결정합니다.
개발자가 각 오퍼나 번들 오퍼의 가격을 지정합니다.
자체적인 상점을 표시하거나, 사전 제작된 포트나이트 상점 UI를 사용할 수 있습니다.
상점 UI
이 스니펫은 사전 제작된 포트나이트 상점 UI를 여는 일반 이벤트 콜백을 정의합니다. 콜백은 구독, 버튼 누르기, 대화 이벤트 등의 형태가 될 수 있습니다.
OnEvent(Agent:agent):void=
if(Player:= player[Agent]):
spawn{ShowOffersDialog(Player, array{
ExampleOffers.shovel{},
ExampleOffers.cornseedpacket{}
})}
구매 처리하기
이 스니펫은 일반 오퍼 구매를 포함하고 있습니다. 구매 실패 시 오류 메시지가 출력됩니다.
TryBuyOffer(Player:player, Offer : offer)<suspends>:void =
Result := BuyOffer(Player, Offer)
if (not Result?):
Print("Failed to buy the {offer.name} offer.")실패한 구매의 디버깅을 지원할 수 있도록, 오류 메시지에 어떤 구매가 실패했는지 알 수 있는 방법이 있어야 합니다. 예를 들어 위 오류 메시지에 오퍼 이름을 넣을 수 있습니다.
추가 함수
유료 랜덤 아이템
섬에서 유료 랜덤 아이템을 제공하는 방법은 두 가지가 있습니다.
V-Bucks로 구매할 수 있도록 직접 제공
플레이어가 V-Bucks로 권한을 구매한 뒤 랜덤 보상으로 교환할 수 있게 하거나 랜덤 보상 획득 확률에 영향을 주는 방법으로 간접 제공할 수도 있습니다.
랜덤 보상을 지급하는 아이템 제작 시 PaidRandomItem을 true로 설정해야 합니다.
랜덤 보상 교환에 사용할 수 있는 콘텐츠를 제공할 경우, RestrictPaidRandomItems 함수를 사용하여 액세스 권한이 없는 플레이어가 랜덤 보상을 획득하지 못하게 해야 합니다.
OnEvent(Agent:agent):void=
if (Player := player[Agent]):
if (RestrictPaidRandomItems[Player]):
Print("Player is not allowed to purchase PaidRandomItems.")
else:
Print("Player is allowed to purchase PaidRandomItems.")
직접 구매 프롬프트
섬에 직접 구매 프롬프트가 있을 경우, RestrictDirectPromptsToPurchase 함수를 사용하여 플레이어에게 프롬프트가 표시될 자격이 있는지 결정해야 합니다.
OnEvent(Agent:agent):void=
if (Player:= player[Agent]):
if (RestrictDirectPromptsToPurchase[Player]):
Print("Player is not allowed to receive direct purchase prompts.")
else:
Print("Player is allowed to receive direct purchase prompts.")
게임플레이 결과에 영향 있음
판매하는 아이템이 섬에서 플레이어에게 실질적인 이점을 제공할 경우, ConsequentialToGameplay를 true로 설정해야 합니다.
게임플레이에 영향이 있는 아이템이란 언제든 구매 시 플레이어에게 게임 내 실질적인 이점을 제공하는 아이템입니다. 이는 직접적(플레이어의 게임플레이 진행 속도, 위력 또는 능력을 상승시키는 아이템) 또는 간접적(플레이어의 게임 진행 속도 또는 승리 확률에 유의미한 영향을 주는 아이템을 이용할 수 있도록 하는 아이템)일 수 있습니다.
판매 중인 아이템과 동일한 이점을 제공하며 오퍼 제공과 동시에 모든 플레이어가 무료로 이용 가능한 대체 아이템이 있을 경우, ConsequentialToGameplay를 true로 설정하지 않아도 됩니다. 게임플레이 아이템이 게임플레이에 중요하지 않은 부수적 영향만 주는 경우(예: 다른 환경에서 약간 다르게 보이는 다른 의상 색상 설정 또는 신체 움직임을 다르게 하는 다른 이모트) 게임플레이 결과에 영향이 있는 것으로 간주되지 않습니다.
# The base entitlement you should define for ALL of your entitlements in your experience.
my_island_entitlement := class<abstract><castable>(entitlement){}
CornSeedPacket<public> := module:
Name<public><localizes> : message = "Corn seed pack"
Description<public><localizes> : message = "A pack of corn seeds. Opening a pack yields 10 corn seeds for planting."
ShortDescription<public><localizes> : message = "Contains 10 corn seeds for planting."
cornseedpacket<public> := class<concrete>(my_island_entitlement):
var Name<override>:message = CornSeedPacket.Name