⬆️ ⬇️

Unreal LED, or load management from Unreal Tournament

The idea may seem like a mockie, but it deserves the right to life. Here we will focus on copying events from virtual reality to the outside, that is, in the direction opposite to the usual replication of reality into virtuality. I call it “augmented reality the other way around.” The idea is to send HTTP requests to the Arduino Ethernet Shield. From UT.



Intro



Perhaps, every buyer of the Ethernet module or shield has made a server to turn on the LED from * Duino, and many sooner or later sewed it to some client like an Android smartphone. I recently had a thought - why not use the Unreal Tournament as a client? UT is an excellent visualization environment, and besides, the engine itself is very convenient and flexible.



Knowledge of the editor at the interface level is enough. The source for UnrealScript will be explained in the course of writing our client, and in general UnrealScript has the usual syntax with quite clear names of functions. For those who see the Unreal Editor for the first time, but still want to try, I recommend reading the article VBKesha Introducing UnrealEngine. Part 1 , and to be honest, I myself am very much looking forward to continuing this course, because in network replication, for example, I am a complete dunduk. All my weapons made for UT99 only work correctly in single / offline mode.



Server



In general, it is assumed that the reader already knows everything about the issue of creating a web server on the Arduino. Nevertheless, repetition is the mother of the teachings, so just in case I cite one of the variants of this example.

')

Server Description
The hardware is represented by an Ethernet module or shield on ENC28J60, for the construction of the sketch, Dr. Ether_28J60 library is used . Monk . It is a wrapper for the Nuelectronics library. Unpack the archives from here and from here to the% arduino% \ libraries directory, take the WebRemote example, change it to fit your needs, build according to the scheme (for a separate module):

Board ENC28J60Arduino
SIMOSI (D11)
SOMISO (D12)
SCLSCK (D13)
RstRESET
5V5V
GNDGND
CSD10
On the D6 output of the Arduino, the LED (through a resistor), the anode (plus) towards the dyne, the cathode towards the ground.



The sketch for me is as follows:

#include "EtherShield.h" #include "ETHER_28J60.h" int outputPin = 6; static uint8_t mac[6] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED}; static uint8_t ip[4] = {192, 168, 0, 40}; static uint16_t port = 80; ETHER_28J60 e; void setup(){ e.setup(mac, ip, port); pinMode(outputPin, OUTPUT); } void loop(){ char* params; if (params = e.serviceRequest()){ e.print("<H1>Web Remote</H1>"); if (strcmp(params, "?cmd=on") == 0) digitalWrite(outputPin, HIGH); else if (strcmp(params, "?cmd=off") == 0) digitalWrite(outputPin, LOW); e.print("LED state: <br>"); if(digitalRead(outputPin)==1) e.print("on"); else e.print("off"); e.print(".<br>"); e.print("<A HREF='?cmd=on'>Turn on</A><br>"); e.print("<A HREF='?cmd=off'>Turn off</A>"); e.respond(); } } 


Pig



To fulfill the request, we need a client class to which we can pass our parameters at run time. Fortunately, there is already such a class there, it is called UBrowser.UBrowserHTTPClient. There is no UBrowser pack in UT2003 / 2004, but there is UWeb.WebApplication and its subclasses. Consider sending a request from UT99, and for older versions it will be possible to sharpen by analogy. UBrowserHTTPClient can only do a GET request, but you can teach it and POST. Actually, you can even change the HTTP-headers in any way, and implement not only the HTTP protocol, but also some other one.



UBrowserHTTPClient does not have a visual display in the editor (mesh or sprite texture) - if you add it to the level, it will appear there, but the editor will not show it. Therefore, we will create a more convenient "wrapper" for it, at the same time endowing with a couple of variables that can be edited at the level. As the parent (superclass), we use the trigger class.



Now we will continue what was not discussed in the article VBKesha . Above the working area with views (viewports) there are browser call buttons (they look like this: ). We are interested in Actor Classes, opens with a button that looks like a pawn (the very first). If there is no checkbox field in this window, it is activated with the command View -> Show packages from the menu. It is necessary to include, it will be needed, since we are going to create our own pack with scripts.



Browser Actor Classes Class creation



