반응형

 

 

※ 명령패턴 설명

 

명령 패턴 (Command pattern)

1. 명령패턴 (Command pattern) 명령(이동, 점프 등)에 대한 함수를 클래스로 래핑하여 해당 클래스를 객체화 한 것이다. 사용자(캐릭터)를 명령에 대한 매개변수로 만들어 요청을 대기시키거나 로깅

srdeveloper.tistory.com


 

 

 

 

1. 실행 취소 기능

명령 패턴으로 실행 단위를 객체로 만들어두면 손쉽게 실행취소 기능을 구현할 수 있다.

위와 같이 Command 객체에 대한 List를 만들고 추가 명령이 생성되면 List의 맨 끝에 추가한 뒤, 현재 명령으로 기억하면 된다. Undo의 기능만 넣게되면 현재명령은 지워주고 현재 명령을 이전 명령으로 위치를 잡아주면 되고, Redo 기능을 같이 추가하게되면 실행 취소 전의 명령은 그대로 살려주면 될것이다.

 

 

 

2. 예시 코드

2-1) Command(추상 클래스)

public abstract class Command
{
    protected int direction;
    protected Actor actor;

    public abstract void Execute(Actor actor);
    public abstract void ExecuteAxis(Actor actor);
    public abstract void Undo();
}

 

2-2) VerticalCommand(Command 상속)

using UnityEngine;

public class VerticalCommand : Command
{
    public VerticalCommand(Actor actor, int direction)
    {
        this.actor = actor;
        this.direction = direction;
    }
    public override void Execute(Actor actor)
    {
        
    }

    public override void ExecuteAxis(Actor actor)
    {
        actor.MoveTo(new Vector2(0, direction));
    }

    public override void Undo()
    {
        actor.MoveTo(new Vector2(0, -direction));
    }
}

 

2-3) HorizontalCommand(Command 상속)

using UnityEngine;

public class HorizontalCommand : Command
{
    public HorizontalCommand(Actor actor, int direction)
    {
        this.actor = actor;
        this.direction = direction;
    }
    public override void Execute(Actor actor)
    {
        
    }

    public override void ExecuteAxis(Actor actor)
    {
        actor.MoveTo(new Vector2(direction, 0));
    }

    public override void Undo()
    {
        actor.MoveTo(new Vector2(-direction, 0));
    }
}

 

2-4) Actor

using UnityEngine;

public class Actor : MonoBehaviour
{
    private Rigidbody2D rb;

    private void Start()
    {
        rb = GetComponent<Rigidbody2D>();
    }

    public void MoveTo(Vector2 pos)
    {
        rb.MovePosition(rb.position + pos);
    }
}

 

2-5) InputHandler

using System.Collections.Generic;
using UnityEngine;

public class InputHandler : MonoBehaviour
{
    [SerializeField]
    private Actor targetActor;

    private List<Command> commands = new List<Command>();
    private int executionIndex = -1;

    private void Update()
    {
        Command targetCommand = HandleInput();
        if(targetCommand != null && targetActor != null)
        {
            commands.Add(targetCommand);
            executionIndex++;

            targetCommand.ExecuteAxis(targetActor);
        }
    }

    private Command HandleInput()
    {
        if (Input.GetKeyDown(KeyCode.W))
        {
            return new VerticalCommand(targetActor, 1);
        }
        else if (Input.GetKeyDown(KeyCode.S))
        {
            return new VerticalCommand(targetActor, - 1);
        }
        else if (Input.GetKeyDown(KeyCode.A))
        {
            return new HorizontalCommand(targetActor, - 1);
        }
        else if (Input.GetKeyDown(KeyCode.D))
        {
            return new HorizontalCommand(targetActor,1);
        }
        else if(executionIndex >= 0 && Input.GetKey(KeyCode.LeftControl) && Input.GetKeyDown(KeyCode.X))
        {
            commands[executionIndex].Undo();
            commands.RemoveAt(executionIndex);
            executionIndex--;
        }

        return null;
    }
}

 

※ 위 코드는 Undo의 기능만 구현해서 짠 코드이다.

※ 생성자에 Actor를 인자로 받는 이유는 플레이어만이 아닌 다른 캐릭터에 대한 실행취소도 포함하기 위함이다.

※ 앞선 커맨트 패턴의 게시글의 코드를 재활용해서 짠 코드이므로 Actor에 붙어있는 Rigidbody의 GravityScale의 값을 0으로 설정해야 정상적으로 작동 된다.

 

 

 

3. 실행 확인

 

반응형

'Stack > DesignPattern' 카테고리의 다른 글

명령 패턴 (Command pattern)  (0) 2024.02.23
반응형

 

 

 

 

1. 명령패턴 (Command pattern)

명령(이동, 점프 등)에 대한 함수를 클래스로 래핑하여 해당 클래스를 객체화 한 것이다.

사용자(캐릭터)를 명령에 대한 매개변수로 만들어 요청을 대기시키거나 로깅하여 되돌리기 기능의 연산을 지원할 수도 있다.

