반응형

아이들이 컴퓨터로 영상을 보거나 게임할 때 습관적으로 소리를 너무 크게 해 놓는 경우가 많다. 소리를 줄이라고 말해도 그때 뿐, 다시 크게 해 놓곤 한다. 시스템 볼륨에 제한을 두고 일정 수준 이상으로 올라가면 자동으로 내리는 프로그램(Turn It Down)을 만들어 보자.

 

※ 참고

2021.11.21 - [C#] - C# Media Player Mp3

2022.01.06 - [C#] - C# AudioSwitcher System Audio/Sound Volume Control - 시스템 오디오/사운드 볼륨 컨트롤 1

2023.10.28 - [C#] - C# Sound Meter 사운드 미터

 

위 링크의 내용들을 참고해 코드를 작성한다.

 

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
using System;
using System.Runtime.InteropServices;
using System.Windows.Media;
 
using AudioSwitcher.AudioApi.CoreAudio;
using NAudio.CoreAudioApi;
 
namespace ConsoleApp1
{
    class Program
    {
        [DllImport("kernel32.dll")]
        static extern IntPtr GetConsoleWindow();
 
        [DllImport("user32.dll")]
        static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
 
        // Usage: filename [maxVol(0~100)] [playTime(sec)]
 
        static void Main(string[] args)
        {
            const int SW_HIDE = 0// 창 숨기기
            IntPtr handle = GetConsoleWindow();
            ShowWindow(handle, SW_HIDE);
            
            MediaPlayer MP = new MediaPlayer();
            MP.Open(new Uri("turndown.mp3", UriKind.Relative));
 
            int maxVol;
            if (args.Length > 0)
                maxVol = int.Parse(args[0]);
            else
                maxVol = 20;
 
            int playTime;
            if (MP.NaturalDuration.HasTimeSpan)
                playTime = MP.NaturalDuration.TimeSpan.Milliseconds;
            else if (args.Length > 1)
                playTime = int.Parse(args[1]) * 1000;
            else
                playTime = 6000;
                // 미디어 파일 시간 정보가 없다면 적당한 값을 넣는다.
 
            MMDeviceEnumerator enumerator = new MMDeviceEnumerator();
            MMDevice device = enumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Console);
            // MMDeviceEnumerator를 CoreAudioController 보다 먼저 초기화해야 한다.
 
            CoreAudioDevice defaultPlaybackDevice = new CoreAudioController().DefaultPlaybackDevice;
            
            // defaultPlaybackDevice.Volume: 시스템 볼륨(0~100)
            // device.AudioMeterInformation.MasterPeakValue: 시스템 오디오 값(0~1)
            while (true)
            {
                float masterPeakValue = device.AudioMeterInformation.MasterPeakValue;
 
                if (defaultPlaybackDevice.Volume > maxVol && masterPeakValue > 0.3f)
                    // 볼륨이 maxVol 보다 크고 오디오 값이 0.3보다 크다면..
                {                    
                    MP.Play();
                    System.Threading.Thread.Sleep(playTime); // 메세지 재생 대기
                    MP.Stop();
 
                    defaultPlaybackDevice.Volume = maxVol; // 볼륨 감소
                }
 
                System.Threading.Thread.Sleep(1000); // 1초 마다 확인
            }
        }
    }
}
 

 

 

TurnItDown.zip
1.04MB

위 압축 파일에는 실행에 필요한 모든 파일과 설치 배치파일이 들어 있다.

 

압축을 풀고 install.bat를 실행한다.

 

C드라이브 turnitdown 폴더에 프로그램이 복사된다.

 

Startup 폴더에 run.bat 파일이 복사된다.

 

 

아이들 컴퓨터에 이렇게 세팅해 놓고 재부팅하면 프로그램이 자동으로 시작된다. 물론 어떤 UI도 없기 때문에 아이들은 이 프로그램이 실행되었다는걸 인식할 수 없다. 초기 조건은 시스템 볼륨 20 이상이고 오디오로 출력되는 소리 값이 0.3 이상이면 지정한 MP3 파일이 플레이되고 볼륨이 20으로 줄어든다.

 

ex) 시스템 볼륨을 70으로 해 놓고 유튜브 영상을 보면 볼륨을 줄인다는 MP3 메세지가 플레이되고 볼륨이 20으로 줄어든다.

 

플레이되는 MP3 메세지는 C:\turnitdown 폴더의 turndown.mp3인데, 원하는 메세지를 녹음해서 바꿀 수 있다.

설치시 제공되는 MP3파일에는 내 조카(준석이)를 위한 메세지가 녹음되어 있다.

 

메세지를 바꾸거나 볼륨 제한 조건을 바꾸고 싶다면 run.bat 파일을 수정한다.

예를들어 볼륨 제한을 35로 바꾸고, 메세지가 녹음된 MP3파일이 8초로 바뀌었다면 run.bat 파일에서 위 그림과 같이 20 6으로 된 부분을 35 8로 바꾼다.

 

반응형
Posted by J-sean
:
반응형

2D 네비게이션 기본 사용법

 

2D 네비게이션을 사용하기 위해 세팅한다.

● NavigationRegion2D - Navigation Polygon을 생성하고 NavigationAgent2D가 이동 가능한 공간을 적당히 그린다.

● CharacterBody2D - 스크립트를 생성한다.

   Sprite2D - 스프라이트 사이즈를 적당히 조절한다.

   CollisionShape2D - 스프라이트 사이즈에 맞게 적당히 조절한다.

   NavigationAgent2D - Debug - Enabled - On 체크한다.

 

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
using Godot;
 
public partial class Nav : CharacterBody2D
{
    public float movement_speed = 300.0f;
    public Vector2 movement_target_position = new Vector2(950500);
    public NavigationAgent2D navigation_agent;
 
    public override void _Ready()
    {
        navigation_agent = GetNode<NavigationAgent2D>("NavigationAgent2D");
 
        // These values need to be adjusted for the actor's speed
        // and the navigation layout.
        navigation_agent.PathDesiredDistance = 4.0f;
        navigation_agent.TargetDesiredDistance = 4.0f;
 
        // Make sure to not await during _ready.
        CallDeferred("actor_setup");
        // Calls the method on the object during idle time. Always returns null,
        // not the method's result.
    }
 
    public async void actor_setup()
    {
        // Wait for the first physics frame so the NavigationServer can sync.
        // On the first frame the NavigationServer map has not synchronized region
        // data and any path query will return empty. Await one frame to pause
        // scripts until the NavigationServer had time to sync.
        
        //await ToSignal(GetTree(), "physics_frame");
        await ToSignal(GetTree(), SceneTree.SignalName.PhysicsFrame);        
        // physics_frame
        // Emitted immediately before Node._physics_process is called on every node
        // in the SceneTree.
 
        // Now that the navigation map is no longer empty, set the movement target.
        set_movement_target(movement_target_position);
    }
 
    public void set_movement_target(Vector2 movement_target)
    {
        navigation_agent.TargetPosition = movement_target;
    }
 
    public override void _PhysicsProcess(double delta)
    {        
        if (navigation_agent.IsNavigationFinished())
        {
            return;
        }
 
        //Vector2 current_agent_position = GlobalPosition;
        //Vector2 next_path_position = navigation_agent.GetNextPathPosition();
        //Vector2 new_velocity = next_path_position - current_agent_position;
        Vector2 new_velocity = ToLocal(navigation_agent.GetNextPathPosition());
 
        new_velocity = new_velocity.Normalized();
        new_velocity = new_velocity * movement_speed;
 
        Velocity = new_velocity;
        MoveAndSlide();
    }
}
 

 

 

CharacterBody2D에 추가한 스크립트에 2D 네비게이션 사용 코드를 작성한다.

movement_target_position은 목표 지점을 적당히 지정한다.

 

목표지점까지 캐릭터가 진행하는 경로가 표시되며 이동한다.

 

이번엔 마우스 포인터를 따라가도록 해 보자.

 

Navigation Polygon을 수정한다.

 

 

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
32
33
34
35
36
using Godot;
 
public partial class Nav : CharacterBody2D
{
    public float movement_speed = 200.0f;
    public NavigationAgent2D navigation_agent;
 
    public override void _Ready()
    {
        navigation_agent = GetNode<NavigationAgent2D>("NavigationAgent2D");
 
        navigation_agent.PathDesiredDistance = 4.0f;
        navigation_agent.TargetDesiredDistance = 4.0f;
    }
 
    public override void _Process(double delta)
    {
        navigation_agent.TargetPosition = GetGlobalMousePosition();
    }
 
    public override void _PhysicsProcess(double delta)
    {
        if (navigation_agent.IsNavigationFinished())
        {
            return;
        }
 
        Vector2 new_velocity = ToLocal(navigation_agent.GetNextPathPosition());
        new_velocity = new_velocity.Normalized();
        new_velocity = new_velocity * movement_speed;
 
        Velocity = new_velocity;
 
        MoveAndSlide();
    }
}
 

 

 

마우스 포인터를 따라가는 코드를 작성한다.

 

Navigation Polygon 내에서 마우스를 따라 움직인다.

 

이번엔 타일맵을 이용해 맵을 만들고 길을 찾아보자.

Tile-Sets (64-64).png
0.01MB

 

NavigationRegion2D를 삭제하고 TileMap을 추가해 위와 같이 맵을 만든다.

TileMap에 Physics Layer와 Navigation Layer를 하나씩 추가한다. 각 타일의 설정은 아래 내용을 참고한다.

 

여러 타일 중 두 가지만(벽과 길) 사용해 맵을 만들어 보자. 벽으로 사용할 타일에 Physics(Collision) Polygon을 생성한다.

 

 

길로 사용할 타일에 Navigation Polygon을 생성한다.

 

CharacterBody2D의 설정도 바꿔야 한다.

● Motion Mode - Floating

● Wall Min Slide Angle - 0

● 자식노드의 CollisionShape2D - Shape이 사각형이라면 원으로 바꾼다.

이렇게 변경하지 않으면 캐릭터가 이동 중 모서리에 걸려 움직이지 못하는 경우가 발생한다.

스크립트는 변경할 필요 없다.

 

게임을 실행하면 마우스를 따라 캐릭터가 길을 찾고 이동한다.

 

※ 참고

2D Navigation Overview

Using NavigationAgents

 

반응형
Posted by J-sean
:

[Godot] RayCast2D C# Example

Godot 2023. 9. 27. 17:45 |
반응형

RayCast2D C# 예제.

 

씬을 준비하고 RayCast2D와 스크립트를 추가한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    public override void _Draw()
    {
        DrawLine(GetNode<RayCast2D>("RayCast2D").Position,
            GetNode<RayCast2D>("RayCast2D").Position +
            GetNode<RayCast2D>("RayCast2D").TargetPosition,
            new Color(1000.5f), 5);
        // Ray를 확인할 수 있도록 선으로 표시한다.
    }
 
    public override void _PhysicsProcess(double delta)
    {    
        if (GetNode<RayCast2D>("RayCast2D").IsColliding())
        {
            Node obj = GetNode<RayCast2D>("RayCast2D").GetCollider() as Node;            
            GD.Print(obj.Name);
            // Ray와 충돌한 오브젝트의 이름을 출력한다.
        }
   ...
 

 

 

스크립트를 작성하고 실행하면 Ray와 충돌하는 오브젝트의 이름이 출력된다.

 

※ 참고

RayCast2D

 

반응형
Posted by J-sean
:
반응형

Instantiate로 생성한 씬을 반복 재사용해 보자.

 

스프라이트 하나를 생성하고 씬으로 저장(Player.tscn)한다.

 

res:// 에 스크립트를 생성하고 Autoload에 추가한다.

 

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
32
33
34
35
36
37
38
39
40
41
42
43
44
using Godot;
 
public partial class Control : Node
{
    public PackedScene Scene;
    public Timer timer;
    public Sprite2D Sprite;
 
    public override void _Ready()
    {
        // C# has no preload, so you have to always use ResourceLoader.Load<PackedScene>().
        Scene = ResourceLoader.Load<PackedScene>("res://Player.tscn");
        Sprite = Scene.Instantiate<Sprite2D>();
 
        timer = new Timer();
        timer.Connect("timeout", Callable.From(OnTimeOut));
        timer.WaitTime = 1.0;
        AddChild(timer);
        timer.Start();        
    }
    
    public override void _Process(double delta)
    {        
    }
 
    public void OnTimeOut()
    {
 
        Sprite.Position = new Vector2(GD.RandRange(0, (int)GetViewport().GetVisibleRect().Size.X),
            GD.RandRange(0, (int)GetViewport().GetVisibleRect().Size.Y));
        if (Sprite.GetParent() == null )
        {
            AddChild(Sprite);
            // Adds a child node. Nodes can have any number of children, but every child must have
            // a unique name. Child nodes are automatically deleted when the parent node is deleted,
            // so an entire scene can be removed by deleting its topmost node.
        }
        else
        {
            RemoveChild(Sprite);
            // Removes a child node. The node is NOT deleted and must be deleted manually.
        }
    }
}
 

 

 

RemoveChild()로 제거한 자식 노드는 삭제되지는 않는다. AddChild()로 다시 추가 할 수 있다.

 

게임을 실행하면 씬에 스프라이트가 반복해서 나타났다 사라진다.

 

게임이 실행된 상태에서 Remote 탭을 확인하면 GlobalControl의 자식 노드로 Sprite2D가 반복적으로 추가됐다 삭제 된다.

 

반응형
Posted by J-sean
:
반응형

게임 실행 중 잠시 동안 비동기 대기해 보자.

 

Timer를 Node2D 자식 노드로 등록하고 이름을 NodeTimer로 바꾼다. Wait Time 속성은 3으로 변경한다.

 

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
32
33
using Godot;
 
public partial class node_2d : Node2D
{
    // Called when the node enters the scene tree for the first time.
    public override async void _Ready()
    {
        Timer timer = GetNode<Timer>("NodeTimer");
        timer.Start();
        GD.Print("Timer started.");
 
        await ToSignal(timer, Timer.SignalName.Timeout);
        GD.Print("Node Timer Timeout.");
 
        await ToSignal(timer, Timer.SignalName.Timeout);
        GD.Print("Node Timer Timeout.");
 
        timer.Stop();
 
        await ToSignal(GetTree().CreateTimer(1.0), SceneTreeTimer.SignalName.Timeout);
        GD.Print("Tree Timer Timeout.");
        
        await ToSignal(GetTree().CreateTimer(1.0), SceneTreeTimer.SignalName.Timeout);
        GD.Print("Tree Timer Timeout.");
 
        GD.Print("End");
    }
 
    // Called every frame. 'delta' is the elapsed time since the previous frame.
    public override void _Process(double delta)
    {
    }
}
 

 

 

Node2D 노드에 스크립트를 추가하고 위와 같이 작성한다.

NodeTimer를 시작하고 3초 간격으로 "Node Timer Timeout." 메세지를 두 번 출력한다.

같은 동작을 구현하기 위해 SceneTree의 CreateTimer()를 사용할 수도 있다. 위 예에서는 1초 간격으로 "Tree Timer Timeout."메세지를 두 번 출력한다.

 

Output 창에 결과가 표시된다.

 

※ 참고

Heads up display

C# signals

 

반응형
Posted by J-sean
:

C# Type Class 타입 클래스

C# 2022. 7. 20. 00:12 |
반응형

Type 클래스를 사용해 보자.

 

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
using System;
using System.Reflection;
 
namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            //Type t = typeof(String);
            //Type t = Type.GetType("System.String");
            String str = "";
            Type t = str.GetType();
            // Gets a Type object that represents the specified type.
 
            MethodInfo[] methods = t.GetMethods();
            foreach (MethodInfo method in methods)
            {
                // String 클래스의 Substring 함수는 2개의 오버로딩 함수가 있다.
                if (method.Name == "Substring")
                {
                    Console.WriteLine("- Method: " + method.Name);
 
                    ParameterInfo[] parameters = method.GetParameters();
                    foreach (ParameterInfo parameter in parameters)
                    {
                        Console.WriteLine("Parameter: " + parameter.Name);
                    }
                }
            }
 
            Console.WriteLine();
 
            MethodInfo substr = t.GetMethod("Substring"new Type[] { typeof(int), typeof(int) });
            // Searches for the specified method whose parameters match the specified generic
            // parameter count, argument types and modifiers, using the specified binding constraints.
 
            Object result = substr.Invoke("Hello, World!"new Object[] { 75 });
            // Invokes the method or constructor represented by the current instance, using the
            // specified parameters.
 
            Console.WriteLine("{0} returned \"{1}\".", substr, result);
        }
    }
}
 

 

