UE4 键盘输入浅析(二)

上一篇中我们讲到了UE4把键盘和鼠标的映射都保存在一个全局对象FInputKeyManager里,并且也提供了通过key来获取key的虚拟码、字符的方法,下面,我们就来用用个简单例子来说明下这个的用法

首先新建一个C++工程,新建一个C++类InputTestDevice,继承自IInputDevice,这个类是输入相关的接口且为抽象基类,为啥要继承这个类下面我会说,

InputTestDevice.h

class PROJECTTEST_RE421_API FInputTestDevice : public IInputDevice
{
public:

	//以下都是父类接口
	FInputTestDevice(const TSharedRef<FGenericApplicationMessageHandler>& InMessageHandler);
	virtual void Tick(float DeltaTime) override;
	/** Poll for controller state and send events if needed */
	virtual void SendControllerEvents() override;

	/** Set which MessageHandler will get the events from SendControllerEvents. */
	virtual void SetMessageHandler(const TSharedRef<FGenericApplicationMessageHandler>& InMessageHandler) override;

	/** Exec handler to allow console commands to be passed through for debugging */
	virtual bool Exec(UWorld* InWorld, const TCHAR* Cmd, FOutputDevice& Ar) override;

	/**
	* IForceFeedbackSystem pass through functions
	*/
	virtual void SetChannelValue(int32 ControllerId, FForceFeedbackChannelType ChannelType, float Value) override;

	virtual void SetChannelValues(int32 ControllerId, const FForceFeedbackValues &values) override;


	//事件类型
	enum class EventType
	{
		UNDEFINED,		/** No value. */
		KEY_DOWN,		/** 按键按下. */
		KEY_UP,			/** 按键松开. */
		KEY_PRESS,		/** 按键按下并且输入一个字符. */
		MOUSE_ENTER,	/** 鼠标进入窗口画板. */
		MOUSE_LEAVE,	/** 鼠标离开窗口画板. */
		MOUSE_MOVE,		/** 鼠标在窗口画板上移动. */
		MOUSE_DOWN,		/** 鼠标点击. */
		MOUSE_UP,		/** 鼠标松开. */
		MOUSE_WHEEL,	/** 鼠标滚轮. */
	};


	/** 事件. */
	struct FEvent
	{
		/** 事件类型. */
		EventType Event;

		/** A generic piece of data which is used to hold information about the
		 * event, specialized by making a union with an appropriate struct. */
		union
		{
			uint64 Word;

			struct   /** KEY_DOWN */
			{
				uint8 KeyCode;
				bool bIsRepeat;
			} KeyDown;

			struct   /* KEY_UP */
			{
				uint8 KeyCode;
			} KeyUp;

			struct   /** KEY_PRESSED */
			{
				TCHAR Character;
			} Character;

			struct   /** MOUSE_MOVE */
			{
				int16 DeltaX;
				int16 DeltaY;
				uint16 PosX;
				uint16 PosY;
			} MouseMove;

			struct   /** MOUSE_DOWN, MOUSE_UP */
			{
				uint8 Button;
				uint16 PosX;
				uint16 PosY;
			} MouseButton;

			struct   /** MOUSE_WHEEL */
			{
				int16 Delta;
				uint16 PosX;
				uint16 PosY;
			} MouseWheel;
		} Data;

		/**
		 * Create a completely empty event.
		 */
		FEvent()
			: Event(EventType::UNDEFINED)
		{
		}

		/**
		 * Create an event of the given type.
		 * @param InEvent - The type of the event.
		 */
		FEvent(EventType InEvent)
			: Event(InEvent)
		{
		}

		/**
		 * An event related to a key being pushed down.
		 * @param InKeyCode - Numerical code identifying the pushed down key.
		 * @param InIsRepeat - Whether the key is being kept down and is repeating.
		 */
		void SetKeyDown(uint8 InKeyCode, bool InIsRepeat)
		{
			check(Event == EventType::KEY_DOWN);
			Data.KeyDown.KeyCode = InKeyCode;
			Data.KeyDown.bIsRepeat = InIsRepeat;
		}

		/**
		 * An event related to a key being released.
		 * @param InKeyCode - Numerical code identifying the released key.
		 */
		void SetKeyUp(uint8 InKeyCode)
		{
			check(Event == EventType::KEY_UP);
			Data.KeyUp.KeyCode = InKeyCode;
		}

		/**
		 * An event related to character input.
		 * @param InCharacter - The character being input.
		 */
		void SetCharCode(TCHAR InCharacter)
		{
			check(Event == EventType::KEY_PRESS);
			Data.Character.Character = InCharacter;
		}

		/**
		 * An event related to mouse movement.
		 * @param InPoxX - The X position of the mouse pointer.
		 * @param InPosY - The Y position of the mouse pointer.
		 * @param InDeltaX - The change in the X position of the mouse pointer.
		 * @param InDeltaY - The change in the Y position of the mouse pointer.
		 */
		void SetMouseDelta(uint16 InPosX, uint16 InPosY, int16 InDeltaX, int16 InDeltaY)
		{
			check(Event == EventType::MOUSE_MOVE);
			Data.MouseMove.DeltaX = InDeltaX;
			Data.MouseMove.DeltaY = InDeltaY;
			Data.MouseMove.PosX = InPosX;
			Data.MouseMove.PosY = InPosY;
		}