※ 일반적으로 실행 취소 기능을 구현하려면 힘들지만 명령 패턴을 사용하면 쉬워진다.

 

 

 

2. 명령패턴 예시 구조 및 코드

InputHandler 클래스에서 틱마다 키 입력을 받을 수 있는 로직을 구성하고, 점프키에 해당하는 조건문이 통과하게 되면 JumpCommand 객체를 반환하고 반환된 Command 객체에 Actor 객체를 매개변수를 넘겨서 Command의 Execute 함수에서 Actor의 Jump함수를 호출해주는 구조이다.

 

2-1) Command(추상 클래스)

public abstract class Command
{
    public abstract void Execute(Actor actor);
}

 

2-2) JumpCommand(Command 상속)

public class JumpCommand : Command
{
    public override void Execute(Actor actor)
    {
        actor.Jump();
    }
}

 

2-3) Actor

using UnityEngine;

public class Actor : MonoBehaviour
{
    private Rigidbody2D rb;

    private void Start()
    {
        rb = GetComponent<Rigidbody2D>();
    }

    public void Jump()
    {
        rb.AddForce(Vector2.up * 5f, ForceMode2D.Impulse);
    }
}

 

2-4) InputHandler

using UnityEngine;

public class InputHandler : MonoBehaviour
{
    [SerializeField]
    private Actor targetActor;

    private Command jumpCommand = new JumpCommand();

    private void Update()
    {
        Command targetCommand = HandleInput();
        if(targetCommand != null && targetActor != null)
        {
            targetCommand.Execute(targetActor);
        }
    }

    private Command HandleInput()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            return jumpCommand;
        }

        return null;
    }
}

 

각 명령(Command)에 대한 객체를 생성함으로써 직접적으로 함수를 호출하는 방식 대신, 한 겹 우회하는 계층을 만들어 입력 키의 교체가 가능하게 되었고, 각 명령 객체의 Execute에 Actor를 인수로 받게 함으로써 플레이어 캐릭터만이 아닌 여러 NPC, 몬스터 등의 캐릭터도 조작이 가능할 수 있도록 구성되어 있다.

※ 키 변경의 경우, 멤버 변수의 객체를 교환함으로써 키 변경이 가능하다는걸 알려주는건 이해하는데, 이게 과연 효율적인가 싶은 의문이 든다. 차라리 키 변경은 Command 객체를 교체하는것이 아닌 키를 변수로 둬서 키 값을 교체하는게 더 효율적일거같은 생각이 든다.

 

 

 

 

 

반응형

'Stack > DesignPattern' 카테고리의 다른 글

명령패턴 실행취소 기능  (0) 2024.02.27
반응형

 

※ 해당 포스팅은 GitBash를 통해 하는 방법이므로 PC에 GitBash가 설치되어있지 않으면 설치하고 진행할 것.

 

1. 깃허브 Repository 생성

1-1) 깃허브에 들어가서 Repositoriy를 만들어 준다.

※ Repository 정보 입력시 Initialize this repository with 부분은 따로 건드리지 않고 Create repository 버튼을 눌러 생성해야한다.

※ 만약 Initialize this repository with에 정보를 입력하게 되면 생성과 동시에 origin/main 브랜치로 커밋이 올라가기 때문에 추가적인 처리를 해줘야 한다. (혹시나 정보를 입력해 origin/main 브랜치가 생성된 경우 해결 방법은 하단에 기술해놓음)

 

 

 

 

2. Local Repository를 깃허브에 푸시

2-1) Local repository에 만들어 놓은 폴더에 들어가 우클릭Open git bash here을 눌러 cmd창을 열어준다.

2-2) cmd창이 열리면git remote add origin https://github.com/계정이름/레포지토리이름.git을 입력한다.

입력한 뒤, GUI툴에 확인해보면 다음과 같이 원격이 활성화된걸 볼 수 있다.

 

2-3) 여기서부턴 GUI툴을 통해서 원격에 Push를 해줘도 되는거같은데, GUI툴을 사용하지 않고 CLI로 git push -u origin 브랜치명을 입력하면 Local repository를 원격에 Push하는걸 볼 수 있다.

※ 필자의 경우 Local repository를 생성하면 기본 브랜치 명이 master로 되어있어서 위와 같이 입력함

 

 

 

 

3. 깃허브 Repository에서 확인해보기

3-1) 정상적으로 처리가 됐다면 다음과 같이 원격 저장소에서 확인이 가능하다.

 

 

 

 

 

번외) Initialize this repository with에 정보를 입력하여 원격 저장소에 브랜치가 생성되어버린 경우

- 이 경우에도 위 같은 방법으로 Local repository 브랜치를 Push할 수는 있지만 원격에서는 2개의 브랜치가 존재하게 된다.

- 이렇게 될 경우, 어차피 사용하는 브랜치는 현재 Local에서 사용하고 있는 브랜치이므로 원격에 생성된 브랜치를 삭제해줘야 한다.

 

1) 깃허브 Repository에 들어가서 Setting에 들어간다

 

