TwitchAPI is widely used in many apps and games. Recently Twitch published “Twitch Game Engine Plugins” for everyone. Integrating TwitchAPI with your game has never been that easy.
Initial setup
Go to: https://dev.twitch.tv/docs/game-engine-plugins/
There you can read about Game Engine Plugins and download one for UE5 (Twitch recommends using 5.2 or later).
Plugin lets you use C++ or Blueprints.
Next, you have to create your application on https://dev.twitch.tv/console/apps
Input name of the game you will be creating, add any necessary OAuth URLs (I’ve added localhost for local testing), select proper category (most likely “Game Integration”) and choose “Client Type” (also most likely “Public”).
After you press “Create” button, navigate to your applications page and press “Manage” button.
From here you need to get your application “Client ID”. You will need this later when you will be configuring the TwitchSDK plugin inside your UE5 project.
You are all set to configure your project. Now you need to authenticate your project before you can use TwitchSDK. Refer to https://dev.twitch.tv/docs/game-engine-plugins/unreal-guide-cpp/ if you want to use C++ implementation or https://dev.twitch.tv/docs/game-engine-plugins/unreal-guide-blueprints/ if you want to use Blueprints. Remember, C++ implementation needs some additional steps when configuring your project. Remember to authenticate with the URL generated by TwitchSDK in a web browser and complete the authentication process.
As of writing this post, Twitch Game Engine Plugin for UE5 has implemented only some TwitchAPI features. I needed to use Twitch chat in my project. I will show you now how to implement the “channel.chat.message” EventSub subscription type. This subscription will let you read messages from chat. Later you can add any more EventSub subscriptions from TwitchAPI. Here is a link to the documentation of this EventSub subscription. You can find any needed information about implementation. https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#channelchatmessage.
Subscribe to channel.chat.messsage event
First we need to add a new OAuth Scope to allow our application subscribe to channel.chat.message event. Navigate to the TwitchSDK plugin in your project and open Plugins/TwitchSDK/Source/Public/TwitchSDK.h.
UENUM(BlueprintType)
enum class FTwitchSDKOAuthScope : uint8 {
ChannelManagePolls UMETA(DisplayName = "channel:manage:polls"),
ChannelManagePredictions UMETA(DisplayName = "channel:manage:predictions"),
ChannelManageBroadcast UMETA(DisplayName = "channel:manage:broadcast"),
ChannelManageRedemptions UMETA(DisplayName = "channel:manage:redemptions"),
ChannelReadHype_Train UMETA(DisplayName = "channel:read:hype_train"),
ClipsEdit UMETA(DisplayName = "clips:edit"),
UserReadSubscriptions UMETA(DisplayName = "user:read:subscriptions"),
BitsRead UMETA(DisplayName = "bits:read"),
UserReadChat UMETA(DisplayName = "user:read:chat"),
UserBot UMETA(DisplayName = "user:bot"),
ChannelBot UMETA(DisplayName = "channel:bot"),
};
C++Here add “UserReadChat”, “UserBot”, “ChannelBot” enum entries. You will need to add these scopes when authenticating the app.
https://dev.twitch.tv/docs/authentication/scopes/#twitch-api-scopes here you can check any TwitchAPI scopes.
Now let’s add a new FTwitchSDKEventStreamKind enum entry. Navigate to Plugins/TwitchSDK/Source/Public/TwitchSDKStructs.h.
/** The type of an EventSub subscription. */
UENUM(BlueprintType)
enum class FTwitchSDKEventStreamKind : uint8
{
/** A notification when a specified channel receives a subscriber. This does not include resubscribers. */
Subscription = 0,
/** A specified channel receives a follow. */
Follower = 1,
/** A user cheers on the specified channel. */
Cheer = 2,
/** A viewer has redeemed a custom channel points reward on the specified channel
or the redemption has been updated (i.e., fulfilled or cancelled). */
CustomRewardRedemption = 3,
/** A Hype Train makes progress on the user's channel. Requires the channel:read:hype_train scope. */
HypeTrain = 4,
/** A broadcaster raids another broadcaster’s channel. */
ChannelRaid = 5,
/** Any user sends a message to a specific chat room. */
ChannelChatMessage = 6,
};
C++We need to add the same enum type in Plugins\TwitchSDK\core\include\r66_structs_autogenerated.hpp.
/// <summary>
/// The type of an EventSub subscription.
/// </summary>
enum class EventStreamKind : uint8_t
{
/// <summary>
/// A notification when a specified channel receives a subscriber. This does not include resubscribers.
/// </summary>
Subscription = 0,
/// <summary>
/// A specified channel receives a follow.
/// </summary>
Follower = 1,
/// <summary>
/// A user cheers on the specified channel.
/// </summary>
Cheer = 2,
/// <summary>
/// A viewer has redeemed a custom channel points reward on the specified channel or the redemption
/// has been updated (i.e., fulfilled or cancelled).
/// </summary>
CustomRewardRedemption = 3,
/// <summary>
/// A Hype Train makes progress on the user's channel.
/// Requires the <c>channel:read:hype_train</c> scope.
/// </summary>
HypeTrain = 4,
/// <summary>
/// A broadcaster raids another broadcaster’s channel.
/// </summary>
ChannelRaid = 5,
/// <summary>
/// Any user sends a message to a specific chat room.
/// </summary>
ChannelChatMessage = 6,
};
C++We will need this when we want to subscribe to “channel.chat.message” event in EventSub.
In the TwitchSDKStructs.h add new event type
/** A specified channel chat receives a message. */
USTRUCT(BlueprintType)
struct FTwitchSDKUserMessageSentEvent {
GENERATED_BODY()
public:
FTwitchSDKUserMessageSentEvent() = default;
FTwitchSDKUserMessageSentEvent(const R66::UserMessageSentEvent& x)
: BroadcasterUserId(R66::ToFString(x.BroadcasterUserId)), BroadcasterUserName(R66::ToFString(x.BroadcasterUserName)),
BroadcasterUserLogin(R66::ToFString(x.BroadcasterUserLogin)), ChatterUserId(R66::ToFString(x.ChatterUserId)),
ChatterUserName(R66::ToFString(x.ChatterUserName)), ChatterUserLogin(R66::ToFString(x.ChatterUserLogin)),
MessageId(R66::ToFString(x.MessageId)), Text(R66::ToFString(x.Text)),
MessageType(R66::ToFString(x.MessageType)){}
operator R66::UserMessageSentEvent() const {
R66::UserMessageSentEvent v;
v.BroadcasterUserId = R66::FromFString(BroadcasterUserId);
v.BroadcasterUserName = R66::FromFString(BroadcasterUserName);
v.BroadcasterUserLogin = R66::FromFString(BroadcasterUserLogin);
v.ChatterUserId = R66::FromFString(ChatterUserId);
v.ChatterUserName = R66::FromFString(ChatterUserName);
v.ChatterUserLogin = R66::FromFString(ChatterUserLogin);
v.MessageId = R66::FromFString(MessageId);
v.Text = R66::FromFString(Text);
v.MessageType = R66::FromFString(MessageType);
return v;
}
/** The broadcaster ID. */
UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Twitch") FString BroadcasterUserId;
/** The broadcaster display name. */
UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Twitch") FString BroadcasterUserName;
/** The broadcaster login. */
UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Twitch") FString BroadcasterUserLogin;
/** The user ID of the user that sent the message. */
UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Twitch") FString ChatterUserId;
/** The user name of the user that sent the message. */
UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Twitch") FString ChatterUserName;
/** The user login of the user that sent the message. */
UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Twitch") FString ChatterUserLogin;
/** A UUID that identifies the message. */
UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Twitch") FString MessageId;
/** The chat message in plain text. */
UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Twitch") FString Text;
/** The type of message. */
UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Twitch") FString MessageType;
};
C++This UStructure is a representation of the information that EventSub sends through subscription. All properties are listed here https://dev.twitch.tv/docs/eventsub/eventsub-reference/#channel-chat-message-event. I’ve omitted some properties that I didn’t need for this demo.
For this UStructure to properly work we need vanilla C++ struct that will be used when we are parsing received event information from EventSub through WebSocket. Navigate to Plugins\TwitchSDK\core\include\r66_structs_autogenerated.hpp and add a new struct in the R66 namespace. This struct has all properties needed for the UStruct above.
/// @class UserMessageSentEvent r66_structs_autogenerated.hpp r66.hpp
/// <summary>
/// User sends a message to the broadcaster's chat.
/// </summary>
struct UserMessageSentEvent {
/// <summary>
/// The broadcaster user ID.
/// </summary>
R66::string_holder BroadcasterUserId;
/// <summary>
/// The broadcaster display name.
/// </summary>
R66::string_holder BroadcasterUserName;
/// <summary>
/// The broadcaster login.
/// </summary>
R66::string_holder BroadcasterUserLogin;
/// <summary>
/// The user ID of the user that sent the message.
/// </summary>
R66::string_holder ChatterUserId;
/// <summary>
/// The user name of the user that sent the message.
/// </summary>
R66::string_holder ChatterUserName;
/// <summary>
/// The user login of the user that sent the message.
/// </summary>
R66::string_holder ChatterUserLogin;
/// <summary>
/// A UUID that identifies the message.
/// </summary>
R66::string_holder MessageId;
/// <summary>
/// The chat message in plain text.
/// </summary>
R66::string_holder Text;
/// <summary>
/// The type of message.
/// </summary>
R66::string_holder MessageType;
};
C++Now let’s make a new ESSubscription for “channel.chat.message” subscription. Navigate to Plugins\TwitchSDK\core\src\r66_es.cpp. We need to check what is the condition of subscribing to the event. This can be checked here https://dev.twitch.tv/docs/eventsub/eventsub-reference/#channel-chat-message-condition.
First parameter is the type of subscription, second is the version and the last one is the condition name. As you can see in the documentation, this event has two required condition elements. I will address this later, for now let’s make it the first one from the documentation.
Now let’s make wait function that we will use to receive messages sent by users in our project. Navigate to the Plugins\TwitchSDK\core\include\r66api_autogenerated.hpp. Here scroll down to the “Wait” functions declarations and add one for the UserMessageSent event.
/// <summary>
/// User sends message to broadcaster's chat.
/// </summary>
/// <remarks>You may only call this with a subscription for the correct event type.</remarks>
/// <param name="desc">An object describing the subscription.</param>
/// <returns>The event.</returns>
/// <seealso cref="SubscribeToEventStream" />
virtual void WaitForUserMessageSentEvent(const EventStreamDesc& desc, ResolveFn<const UserMessageSentEvent&> resolve, RejectFn reject) = 0;
C++Now, we need to add definition of this function in R66::R66ApiImpl class. Navigate to Plugins\TwitchSDK\core\src\r66_impl.hpp. Here, add
/* Other WaitFor… function definitions*/
virtual void WaitForUserMessageSentEvent(const EventStreamDesc& desc, ResolveFn<const UserMessageSentEvent&> resolve, RejectFn reject) override;
C++Also while we are in this class, let’s add MulticastEventQueue<> with UserMessageSentEvent struct type.
/* Other MulticastEventQueue<...> …Listeners */
MulticastEventQueue<UserMessageSentEvent> UserMessageSentListeners;
C++Now, let’s go back to the Plugins\TwitchSDK\core\src\r66_es.cpp. Scroll down to the bottom of the file and add two entries: one for ES_IMPL_EVENT() and another one for ES_MM_ENTRY() in the MetaMap.
These macros create implementation of the “WaitFor” function we defined in the interface and create entry in the MetaMap for mapping EventStreamKind with MulticastEventQueue<> we defined earlier.
Now let’s implement parsing information from the JSON event object to our struct that we created earlier. In the same file, navigate to the
void R66::R66ApiImpl::ESReceiveNotification(const Self& self, const JsonRef& json, string_view messageId)
C++method and scroll down to the if statement where we check what “subType” is being received. Here add
else if (subType == UserMessageSent.Type)
{
auto e = json.at("event");
UserMessageSentEvent umse;
umse.BroadcasterUserId = e.at("broadcaster_user_id").to_string();
umse.BroadcasterUserName = e.at("broadcaster_user_name").to_string();
umse.BroadcasterUserLogin = e.at("broadcaster_user_login").to_string();
umse.ChatterUserId = e.at("chatter_user_id").to_string();
umse.ChatterUserName = e.at("chatter_user_name").to_string();
umse.ChatterUserLogin = e.at("chatter_user_login").to_string();
umse.MessageId = e.at("message_id").to_string();
umse.Text = e.at("message").at("text").to_string();
self->UserMessageSentListeners.Push(umse);
}
C++Now scroll up to the
void R66ApiImpl::ESUpdateSubscriptions(Self self, string broadcasterId)
C++method, and here we will address the condition of the EventSub type. We need to add the second required condition here manually to the condition JSON body. We are setting the “user_id” property to be the same value as “broadcaster_id”. This just tells the subscription that our code is reading broadcaster chat as a broadcaster user.
// register
Json body, condition, transport;
condition.set_property(subscription->FilterConditionName, Json(broadcasterId));
if (subscription->Type == STR("channel.follow"sv))
{
condition.set_property("moderator_user_id", Json(broadcasterId));
}
else if (subscription->Type == STR("channel.chat.message"sv))
{
condition.set_property("user_id", Json(broadcasterId));
}
C++BlueprintAsync node
If you want to use this subscription in Blueprints, you need to create a BlueprintAsync node. This is created based on the different nodes supplied with the TwitchSDK plugin. Take any “ApiWaitFor…Event” class and change types for our new subscription event types. I’ve added this class to the private source folder of the plugin.
#pragma once
#include "CoreMinimal.h"
#include "Kismet/BlueprintAsyncActionBase.h"
#include "TwitchSDKStructs.h"
#include "ApiWaitForUserMessageSentEvent.generated.h"
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FAsyncDoneApiWaitForUserMessageSentEvent, FTwitchSDKUserMessageSentEvent, Result, FString, Error);
/**
*
*/
UCLASS(meta = (HideThen = true))
class UApiWaitForUserMessageSentEvent : public UBlueprintAsyncActionBase
{
GENERATED_BODY()
FTwitchSDKEventStreamDesc Desc;
public:
UPROPERTY(BlueprintAssignable)
FAsyncDoneApiWaitForUserMessageSentEvent Done;
UPROPERTY(BlueprintAssignable)
FAsyncDoneApiWaitForUserMessageSentEvent Error;
virtual void Activate() override;
/**
* A specified channel chat receives a message.
*
* You may only call this with a subscription for the correct event type.
*
* @param desc An object describing the subscription.
*/
UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true"), Category = "Twitch")
static UApiWaitForUserMessageSentEvent* WaitForUserMessageSentEvent(FTwitchSDKEventStreamDesc desc);
};
C++#include "ApiWaitForUserMessageSentEvent.h"
void UApiWaitForUserMessageSentEvent::Activate() {
TWeakObjectPtr<UApiWaitForUserMessageSentEvent> weak(this);
auto exception_handler = [weak](const std::exception& e) {
if (weak.IsValid() && weak->Error.IsBound())
weak->Error.Broadcast(FTwitchSDKUserMessageSentEvent(), FString(e.what()));
else
UE_LOG(LogTwitchSDK, Error, TEXT("WaitForUserMessageSentEvent error: %s"), UTF8_TO_TCHAR(e.what()));
};
auto v = Desc;
try {
FTwitchSDKModule::Get().Core->WaitForUserMessageSentEvent(
v,
[weak](const R66::UserMessageSentEvent& r) {
if (!weak.IsValid()) return;
FTwitchSDKUserMessageSentEvent v(r);
weak->Done.Broadcast(v, FString());
},
exception_handler
);
}
catch (const std::exception& e) {
exception_handler(e);
}
}
UApiWaitForUserMessageSentEvent* UApiWaitForUserMessageSentEvent::WaitForUserMessageSentEvent(FTwitchSDKEventStreamDesc desc) {
auto ptr = NewObject<UApiWaitForUserMessageSentEvent>();
ptr->Desc = desc;
return ptr;
}
C++C++ subscribe to event method
For using this subscription in C++ we need to create a function in the Plugins\TwitchSDK\core\include\r66.hpp file. Here add:
/** @copydoc UserMessageSentEvent */
TwitchEventStream<UserMessageSentEvent> SubscribeToUserMessageSentEvents() { return { get_shared_from_this(), EventStreamKind::ChannelChatMessage, &IR66ApiCommon::WaitForUserMessageSentEvent }; }
C++You can subscribe to this event the same way like in the getting started examples https://dev.twitch.tv/docs/game-engine-plugins/unreal-guide-cpp/
Summary
That is all. Now after you authenticate with the new TwitchAPI scopes we added, you should be able to subscribe to the “channel.chat.message” event with the “SubscribeToEventStream” node or use “SubscribeToUserMessageSentEvents()” function that we created for C++ use. Remember to put your application “Client ID” inside plugin settings in the project settings editor window.
All you need now is to go to your Twitch channel www.twitch.tv/your-twitch-username and type something in the chat.
Please check out our demo project and see how we connected Twitch chat to the gameplay:
https://github.com/TheSamurais/UE5TwitchIntegration