		/**
		 * An event related to mouse buttons.
		 * @param InButton - The button number corresponding to left, middle, right, etc.
		 * @param InPoxX - The X position of the mouse pointer.
		 * @param InPosY - The Y position of the mouse pointer.
		 */
		void SetMouseClick(uint8 InButton, uint16 InPosX, uint16 InPosY)
		{
			check(Event == EventType::MOUSE_DOWN || Event == EventType::MOUSE_UP);
			Data.MouseButton.Button = InButton;
			Data.MouseButton.PosX = InPosX;
			Data.MouseButton.PosY = InPosY;
		}

		/**
		 * An event related to the mouse scroll wheel.
		 * @param InButton - The amount by which the mouse wheel was scrolled.
		 * @param InPoxX - The X position of the mouse pointer when the wheel was scrolled.
		 * @param InPosY - The Y position of the mouse pointer when the wheel was scrolled.
		 */
		void SetMouseWheel(int16 InDelta, uint16 InPosX, uint16 InPosY)
		{
			check(Event == EventType::MOUSE_WHEEL);
			Data.MouseWheel.Delta = InDelta;
			Data.MouseWheel.PosX = InPosX;
			Data.MouseWheel.PosY = InPosY;
		}

		

		/**
		 * Get information about an event related to a key being pushed down.
		 * @param OutKeyCode - Numerical code identifying the pushed down key.
		 * @param OutIsRepeat - Whether the key is being kept down and is repeating.
		 */
		void GetKeyDown(uint8& OutKeyCode, bool& OutIsRepeat)
		{
			check(Event == EventType::KEY_DOWN);
			OutKeyCode = Data.KeyDown.KeyCode;
			OutIsRepeat = Data.KeyDown.bIsRepeat;
		}

		/**
		 * Get information about an event related to a key being released.
		 * @param OutKeyCode - Numerical code identifying the released key.
		 */
		void GetKeyUp(uint8& OutKeyCode)
		{
			check(Event == EventType::KEY_UP);
			OutKeyCode = Data.KeyUp.KeyCode;
		}

		/**
		 * Get information about an event related to character input.
		 * @param OutCharacter - The character being input.
		 */
		void GetCharacterCode(TCHAR& OutCharacter)
		{
			check(Event == EventType::KEY_PRESS);
			OutCharacter = Data.Character.Character;
		}

		/**
		 * Get information about an event related to mouse movement.
		 * @param OutPoxX - The X position of the mouse pointer.
		 * @param OutPosY - The Y position of the mouse pointer.
		 * @param OutDeltaX - The change in the X position of the mouse pointer.
		 * @param OutDeltaY - The change in the Y position of the mouse pointer.
		 */
		void GetMouseDelta(uint16& OutPosX, uint16& OutPosY, int16& OutDeltaX, int16& OutDeltaY)
		{
			check(Event == EventType::MOUSE_MOVE);
			OutPosX = Data.MouseMove.PosX;
			OutPosY = Data.MouseMove.PosY;
			OutDeltaX = Data.MouseMove.DeltaX;
			OutDeltaY = Data.MouseMove.DeltaY;
		}

		/**
		 * Get information about an event related to mouse buttons.
		 * @param OutButton - The button number corresponding to left, middle, right, etc.
		 * @param OutPosX - The X position of the mouse pointer.
		 * @param OutPosY - The Y position of the mouse pointer.
		 */
		void GetMouseClick(EMouseButtons::Type& OutButton, uint16& OutPosX, uint16& OutPosY)
		{
			check(Event == EventType::MOUSE_DOWN || Event == EventType::MOUSE_UP);
			// https://developer.mozilla.org/en-US/docs/Web/Events/mousedown
			uint8 Button = Data.MouseButton.Button;
			switch (Button)
			{
			case 0:
			{
				OutButton = EMouseButtons::Left;
			}
			break;
			case 1:
			{
				OutButton = EMouseButtons::Middle;
			}
			break;
			case 2:
			{
				OutButton = EMouseButtons::Right;
			}
			break;
			default:
			{
				UE_LOG(LogTemp, Error, TEXT("Unknown Pixel Streaming mouse click with button %d and word 0x%016llx"), Button, Data.Word);
			}
			break;
			}
			OutPosX = Data.MouseButton.PosX;
			OutPosY = Data.MouseButton.PosY;
		}

		/**
		 * Get information about an event related to the mouse wheel.
		 * @param OutDelta - The amount by which the mouse wheel was scrolled.
		 * @param PosX - The X position of the mouse pointer when the wheel was scrolled.
		 * @param PosY - The Y position of the mouse pointer when the wheel was scrolled.
		 */
		void GetMouseWheel(int16& OutDelta, uint16& OutPosX, uint16& OutPosY)
		{
			check(Event == EventType::MOUSE_WHEEL);
			OutDelta = Data.MouseWheel.Delta;
			OutPosX = Data.MouseWheel.PosX;
			OutPosY = Data.MouseWheel.PosY;
		}
	};

	/**
	 * 向输入设备添加一个新事件以供稍后处理。.
	 * @param InEvent - The new event.
	 */
	void ProcessEvent(const FEvent& InEvent);

	/**
	 * 向输入设备添加一个新的UI交互描述符,以便稍后处理.
	 * @param InDescriptor - The new UI interaction descriptor.
	 */
	void ProcessUIInteraction(const FString& InDescriptor);

	private:

		/**
		 * A special wrapper over the GenericApplication layer which allows us to
		 * override certain behavior.
		 */
		TSharedPtr<class FTestApplicationWrapper> TestApplicationWrapper;

		//鼠标光标的指针
		TSharedPtr<class FTestCursor> TestCursor;

