ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [UE5] 사용자 제작 퍼즐 - 퍼즐 목록 받아오기
    언리얼엔진/노노그램 2023. 11. 14. 18:21

     

    마지막으로 사용자가 업로드한 퍼즐의 목록을 불러와서 위젯으로 띄운다.

    퍼즐의 목록은 위젯의 리스트 뷰에서 목록 뷰를 사용하고, 무한 스크롤을 구현할 것이다.

     

     

     


     

     

     

    먼저 무한 스크롤 먼저 구현한다.

    무한 스크롤의 기본 동작은 어느정도 스크롤이 됐을 때 다음 아이템들을 새로 생성해서 추가하면 된다.

    중요한 부분은 스크롤이 얼마나 내려갔는지를 확인하는 부분이다.

    언리얼 엔진 위젯의 리스트 뷰는 목록 뷰 스크롤 시 이벤트에서 스크롤이 얼마나 내려갔는지, 얼마나 남았는지를 알 수 있다.

     

    리스트 뷰에 아이템이 추가될 수록 전체 스크롤 크기는 계속 증가하므로, 특정 값과 Distance Remaining의 값을 비교해서 아이템을 추가할 때를 판단하면 된다.

    일반적으로는 이렇게 하면 된다.

     


     

    위 방법을 먼저 알아냈다면 이 방법을 사용했겠지만 그렇지 못했고, 다른방식으로 구현했다.

     

    리스트 뷰에는 전체 스크롤 크기와 남은 스크롤 크기를 반환해주는 블루프린트 함수가 없어서 스크롤의 값을 사용하지 못했다.

    대신에 아래에서부터 몇번째 아이템이 새로 세팅 되는 순간 이벤트를 호출시켜서 새로운 아이템을 추가하는 방법을 사용했다.

    리스트 뷰의 특징을 이용한 방법이다.

     

    리스트 뷰는 스크롤으로 인해서 화면에서 사라진 엔트리 위젯을 재사용해서 화면에 새로 나올 아이템에 할당해준다.

    그 과정에서 엔트리 위젯을 다시 설정하는 이벤트가 호출된다.

    만약 이전에 아이템이 리스트 뷰에 추가되는 과정에서 마지막에서 두번째 아이템을 기억해두고 있었다면, 그 마지막에서 두번째 아이템에 엔트리 위젯이 할당되는 순간은 그 만큼 스크롤이 내려왔다는 뜻이므로, 그때 리스트 뷰에게 새로운 아이템을 생성해서 넣게 한다.

    새로운 아이템을 생성해서 넣을 때에도 마지막에서 두번째 아이템을 기억해두고 있으면 계속 반복될 것이다.

    비슷한 방법으로는 리스트 뷰의 IsItemVisible 함수로 기억해 둔 아이템이 화면에 나타났는지를 검사하는 방법이 있지만, 매 틱마다 검사해야하므로 좋지 않은 방법이다.

     

    이런 방법으로는 특정 아이템에 특별한 작업을 해야하고, 그 작업으로 인해서 부모 위젯인 리스트 뷰가 작동해야한다.

    따라서 아이템에 이벤트 디스패처를 생성해놓고 특정 아이템은 리스트 뷰의 함수를 바인딩하기로 했다.

    그 다음에는 아이템에 엔트리 위젯이 할당될 때마다 이벤트를 호출하고, 만약 그 이벤트에 어느 함수가 바인딩 되어있다면 그 함수가 호출될 것이다.

    이렇게 하면 아이템과 엔트리 위젯은 부모 위젯인 리스트 뷰를 추가로 참조하고있거나 함수를 직접 호출하지 않아도 된다.

     

    엔트리 위젯이 새로운 아이템에 할당될 때 호출되는 이벤트

    사용자 퍼즐의 정보를 저장하는 아이템인 BP_UserPuzzle_Item에 LoadTriggered 이벤트 디스패처를 생성해서 엔트리 위젯이 할당될 때마다 호출한다.

    해당 아이템은 트리거의 역할을 다 했으니 이벤트에 바인딩된 함수를 모두 언바인드한다.

     

    트리거 아이템은 리스트 뷰에 아이템을 추가할때 지정한다.

    추가할 아이템의 전체 개수와 for 루프의 인덱스를 비교해서 어느 아이템에 이벤트를 바인딩 할 지를 결정한다.

     

    뒤에서 몇번째 아이템을 트리거로 사용할지는 LoadTriggerPadding의 값으로 결정한다.

     

    테스트한 결과는 아래와 같다.

     

    위젯에 적혀있는 숫자가 해당 아이템의 인덱스다.

    화면 오른쪽의 스크롤 바를 보면 아이템이 계속 추가되는 것을 확인할 수 있다.

     


     

    위의 경우는 클라이언트에서 작동하므로 딜레이없이 새로운 아이템이 추가된다.

    하지만 서버와 통신해야하는 경우에는 딜레이가 존재할 수 있다.

    따라서 새로운 데이터를 불러오는 동안에는 로딩중이라는 표시를 하기로 했다.

    언리얼 엔진의 위젯에 원형 쓰로버라는 위젯을 사용했다.

     

    사용자 퍼즐 엔트리 위젯

    리스트 뷰에는 엔트리 위젯을 하나만 지정할 수 있으므로 엔트리 위젯을 이용해서 원형 쓰로버를 넣기로 했다.

    위의 이미지처럼 원형 쓰로버와 사용자 퍼즐 위젯이 같이 존재한다.

    그리고 서버에서 받은 퍼즐 목록을 리스트 뷰에 넣은 다음에 로딩용으로 사용할 아이템을 새로 생성해서 마지막에 넣어주는 방식이다.

    아이템의 변수에 bIsLoadingItem이라는 변수를 만들어서 참인 경우 원형 쓰로버만 보이게 했다.

     

    사용자 퍼즐 아이템의 변수
    로딩 아이템인 경우 퍼즐 정보를 숨긴다

     

    만약 새로운 퍼즐 목록을 서버에서 받으면 로딩 아이템을 지우고 퍼즐의 목록을 저장하고 있는 새로운 아이템들을 넣는다.

     

    서버에서 새로운 퍼즐 목록을 받은 경우 작동하는 노드

     

    이 로딩 아이템은 서버에서 퍼즐 목록을 더 가져올 수 있을 때에만 추가한다.

    만약 서버에 요청한 퍼즐의 개수보다 받은 퍼즐의 개수가 적다면 더이상 불러올 퍼즐이 없다는 뜻이라고 판단한다.

    모든 경우에 제대로 작동하는 방법은 아니지만 대부분의 경우에는 의도대로 작동할 것이고, 의도대로 작동하지 않아도 치명적이지 않고, 서버와 통신이 되고나면 로딩 아이템이 사라지므로 일단 이렇게 진행했다.

     

     

     


     

     

     

    다음은 서버에서 사용자 제작 퍼즐의 목록을 줘야한다.

     

    퍼즐의 목록은 가장 최근에 등록된 퍼즐이 먼저 나타나게 한다.

    그리고 무한 스크롤이면 이전에 전달해줬던 퍼즐 중에서 마지막 퍼즐의 다음 퍼즐부터 전달해줘야 한다.

    그렇게 하지 않으면 사용자가 목록을 불러오는 중에 다른 사용자가 퍼즐을 업로드하게되면 목록이 밀려서 이전에 가져왔던 퍼즐을 다시 가져올 수 있다.

    따라서 서버에 요청할 때에는 필요한 퍼즐의 개수와 마지막 퍼즐의 id를 전달해줘서 마지막 퍼즐의 다음부터 요청한 개수만큼의 퍼즐 목록을 전달해주기로 했다.

     

    # urls.py
    
    app_name = "userpuzzle"
    urlpatterns = [
        path("", views.upload_request, name="userpuzzle"),
        path("list/<int:num>/", views.get_list, name="get_list"),
        path("list/<int:num>/<int:last_id>/", views.get_list, name="get_list"),
    ]
    
    
    
    # views.py
    
    def get_list(request, num: int = 10, last_id: int = None):
        puzzle_list = []
        puzzle_dict = {}
    
        if last_id == None:
            # 마지막으로 받았던 퍼즐의 id가 주어지지 않은 경우. 가장 최근의 퍼즐부터 리턴.
            puzzles = UserPuzzle.objects.all().order_by('-upload_date')[:num]
            puzzle_list = list(puzzles.values())
        else:
            # 마지막으로 받았던 퍼즐의 id가 주어진 경우. 해당 id의 퍼즐을 찾아서 그 이후의 퍼즐부터 리턴.
            try:
                last_puzzle = UserPuzzle.objects.get(id=last_id)
            except UserPuzzle.DoesNotExist:
                # 해당 id의 퍼즐을 찾지 못하면 비어있는 딕셔너리를 리턴
                return JsonResponse({}, status=200)
            
            puzzles_after_last_id = UserPuzzle.objects.filter(upload_date__lt=last_puzzle.upload_date).order_by("-upload_date")[:num]
            puzzle_list = list(puzzles_after_last_id.values())
    
        
        # 언리얼 엔진에서 사용할 수 있게 값 변환
        for i in range(len(puzzle_list)):
            puzzle_dict[i] = puzzle_list[i]
            if 'puzzle_image' in puzzle_dict[i]:
                puzzle_dict[i]['puzzle_image'] = os.path.join(settings.MEDIA_URL, puzzle_dict[i]['puzzle_image'])
            if 'upload_date' in puzzle_dict[i]:
                puzzle_dict[i]['upload_date'] = puzzle_dict[i]['upload_date'].strftime('%Y-%m-%d')
        
        return JsonResponse(puzzle_dict)

    필요한 정보는 URL을 통해 전달하는 방법을 썼다.

    만약 last_id를 지정해줬다면 그 id에 해당하는 퍼즐 다음의 퍼즐들을 날짜순으로 정렬해서 필요한 개수만큼 전달해준다.

    last_id를 지정해주지 않는 경우는 제일 처음에 목록을 요청하는 경우이므로, 가장 최근의 데이터브터 전달해준다.

     

    결과는 다음과 같다.

    사용자 퍼즐은 No2g의 방식을 따라해서 퍼즐의 실루엣을 미리 보여준다.

     

    원형 쓰로버 테스트를 위해 새로운 정보를 서버에서 불러올때 딜레이를 1초씩 넣어서 테스트했다.

     

     

     

     

     

Designed by Tistory.