To create a test trigger that allows us to manage the request, open the Triggers group in this window (principle - as in the Windows registry editor), then select the Trigger and press the button . The top line of the window that appears is the name of the file with a pack for the class being created, the bottom line is the name of the class. So, if you press Enter, you get the class MyPackage.MyTrigger. I called my own stdHTTPTrig.stdHTTPTrigger. Next, write the actual code:



 class stdHTTPTrigger expands Trigger; var() string BrowseURL,BrowseArg; function Touch(actor Other){ local UBrowserHTTPClient uhc; foreach allactors(class'UBrowserHTTPClient',uhc) uhc.destroy(); uhc=spawn(class'UBrowserHTTPClient'); if(uhc != none) uhc.Browse(browseurl,browsearg); } 


The script editor (where to write the source code) is opened by double clicking on the class, and automatically when creating the class; compilation done button (Compile changed scripts) at the top of the window. I do not advise pressing Compile ALL - first, the UE will think for a long time, then it will complain that some classes are not found (due to the fact that only the necessary things are loaded), and then, most likely, it will make the GPF and fall out, or it will hang tightly shoot with three fingers. After compilation, the pack must be saved; to do this, we find its name (what was written in the Package name line) at the bottom of the Actor Classes window, put a flag opposite it, and press the button . By the way, if you need to quickly correct the source and save, it is convenient to open the class browser window with the button without getting out of the script editor (if the latter is maximized).



As you can see, the considered trigger acts quite roughly: it destroys all the client’s actors in the level in general (just to save memory), then creates a new one, and looks at the specified resource. After the browse () call, the client is not destroyed, since it takes some time to work. The browse () method has two optional parameters: TCP port and timeout. Using the latter, you can track the success of the request, thus assessing the availability of the resource. Also, this client has callback functions for returning server response and error handling. To enable this feature, you need to override these functions, that is, to create your own subclass, the parent of which will be UBrowserHTTPClient.



Test Level 1


The test room consists of two triggers, one PlayerStart and light sources. The BrowseURL properties are the same for both - this is the IP address of the server. BrowseArg is a parameter consisting of the resource name (slash /), and data (question mark and variables), that is, one has /? Cmd = on , the other has /? Cmd = off . The properties window opens with the RMB on the object -> properties, or F4. If F4 is not pressed, you must click on any view with the left mouse button. Having drawn a room, we press F6, in the opened window (level properties) we find LevelInfo, there - DefaultGameType. We change to singleplayer and press Enter (UnrealShare.SinglePlayer will be substituted), otherwise a bunch of bots will appear and interfere. Then build the level (Rebuild geometry), save, run. Burning from a blaster on triggers (by default, their radius of action is 40), enjoy the effect.







Getting server response



Now, having checked out the idea in general terms, you can proceed to the development of the final one. If you plan to call the pack with the new class in the same way, and the old “oak” trigger is not needed, exit the editor and do something with the file packname.u in the% ut99% \ system directory (packname is what we called the pack with the class ,% ut99% - path to Unreal Tournament): move to the old directory, for example. Or immediately in the basket, since we will not need it. Start the editor again.



Level events



The U engine has no other mechanism for creating events, except bypassing all the actors in turn with suitable properties, and calling the appropriate functions. For all types of triggers, the Tag property is used for this - a variable of type name (object name) and the Trigger () function, Untrigger (). Untrigger () is intended for the effect of "destroying" an event, for example, in sliding doors. A trigger is placed in the middle of the opening, creating an event specified in the Tag's property of movers (Mover, or Moveable brush - moving brush, or brush) that forms the door, and the TriggerControl is specified in the InitialState property (in the Object group) - a mode specifically designed for this behavior. When a player enters the range of a trigger, the Trigger () function is called by the movers, while leaving the radius, the Untrigger () function is called. As a result, the doors will open only while the player is in range of the trigger. We will do the same, pursuing 2 goals: usability when building a level, and compatibility.



In this example, in fact, only the on / off state is checked. This is done using the condition and the function of finding the substring in the string. But nothing prevents you from developing this system before pulling out of the server a certain value, say, PWM levels for RGB LEDs. However, please note that the RGB LED requires data in the form of RGB, and for Unreal the color will have to be recalculated in HSB.



The new trigger will have 8 line patterns that should be searched for in the server response, and the same number of names for creating / destroying events in case the line is found / not found. If desired, you can increase, remove, etc. For HTTP errors, only the creation of events is provided - IMHO, the use of Untrigger is not necessary in this case. Again, no one forbids adding.