		/** 系统传递事件的消息处理程序的引用 */
		TSharedRef<FGenericApplicationMessageHandler> MessageHandler;

		/** 事件的消息队列. */
		TQueue<FEvent> Events;

		/**
		 * A queue of UI interaction descriptor strings which contain arbitrary
		 * information related to the interaction.
		 */
		TQueue<FString> UIInteractions;

};

InputTestDevice.cpp

在cpp里,我们新建了一个继承自鼠标光标的类TestCursor,以便自定义我们自己的鼠标光标事件

class FTestCursor : public ICursor
{
public:

	FTestCursor() {}
	virtual ~FTestCursor() = default;
	virtual FVector2D GetPosition() const override { return Position; }
	virtual void SetPosition(const int32 X, const int32 Y) override { Position = FVector2D(X, Y); };
	virtual void SetType(const EMouseCursor::Type InNewCursor) override {};
	virtual EMouseCursor::Type GetType() const override { return EMouseCursor::Type::Default; };
	virtual void GetSize(int32& Width, int32& Height) const override {};
	virtual void Show(bool bShow) override {};
	virtual void Lock(const RECT* const Bounds) override {};
	virtual void SetTypeShape(EMouseCursor::Type InCursorType, void* CursorHandle) override {};

private:

	/** The cursor position sent across with mouse events. */
	FVector2D Position;
};

接下来就是InputTestDevice.cpp

FInputTestDevice::FInputTestDevice(const TSharedRef<FGenericApplicationMessageHandler>& InMessageHandler)
	: TestApplicationWrapper(MakeShareable(new FTestApplicationWrapper(FSlateApplication::Get().GetPlatformApplication())))
	, TestCursor(MakeShareable(new FTestCursor()))
	, MessageHandler(InMessageHandler)
{

}

