Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 |
Tags
- spread 연산자
- Camera Zoom
- draganddrop
- Spring Boot
- MySQL
- mongoDB
- react
- Unity IAP
- springboot
- Google Refund
- java
- Packet Network
- OverTheWire
- Unity Editor
- nodejs
- Digital Ocean
- unity
- screencapture
- Git
- SDK upgrade
- --watch
- Camera Movement
- docker
- css framework
- express
- critical rendering path
- Google Developer API
- linux
- rpg server
- server
Archives
- Today
- Total
우당탕탕 개발일지
[방치RPG 서버 제작기] 10. csv를 이용한 확률 관리 & 뽑기 API 본문
유니티 게임에서 뽑기 확률표(CSV)를 Spring Boot 서버에 올리고, API로 유저가 뽑기 요청하면 결과를 보내주는 시스템을 만들어보자.
1. CSV 확률표 구조
csv는 프로젝트 최상위 폴더에 csvs 폴더를 놓는다. 동일한 포맷을 가지고있는게 좋다. (파일 이름이든, 파일 내용이든). csv 포맷은 다음처럼 assetType, assetId, rate로 이루어져있다.
csvs/gachas/
├── gacha-skills.csv
├── gacha-pets.csv
assetType,assetId,rate
SKILL,101,0.5
SKILL,102,0.3
SKILL,103,0.2
spring 서버에서 csv 를 읽어오기 위해서는 application.yml 에 경로를 설정해줘야한다.
배포시에도 마찬가지로 .jar파일이 있는 위치에 csvs 폴더를 함께 놓는다.
2. application.yml 설정
gacha:
csv-dir: ./csvs/gachas
3. csv 파일 로드하기
gacha > entity > gachaEntry.java
public record GachaEntry(int assetType, int assetId, double rate) {}
gacha > config > gachaProperties.java
@Getter
@Setter
@Component
@ConfigurationProperties(prefix = "gacha")
public class GachaProperties {
private String csvDir;
}
gacha > loader > gachaCsvLoader.java
@Component
@RequiredArgsConstructor
public class GachaCsvLoader {
private final GachaProperties properties;
private final Map<String, List<GachaEntry>> gachaTables = new HashMap<>();
@PostConstruct
public void loadCsvs() throws IOException {
Path dir = Paths.get(properties.getCsvDir());
if (!Files.exists(dir) || !Files.isDirectory(dir)) {
throw new IllegalStateException("CSV 디렉토리를 찾을 수 없습니다: " + dir.toAbsolutePath());
}
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, "gacha-*.csv")) {
for (Path path : stream) {
String key = path.getFileName().toString()
.replace("gacha-", "")
.replace(".csv", "");
List<GachaEntry> entries = parseCsv(path);
gachaTables.put(key, entries);
}
}
}
private List<GachaEntry> parseCsv(Path path) throws IOException {
List<GachaEntry> result = new ArrayList<>();
try (BufferedReader reader = Files.newBufferedReader(path)) {
reader.readLine(); // skip header
String line;
while ((line = reader.readLine()) != null) {
String[] tokens = line.split(",");
result.add(new GachaEntry(
Integer.parseInt(tokens[0].trim()),
Integer.parseInt(tokens[1].trim()),
Double.parseDouble(tokens[2].trim())
));
}
}
return result;
}
public List<GachaEntry> getEntries(String type) {
return gachaTables.getOrDefault(type, List.of());
}
public void reload() throws IOException {
gachaTables.clear();
loadCsvs();
}
}
4. 뽑기 API 만들기
GachaService 와 GachaController에서 실제 API를 구현해보자
@Service
@RequiredArgsConstructor
public class GachaService {
private final GachaCsvLoader loader;
private final Random random = new Random();
public List<GachaEntry> drawMany(String type, int count) {
List<GachaEntry> entries = loader.getEntries(type);
if (entries.isEmpty()) throw new IllegalArgumentException("타입 없음");
List<GachaEntry> result = new ArrayList<>();
for (int i = 0; i < count; i++) {
result.add(drawOne(entries));
}
return result;
}
private GachaEntry drawOne(List<GachaEntry> entries) {
double r = random.nextDouble();
double acc = 0.0;
for (GachaEntry e : entries) {
acc += e.rate();
if (r < acc) return e;
}
return entries.get(entries.size() - 1); // fallback
}
}
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/gacha")
public class GachaController {
private final GachaService gachaService;
@GetMapping("/{type}")
public ResponseEntity<GachaResponse> draw(@PathVariable String type,
@RequestParam(defaultValue = "1") int count) {
if (count < 1 || count > 1000) {
return ResponseEntity.badRequest().body(new GachaResponse(ResponseCode.FAIL, List.of()));
}
try {
List<GachaEntry> result = gachaService.drawMany(type, count);
List<Asset> rewards = result.stream()
.map(e -> new Asset(e.assetType(), e.assetId(), 1))
.toList();
return ResponseEntity.ok(new GachaResponse(ResponseCode.SUCCESS, rewards));
} catch (IllegalArgumentException e) {
return ResponseEntity.badRequest().body(new GachaResponse(ResponseCode.FAIL, List.of()));
}
}
}
public record Asset(int type, int id, int cnt) {}
public enum ResponseCode {
SUCCESS,
FAIL
}
public record GachaResponse(ResponseCode code, List<Asset> rewards) {}
docker-compose 를 이용해서 서버를 올리고있는데 , CSV 폴더를 docker에 복새해야 정상적으로 작동한다. 따라서 기존의 dockerfile 에 복사하는 부분을 추가한다.
5. Dockerfile에 CSV 복사하기
FROM openjdk:21-jdk-slim
WORKDIR /danteRPG
COPY dante-server.jar app.jar
COPY csvs ./csvs ## 이부분 추가! 서버쪽에 파일이 있더라도 docker에 복사해주지 않으면 작동이 안됨.
EXPOSE 8080
CMD ["java", "-jar", "app.jar", "--spring.profiles.active=prod"]
✅ 정리
- CSV는 csvs/gachas에 넣고, gacha.csv-dir 경로 지정
- record + Spring의 @ConfigurationProperties로 깔끔한 설정
- API 응답도 유니티에 맞춰 code + rewards로 통일
- Docker 이미지에 확률표까지 넣어서 서버 배포 완성!
'Server > 방치RPG 서버' 카테고리의 다른 글
[방치RPG 서버 제작기] 12. Apple 계정연동하기 (1) | 2025.05.31 |
---|---|
[방치RPG 서버 제작기] 11. 점검모드 & 화이트리스트 관리 (0) | 2025.05.24 |
[방치RPG 서버 제작기] 9. 유니티 파일 서버로 전송하기 (0) | 2025.05.18 |
[방치RPG 서버 제작기] Docker 내의 db에 접속하기 ( DBeaver ) (0) | 2025.05.06 |
[방치RPG 서버 제작기] 8. 서버배포 및 SSL 인증서발급하기 (1) | 2025.04.26 |