2) General페이지에서 Default branch를 Local repository에서 push한 브랜치로 변경해준다.

※ 위 과정을 거치지 않고 Default branch로 설정된 브랜치를 삭제하려고하면 에러 발생

 

3) gitbash 창에서 git push origin --delete 삭제할 브랜치명을 입력해주면 삭제가 가능하다.

 

 

 

 

 

반응형
반응형

 

 

 

 

언리얼에서 위젯을 생성할 때, 마우스나 키보드에 대한 인풋에 대한 처리를 할때 UserWidget에서 각 입력장치마다 인풋 이벤트 함수가 존재하며 오버라이딩이 가능하다.

해당 이벤트들을 생성하면 ReturnNode에 ReturnValue를 반환할 수 있는데, 여기에는 Handled와 Unhandled가 존재한다.

 

 

 

1. (Un)handled

- Handled : 해당 위젯에 인풋 이벤트를 마지막으로 받고 뒤의 위젯에 입력 이벤트를 넘기지 않는다.

- Unhandled : 뒤의 위젯에 인풋 이벤트를 넘긴다.

 

 

 

2. 실험을 위한 위젯 구조

- Main위젯에 하이어라키 구조로 위젯을 추가한 형태와 Add to viewport로 z order를 나눈 두가지 형태로 실험을 해보았다.

- Main 위젯에는 OnMouseButtonDown 이벤트 함수와 LeftMouseButton 인풋 이벤트 노드 2가지를 두었고, Overlay 위젯은 OnMouseButtonDown 이벤트 함수만 사용했다.

 

2-1) Main Widget

 

2-2) Overlay Widget

 

 

 

 

3. Handled 유효 범위

3-1) 하이어라키 구조일때의 (Un)handled 동작 차이

3-1-1) Overlay Widget에 ReturnValue를 Handled로 두면 OverlayWidget의 이벤트함수만 호출이 된다. (MainWidget의 이벤트 함수와 인풋 이벤트 노드 호출 안됨)

3-1-2) OverlayWidget의 ReturnValue를 Unhandled로 두면 MainWidget의 이벤트 함수도 호출된다.

※ MainWidget의 이벤트 함수도 Handled로 두면 인풋 이벤트는 호출되지 않는다.

 

 

3-2) Add to viewport로 배치했을때의 (Un)handled 동작 차이

3-2-1) OverlayWidget에 ReturnValue를 Handled로 두면 OverlayWidget의 이벤트함수만 호출된다.

3-2-2) OverlayWidget의 ReturnValue를 Unhandled로 두면 MainWidget의 인풋 이벤트 노드만 호출된다.

※ 추측으로는 OverlayWidget이 MainWidget을 덮고있어서 이벤트 함수가 호출이 되지 않을거라 생각중이다.

 

 

 

 

 

반응형
반응형

 

 

 

 

언리얼에서 웹브라우저를 적용했는데 영어만 입력되는 현상이 있었다. (한영 전환 키가 먹히지 않음)

그래서 상태표시줄을 확인해보니 IME(Input Method Editor)를 사용하지 않는다는것을 확인할 수 있었다.

 

찾아보니 블루프린트에서 해결할 수 있는 방법은 없고  C++에서 Input Method를 적용해줘야 해결이 되는 문제였다.

 

 

해결방법

1. 웹브라우저 위젯을 상속받는 클래스 생성

- 설명을 위한 클래스이니 클래스명은 "MyWebBrowser"로 설정했다.

 

2. 생성된 CPP파일에 Input Method 작성

2-1) 헤더

#pragma once

#include "CoreMinimal.h"
#include "WebBrowser.h"
#include "MyWebBrowser.generated.h"

UCLASS()
class WEBBROWSERTEST_API UMyWebBrowser : public UWebBrowser
{
	GENERATED_BODY()

public:
	UFUNCTION(BlueprintCallable, Category = "WebBrowser")
	void EnableIME();
};

 

2-2) CPP

#include "MyWebBrowser.h"
#include "SWebBrowser.h"

void UMyWebBrowser::EnableIME()
{
    if (WebBrowserWidget.IsValid())
    {
        ITextInputMethodSystem* const TextInputMethodSystem = FSlateApplication::Get().GetTextInputMethodSystem();
        WebBrowserWidget->BindInputMethodSystem(TextInputMethodSystem);
    }
}

※ WebBrowserWidget은 SWebBrowser 클래스의 변수인데, UWebBrowser를 상속해서 클래스를 생성했으면 해당 클래스에 선언되어있는 멤버 변수이므로 따로 선언해줄 필요가 없다.

 

※ 참조할수 없는 외부형식 에러가 발생하면 종속성 모듈 추가를 해준다. (project.Build.cs)

PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "WebBrowser", "UMG" });
PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" });

 

 

3. 웹브라우저를 생성할 때, 작성한 'EnableIME' 함수를 호출해준다.

※ BlueprintCallable을 지정했으니 블루프린트해서 호출해도 되고, CPP에서 호출해도 된다.

 

 

 

결과

 

 

 

 

 

 

반응형

+ Recent posts