Gamasutra is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Gamasutra: The Art & Business of Making Gamesspacer
arrowPress Releases







If you enjoy reading this site, you might also want to check out these UBM Tech sites:


 

StreamSDK Powered SNES w/Online Multiplayer Tutorial

by Jeremy Alessi on 04/10/20 10:54:00 am   Expert Blogs   Featured Blogs

The following blog post, unless otherwise noted, was written by a member of Gamasutra’s community.
The thoughts and opinions expressed are those of the writer and not Gamasutra or its parent company.

 

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?

No alt text provided for this image

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!

No alt text provided for this image

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

No alt text provided for this image

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] 


Related Jobs

Deep Silver Volition
Deep Silver Volition — Champaign, Illinois, United States
[09.24.20]

Senior Engine Programmer
Deep Silver Volition
Deep Silver Volition — Champaign, Illinois, United States
[09.24.20]

Senior Technical Designer
Random42
Random42 — London, England, United Kingdom
[09.24.20]

UE4 Technical Artist
OPGG, Inc.
OPGG, Inc. — Remote, Remote, Remote
[09.08.20]

React JS Front-end Engineer (Fortnite) - Remote Hire





Loading Comments

loader image