Open the class browser (Actor Classes), open the Actor \ Info \ InternetInfo \ InternetLink \ TcpLink \ UBrowserBufferedTcpLink group in it, select the UBrowserHTTPClient class, click the button (New script). Specify the class name and pack, write the code:



 class stdHTTPBrowser expands UBrowserHTTPClient; var string SearchRespData[8]; var name SearchRespEvent[8]; var name SearchNotInRespEvent[8]; var name SearchRespCancelEvent[8]; var name SearchNotInRespCancelEvent[8]; var int HTTPErrCode[8]; var name HTTPErrEvent[8]; event HTTPReceivedData(string Data){ local int i; local actor a; for(i=0;i<=7;i++){ if(SearchRespData[i] != ""){ if(InStr(Caps(Data),Caps(SearchRespData[i])) != -1){ if(SearchRespEvent[i] != '') foreach allactors(class'Actor',a,SearchRespEvent[i]) a.Trigger(self,self.instigator); if(SearchRespCancelEvent[i] != '') foreach allactors(class'Actor',a,SearchRespCancelEvent[i]) a.Untrigger(self,self.instigator); }else{ if(SearchNotInRespEvent[i] != '') foreach allactors(class'Actor',a,SearchNotInRespEvent[i]) a.Trigger(self,self.instigator); if(SearchNotInRespCancelEvent[i] != '') foreach allactors(class'Actor',a,SearchNotInRespCancelEvent[i]) a.Untrigger(self,self.instigator); } } } } event HTTPError(int Code){ local int i; local actor a; for(i=0;i<=7;i++){ if(HTTPErrCode[i] == Code && HTTPErrEvent[i] != '') foreach allactors(class'Actor',a,HTTPErrEvent[i]) a.Trigger(self,self.instigator); } } 


The name type, IMHO, is not much different from string, except that there is a localized string for strings in Chinese, Russian, etc. However, it is extremely important: under test conditions, empty names are '' (in single quotes), empty strings - "" (in double quotes). The event keyword is the same as the callback in the source code for the Windows API. In short, the callback, that is, the function does not run a class script, but an external event.



Anticipating the comments akin to Bo’s shop “climbed into the hole,” rejoicing that we will not add this class to the level with the Add YourHTTPClientClass here command. The spawn () function in the trigger will do this for us. In my opinion, this is the most appropriate class for such things. Therefore, we close the script editor window, and in the class browser open the Actor \ Triggers group and select the Trigger . Click the New script and write the trigger code:



 class stdHTTPTriggerX expands Trigger; var() enum EHTTPTriggerLaunchType{ // launch type HTTLT_Touch, HTTLT_Trigger } HTTPTriggerLaunchType; var() string SearchRespData[8]; // search template in HTTP response var() name SearchRespEvent[8]; // events generated on match var() name SearchNotInRespEvent[8]; // events generated if no match var() name SearchRespCancelEvent[8]; // events cancelled on match var() name SearchNotInRespCancelEvent[8]; // events cancelled if no match var() int HTTPErrCode[8]; // HTTP error templates var() name HTTPErrEvent[8]; // events generated if defined HTTP error(s) ocurrs var() string BrowseURL,BrowseArg; // URL var() bool bAutoExec; // enable auto launch var stdHTTPBrowser hClient; // client handle function CreateClientRequest(){ local int i; if(hClient != none) hClient.destroy(); // destroy previous request hClient=spawn(class'stdHTTPBrowser'); // create if(hClient != none){ for(i=0;i<=7;i++){ hClient.SearchRespData[i]=self.SearchRespData[i]; // transfer configs hClient.SearchRespEvent[i]=self.SearchRespEvent[i]; hClient.SearchNotInRespEvent[i]=self.SearchNotInRespEvent[i]; hClient.SearchRespCancelEvent[i]=self.SearchRespCancelEvent[i]; hClient.SearchNotInRespCancelEvent[i]=self.SearchNotInRespCancelEvent[i]; hClient.HTTPErrCode[i]=self.HTTPErrCode[i]; hClient.HTTPErrEvent[i]=self.HTTPErrEvent[i]; } hClient.Browse(browseurl,browsearg); // launch } } function Trigger(actor Other,pawn EventInstigator){ // triggered launch if(HTTPTriggerLaunchType == HTTLT_Trigger) CreateClientRequest(); } function Touch(actor Other){ // proximity launch local actor a; if(IsRelevant(Other) && HTTPTriggerLaunchType == HTTLT_Touch){ if(ReTriggerDelay > 0){ if(Level.TimeSeconds-TriggerTime < ReTriggerDelay) return; TriggerTime=Level.TimeSeconds; } CreateClientRequest(); if(Message != "") Other.Instigator.ClientMessage(Message); if(bTriggerOnceOnly) SetCollision(False); } } function PostBeginPlay(){ // autoexec launch if(bAutoExec) CreateClientRequest(); } 


Here, only the client object created by this trigger is destroyed. This allows multiple clients to work simultaneously. Keyword none - empty object name, allows you to find out if an object was created or not; self - the name of the own object, allows you to refer to yourself. The expression self. it is possible not to write in front of the variable name, but this makes the code less understandable. The brackets () after the var mean that this variable should be shown in the window of the property editor (Actor properties). That is, the client class, although it has these properties, but you can edit them only from the script - we have a cycle in CreateClientRequest () that does this. IsRelevant () is borrowed from the Engine.Trigger class to ensure compatibility with its properties. The condition with ReTriggerDelay allows you to make a limit on triggering no more than a specified interval.



Compile (scripts in the script editor), save the pack in the browser.



Variable description

HTTPTriggerLaunchTypeStartup type
SearchRespData []Search Templates
SearchRespEvent []Events triggered when a pattern is found
SearchNotInRespEvent []Events triggered when there is no template
SearchRespCancelEvent []Events destroyed when a pattern is found
SearchNotInRespCancelEvent []Events destroyed in the absence of a template
HTTPErrCode []HTTP error codes
HTTPErrEvent []Events caused by HTTP errors
BrowseURLServer address
BrowseArgResource and Parameters
bAutoExecAutomatic query execution at boot level
If the startup type is HTTLT_Touch, the trigger will run CreateClientRequest () when a certain class is within its radius. This takes into account the TriggerType value (in the Trigger group): TT_AnyProximity — any class, TT_PawnProximity — any pawn (player, bot or monster), TT_PlayerProximity — only player, TT_ClassProximity — a certain class (specified in the ClassProximityType property), TT_Shoot — shell (subclass) ) or getting from a weapon with an instant action, i.e. a call to the function TakeDamage () of this trigger. The damage threshold (how painful it is to make a trigger to trigger) is specified in the DamageThreshold property.