void FInputTestDevice::Tick(float DeltaTime)
{
	FEvent Event;
	while (Events.Dequeue(Event))
	{
		switch (Event.Event)
		{
		case EventType::UNDEFINED:
		{
			checkNoEntry();
		}
		break;
		case EventType::KEY_DOWN:
		{
			uint8 JavaScriptKeyCode;
			bool IsRepeat;
			Event.GetKeyDown(JavaScriptKeyCode, IsRepeat);
			const FKey* AgnosticKey = AgnosticKeys[JavaScriptKeyCode];
			const uint32* KeyCode;
			const uint32* CharacterCode;
			FInputKeyManager::Get().GetCodesFromKey(*AgnosticKey, KeyCode, CharacterCode);
			MessageHandler->OnKeyDown(KeyCode ? *KeyCode : 0, CharacterCode ? *CharacterCode : 0, IsRepeat);
			UE_LOG(LogTemp, Verbose, TEXT("KEY_DOWN: KeyCode = %d; CharacterCode = %d; IsRepeat = %s"), KeyCode, CharacterCode, IsRepeat ? TEXT("True") : TEXT("False"));
		}
		break;
		case EventType::KEY_UP:
		{
			uint8 JavaScriptKeyCode;
			Event.GetKeyUp(JavaScriptKeyCode);
			const FKey* AgnosticKey = AgnosticKeys[JavaScriptKeyCode];
			const uint32* KeyCode;
			const uint32* CharacterCode;
			FInputKeyManager::Get().GetCodesFromKey(*AgnosticKey, KeyCode, CharacterCode);
			MessageHandler->OnKeyUp(KeyCode ? *KeyCode : 0, CharacterCode ? *CharacterCode : 0, false);   // Key up events are never repeats.
			UE_LOG(LogTemp, Verbose, TEXT("KEY_UP: KeyCode = %d; CharacterCode = %d"), KeyCode, CharacterCode);
		}
		break;
		case EventType::KEY_PRESS:
		{
			TCHAR UnicodeCharacter;
			Event.GetCharacterCode(UnicodeCharacter);
			MessageHandler->OnKeyChar(UnicodeCharacter, false);   // Key press repeat not yet available but are not intrinsically used.
			UE_LOG(LogTemp, Verbose, TEXT("KEY_PRESSED: Character = '%c'"), UnicodeCharacter);
		}
		break;
		case EventType::MOUSE_ENTER:
		{
			// 
			//FSlateApplication::Get().OverridePlatformApplication(TestApplicationWrapper);
			FSlateApplication::Get().OnCursorSet();

			// Make sure the viewport is active.
			FSlateApplication::Get().ProcessApplicationActivationEvent(true);

			// Double the number of hit test cells to cater for the possibility
			// that the window will be off screen.
			UGameEngine* GameEngine = Cast<UGameEngine>(GEngine);
			TSharedPtr<SWindow> Window = GameEngine->SceneViewport->FindWindow();
			Window->GetHittestGrid()->SetNumCellsExcess(Window->GetHittestGrid()->GetNumCells());

			UE_LOG(LogTemp, Verbose, TEXT("MOUSE_ENTER"));
		}
		break;
		case EventType::MOUSE_LEAVE:
		{
			// Restore normal application layer.
			//FSlateApplication::Get().OverridePlatformApplication(TestApplicationWrapper->WrappedApplication);

			// Reduce the number of hit test cells back to normal.
			UGameEngine* GameEngine = Cast<UGameEngine>(GEngine);
			TSharedPtr<SWindow> Window = GameEngine->SceneViewport->FindWindow();
			Window->GetHittestGrid()->SetNumCellsExcess(FIntPoint(0, 0));

			UE_LOG(LogTemp, Verbose, TEXT("MOUSE_LEAVE"));
		}
		break;
		case EventType::MOUSE_MOVE:
		{
			uint16 PosX;
			uint16 PosY;
			int16 DeltaX;
			int16 DeltaY;
			Event.GetMouseDelta(PosX, PosY, DeltaX, DeltaY);
			FVector2D CursorPos = GEngine->GameViewport->GetWindow()->GetPositionInScreen() + FVector2D(PosX, PosY);
			//TestApplicationWrapper->Cursor->SetPosition(CursorPos.X, CursorPos.Y);
			TestCursor->SetPosition(CursorPos.X, CursorPos.Y);
			MessageHandler->OnRawMouseMove(DeltaX, DeltaY);
			UE_LOG(LogTemp, VeryVerbose, TEXT("MOUSE_MOVE: Pos = (%d, %d); CursorPos = (%d, %d); Delta = (%d, %d)"), PosX, PosY, static_cast<int>(CursorPos.X), static_cast<int>(CursorPos.Y), DeltaX, DeltaY);
		}
		break;
		case EventType::MOUSE_DOWN:
		{
			// If a user clicks on the application window and then clicks on the
			// browser then this will move the focus away from the application
			// window which will deactivate the application, so we need to check
			// if we must reactivate the application.
			if (!FSlateApplication::Get().IsActive())
			{
				FSlateApplication::Get().ProcessApplicationActivationEvent(true);
			}

			EMouseButtons::Type Button;
			uint16 PosX;
			uint16 PosY;
			Event.GetMouseClick(Button, PosX, PosY);
			FVector2D CursorPos = GEngine->GameViewport->GetWindow()->GetPositionInScreen() + FVector2D(PosX, PosY);
			//TestApplicationWrapper->Cursor->SetPosition(CursorPos.X, CursorPos.Y);
			TestCursor->SetPosition(CursorPos.X, CursorPos.Y);
			MessageHandler->OnMouseDown(GEngine->GameViewport->GetWindow()->GetNativeWindow(), Button, CursorPos);
			UE_LOG(LogTemp, Verbose, TEXT("MOUSE_DOWN: Button = %d; Pos = (%d, %d); CursorPos = (%d, %d)"), Button, PosX, PosY, static_cast<int>(CursorPos.X), static_cast<int>(CursorPos.Y));
		}
		break;
		case EventType::MOUSE_UP:
		{
			EMouseButtons::Type Button;
			uint16 PosX;
			uint16 PosY;
			Event.GetMouseClick(Button, PosX, PosY);
			FVector2D CursorPos = GEngine->GameViewport->GetWindow()->GetPositionInScreen() + FVector2D(PosX, PosY);
			//TestApplicationWrapper->Cursor->SetPosition(CursorPos.X, CursorPos.Y);
			TestCursor->SetPosition(CursorPos.X, CursorPos.Y);
			MessageHandler->OnMouseUp(Button);
			UE_LOG(LogTemp, Verbose, TEXT("MOUSE_UP: Button = %d; Pos = (%d, %d); CursorPos = (%d, %d)"), Button, PosX, PosY, static_cast<int>(CursorPos.X), static_cast<int>(CursorPos.Y));
		}
		break;
		case EventType::MOUSE_WHEEL:
		{
			int16 Delta;
			uint16 PosX;
			uint16 PosY;
			Event.GetMouseWheel(Delta, PosX, PosY);
			const float SpinFactor = 1 / 120.0f;
			FVector2D CursorPos = GEngine->GameViewport->GetWindow()->GetPositionInScreen() + FVector2D(PosX, PosY);
			MessageHandler->OnMouseWheel(Delta * SpinFactor, CursorPos);
			UE_LOG(LogTemp, Verbose, TEXT("MOUSE_WHEEL: Delta = %d; Pos = (%d, %d); CursorPos = (%d, %d)"), Delta, PosX, PosY, static_cast<int>(CursorPos.X), static_cast<int>(CursorPos.Y));
		}
		break;
		
		}
	}

	
	
}

void FInputTestDevice::SendControllerEvents()
{

}

void FInputTestDevice::SetMessageHandler(const TSharedRef<FGenericApplicationMessageHandler>& InMessageHandler)
{
	MessageHandler = InMessageHandler;
}

bool FInputTestDevice::Exec(UWorld* InWorld, const TCHAR* Cmd, FOutputDevice& Ar)
{
	return true;
}

void FInputTestDevice::SetChannelValue(int32 ControllerId, FForceFeedbackChannelType ChannelType, float Value)
{

}

void FInputTestDevice::SetChannelValues(int32 ControllerId, const FForceFeedbackValues &values)
{

}

void FInputTestDevice::ProcessEvent(const FEvent& InEvent)
{
	bool Success = Events.Enqueue(InEvent);
	checkf(Success, TEXT("Unable to enqueue new event of type %d"), static_cast<int>(InEvent.Event));
}

void FInputTestDevice::ProcessUIInteraction(const FString& InDescriptor)
{
	bool Success = UIInteractions.Enqueue(InDescriptor);
	checkf(Success, TEXT("Unable to enqueue new UI Interaction %s"), *InDescriptor);
}

我们在Tick里从消息队列中执行相应的事件消息,其中对于键盘事件,我们通过一个数组AgnosticKeys来保存所有的键盘Key类型:

