Trending
Opinion: How will Project 2025 impact game developers?
The Heritage Foundation's manifesto for the possible next administration could do great harm to many, including large portions of the game development community.
Featured Blog | This community-written post highlights the best of what the game industry has to offer. Read more like it on the Game Developer Blogs or learn how to Submit Your Own Blog Post
How to convert a 30 year old SNES local multiplayer game into a modern cross-platform multiplayer game in under an hour with StreamSDK.
Introduction
Marketing a new product can be difficult. It’s doubly difficult when it’s in a new space, like real-time streaming … if that’s even the right way to classify StreamSDK. Pursuant to this challenge, one way to reach people is to present novel new demos that showcase the power of the platform and play off of something previously known to people.
Cue the StreamSDK SNES Teenage Mutant Ninja w/ Online Multiplayer Demo.
With this little piece, a nearly 30 year old game that was made for a console with no online capabilities can now be run on any platform and its two-player mode can be enjoyed between any two platforms from around the world.
In a nutshell, it’s a game people know, it’s a novel and powerful demonstration, and it highlights StreamSDK’s ability to empower things that just weren’t possible before.
Approach
Last weekend, the idea of converting older games to online multiplayer popped into mind. To be certain, this wasn’t the first time StreamSDK was used to convert a local multiplayer game into online multiplayer. During the first month of StreamSDK 3.0.0’s release, the Tanks demo from the Unity Asset Store as well as Swap Fire (Nintendo Wii U) both received the same treatment.
Of course, the Unity Tanks demo and Swap Fire are both pretty obscure. In short, they don’t fit the criteria of being previously known to people. Furthermore, it would be unsurprising for either of those Unity native games to re-emerge with online multiplayer capabilities.
So, the idea of instantly converting entire libraries of games, that were previously unplayable with online multiplayer, into online multiplayer was very intriguing. It also seemed like an impossibility at first. A short search quickly brought hope though. Sure enough, there was a MAME emulator written in C#. Wow! Maybe that could work with Unity?
More searching resulted in better prospects. It really seemed like there was a solution out there. Then, finally … a Game Boy emulator written directly in Unity appeared. The result was that an idea conceived at noon was actually functional by midnight (and there was a load of family time in between since it was a Sunday ;)
Acceleration
The StreamSDK Game Boy was neat, but to really achieve the original idea, the Super Nintendo would be golden. The idea of using the code structure from the Game Boy emulator to create a virtual SNES was an option but thankfully, more searching turned up Libretro and several Libretro powered Unity projects. Most didn’t work… and they were usually VR demos. Then finally, a bare bones example emerged called RetroUnity. It didn’t work out of the box, but by mixing and matching some elements from the other projects, it became functional. Making it work with StreamSDK after that was cake.
Technical (updated to represent StreamSDK3.1.0)
Now for the cream filling; just how does StreamSDK interface with a 30 year old game and make it online multiplayer?
Download RetroUnity project from GitHub
Import StreamSDK assets per docs at www.StreamSDK.com
Project View, Create Folder “StreamSDK/Experimental”
Project View, Drag “Plugins/RetroUnity into StreamSDK/Experimental”
Hierarchy, Create Empty
Rename, “RetroUnityContent”
Hierarchy, Drag all other game objects onto “RetroUnityContent”
Hierarchy, Make Prefab, Drag “RetroUnityContent” to Project View “StreamSDK/Experimental/RetroUnity”
Project View, Open “StreamSDK/Demo/Car3D/CarCloudcast.unity”
Hierarchy, Expand “Content”
Hierarchy, Delete children of “Content” (DO NOT DELETE StreamDisplayCanvas)
Project View, Drag “StreamSDK/Experimental/RetroUnity/RetroUnityContent.prefab” onto existing “Content” game object in the Hierarchy
Right Click, Unpack Prefab
Inspector, Set StreamSDK
Inspector, Set StreamSDK
Axes Size = 10
Element 0 = A
Element 1 = B
Element 2 = X
Element 3 = Y
Element 4 = DpadX
Element 5 = DpadY
Element 6 = START
Element 7 = SELECT
Element 8 = L
Element 9 = R
Stream Camera = (Scene) “Content/RetroUnityContent/Main Camera”
Hierarchy, Select StreamSDKTransporter
Inspector, Set StreamSDKTransporter
Room Name = SNES
Send Rate = 60
Project View, Open “StreamSDK/Experimental/RetroUnity/Scripts/LibreroWrapper.cs”
On ~line 30 Add
public bool streaming;
On ~line 43 Add
public static LibretroWrapper instance;
On ~line 65 Add
instance = this;
On ~line 362, Replace Method public static short RetroInputState with:
public static short RetroInputState(uint port, uint device, uint index, uint id) {
if( instance == null || StreamSDK.instance == null )
return 0;
if( instance.streaming ) {
int i = (int)port;
switch (id) {
case 0:
return StreamSDK.GetAxis("B", i ) != 0 ? (short) 1 : (short)0;
case 1:
return StreamSDK.GetAxis("Y", i ) != 0 ? (short) 1 : (short)0;
case 2:
return StreamSDK.GetAxis("SELECT", i ) != 0 ? (short) 1 : (short)0;
case 3:
return StreamSDK.GetAxis("START", i ) != 0 ? (short) 1 : (short)0;
case 4:
return StreamSDK.GetAxis("DpadX", i ) >= 1.0f ? (short) 1 : (short)0;
case 5:
return StreamSDK.GetAxis("DpadX", i ) <= -1.0f ? (short) 1 : (short)0;
case 6:
return StreamSDK.GetAxis("DpadY", i ) <= -1.0f ? (short) 1 : (short)0;
case 7:
return StreamSDK.GetAxis("DpadY", i ) >= 1.0f ? (short) 1 : (short)0;
case 8:
return StreamSDK.GetAxis("A", i ) != 0 ? (short) 1 : (short)0;
case 9:
return StreamSDK.GetAxis("X", i ) != 0 ? (short) 1 : (short)0;
case 10:
return StreamSDK.GetAxis("L", i ) != 0 ? (short) 1 : (short)0;
case 11:
return StreamSDK.GetAxis("R", i ) != 0 ? (short) 1 : (short)0;
case 12:
return Input.GetKey(KeyCode.E) ? (short) 1 : (short) 0;
case 13:
return Input.GetKey(KeyCode.R) ? (short) 1 : (short) 0;
case 14:
return Input.GetKey(KeyCode.T) ? (short) 1 : (short) 0;
case 15:
return Input.GetKey(KeyCode.Y) ? (short) 1 : (short) 0;
default:
return 0;
}
return 0;
} else {
switch (id) {
case 0:
return Input.GetKey(KeyCode.Z) || Input.GetButton("B") ? (short) 1 : (short) 0; // B
case 1:
return Input.GetKey(KeyCode.A) || Input.GetButton("Y") ? (short) 1 : (short) 0; // Y
case 2:
return Input.GetKey(KeyCode.Space) || Input.GetButton("SELECT") ? (short) 1 : (short) 0; // SELECT
case 3:
return Input.GetKey(KeyCode.Return) || Input.GetButton("START") ? (short) 1 : (short) 0; // START
case 4:
return Input.GetKey(KeyCode.UpArrow) || Input.GetAxisRaw("DpadX") >= 1.0f ? (short) 1 : (short) 0; // UP
case 5:
return Input.GetKey(KeyCode.DownArrow) || Input.GetAxisRaw("DpadX") <= -1.0f ? (short) 1 : (short) 0; // DOWN
case 6:
return Input.GetKey(KeyCode.LeftArrow) || Input.GetAxisRaw("DpadY") <= -1.0f ? (short) 1 : (short) 0; // LEFT
case 7:
return Input.GetKey(KeyCode.RightArrow) || Input.GetAxisRaw("DpadY") >= 1.0f ? (short) 1 : (short) 0; // RIGHT
case 8:
return Input.GetKey(KeyCode.X) || Input.GetButton("A") ? (short) 1 : (short) 0; // A
case 9:
return Input.GetKey(KeyCode.S) || Input.GetButton("X") ? (short) 1 : (short) 0; // X
case 10:
return Input.GetKey(KeyCode.Q) || Input.GetButton("L") ? (short) 1 : (short) 0; // L
case 11:
return Input.GetKey(KeyCode.W) || Input.GetButton("R") ? (short) 1 : (short) 0; // R
case 12:
return Input.GetKey(KeyCode.E) ? (short) 1 : (short) 0;
case 13:
return Input.GetKey(KeyCode.R) ? (short) 1 : (short) 0;
case 14:
return Input.GetKey(KeyCode.T) ? (short) 1 : (short) 0;
case 15:
return Input.GetKey(KeyCode.Y) ? (short) 1 : (short) 0;
default:
return 0;
}
}
}
Save LibretroWrapper.cs
Open Unity > Project Settings > Input, Setup Axes:
A
B
X
Y
DpadX
DpadY
START
SELECT
L
R
Save Scene as “StreamSDK/Experimental/RetroUnity/RetroUnityStream.unity”
Hierarchy, Select StreamSDKTransporter
Inspector, Set StreamSDKTransporter
Room Name = SNES
Send Rate = 60
Save Scene as “StreamSDK/Experimental/RetroUnity/RetroUnityControl.unity”
Playtime!
With a few downloads, a few drag ’n drops, and some very minor code editing; 1992’s Teenage Mutant Ninja Turtles: Turtles in Time is now a cross-platform, online multiplayer title.
The “RetroUnityStream.unity” scene is a “caster” and the “RetroUnityControl.unity” scene is a “controller”.
Build and run the “caster” on one machine and then build and run the “controller” on other machines (anywhere in the world) to connect to the “caster” and play the game.
Poof! It’s like magic!
Conclusion
As the Genie from Aladdin would say, "Uh , ah, almost. There are a few, uh, provisos. Ah, a couple of quid pro quo".
StreamSDK’s mission is Any-to-Any™, the SNES demos are somewhat limited because Libretro is not Unity native. However, it does have wrappers for almost every platform in existence, so it’s not far off. At this current time the “caster” has been run from a Mac and a PC, but it seems to run considerably better from the PC; indicating that the PC wrapper for the SNES core library (a sub-component used by Libretro) is superior to its Mac counterpart. Other platform core dll’s have not been checked yet.
The “controller” on the other hand is completely cross-platform and can be run from any device. This tightly fits StreamSDK’s Any-to-Any™ mission. And, although the “caster” might be limited, it does not require a big infrastructure to be useful. It is still decentralized and able to be leveraged by anyone with a computer and an Internet connection.
This was a fun little experiment with a classic game and a testament to the ease of use as well as the power of StreamSDK. Thanks for reading, and as always send any questions, concerns, or thoughts to [email protected]
Read more about:
Featured BlogsYou May Also Like