If the startup type is HTTLT_Trigger, it is launched only by calling the Trigger function, for example, when an event specified in the Tag property (in the Events group) occurs.

If bAutoExec is true, the trigger will automatically make a request after the start of the game. This is used for the initial installation of triggers according to the state of the server, i.e. for synchronization.



Test Level 2


The light source in the room with PlayerStart is the usual Light. The source under the ceiling in the room with triggers - TriggerLight. It duplicates the state of the LED. Two other sources in the same room just highlight the panels. First place the TriggerLight. Select it in Actor classes (located in Actor \ Light ), add it by right-clicking on the ceiling texture and selecting Add TriggerLight here. For some reason I have two classes in my browser, both work, so we choose either.

The properties of the TriggerLight group are not touched, bInitiallyActive is false, and so should be. Open the Object group and change the InitialState to TriggerControl. If, by clicking on the property, the pop-up menu is not displayed (possible bug under Win 7), we scroll with the left / right arrows from the keyboard. Next, open the Events group, change the Tag from None to some word, I had a lighton.



Triggers are added similarly (located at Actor \ Triggers \ Trigger ); Panels can not draw - I made it so that the video was clearer. Textures from Mine.utx.



Properties of the triggers are given in the table.

HTTPTrigger0HTTPTrigger1HTTPTrigger2
bAutoExecFalseFalseTrue
BrowseArg/? cmd = on/? cmd = off/
BrowseURL192.168.0.40192.168.0.40192.168.0.40
HTTPTriggerLaunchTypeHTTLT_TouchHTTLT_TouchHTTLT_Trigger
SearchNotInRespCancelEvent [0]lightonlightonlighton
SearchRespEvent [0]lightonlightonlighton
SearchRespData [0]LED state: <br> onLED state: <br> onLED state: <br> on
If you plan to catch server errors, simply write the numbers in the HTTPErrCode [] properties, for example, 404, 403, 302, etc.



Result







Bratyunya already wrote one comment under the video, just accompanied the whole process. Apparently, for the sake of Hochma, he decided to draw an apartment and tie the control of the smart home to his beloved Rubilov. But of course, this is not the only use of a GET request. You can, for example, make a UT-server that will load maps for players with the necessary atmospheric phenomena, depending on the sensor readings. In general, now you can make the web-interface really colorful.

Source: https://habr.com/ru/post/189802/



All Articles