static const FKey* AgnosticKeys[256] =
{
	/*   0 */ &EKeys::Invalid,
	/*   1 */ &EKeys::Invalid,
	/*   2 */ &EKeys::Invalid,
	/*   3 */ &EKeys::Invalid,
	/*   4 */ &EKeys::Invalid,
	/*   5 */ &EKeys::Invalid,
	/*   6 */ &EKeys::Invalid,
	/*   7 */ &EKeys::Invalid,
	/*   8 */ &EKeys::BackSpace,
	/*   9 */ &EKeys::Tab,
	/*  10 */ &EKeys::Invalid,
	/*  11 */ &EKeys::Invalid,
	/*  12 */ /*&EKeys::Clear*/ &EKeys::Invalid,
	/*  13 */ &EKeys::Enter,
	/*  14 */ &EKeys::Invalid,
	/*  15 */ &EKeys::Invalid,
	/*  16 */ &EKeys::LeftShift,
	/*  17 */ &EKeys::LeftControl,
	/*  18 */ &EKeys::LeftAlt,
	/*  19 */ &EKeys::Pause,
	/*  20 */ &EKeys::CapsLock,
	/*  21 */ &EKeys::Invalid,
	/*  22 */ &EKeys::Invalid,
	/*  23 */ &EKeys::Invalid,
	/*  24 */ &EKeys::Invalid,
	/*  25 */ &EKeys::Invalid,
	/*  26 */ &EKeys::Invalid,
	/*  27 */ &EKeys::Escape,
	/*  28 */ &EKeys::Invalid,
	/*  29 */ &EKeys::Invalid,
	/*  30 */ &EKeys::Invalid,
	/*  31 */ &EKeys::Invalid,
	/*  32 */ &EKeys::SpaceBar,
	/*  33 */ &EKeys::PageUp,
	/*  34 */ &EKeys::PageDown,
	/*  35 */ &EKeys::End,
	/*  36 */ &EKeys::Home,
	/*  37 */ &EKeys::Left,
	/*  38 */ &EKeys::Up,
	/*  39 */ &EKeys::Right,
	/*  40 */ &EKeys::Down,
	/*  41 */ &EKeys::Invalid,
	/*  42 */ &EKeys::Invalid,
	/*  43 */ &EKeys::Invalid,
	/*  44 */ /*&EKeys::PrintScreen*/ &EKeys::Invalid,
	/*  45 */ &EKeys::Insert,
	/*  46 */ &EKeys::Delete,
	/*  47 */ &EKeys::Invalid,
	/*  48 */ &EKeys::Zero,
	/*  49 */ &EKeys::One,
	/*  50 */ &EKeys::Two,
	/*  51 */ &EKeys::Three,
	/*  52 */ &EKeys::Four,
	/*  53 */ &EKeys::Five,
	/*  54 */ &EKeys::Six,
	/*  55 */ &EKeys::Seven,
	/*  56 */ &EKeys::Eight,
	/*  57 */ &EKeys::Nine,
	/*  58 */ &EKeys::Invalid,
	/*  59 */ &EKeys::Invalid,
	/*  60 */ &EKeys::Invalid,
	/*  61 */ &EKeys::Invalid,
	/*  62 */ &EKeys::Invalid,
	/*  63 */ &EKeys::Invalid,
	/*  64 */ &EKeys::Invalid,
	/*  65 */ &EKeys::A,
	/*  66 */ &EKeys::B,
	/*  67 */ &EKeys::C,
	/*  68 */ &EKeys::D,
	/*  69 */ &EKeys::E,
	/*  70 */ &EKeys::F,
	/*  71 */ &EKeys::G,
	/*  72 */ &EKeys::H,
	/*  73 */ &EKeys::I,
	/*  74 */ &EKeys::J,
	/*  75 */ &EKeys::K,
	/*  76 */ &EKeys::L,
	/*  77 */ &EKeys::M,
	/*  78 */ &EKeys::N,
	/*  79 */ &EKeys::O,
	/*  80 */ &EKeys::P,
	/*  81 */ &EKeys::Q,
	/*  82 */ &EKeys::R,
	/*  83 */ &EKeys::S,
	/*  84 */ &EKeys::T,
	/*  85 */ &EKeys::U,
	/*  86 */ &EKeys::V,
	/*  87 */ &EKeys::W,
	/*  88 */ &EKeys::X,
	/*  89 */ &EKeys::Y,
	/*  90 */ &EKeys::Z,
	/*  91 */ /*&EKeys::LeftWindowKey*/ &EKeys::Invalid,
	/*  92 */ /*&EKeys::RightWindowKey*/ &EKeys::Invalid,
	/*  93 */ /*&EKeys::SelectKey*/ &EKeys::Invalid,
	/*  94 */ &EKeys::Invalid,
	/*  95 */ &EKeys::Invalid,
	/*  96 */ &EKeys::NumPadZero,
	/*  97 */ &EKeys::NumPadOne,
	/*  98 */ &EKeys::NumPadTwo,
	/*  99 */ &EKeys::NumPadThree,
	/* 100 */ &EKeys::NumPadFour,
	/* 101 */ &EKeys::NumPadFive,
	/* 102 */ &EKeys::NumPadSix,
	/* 103 */ &EKeys::NumPadSeven,
	/* 104 */ &EKeys::NumPadEight,
	/* 105 */ &EKeys::NumPadNine,
	/* 106 */ &EKeys::Multiply,
	/* 107 */ &EKeys::Add,
	/* 108 */ &EKeys::Invalid,
	/* 109 */ &EKeys::Subtract,
	/* 110 */ &EKeys::Decimal,
	/* 111 */ &EKeys::Divide,
	/* 112 */ &EKeys::F1,
	/* 113 */ &EKeys::F2,
	/* 114 */ &EKeys::F3,
	/* 115 */ &EKeys::F4,
	/* 116 */ &EKeys::F5,
	/* 117 */ &EKeys::F6,
	/* 118 */ &EKeys::F7,
	/* 119 */ &EKeys::F8,
	/* 120 */ &EKeys::F9,
	/* 121 */ &EKeys::F10,
	/* 122 */ &EKeys::F11,
	/* 123 */ &EKeys::F12,
	/* 124 */ &EKeys::Invalid,
	/* 125 */ &EKeys::Invalid,
	/* 126 */ &EKeys::Invalid,
	/* 127 */ &EKeys::Invalid,
	/* 128 */ &EKeys::Invalid,
	/* 129 */ &EKeys::Invalid,
	/* 130 */ &EKeys::Invalid,
	/* 131 */ &EKeys::Invalid,
	/* 132 */ &EKeys::Invalid,
	/* 133 */ &EKeys::Invalid,
	/* 134 */ &EKeys::Invalid,
	/* 135 */ &EKeys::Invalid,
	/* 136 */ &EKeys::Invalid,
	/* 137 */ &EKeys::Invalid,
	/* 138 */ &EKeys::Invalid,
	/* 139 */ &EKeys::Invalid,
	/* 140 */ &EKeys::Invalid,
	/* 141 */ &EKeys::Invalid,
	/* 142 */ &EKeys::Invalid,
	/* 143 */ &EKeys::Invalid,
	/* 144 */ &EKeys::NumLock,
	/* 145 */ &EKeys::ScrollLock,
	/* 146 */ &EKeys::Invalid,
	/* 147 */ &EKeys::Invalid,
	/* 148 */ &EKeys::Invalid,
	/* 149 */ &EKeys::Invalid,
	/* 150 */ &EKeys::Invalid,
	/* 151 */ &EKeys::Invalid,
	/* 152 */ &EKeys::Invalid,
	/* 153 */ &EKeys::Invalid,
	/* 154 */ &EKeys::Invalid,
	/* 155 */ &EKeys::Invalid,
	/* 156 */ &EKeys::Invalid,
	/* 157 */ &EKeys::Invalid,
	/* 158 */ &EKeys::Invalid,
	/* 159 */ &EKeys::Invalid,
	/* 160 */ &EKeys::Invalid,
	/* 161 */ &EKeys::Invalid,
	/* 162 */ &EKeys::Invalid,
	/* 163 */ &EKeys::Invalid,
	/* 164 */ &EKeys::Invalid,
	/* 165 */ &EKeys::Invalid,
	/* 166 */ &EKeys::Invalid,
	/* 167 */ &EKeys::Invalid,
	/* 168 */ &EKeys::Invalid,
	/* 169 */ &EKeys::Invalid,
	/* 170 */ &EKeys::Invalid,
	/* 171 */ &EKeys::Invalid,
	/* 172 */ &EKeys::Invalid,
	/* 173 */ &EKeys::Invalid,
	/* 174 */ &EKeys::Invalid,
	/* 175 */ &EKeys::Invalid,
	/* 176 */ &EKeys::Invalid,
	/* 177 */ &EKeys::Invalid,
	/* 178 */ &EKeys::Invalid,
	/* 179 */ &EKeys::Invalid,
	/* 180 */ &EKeys::Invalid,
	/* 181 */ &EKeys::Invalid,
	/* 182 */ &EKeys::Invalid,
	/* 183 */ &EKeys::Invalid,
	/* 184 */ &EKeys::Invalid,
	/* 185 */ &EKeys::Invalid,
	/* 186 */ &EKeys::Semicolon,
	/* 187 */ &EKeys::Equals,
	/* 188 */ &EKeys::Comma,
	/* 189 */ &EKeys::Hyphen,
	/* 190 */ &EKeys::Period,
	/* 191 */ &EKeys::Slash,
	/* 192 */ &EKeys::Tilde,
	/* 193 */ &EKeys::Invalid,
	/* 194 */ &EKeys::Invalid,
	/* 195 */ &EKeys::Invalid,
	/* 196 */ &EKeys::Invalid,
	/* 197 */ &EKeys::Invalid,
	/* 198 */ &EKeys::Invalid,
	/* 199 */ &EKeys::Invalid,
	/* 200 */ &EKeys::Invalid,
	/* 201 */ &EKeys::Invalid,
	/* 202 */ &EKeys::Invalid,
	/* 203 */ &EKeys::Invalid,
	/* 204 */ &EKeys::Invalid,
	/* 205 */ &EKeys::Invalid,
	/* 206 */ &EKeys::Invalid,
	/* 207 */ &EKeys::Invalid,
	/* 208 */ &EKeys::Invalid,
	/* 209 */ &EKeys::Invalid,
	/* 210 */ &EKeys::Invalid,
	/* 211 */ &EKeys::Invalid,
	/* 212 */ &EKeys::Invalid,
	/* 213 */ &EKeys::Invalid,
	/* 214 */ &EKeys::Invalid,
	/* 215 */ &EKeys::Invalid,
	/* 216 */ &EKeys::Invalid,
	/* 217 */ &EKeys::Invalid,
	/* 218 */ &EKeys::Invalid,
	/* 219 */ &EKeys::LeftBracket,
	/* 220 */ &EKeys::Backslash,
	/* 221 */ &EKeys::RightBracket,
	/* 222 */ &EKeys::Apostrophe,
	/* 223 */ &EKeys::Quote,
	/* 224 */ &EKeys::Invalid,
	/* 225 */ &EKeys::Invalid,
	/* 226 */ &EKeys::Invalid,
	/* 227 */ &EKeys::Invalid,
	/* 228 */ &EKeys::Invalid,
	/* 229 */ &EKeys::Invalid,
	/* 230 */ &EKeys::Invalid,
	/* 231 */ &EKeys::Invalid,
	/* 232 */ &EKeys::Invalid,
	/* 233 */ &EKeys::Invalid,
	/* 234 */ &EKeys::Invalid,
	/* 235 */ &EKeys::Invalid,
	/* 236 */ &EKeys::Invalid,
	/* 237 */ &EKeys::Invalid,
	/* 238 */ &EKeys::Invalid,
	/* 239 */ &EKeys::Invalid,
	/* 240 */ &EKeys::Invalid,
	/* 241 */ &EKeys::Invalid,
	/* 242 */ &EKeys::Invalid,
	/* 243 */ &EKeys::Invalid,
	/* 244 */ &EKeys::Invalid,
	/* 245 */ &EKeys::Invalid,
	/* 246 */ &EKeys::Invalid,
	/* 247 */ &EKeys::Invalid,
	/* 248 */ &EKeys::Invalid,
	/* 249 */ &EKeys::Invalid,
	/* 250 */ &EKeys::Invalid,
	/* 251 */ &EKeys::Invalid,
	/* 252 */ &EKeys::Invalid,
	/* 253 */ &EKeys::Invalid,
	/* 254 */ &EKeys::Invalid,
	/* 255 */ &EKeys::Invalid
};

通过外面传入的数组下标来获取相应的Key,然后通过Key找到对应键盘的虚拟码,最后由系统传递事件的消息处理程序的引用MessageHandler来触发系统按键消息响应,这样我们就能通过外接传入某一个值就能直接模拟按键的行为了。下面是Key_Up的代码:

case EventType::KEY_UP:
		{
			uint8 JavaScriptKeyCode;
			Event.GetKeyUp(JavaScriptKeyCode);
			const FKey* AgnosticKey = AgnosticKeys[JavaScriptKeyCode];
			const uint32* KeyCode;
			const uint32* CharacterCode;
			FInputKeyManager::Get().GetCodesFromKey(*AgnosticKey, KeyCode, CharacterCode);
			MessageHandler->OnKeyUp(KeyCode ? *KeyCode : 0, CharacterCode ? *CharacterCode : 0, false);   // Key up events are never repeats.
			UE_LOG(LogTemp, Verbose, TEXT("KEY_UP: KeyCode = %d; CharacterCode = %d"), KeyCode, CharacterCode);
		}

接着我们在该工程模块中来创建并获取这个InputTestDevice,首先让工程模块继承自IInputDeviceModule,这个类里有个 CreateInputDevice()函数用来创建输入驱动。代码比较简单,这里就直接贴上代码:

工程模块.h

class IInputTestDeviceModule : public IInputDeviceModule
{
public:
	/** 父类接口 */
	virtual void StartupModule() override;
	virtual void ShutdownModule() override;
	virtual TSharedPtr<class IInputDevice> CreateInputDevice(const TSharedRef<FGenericApplicationMessageHandler>& InMessageHandler) override;

	/**
	 * 返回输入驱动的智能指针
	 * @return The shared pointer to the input device.
	 */
	TSharedPtr<FInputTestDevice> GetInputDevicePtr();

private:

	TSharedPtr<FInputTestDevice> InputTestDevice;
	
};

工程模块.cpp

IMPLEMENT_PRIMARY_GAME_MODULE(IInputTestDeviceModule, ProjectTest_Re421, "ProjectTest_Re421" );

void IInputTestDeviceModule::StartupModule()
{
	IModularFeatures::Get().RegisterModularFeature(GetModularFeatureName(), this);
}

void IInputTestDeviceModule::ShutdownModule()
{
	IModularFeatures::Get().UnregisterModularFeature(GetModularFeatureName(), this);
}


 TSharedPtr<class IInputDevice> IInputTestDeviceModule::CreateInputDevice(const TSharedRef<FGenericApplicationMessageHandler>& InMessageHandler)
 {
 	InputTestDevice = MakeShareable(new FInputTestDevice(InMessageHandler));
 	return InputTestDevice;
 }


TSharedPtr<FInputTestDevice> IInputTestDeviceModule::GetInputDevicePtr()
{
	if (InputTestDevice.IsValid())
	{
		return InputTestDevice;
	}
	return nullptr;
}

最后我们新建一个继承自Actor的类TestActor用来测试上面的功能,代码如下:

TestActor.h

class FInputTestDevice;

UCLASS()
class PROJECTTEST_RE421_API ATestActor : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	ATestActor();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

public:
	UFUNCTION(BlueprintCallable,Category = Test)
	void Key_Down();
	UFUNCTION(BlueprintCallable, Category = Test)
	void Key_Up();
	UFUNCTION(BlueprintCallable, Category = Test)
	void Key_Press();

public:
	UPROPERTY(EditAnywhere,BlueprintReadWrite,Category = Test)
	int32 TestData;
private:
	TSharedPtr<FInputTestDevice> InputTestDevice;

};

在该头文件里,我定义了3个有蓝图调用的函数分别模拟按键的按下,松开,字符输入,然后定义了一个变量模拟存在AgnosticKeys数组中Key的下标值,通过改变这个变量我们可以模拟键盘上的按键的按下,松开以及输入字符

TestActor.cpp

ATestActor::ATestActor()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;

	TestData = 0;
}

// Called when the game starts or when spawned
void ATestActor::BeginPlay()
{
	Super::BeginPlay();
	IInputTestDeviceModule InputTestModule =FModuleManager::Get().GetModuleChecked<IInputTestDeviceModule>("ProjectTest_Re421");
	if (!InputTestModule.GetInputDevicePtr())
	{
		InputTestModule.CreateInputDevice(MakeShareable(new FGenericApplicationMessageHandler()));
	}
	InputTestDevice = InputTestModule.GetInputDevicePtr();

}

// Called every frame
void ATestActor::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);
	
}

void ATestActor::Key_Down()
{
	
	FInputTestDevice::FEvent KeyEvent(FInputTestDevice::EventType::KEY_DOWN);
	KeyEvent.SetKeyDown(TestData, false);
	InputTestDevice->ProcessEvent(KeyEvent);

}

void ATestActor::Key_Up()
{
	FInputTestDevice::FEvent KeyEvent(FInputTestDevice::EventType::KEY_UP);
	KeyEvent.SetKeyUp(TestData);
	InputTestDevice->ProcessEvent(KeyEvent);
}

void ATestActor::Key_Press()
{
	FInputTestDevice::FEvent KeyEvent(FInputTestDevice::EventType::KEY_PRESS);
	KeyEvent.SetCharCode('s');
	InputTestDevice->ProcessEvent(KeyEvent);
}

我们在beginplay里获取到工程模块,并且通过工程模块获取到输入驱动类InputTestDevice,然后再定义有蓝图调用的函数中实现InputTestDevice驱动按键的事件,这里的输入字符我是固定传入按键“s”

最后编译打开UE4编辑器,创建一个继承自TestActor蓝图类放入场景里,创建Widget主界面蓝图,放置三个按钮,三个按钮分别调用TestActor里的Key_up,Key_down,Key_Press

另外,在TestActor蓝图里我把TestData设置成83,

在数组AgnosticKeys里下标83代表的是s键,意思是当点击按钮Key_down时,相当于按下键盘上的s键,在默认地图里s键是视角后移,此时,场景视角会一直后移,当点击按钮Key_up时,相当于松开s键,此时,场景视角停止后移,当点击Key_press时,相当于我们输出字符s,这里要注意的是,输入字符首先要光标聚焦在输入模式下,才能输入字符,如图,当我开启命令行,并且光标聚焦在输入模式下,点击Key_press,命令行里会输出符号s

最后还有个问题,就说这个InputTestDevice可能大家没有发现是在哪创建的(MessageHandler从哪传进来的),这个也困扰我很久了,最终发现InputDevice创建的地方:

if (!bHasLoadedInputPlugins)
	{
		TArray<IInputDeviceModule*> PluginImplementations = IModularFeatures::Get().GetModularFeatureImplementations<IInputDeviceModule>( IInputDeviceModule::GetModularFeatureName() );
		for( auto InputPluginIt = PluginImplementations.CreateIterator(); InputPluginIt; ++InputPluginIt )
		{
            //创建Inputdevice
			TSharedPtr<IInputDevice> Device = (*InputPluginIt)->CreateInputDevice(MessageHandler);
			AddExternalInputDevice(Device);			
		}

		bHasLoadedInputPlugins = true;
	}

这段代码是在WindowsApplication.cpp的void FWindowsApplication::PollGameDeviceState( const float TimeDelta )函数里执行的,这个函数的作用和调用时机暂时还有弄明白(萌新的UE4源码爬坑之旅真的很艰辛),希望有了解的可以告知下或者一起讨论研究研究!

还有,最后大家可能依旧还是不明白在什么地方会用到这些,UE4的像素流网页端的输入同步到本地服务的的输入就是用的这种方法!

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
以下是使用UE4键盘控制开关灯的详细步骤: 1. 首先,创建一个新的关卡,然后添加一个点光源(Light)和一个地面(Floor)。 2. 选择点光源,打开其属性编辑器(Details Panel),将Intensity属性设置为1000,Light Color属性设置为白色。 3. 在地面上创建一个Actor蓝图(Blueprint),并将其命名为“LightSwitch”。 4. 在LightSwitch蓝图中,添加一个静态网格组件(Static Mesh Component),并将其网格设置为一个开关的模型(Switch Model)。 5. 将开关模型的位置设置为地面上的一个合适位置,并将其缩放为合适的大小。 6. 在LightSwitch蓝图中,添加一个碰撞组件(Collision Component),并将其类型设置为Box Collision。 7. 在碰撞组件的属性中,将Collision Preset属性设置为OverlapAll,这样玩家就可以与开关进行互动。 8. 在LightSwitch蓝图中,添加一个布尔型变量(Boolean Variable),并将其命名为“IsLightOn”。 9. 在LightSwitch蓝图中,添加一个事件(Event),并将其类型设置为Input Action。 10. 将事件命名为“ToggleLight”,然后将其绑定到一个按键上(例如空格键)。 11. 在ToggleLight事件中,添加一个分支(Branch)节点,用于检查IsLightOn变量的值。 12. 如果IsLightOn为True,则将点光源的亮度设置为0,并将IsLightOn设置为False。 13. 如果IsLightOn为False,则将点光源的亮度设置为1000,并将IsLightOn设置为True。 14. 最后,将LightSwitch蓝图拖动到关卡中,并将其放置在开关模型的位置上。 现在,当玩家按下空格键时,开关模型将会切换,并且点光源的亮度也会相应地切换。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值