NGMsoftware

NGMsoftware
로그인 회원가입
  • 매뉴얼
  • 팁 앤 테크
  • 매뉴얼

    팁과 테크니컬 노하우를 확인하세요.

    팁 앤 테크

    팁과 테크니컬 노하우를 확인하세요.

    본 사이트의 컨텐츠는 저작권법의 보호를 받으므로 무단 복사, 게재, 배포 등을 금합니다.

    커스텀 누구나 할 수 있는 이미지 체크, 이미지 서치, 이미지 매치 매크로 만들기. (Create image check, image …

    페이지 정보

    본문

    안녕하세요. 엔지엠소프트웨어입니다. 누구나 쉽게 이미지 체크, 이미지 서치, 이미지 매치를 이용한 매크로 프로그램을 만들 수 있도록 최대한 쉽고 간단하게 설명하도록 하겠습니다. 우선, 엔지엠 에디터 엔터프라이즈 라이센스가 있는 상태에서 커스텀 모듈을 개발할 수 있는 환경이 갖춰져 있어야 합니다. Visual Studio Community가 설치되어 있어야 하고, 관리자에게 요청해서 커스텀 모듈 개발 킷을 받으셔야 합니다. 또는 [ 여기 ]에서 커스텀 모듈을 다운로드 받아서 사용하셔도 됩니다. 자~ 이제 커스텀 모듈을 개발하기 위해 Visual Studio Community를 실행하세요. 새 솔루션 또는 프로젝트를 추가하세요. 이 프로젝트는 닷넷 프레임워크의 라이브러리입니다.

    Ticc2q0.png

     

     

    프로젝트 이름은 사용자가 임의로 만들어도 됩니다. 저는 직관적인 이름인 "CustomImageConditionModule"로 지었습니다.  위치는 원하는 곳을 지정하고, 프레임워크는 4.6.1 이상으로 선택하세요. 아래 그림에서는 "ChartToInt"로 되어 있지만, 무시하고 "CustomImageConditionModule"이라고 프로젝트 이름에 입력하세요.

    guWkbdr.png

     

     

    아래 그림처럼 새로운 프로젝트가 생성 되었을겁니다. 여러분들은 아마도~ 아래 그림과는 다를겁니다. 제 경우에는 NGM의 제품 및 기타 개발중인 프로그램들이 모두 표시되어 있습니다. 이제 커스텀 모듈 개발 킷을 프로젝트의 참조에 추가 시켜야 합니다. 아래 동영상을 참고해서 참조에 추가하세요. 참고로, 바탕화면에 커스텀 모듈 개발 킷(3개)을 복사해두고 참조에 추가 했습니다.

    zJQjyBh.gif

     

     

    혹시라도 모듈명과 클래스명을 네이밍룰에 맞게 작성하지 않았다면 아래 동영상을 참고해서 이름을 변경해줘야 합니다. 커스텀 모듈을 개발하려면 모듈명은 반드시 "Custom"으로 시작해야 합니다. 그리고, 클래스명은 "Model"로 끝나야 합니다. 클래스명은 "MultiImageCheckModel"로 변경하세요. 아래 동영상과는 다르지만, 이름을 변경하는 방법만 참고하시면 됩니다.

    5MJm6tk.gif

     

     

    위에서도 언급했듯이 커스텀 모듈은 2가지 네이밍룰이 존재합니다. 프로젝트명은 Custom으로 시작해야 하고, 엔지엠 에디터에 액션으로 표시되는 클래스는 이름이 Model로 끝나야 합니다. 이는, 엔지엠 에디터가 수용할 수 있는 커스텀 모듈인지 체크하는 최소한의 안전 장치입니다. 이외에도 몇가지 더 존재하지만, 개발하는데 중요한 내용은 아니므로 자세한 설명은 건너뛰도록 하겠습니다. 프로젝트를 선택하고, 우클릭 후 속성을 클릭하세요.

    HuvzWxt.png

     

     

    어셈블리 이름을 "CustomImageConditionModule"로 변경 해주세요. 관리하기 위한 프로젝트 이름은 변경했지만, 실제 프로젝트가 컴파일되어 만들어지는 라이브러리 이름은 그대로입니다. 프로젝트명과 동일하게 Custom을 붙여주세요. 참고로, 프로젝트를 처음 새성할 때 이름을 제대로 만들었다면~ 이 부분은 안해도 되는 작업입니다.

    KE0OhvL.png

     

     

    이제 본격적으로 코딩을 해봅시다. 코딩은 MultiImageCheckModel.cs 클래스 파일에 작성합니다. 클래스를 더블 클릭하세요. 요즘~ 초딩 코딩 교육이 열풍이죠? 엔지엠 에디터와 비슷한 엔트리나 스크레치를 코딩 교육에 사용합니다. 우리 아이들도~ 엔트리로 코딩 교육을 하고 있습니다. 언젠가는 엔지엠 에디터도 사용하겠죠? 이처럼 블록을 쌓아서 코딩하는 방식이 가장 직관적이고 쉽다는걸 알 수 있습니다. 하지만, 커스텀 모듈은 개발자가 아니라면 혼자서 개발하기가 쉽지 않죠. 그래서~ Step by Step으로 작성된 메뉴얼을 보고, 직접 학습해야 합니다.

    sQ7LA7s.png

     

     

    도구 상자에 표시되는 커스텀 액션을 만들기 위해서는 아래 그림과 같이 총 4개의 추상 클래스를 상속 받아야 합니다. BaseCustom은으로 시작하는 클래스는 커스텀 모듈을 개발하기 위한 추상 클래스를 말합니다. 뒤에 Condition이 붙으면 조건에 사용할 수 있는 액션이라는 뜻입니다. 마지막으로 Function과 Tool은 사용자 도구 상자에 표시할지 사용자 함수 상자에 표시할지 결정합니다. 관리 편의성을 위한 일종의 카테고리라고 생각하시면 됩니다. 아래 그림과 같이 첫번째 BaseCustomConditionFunctionModel을 상속 받으면 조건을 처리할 수 있는 커스텀 함수 액션을 만든다는 뜻입니다.

    M3H0Qg5.png

     

     

    아래는 추상 클래스를 상속 받은 후 구현한 코드입니다.

    using NGM.Interface;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace CustomImageConditionModule
    {
        [Serializable]
        public class MultiImageCheckModel : NGM.Models.Interface.BaseCustomConditionFunctionModel
        {
            public override string DisplayCategory => throw new NotImplementedException();
    
            public override string DisplayName => throw new NotImplementedException();
    
            public override bool? Execute(IMainView mainView, IPlayer player)
            {
                throw new NotImplementedException();
            }
        }
    }

     

    코딩은 아래 동영상을 참고하세요. 먼저 엔지엠 에디터에서 사용하는 모든 액션은 클래스에 Serializable(시리얼라이저블: 직렬화 가능) 특성을 부여해야 합니다. 직렬화와 역직렬화에 대해 궁금하신 분은 [ 여기 ]에서 자세한 설명을 볼 수 있습니다. 이 특성이 누락되면, 액션이 자신의 상태를 유지할 수 없게됩니다. 그리고, 접근 제한자(Access Modifier: 액세스 모디파이어) 또는 접근 한정자라고 부르는 public을 클래스명 앞에 붙여줘야 합니다. public(퍼블릭: 공공의)을 붙여주면 의미와 같이 누구나 이 클래스를 사용할 수 있게됩니다. 접근 제한자는 public, private, internal, protected, internal protected와 같이 5가지가 존재합니다. 개발자 교육이 아니라서 간단하게 이런것들이 있구나 정도로만 알고 넘어가시면 됩니다.

    8Qtj5Zw.gif

     

     

    아래와 같이 속성을 처리 해줍니다. public new string TrueID 속성은 특이하게도 new 키워드가 붙어 있는걸 알 수 있습니다. 이는 추상 클래스에서 정의한 메소드 또는 속성과 같은 이름으로 정의할 수 있도록 해줍니다. 기본적으로 하나의 조건만을 처리할 수 있기 때문에 멀티 조건에서는 불필요한 속성이므로 [Browsable(false)] 특성을 부여해서 사용자가 핸들링하지 못하도록 한겁니다. 개발자가 아니라면 이해하기 어려운 개념일거에요. 사실 추상화라던가 일반화라는 개념 자체가 어렵거든요^^;

    using System;
    using System.ComponentModel;
    using System.Drawing;
    using System.IO;
    
    namespace CustomFunctionConditionModule
    {
        [Serializable]
        public class MultiImageCheckModel : NGM.Models.Interface.BaseCustomConditionFunctionModel
        {
            [Browsable(false)]
            public new string TrueID { get; set; }
    
            [Browsable(false)]
            public new string FalseID { get; set; }
    
            private string _imageName1;
            private string _imageName2;
            private string _imageName3;
            private string _imageName4;
            private string _imageName5;
    
            /// <summary>
            /// 도구 상자에 표시할 카테고리 이름입니다.
            /// </summary>
            public override string DisplayCategory { get { return "NGM 커스텀 컨디션 도구"; } }
    
            /// <summary>
            /// 도구 상자에 표시할 액션 이름입니다.
            /// </summary>
            public override string DisplayName { get { return "멀티 이미지 체크"; } }

     

    커스텀 모듈을 제작하기 위한 여러가지 팁들을 처음부터 읽어보셨다면 이미 익숙한 속성이 2개 보입니다. ①DisplayCategory와 ②DisplayName인데요. 이 둘은 아래 그림과 같이 엔지엠 에디터에 표시해주는 카테고리와 액션의 이름입니다.

    2BGv3EB.png

     

     

    아래 코드와 같이 5개의 이미지 조건을 처리할 속성을 추가 해줍니다. 이미지 파일의 전체 경로를 표시해줄 Data 속성을 만들어줍니다. 엔지엠 에디터에서 Data 특성들은 액션이 실행된 후 만들어지는 결과를 표시하는 속성들입니다. 예를 들어서 어떤 이미지를 찾으면 찾은 이미지의 좌표를 알고 싶을 수 있습니다. 찾은 이미지의 좌표는 실행되기전에는 알 수 없는 값이므로 액션이 실행된 후 Data 속성이 채워집니다. 이외에도 사용자가 선택한 이미지나 위치등등을 보여주기 위한 용도로도 사용됩니다. 그렇기 때문에 사용자가 이 값을 직접 처리할 수 없도록 [ReadOnly(true)] 특성을 달아줍니다. Image1~5까지 속성은 사용자가 선택한 이미지의 전체 경로를 반환하는 속성입니다. 에디터 또는 플레이어의 옵션에 이미지 경로와 선택한 이미지 이름을 합쳐서 전체 경로를 만들어줍니다.

            /// <summary>
            /// 도구 상자에 표시할 액션 이름입니다.
            /// </summary>
            public override string DisplayName { get { return "멀티 이미지 체크"; } }
    
            [Category("Data")]
            [DisplayName("이미지 1 전체 경로")]
            [Description("선택한 이미지 1의 전체 경로입니다.")]
            [Browsable(true)]
            [ReadOnly(true)]
            public string ImageFullName1 { get; set; }
    
            [Category("Data")]
            [DisplayName("이미지 2 전체 경로")]
            [Description("선택한 이미지 2의 전체 경로입니다.")]
            [Browsable(true)]
            [ReadOnly(true)]
            public string ImageFullName2 { get; set; }
    
            [Category("Data")]
            [DisplayName("이미지 3 전체 경로")]
            [Description("선택한 이미지 3의 전체 경로입니다.")]
            [Browsable(true)]
            [ReadOnly(true)]
            public string ImageFullName3 { get; set; }
    
            [Category("Data")]
            [DisplayName("이미지 4 전체 경로")]
            [Description("선택한 이미지 4의 전체 경로입니다.")]
            [Browsable(true)]
            [ReadOnly(true)]
            public string ImageFullName4 { get; set; }
    
            [Category("Data")]
            [DisplayName("이미지 5 전체 경로")]
            [Description("선택한 이미지 5의 전체 경로입니다.")]
            [Browsable(true)]
            [ReadOnly(true)]
            public string ImageFullName5 { get; set; }
    
            [Category("이미지 1")]
            [DisplayName("이미지 1 선택")]
            [Description("체크할 이미지 1을 선택하세요.")]
            [Browsable(true)]
            [DefaultValue(null)]
            [TypeConverter(typeof(NGM.Models.TypeConverter.ImageConverter))]
            public virtual string Image1
            {
                get { return _imageName1; }
                set
                {
                    _imageName1 = value;
                    ImageFullName1 = new NGM.Models.Helper().GetImageFullName(MainView.Text, _imageName1);
                }
            }
    
            [Category("이미지 1")]
            [DisplayName("이미지 1 조건")]
            [Description("이미지 체크 결과 같으면 이동할 아이디를 선택하세요.")]
            [Browsable(true)]
            [DefaultValue(null)]
            [TypeConverter(typeof(NGM.Models.TypeConverter.ActionIDConverter))]
            public string Image1TrueID { get; set; }
    
            [Category("이미지 2")]
            [DisplayName("이미지 2 선택")]
            [Description("체크할 이미지 2를 선택하세요.")]
            [Browsable(true)]
            [DefaultValue(null)]
            [TypeConverter(typeof(NGM.Models.TypeConverter.ImageConverter))]
            public virtual string Image2
            {
                get { return _imageName2; }
                set
                {
                    _imageName2 = value;
                    ImageFullName2 = new NGM.Models.Helper().GetImageFullName(MainView.Text, _imageName2);
                }
            }
    
            [Category("이미지 2")]
            [DisplayName("이미지 2 조건")]
            [Description("이미지 체크 결과 같으면 이동할 아이디를 선택하세요.")]
            [Browsable(true)]
            [DefaultValue(null)]
            [TypeConverter(typeof(NGM.Models.TypeConverter.ActionIDConverter))]
            public string Image2TrueID { get; set; }
    
            [Category("이미지 3")]
            [DisplayName("이미지 3 선택")]
            [Description("체크할 이미지 3을 선택하세요.")]
            [Browsable(true)]
            [DefaultValue(null)]
            [TypeConverter(typeof(NGM.Models.TypeConverter.ImageConverter))]
            public virtual string Image3
            {
                get { return _imageName3; }
                set
                {
                    _imageName3 = value;
                    ImageFullName3 = new NGM.Models.Helper().GetImageFullName(MainView.Text, _imageName3);
                }
            }
    
            [Category("이미지 3")]
            [DisplayName("이미지 3 조건")]
            [Description("이미지 체크 결과 같으면 이동할 아이디를 선택하세요.")]
            [Browsable(true)]
            [DefaultValue(null)]
            [TypeConverter(typeof(NGM.Models.TypeConverter.ActionIDConverter))]
            public string Image3TrueID { get; set; }
    
            [Category("이미지 4")]
            [DisplayName("이미지 4 선택")]
            [Description("체크할 이미지 4을 선택하세요.")]
            [Browsable(true)]
            [DefaultValue(null)]
            [TypeConverter(typeof(NGM.Models.TypeConverter.ImageConverter))]
            public virtual string Image4
            {
                get { return _imageName4; }
                set
                {
                    _imageName4 = value;
                    ImageFullName4 = new NGM.Models.Helper().GetImageFullName(MainView.Text, _imageName4);
                }
            }
    
            [Category("이미지 4")]
            [DisplayName("이미지 4 조건")]
            [Description("이미지 체크 결과 같으면 이동할 아이디를 선택하세요.")]
            [Browsable(true)]
            [DefaultValue(null)]
            [TypeConverter(typeof(NGM.Models.TypeConverter.ActionIDConverter))]
            public string Image4TrueID { get; set; }
    
            [Category("이미지 5")]
            [DisplayName("이미지 5 선택")]
            [Description("체크할 이미지 5를 선택하세요.")]
            [Browsable(true)]
            [DefaultValue(null)]
            [TypeConverter(typeof(NGM.Models.TypeConverter.ImageConverter))]
            public virtual string Image5
            {
                get { return _imageName5; }
                set
                {
                    _imageName5 = value;
                    ImageFullName5 = new NGM.Models.Helper().GetImageFullName(MainView.Text, _imageName5);
                }
            }
    
            [Category("이미지 5")]
            [DisplayName("이미지 5 조건")]
            [Description("이미지 체크 결과 같으면 이동할 아이디를 선택하세요.")]
            [Browsable(true)]
            [DefaultValue(null)]
            [TypeConverter(typeof(NGM.Models.TypeConverter.ActionIDConverter))]
            public string Image5TrueID { get; set; }
    
            [Category("Action")]
            [DisplayName("이미지 전체 실패 조건")]
            [Description("모든 이미지가 같지 않으면 이동할 아이디를 선택하세요.")]
            [Browsable(true)]
            [DefaultValue(null)]
            [TypeConverter(typeof(NGM.Models.TypeConverter.ActionIDConverter))]
            public string ImageAllFalseID { get; set; }

     

    이미지를 비교합니다. 아래 핵심 코드는 주석을 달아두었으므로, 참고해주세요.

            public override bool? Execute(NGM.Interface.IMainView mainView, NGM.Interface.IPlayer player)
            {
                // NGM Framework의 API 서비스를 제공하는 도우미 클래스를 인스턴스화 합니다.
                var apiHelper = new NGM.API.Helper();
    
                // 이미지 모델을 비롯한 데이타 모델을 쉽게 처리하기 위한 모델 도우미 클래스를 인스턴스화 합니다.
                var modelHelper = new NGM.Models.Helper();
    
                // 이미지 비교 결과를 저장할 로컬 변수입니다.
                bool isSame;
    
                // 쉽게 반복할 수 있도록 사용자가 설정한 이미지 경로를 배열로 저장해둡니다.
                string[] imageFullNames = { ImageFullName1, ImageFullName2, ImageFullName3, ImageFullName4, ImageFullName5 };
                string[] trueIDs = { Image1TrueID, Image2TrueID, Image3TrueID, Image4TrueID, Image5TrueID };
    
                int index = 0;
    
                // 등록된 이미지를 반복하면서 비교하는 루틴입니다.
                foreach (string imageFullName in imageFullNames)
                {
                    // 이미지 경로가 없으면 건너뜁니다.
                    if (string.IsNullOrEmpty(imageFullName))
                    {
                        index++;
                        continue;
                    }
    
                    // 전체 이미지 경로를 만들어줍니다.
                    string fullName = Path.Combine((mainView.OptionConfig as NGM.Models.Configuration.OptionConfigModel)
                        .ImageDirectory, imageFullName);
    
                    // 이미지가 존재하지 않으면 아웃풋에 메시지를 출력합니다.
                    if (!File.Exists(fullName))
                    {
                        if (mainView != null && mainView.ShowConditionOutput)
                            player.WriteOutput($"" +
                                $"{mainView.ResxMessage.GetString("FileNotFound")}" +
                                $"{Environment.NewLine}" +
                                $"{mainView.ResxCaption.GetString("FilePath")}: " +
                                $"{imageFullName}");
    
                        index++;
                        continue;
                    }
    
                    // NGM 전용 이미지인 ngi를 역직렬화 합니다.
                    NGM.Models.ImageModel imageModel = modelHelper.GetNgmImage(fullName);
    
                    Image sourceImage = null;
                    Image targetImage = null;
    
                    sourceImage = (Image)imageModel.Image;
    
                    // 윈도우 화면을 캡쳐합니다.
                    if (player.MainHandle == IntPtr.Zero) // 활성 모드
                        targetImage = apiHelper.GetActiveScreenCapture(imageModel.ImageRectangle, player);
                    else // 비활성 모드
                        targetImage = apiHelper.GetInactiveScreenCapture(imageModel.ControlWindowRectangle, imageModel.ImageRectangle, Rectangle.Empty, player);
    
                    // sourceImage는 ngi 이미지이고, targetImage는 윈도우 화면입니다.
                    // targetImage(윈도우 화면)에서 sourceImage(사용자가 만든 이미지)를 찾습니다.
                    isSame = apiHelper.IsCheck(sourceImage, targetImage);
    
                    // sourceImage와 targetImage가 같으면 isSame는 true입니다.
                    if (isSame)
                    {
                        base.TrueID = trueIDs[index];
                        return true;
                    }
    
                    #region 리소스 해제
                    if (sourceImage != null)
                    {
                        sourceImage.Dispose();
                        sourceImage = null;
                    }
    
                    if (targetImage != null)
                    {
                        targetImage.Dispose();
                        targetImage = null;
                    }
    
                    if (imageModel != null)
                    {
                        if (imageModel.Image != null)
                        {
                            imageModel.Image.Dispose();
                            imageModel.Image = null;
                        }
    
                        imageModel = null;
                    }
                    #endregion
    
                    index++;
                }
    
                // isSame이 true가 아니면 모든 이미지 비교가 실패했기 때문에 FalseID를 만듭니다.
                base.FalseID = ImageAllFalseID;
                return false;
            }

     

    이미지 멀티 서치도 구현하기 위해 "MultiImageSearchModel.cs" 클래스를 추가하세요. 멀티 이미지 체크와 대부분의 내용은 동일합니다.

    using System;
    using System.ComponentModel;
    using System.Drawing;
    using System.IO;
    using System.Linq;
    
    namespace CustomFunctionConditionModule
    {
        [Serializable]
        public class MultiImageSearchModel : NGM.Models.Interface.BaseCustomConditionFunctionModel
        {
            [Browsable(false)]
            public new string TrueID { get; set; }
    
            [Browsable(false)]
            public new string FalseID { get; set; }
    
            private string _imageName1;
            private string _imageName2;
            private string _imageName3;
            private string _imageName4;
            private string _imageName5;
    
            /// <summary>
            /// 도구 상자에 표시할 카테고리 이름입니다.
            /// </summary>
            public override string DisplayCategory { get { return "NGM 커스텀 컨디션 도구"; } }
    
            /// <summary>
            /// 도구 상자에 표시할 액션 이름입니다.
            /// </summary>
            public override string DisplayName { get { return "멀티 이미지 서치"; } }
    
            [Category("Data")]
            [DisplayName("이미지 1 전체 경로")]
            [Description("선택한 이미지 1의 전체 경로입니다.")]
            [Browsable(true)]
            [ReadOnly(true)]
            public string ImageFullName1 { get; set; }
    
            [Category("Data")]
            [DisplayName("이미지 2 전체 경로")]
            [Description("선택한 이미지 2의 전체 경로입니다.")]
            [Browsable(true)]
            [ReadOnly(true)]
            public string ImageFullName2 { get; set; }
    
            [Category("Data")]
            [DisplayName("이미지 3 전체 경로")]
            [Description("선택한 이미지 3의 전체 경로입니다.")]
            [Browsable(true)]
            [ReadOnly(true)]
            public string ImageFullName3 { get; set; }
    
            [Category("Data")]
            [DisplayName("이미지 4 전체 경로")]
            [Description("선택한 이미지 4의 전체 경로입니다.")]
            [Browsable(true)]
            [ReadOnly(true)]
            public string ImageFullName4 { get; set; }
    
            [Category("Data")]
            [DisplayName("이미지 5 전체 경로")]
            [Description("선택한 이미지 5의 전체 경로입니다.")]
            [Browsable(true)]
            [ReadOnly(true)]
            public string ImageFullName5 { get; set; }
    
            [Category("서치 결과")]
            [DisplayName("좌표 정보")]
            [Description("찾은 이미지의 좌표 정보를 표시합니다.")]
            [Browsable(true)]
            [ReadOnly(true)]
            public Point SearchPoint { get; set; }
    
            [Category("서치 결과")]
            [DisplayName("이미지 정보")]
            [Description("찾은 이미지의 좌표와 크기 정보를 표시합니다.")]
            [Browsable(true)]
            [ReadOnly(true)]
            public Rectangle SearchRectangle { get; set; }
    
            [Category("이미지 1")]
            [DisplayName("이미지 1 선택")]
            [Description("서치할 이미지 1을 선택하세요.")]
            [Browsable(true)]
            [DefaultValue(null)]
            [TypeConverter(typeof(NGM.Models.TypeConverter.ImageConverter))]
            public virtual string Image1
            {
                get { return _imageName1; }
                set
                {
                    _imageName1 = value;
                    ImageFullName1 = new NGM.Models.Helper().GetImageFullName(MainView.Text, _imageName1);
                }
            }
    
            [Category("이미지 1")]
            [DisplayName("이미지 1 조건")]
            [Description("이미지 서치 결과 같으면 이동할 아이디를 선택하세요.")]
            [Browsable(true)]
            [DefaultValue(null)]
            [TypeConverter(typeof(NGM.Models.TypeConverter.ActionIDConverter))]
            public string Image1TrueID { get; set; }
    
            [Category("이미지 2")]
            [DisplayName("이미지 2 선택")]
            [Description("서치할 이미지 2를 선택하세요.")]
            [Browsable(true)]
            [DefaultValue(null)]
            [TypeConverter(typeof(NGM.Models.TypeConverter.ImageConverter))]
            public virtual string Image2
            {
                get { return _imageName2; }
                set
                {
                    _imageName2 = value;
                    ImageFullName2 = new NGM.Models.Helper().GetImageFullName(MainView.Text, _imageName2);
                }
            }
    
            [Category("이미지 2")]
            [DisplayName("이미지 2 조건")]
            [Description("이미지 서치 결과 같으면 이동할 아이디를 선택하세요.")]
            [Browsable(true)]
            [DefaultValue(null)]
            [TypeConverter(typeof(NGM.Models.TypeConverter.ActionIDConverter))]
            public string Image2TrueID { get; set; }
    
            [Category("이미지 3")]
            [DisplayName("이미지 3 선택")]
            [Description("서치할 이미지 3을 선택하세요.")]
            [Browsable(true)]
            [DefaultValue(null)]
            [TypeConverter(typeof(NGM.Models.TypeConverter.ImageConverter))]
            public virtual string Image3
            {
                get { return _imageName3; }
                set
                {
                    _imageName3 = value;
                    ImageFullName3 = new NGM.Models.Helper().GetImageFullName(MainView.Text, _imageName3);
                }
            }
    
            [Category("이미지 3")]
            [DisplayName("이미지 3 조건")]
            [Description("이미지 서치 결과 같으면 이동할 아이디를 선택하세요.")]
            [Browsable(true)]
            [DefaultValue(null)]
            [TypeConverter(typeof(NGM.Models.TypeConverter.ActionIDConverter))]
            public string Image3TrueID { get; set; }
    
            [Category("이미지 4")]
            [DisplayName("이미지 4 선택")]
            [Description("서치할 이미지 4을 선택하세요.")]
            [Browsable(true)]
            [DefaultValue(null)]
            [TypeConverter(typeof(NGM.Models.TypeConverter.ImageConverter))]
            public virtual string Image4
            {
                get { return _imageName4; }
                set
                {
                    _imageName4 = value;
                    ImageFullName4 = new NGM.Models.Helper().GetImageFullName(MainView.Text, _imageName4);
                }
            }
    
            [Category("이미지 4")]
            [DisplayName("이미지 4 조건")]
            [Description("이미지 서치 결과 같으면 이동할 아이디를 선택하세요.")]
            [Browsable(true)]
            [DefaultValue(null)]
            [TypeConverter(typeof(NGM.Models.TypeConverter.ActionIDConverter))]
            public string Image4TrueID { get; set; }
    
            [Category("이미지 5")]
            [DisplayName("이미지 5 선택")]
            [Description("서치할 이미지 5를 선택하세요.")]
            [Browsable(true)]
            [DefaultValue(null)]
            [TypeConverter(typeof(NGM.Models.TypeConverter.ImageConverter))]
            public virtual string Image5
            {
                get { return _imageName5; }
                set
                {
                    _imageName5 = value;
                    ImageFullName5 = new NGM.Models.Helper().GetImageFullName(MainView.Text, _imageName5);
                }
            }
    
            [Category("이미지 5")]
            [DisplayName("이미지 5 조건")]
            [Description("이미지 서치 결과 같으면 이동할 아이디를 선택하세요.")]
            [Browsable(true)]
            [DefaultValue(null)]
            [TypeConverter(typeof(NGM.Models.TypeConverter.ActionIDConverter))]
            public string Image5TrueID { get; set; }
    
            [Category("Action")]
            [DisplayName("이미지 전체 실패 조건")]
            [Description("모든 이미지가 같지 않으면 이동할 아이디를 선택하세요.")]
            [Browsable(true)]
            [DefaultValue(null)]
            [TypeConverter(typeof(NGM.Models.TypeConverter.ActionIDConverter))]
            public string ImageAllFalseID { get; set; }
    
            public override bool? Execute(NGM.Interface.IMainView mainView, NGM.Interface.IPlayer player)
            {
                var apiHelper = new NGM.API.Helper();
                var modelHelper = new NGM.Models.Helper();
    
                Rectangle rect;
                bool isSame;
                string[] imageFullNames = { ImageFullName1, ImageFullName2, ImageFullName3, ImageFullName4, ImageFullName5 };
                string[] trueIDs = { Image1TrueID, Image2TrueID, Image3TrueID, Image4TrueID, Image5TrueID };
                int index = 0;
    
                foreach (string imageFullName in imageFullNames)
                {
                    if (string.IsNullOrEmpty(imageFullName))
                    {
                        index++;
                        continue;
                    }
    
                    string fullName = Path.Combine((mainView.OptionConfig as NGM.Models.Configuration.OptionConfigModel)
                        .ImageDirectory, imageFullName);
    
                    if (!File.Exists(fullName))
                    {
                        if (mainView != null && mainView.ShowConditionOutput)
                            player.WriteOutput($"{mainView.ResxMessage.GetString("FileNotFound")}{Environment.NewLine}{mainView.ResxCaption.GetString("FilePath")}: {imageFullName}");
    
                        index++;
                        continue;
                    }
    
                    NGM.Models.ImageModel imageModel = modelHelper.GetNgmImage(fullName);
    
                    Image sourceImage = null;
                    Image targetImage = null;
    
                    sourceImage = (Image)imageModel.Image;
    
                    if (player.MainHandle == IntPtr.Zero)
                        targetImage = apiHelper.GetActiveScreenCapture(Rectangle.Empty, player);
                    else
                        targetImage = apiHelper.GetInactiveScreenCapture(Rectangle.Empty, Rectangle.Empty, Rectangle.Empty, player);
    
                    rect = apiHelper.IsSearch(sourceImage, targetImage);
    
                    isSame = !rect.IsEmpty;
    
                    if (isSame)
                    {
                        base.TrueID = trueIDs[index];
                        this.SearchRectangle = rect;
                        this.SearchPoint = new Point(rect.X + rect.Width / 2, rect.Y + rect.Height / 2);
    
                        return true;
                    }
    
                    #region 리소스 해제
                    if (sourceImage != null)
                    {
                        sourceImage.Dispose();
                        sourceImage = null;
                    }
    
                    if (targetImage != null)
                    {
                        targetImage.Dispose();
                        targetImage = null;
                    }
    
                    if (imageModel != null)
                    {
                        if (imageModel.Image != null)
                        {
                            imageModel.Image.Dispose();
                            imageModel.Image = null;
                        }
    
                        imageModel = null;
                    }
                    #endregion
    
                    index++;
                }
    
                base.FalseID = ImageAllFalseID;
                return false;
            }
        }
    }

     

    아래는 이미지 멀치 매치입니다.

    using System;
    using System.ComponentModel;
    using System.Drawing;
    using System.IO;
    using System.Linq;
    using System.Runtime.Serialization.Formatters.Binary;
    
    namespace CustomFunctionConditionModule
    {
        [Serializable]
        public class MultiImageMatchModel : NGM.Models.Interface.BaseCustomConditionFunctionModel
        {
            [Browsable(false)]
            public new string TrueID { get; set; }
    
            [Browsable(false)]
            public new string FalseID { get; set; }
    
            private string _imageName1;
            private string _imageName2;
            private string _imageName3;
            private string _imageName4;
            private string _imageName5;
    
            /// <summary>
            /// 도구 상자에 표시할 카테고리 이름입니다.
            /// </summary>
            public override string DisplayCategory { get { return "NGM 커스텀 컨디션 도구"; } }
    
            /// <summary>
            /// 도구 상자에 표시할 액션 이름입니다.
            /// </summary>
            public override string DisplayName { get { return "멀티 이미지 매치"; } }
    
            [Category("Data")]
            [DisplayName("이미지 1 전체 경로")]
            [Description("선택한 이미지 1의 전체 경로입니다.")]
            [Browsable(true)]
            [ReadOnly(true)]
            public string ImageFullName1 { get; set; }
    
            [Category("Data")]
            [DisplayName("이미지 2 전체 경로")]
            [Description("선택한 이미지 2의 전체 경로입니다.")]
            [Browsable(true)]
            [ReadOnly(true)]
            public string ImageFullName2 { get; set; }
    
            [Category("Data")]
            [DisplayName("이미지 3 전체 경로")]
            [Description("선택한 이미지 3의 전체 경로입니다.")]
            [Browsable(true)]
            [ReadOnly(true)]
            public string ImageFullName3 { get; set; }
    
            [Category("Data")]
            [DisplayName("이미지 4 전체 경로")]
            [Description("선택한 이미지 4의 전체 경로입니다.")]
            [Browsable(true)]
            [ReadOnly(true)]
            public string ImageFullName4 { get; set; }
    
            [Category("Data")]
            [DisplayName("이미지 5 전체 경로")]
            [Description("선택한 이미지 5의 전체 경로입니다.")]
            [Browsable(true)]
            [ReadOnly(true)]
            public string ImageFullName5 { get; set; }
    
            [Category("서치 결과")]
            [DisplayName("좌표 정보")]
            [Description("찾은 이미지의 좌표 정보를 표시합니다.")]
            [Browsable(true)]
            [ReadOnly(true)]
            public Point SearchPoint { get; set; }
    
            [Category("서치 결과")]
            [DisplayName("이미지 정보")]
            [Description("찾은 이미지의 좌표와 크기 정보를 표시합니다.")]
            [Browsable(true)]
            [ReadOnly(true)]
            public Rectangle SearchRectangle { get; set; }
    
            [Category("이미지 1")]
            [DisplayName("이미지 1 선택")]
            [Description("서치할 이미지 1을 선택하세요.")]
            [Browsable(true)]
            [DefaultValue(null)]
            [TypeConverter(typeof(NGM.Models.TypeConverter.ImageConverter))]
            public virtual string Image1
            {
                get { return _imageName1; }
                set
                {
                    _imageName1 = value;
                    ImageFullName1 = new NGM.Models.Helper().GetImageFullName(MainView.Text, _imageName1);
                }
            }
    
            [Category("이미지 1")]
            [DisplayName("이미지 1 조건")]
            [Description("이미지 서치 결과 같으면 이동할 아이디를 선택하세요.")]
            [Browsable(true)]
            [DefaultValue(null)]
            [TypeConverter(typeof(NGM.Models.TypeConverter.ActionIDConverter))]
            public string Image1TrueID { get; set; }
    
            [Category("이미지 2")]
            [DisplayName("이미지 2 선택")]
            [Description("서치할 이미지 2를 선택하세요.")]
            [Browsable(true)]
            [DefaultValue(null)]
            [TypeConverter(typeof(NGM.Models.TypeConverter.ImageConverter))]
            public virtual string Image2
            {
                get { return _imageName2; }
                set
                {
                    _imageName2 = value;
                    ImageFullName2 = new NGM.Models.Helper().GetImageFullName(MainView.Text, _imageName2);
                }
            }
    
            [Category("이미지 2")]
            [DisplayName("이미지 2 조건")]
            [Description("이미지 서치 결과 같으면 이동할 아이디를 선택하세요.")]
            [Browsable(true)]
            [DefaultValue(null)]
            [TypeConverter(typeof(NGM.Models.TypeConverter.ActionIDConverter))]
            public string Image2TrueID { get; set; }
    
            [Category("이미지 3")]
            [DisplayName("이미지 3 선택")]
            [Description("서치할 이미지 3을 선택하세요.")]
            [Browsable(true)]
            [DefaultValue(null)]
            [TypeConverter(typeof(NGM.Models.TypeConverter.ImageConverter))]
            public virtual string Image3
            {
                get { return _imageName3; }
                set
                {
                    _imageName3 = value;
                    ImageFullName3 = new NGM.Models.Helper().GetImageFullName(MainView.Text, _imageName3);
                }
            }
    
            [Category("이미지 3")]
            [DisplayName("이미지 3 조건")]
            [Description("이미지 서치 결과 같으면 이동할 아이디를 선택하세요.")]
            [Browsable(true)]
            [DefaultValue(null)]
            [TypeConverter(typeof(NGM.Models.TypeConverter.ActionIDConverter))]
            public string Image3TrueID { get; set; }
    
            [Category("이미지 4")]
            [DisplayName("이미지 4 선택")]
            [Description("서치할 이미지 4을 선택하세요.")]
            [Browsable(true)]
            [DefaultValue(null)]
            [TypeConverter(typeof(NGM.Models.TypeConverter.ImageConverter))]
            public virtual string Image4
            {
                get { return _imageName4; }
                set
                {
                    _imageName4 = value;
                    ImageFullName4 = new NGM.Models.Helper().GetImageFullName(MainView.Text, _imageName4);
                }
            }
    
            [Category("이미지 4")]
            [DisplayName("이미지 4 조건")]
            [Description("이미지 서치 결과 같으면 이동할 아이디를 선택하세요.")]
            [Browsable(true)]
            [DefaultValue(null)]
            [TypeConverter(typeof(NGM.Models.TypeConverter.ActionIDConverter))]
            public string Image4TrueID { get; set; }
    
            [Category("이미지 5")]
            [DisplayName("이미지 5 선택")]
            [Description("서치할 이미지 5를 선택하세요.")]
            [Browsable(true)]
            [DefaultValue(null)]
            [TypeConverter(typeof(NGM.Models.TypeConverter.ImageConverter))]
            public virtual string Image5
            {
                get { return _imageName5; }
                set
                {
                    _imageName5 = value;
                    ImageFullName5 = new NGM.Models.Helper().GetImageFullName(MainView.Text, _imageName5);
                }
            }
    
            [Category("이미지 5")]
            [DisplayName("이미지 5 조건")]
            [Description("이미지 서치 결과 같으면 이동할 아이디를 선택하세요.")]
            [Browsable(true)]
            [DefaultValue(null)]
            [TypeConverter(typeof(NGM.Models.TypeConverter.ActionIDConverter))]
            public string Image5TrueID { get; set; }
    
            [Category("Action")]
            [DisplayName("이미지 전체 실패 조건")]
            [Description("모든 이미지가 같지 않으면 이동할 아이디를 선택하세요.")]
            [Browsable(true)]
            [DefaultValue(null)]
            [TypeConverter(typeof(NGM.Models.TypeConverter.ActionIDConverter))]
            public string ImageAllFalseID { get; set; }
    
            public override bool? Execute(NGM.Interface.IMainView mainView, NGM.Interface.IPlayer player)
            {
                var apiHelper = new NGM.API.Helper();
                var modelHelper = new NGM.Models.Helper();
    
                Rectangle rect;
                bool isSame;
                string[] imageFullNames = { ImageFullName1, ImageFullName2, ImageFullName3, ImageFullName4, ImageFullName5 };
                string[] trueIDs = { Image1TrueID, Image2TrueID, Image3TrueID, Image4TrueID, Image5TrueID };
                int index = 0;
    
                foreach (string imageFullName in imageFullNames)
                {
                    if (string.IsNullOrEmpty(imageFullName))
                    {
                        index++;
                        continue;
                    }
    
                    string fullName = Path.Combine((mainView.OptionConfig as NGM.Models.Configuration.OptionConfigModel)
                        .ImageDirectory, imageFullName);
    
                    if (!File.Exists(fullName))
                    {
                        if (mainView != null && mainView.ShowConditionOutput)
                            player.WriteOutput($"{mainView.ResxMessage.GetString("FileNotFound")}{Environment.NewLine}{mainView.ResxCaption.GetString("FilePath")}: {imageFullName}");
    
                        index++;
                        continue;
                    }
    
                    NGM.Models.ImageModel imageModel = modelHelper.GetNgmImage(fullName);
    
                    Image sourceImage = null;
                    Image targetImage = null;
    
                    sourceImage = (Image)imageModel.Image;
    
                    if (player.MainHandle == IntPtr.Zero)
                        targetImage = apiHelper.GetActiveScreenCapture(Rectangle.Empty, player);
                    else
                        targetImage = apiHelper.GetInactiveScreenCapture(Rectangle.Empty, Rectangle.Empty, Rectangle.Empty, player);
    
                    rect = apiHelper.IsFind(sourceImage, targetImage);
    
                    isSame = !rect.IsEmpty;
    
                    if (isSame)
                    {
                        base.TrueID = trueIDs[index];
                        this.SearchRectangle = rect;
                        this.SearchPoint = new Point(rect.X + rect.Width / 2, rect.Y + rect.Height / 2);
    
                        return true;
                    }
    
                    #region 리소스 해제
                    if (sourceImage != null)
                    {
                        sourceImage.Dispose();
                        sourceImage = null;
                    }
    
                    if (targetImage != null)
                    {
                        targetImage.Dispose();
                        targetImage = null;
                    }
    
                    if (imageModel != null)
                    {
                        if (imageModel.Image != null)
                        {
                            imageModel.Image.Dispose();
                            imageModel.Image = null;
                        }
    
                        imageModel = null;
                    }
                    #endregion
    
                    index++;
                }
    
                base.FalseID = ImageAllFalseID;
                return false;
            }
        }
    }

     

    이렇게해서 Case by case 커스텀 멀티 이미지 조건 모듈을 만들어봤습니다. 이 커스텀 모듈은 이미지 1부터 5까지 설정할 수 있습니다. 물론, 꼭 5개 모두 설정해야 하는건 아닙니다. 설정된만큼 반복하면서 이미지 조건에 참(True)이 걸리면 설정한 아이디로 점프하게 됩니다. 이미지 1부터 5개가 있고, 각각 이동할 아이디를 설정할 수 있죠. 이 기능을 엔지엠 에디터로 구현할 수 없는건 아니지만, 스크립트가 약간 복잡해지고 관리하기가 어려워지는 단점이 있습니다. 원하는 기능을 커스텀 모듈로 쉽게 만들어서 사용한다면 좀 더 깔끔하고 효율이 좋은 스크립트를 작성할 수 있습니다. 엔지엠 에디터는 확장 기능에 대해 많은 기능을 제공하고 있지만 진입 장벽이 높다는 단점도 가지고 있습니다. 물론, 개발 회사의 경우 내부 개발자가 있기 때문에 메뉴얼만 보고도 쉽게 원하는 기능을 만들 수 있을겁니다. 하지만, 개인이 하기에는 다소 어려운 부분들이 있습니다.

     

    이 커스텀 모듈을 테스트하려면 CustomImageConditionModule 프로젝트를 컴파일하세요. 아래 그림과 같이 프로젝트에서 우클릭 후 "다시 빌드"를 클릭하시면 됩니다.

    srgZm7a.png

     

     

    컴파일이 정상적으로 완료 되었으면, CustomImageConditionModule 프로젝트에서 우클릭 후 "파일 탐색기에서 폴더 열기"를 선택하세요.

    l6uhRx7.png

     

     

    bin/Debug 폴더로 이동하세요. 만약, Release 모드로 컴파일했다면 bin/Release 폴더로 이동해야 합니다. 가급적이면 배포시에는 Release로 컴파일하세요. 아래 그림과 같이 CustomImageConditionModule.dll이 있을겁니다. 이 라이브러리를 복사한 후 바탕화면에 붙여넣기하세요.

    rIbiL87.png

     

     

    엔지엠 에디터를 실행한 후 "관리자" 메뉴의 "도구 모듈 설치" 버튼을 클릭하세요. (아래 그림은 함수 모듈 설치지만, 도구 모듈 설치로 해야 합니다.)

    ySJp52M.png

     

     

    모듈이 정상적이라면 아래와 같이 설치 작업이 성공했다는 메시지를 표시합니다.

    baqVyFP.png

     

     

    사용자 도구 상자에 보면 추가한 모듈의 액션이 추가된 것을 확인할 수 있습니다. 새로운 스크립트를 생성하고, 액션을 추가하세요. 속성은 알아서 넣어줍니다. 각각의 이미지와 이동할 아이디를 설정해주세요. 스크립트는 이동할 아이디가 있는 액션들과 조건을 판단할 이미지들을 설정하세요. 테스트가 잘 이루어지지 않는다면 댓글로 질문을 남겨주시면 답변 드리도록 하겠습니다.

    WjdPJ2p.png

     

     

    개발자에게 후원하기

    MGtdv7r.png

     

    추천, 구독, 홍보 꼭~ 부탁드립니다.

    여러분의 후원이 빠른 귀농을 가능하게 해줍니다~ 답답한 도시를 벗어나 귀농하고 싶은 개발자~

    감사합니다~

    • 네이버 공유하기
    • 페이스북 공유하기
    • 트위터 공유하기
    • 카카오스토리 공유하기
    추천0 비추천0

    댓글목록

    등록된 댓글이 없습니다.