소스를 빌드하고 실행한다.

 

 

반응형
Posted by J-sean
:
반응형

2022.04.05 - [C#] - C# Joystick(Gamepad) Input Check - 조이스틱(게임패드) 입력 확인 1 의 내용을 참고하여 조금 더 간단히 조이스틱(게임패드) 입력을 확인해 보자.

 

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
using System.Threading;
using System.Diagnostics;
 
using HidSharp;
using HidSharp.Utility;
using HidSharp.Reports;
 
namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            HidSharpDiagnostics.EnableTracing = true;
            HidSharpDiagnostics.PerformStrictChecks = true;
 
            DeviceList list = DeviceList.Local;
            list.Changed += (sender, e) => Console.WriteLine("Device list changed.");
 
            HidDevice[] hidDeviceList = list.GetHidDevices().ToArray();
 
            foreach (HidDevice dev in hidDeviceList)
            {
                if (!dev.ToString().Contains("gamepad"))
                {
                    Console.WriteLine(dev.GetProductName() + " is not a gamepad.");
                    continue;
                }
 
                try
                {
                    ReportDescriptor reportDescriptor = dev.GetReportDescriptor();
 
                    Debug.Assert(dev.GetMaxInputReportLength() == reportDescriptor.MaxInputReportLength);
                    Debug.Assert(dev.GetMaxOutputReportLength() == reportDescriptor.MaxOutputReportLength);
                    Debug.Assert(dev.GetMaxFeatureReportLength() == reportDescriptor.MaxFeatureReportLength);
 
                    foreach (DeviceItem deviceItem in reportDescriptor.DeviceItems)
                    {
                        {
                            HidStream hidStream;
                            if (dev.TryOpen(out hidStream))
                            {
                                Console.WriteLine(dev.GetProductName() + " opened.");
                                hidStream.ReadTimeout = Timeout.Infinite;
 
                                using (hidStream)
                                {
                                    byte[] inputReportBuffer = new byte[dev.GetMaxInputReportLength()];
                                    HidSharp.Reports.Input.HidDeviceInputReceiver inputReceiver =
                                        reportDescriptor.CreateHidDeviceInputReceiver();
                                    HidSharp.Reports.Input.DeviceItemInputParser inputParser =
                                        deviceItem.CreateDeviceItemInputParser();
 
                                    inputReceiver.Start(hidStream);
 
                                    while (true)
                                    {
                                        if (inputReceiver.WaitHandle.WaitOne(1000))
                                        {
                                            if (!inputReceiver.IsRunning) { break; } // Disconnected?
 
                                            Report report;
                                            while (inputReceiver.TryRead(inputReportBuffer, 0out report))
                                            {
                                                if (inputParser.TryParseReport(inputReportBuffer, 0, report) && inputParser.HasChanged)
                                                {
                                                    //inputParser.HasChanged는 버튼을 누르거나 놓는 등 상태가 변경되는 경우를 뜻한다.
 
                                                    //Console.WriteLine(BitConverter.ToString(inputReportBuffer));
 
                                                    if (inputReportBuffer[3== 0x00)
                                                    {
                                                        Console.WriteLine("Left");
                                                    }
                                                    else if (inputReportBuffer[3== 0xFF)
                                                    {
                                                        Console.WriteLine("Right");
                                                    }
                                                    else if (inputReportBuffer[4== 0x00)
                                                    {
                                                        Console.WriteLine("Up");
                                                    }
                                                    else if (inputReportBuffer[4== 0xFF)
                                                    {
                                                        Console.WriteLine("Down");
                                                    }
                                                    else if (inputReportBuffer[5== 0x1F)
                                                    {
                                                        Console.WriteLine("X");
                                                    }
                                                    else if (inputReportBuffer[5== 0x2F)
                                                    {
                                                        Console.WriteLine("A");
                                                    }
                                                    else if (inputReportBuffer[5== 0x4F)
                                                    {
                                                        Console.WriteLine("B");
                                                    }
                                                    else if (inputReportBuffer[5== 0x8F)
                                                    {
                                                        Console.WriteLine("Y");
                                                    }
                                                    else if (inputReportBuffer[6== 0x01)
                                                    {
                                                        Console.WriteLine("L Shoulder");
                                                    }
                                                    else if (inputReportBuffer[6== 0x02)
                                                    {
                                                        Console.WriteLine("R Shoulder");
                                                    }
                                                    else if (inputReportBuffer[6== 0x10)
                                                    {
                                                        Console.WriteLine("Select");
                                                    }
                                                    else if (inputReportBuffer[6== 0x20)
                                                    {
                                                        Console.WriteLine("Start");
                                                    }
                                                }
                                            }
                                        }
 
                                        if (Console.KeyAvailable)
                                        {
                                            ConsoleKeyInfo consoleKeyInfo = Console.ReadKey(true);
 
                                            if (consoleKeyInfo.Key == ConsoleKey.Escape)
                                            {
                                                Console.WriteLine("User stopped.");
                                                break;
                                                //hidStream.Close();
                                            }
                                        }
                                    }
                                }
 
                                Console.WriteLine("Gamepad closed.");
                            }
                            else
                            {
                                Console.WriteLine("Failed to open device.");
                            }
                        }
                    }
                }
                catch (Exception e)
                {
                    Console.WriteLine(e);
                }
            }
        }
    }
}
 

 

소스를 입력하고 빌드한다.

 

 

프로그램을 실행하고 게임패드의 버튼을 누르면 어떤 버튼이 눌리는지 표시된다.

 

반응형
Posted by J-sean
:
반응형

컴퓨터에 연결된 모든 HID(Human Interface Devices)를 확인하고 조이스틱(게임패드)의 입력을 체크해 보자.

 

HidSharp를 설치한다.

 

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
using System.Threading;
using System.Diagnostics;
 
using HidSharp;
using HidSharp.Utility;
using HidSharp.Reports;
 
namespace ConsoleApp1
{
    class Program
    {
        static void WriteDeviceItemInputParserResult(HidSharp.Reports.Input.DeviceItemInputParser parser)
        {
            while (parser.HasChanged)
            {
                int changedIndex = parser.GetNextChangedIndex();
                DataValue previousDataValue = parser.GetPreviousValue(changedIndex);
                DataValue dataValue = parser.GetValue(changedIndex);
 
                Console.WriteLine(string.Format("  {0}: {1} -> {2}", (Usage)dataValue.Usages.FirstOrDefault(),
                    previousDataValue.GetPhysicalValue(), dataValue.GetPhysicalValue()));
            }
        }
 
        static void Main(string[] args)
        {
            HidSharpDiagnostics.EnableTracing = true;
            HidSharpDiagnostics.PerformStrictChecks = true;
 
            DeviceList list = DeviceList.Local;
            list.Changed += (sender, e) => Console.WriteLine("Device list changed.");
 
            Device[] allDeviceList = list.GetAllDevices().ToArray();
            Console.WriteLine("■ All device list:");
            foreach (Device dev in allDeviceList)
            {
                Console.WriteLine("- Name: " + dev.ToString() + "\n" + "  Path: " + dev.DevicePath);
            }
 
            Console.WriteLine("-----------------------------------------------");
 
            Stopwatch stopwatch = Stopwatch.StartNew();
            HidDevice[] hidDeviceList = list.GetHidDevices().ToArray();
 
            Console.WriteLine("■ HID device list (took {0} ms to get {1} devices):",
                              stopwatch.ElapsedMilliseconds, hidDeviceList.Length);
 
            foreach (HidDevice dev in hidDeviceList)
            {
                Console.WriteLine("- Name: " + dev);
                Console.WriteLine("  Path: " + dev.DevicePath);
 
                try
                {
                    ReportDescriptor reportDescriptor = dev.GetReportDescriptor();
 
                    // Lengths should match.
                    Debug.Assert(dev.GetMaxInputReportLength() == reportDescriptor.MaxInputReportLength);
                    Debug.Assert(dev.GetMaxOutputReportLength() == reportDescriptor.MaxOutputReportLength);
                    Debug.Assert(dev.GetMaxFeatureReportLength() == reportDescriptor.MaxFeatureReportLength);
 
                    foreach (DeviceItem deviceItem in reportDescriptor.DeviceItems)
                    {
                        foreach (uint usage in deviceItem.Usages.GetAllValues())
                        {
                            Console.WriteLine(string.Format("  Usage: {0:X4} {1}", usage, (Usage)usage));
                        }
 
                        {
                            Console.WriteLine("Opening device for 20 seconds...");
 
                            HidStream hidStream;
                            if (dev.TryOpen(out hidStream))
                            {
                                Console.WriteLine("Opened device.");
                                hidStream.ReadTimeout = Timeout.Infinite;
 
                                using (hidStream)
                                {
                                    byte[] inputReportBuffer = new byte[dev.GetMaxInputReportLength()];
                                    HidSharp.Reports.Input.HidDeviceInputReceiver inputReceiver = 
                                        reportDescriptor.CreateHidDeviceInputReceiver();
                                    HidSharp.Reports.Input.DeviceItemInputParser inputParser = 
                                        deviceItem.CreateDeviceItemInputParser();
 
                                    inputReceiver.Start(hidStream);
 
                                    int startTime = Environment.TickCount;
                                    while (true)
                                    {
                                        if (inputReceiver.WaitHandle.WaitOne(1000))
                                        {
                                            if (!inputReceiver.IsRunning) { break; } // Disconnected?
 
                                            Report report;
                                            while (inputReceiver.TryRead(inputReportBuffer, 0out report))
                                            {
                                                // Parse the report if possible.
                                                // This will return false if (for example) the report applies to a different DeviceItem.
                                                if (inputParser.TryParseReport(inputReportBuffer, 0, report))
                                                {
                                                    WriteDeviceItemInputParserResult(inputParser);
                                                }
                                            }
                                        }
 
                                        uint elapsedTime = (uint)(Environment.TickCount - startTime);
                                        if (elapsedTime >= 20000) { break; } // Stay open for 20 seconds.
                                    }
 
                                }
 
                                Console.WriteLine("Closed device.");
                            }
                            else
                            {
                                Console.WriteLine("Failed to open device.");
                            }
 
                            Console.WriteLine("  ---------------------------------------------");
                        }
                    }
                }
                catch (Exception e)
                {
                    Console.WriteLine(e);
                }
            }
        }
    }
}
 

 

소스를 입력하고 빌드한다.

 

 

프로그램을 실행하면 연결된 모든 디바이스와 HID가 표시되고 open 가능한 디바이스(usb gamepad)는 입력을 받아 표시한다.

포커스가 다른 프로그램에 있어도 디바이스의 입력을 확인 할 수 있다.

 

 

각 버튼이 눌렸을때 inputReportBuffer의 변화되는 내용을 확인해 보면 아래와 같다.

(첫 번째 바이트의 '01'은 Report ID인거 같다)

 

1
Console.WriteLine(BitConverter.ToString(inputReportBuffer));
 

 

01-80-80-7F-7F-0F-00-00
01-80-80-00-7F-0F-00-00
  GenericDesktopX: 127 -> 0

01-80-80-7F-7F-0F-00-00
01-80-80-FF-7F-0F-00-00
  GenericDesktopX: 127 -> 255

01-80-80-7F-7F-0F-00-00
01-80-80-7F-00-0F-00-00
  GenericDesktopY: 127 -> 0
  
01-80-80-7F-7F-0F-00-00
01-80-80-7F-FF-0F-00-00
  GenericDesktopY: 127 -> 255
※게임패드의 방향버튼은 기본이(버튼이 눌리지 않았을때) 127

01-80-80-7F-7F-0F-00-00
01-80-80-7F-7F-1F-00-00
  Button1: 0 -> 1

01-80-80-7F-7F-0F-00-00
01-80-80-7F-7F-2F-00-00
  Button2: 0 -> 1
  
01-80-80-7F-7F-0F-00-00
01-80-80-7F-7F-4F-00-00
  Button3: 0 -> 1

01-80-80-7F-7F-0F-00-00
01-80-80-7F-7F-8F-00-00
  Button4: 0 -> 1
※게임패드의 버튼은 기본이(버튼이 눌리지 않았을때) 0

 

※ HidSharp

 

반응형
Posted by J-sean
: