The kiosks are in the midst of being revised, but I figured I would share the previous setup with you, so you can at least get things to start coming together. The way it works currently includes mouse moving, and little else. Double clicking occasionally gets sent through to the kiosk, but its not as responsive as I would like.
Let’s get started though.
The next phase is getting the kiosk to work, and to wrap up the enabling and disabling of the kiosk, once the player hits the use key.
To begin with our kiosk base class will need to implement an interface, to provide the contract that our player can use. This interface will also open up the door to being able to use other actors in the world.
/* Interface IBTUsable * * All implementors need to include * bCollideActors=true and bCollideWorld=true * to be able to be captured when used. */ interface IBTUsable DependsOn(BTPC); function Use(BTPC Controller);
Shifting our attention over to the player controller, which will do the triggering of our usable element. Thankfully, it already has a bit of the functionality we are looking for. All we need to do is a little vector magic to make sure we get the best usable actor:
inside BTPC.uc
simulated function bool PerformedUseAction() { local IBTUsable BestUsable; // If we are paused, use will quit... if (BTHUD(myHUD).inPause) { BTHUD(myHUD).HUDMovie.onQuit(); return true; } // Check for usable elements if (Super.PerformedUseAction() == false) { BestUsable = IBTUsable(GetBestActor()); if (BestUsable == none) return false; BestUsable.Use(self); } return true; } function Actor GetBestActor() { local Actor tmp, Best; local vector ViewDir, PawnLoc2D, ALoc2D; local float NewDot, BestDot; PawnLoc2D = Pawn.Location; PawnLoc2D.Z = 0; ViewDir = vector(Pawn.Rotation); ForEach Pawn.OverlappingActors(class'Actor', tmp, MaxUseScanDistance) { if (IBTUsable(tmp) == none) continue; ALoc2D = tmp.Location; ALoc2D.Z = 0; NewDot = Normal(ALoc2D - PawnLoc2D) Dot ViewDir; if ( (Best == None) || (NewDot > BestDot) ) { if ( FastTrace(tmp.Location, Pawn.Location) ) { Best = tmp; BestDot = NewDot; } } } return Best; }
With this we have the ability to walk up to and press our use key to trigger a Use call on the usable element. This can be sped up by changing from Actor to IBTUsable, but for the most usability from this getBestActor function, I have chosen to leave it as Actor.
The kiosk and a base material are all that remain. The kiosk will setup the scene, initialize the swf movie and connect the render target to a material. The trick really comes down to the movie player only rendering to a render target OR the main viewport. Thankfully, it does that by default.
class Kiosk extends Actor Implements(IBTUsable) placeable; var globalconfig int RenderTargetResolution; var SwfMovie ViewscreenSWF; var class ViewscreenMovieClass; var ScriptedTexture ViewscreenTexture; var StaticMeshComponent ViewscreenMesh; var BTGFxKioskMoviePlayer ViewscreenMovie; var MaterialInstanceConstant ViewscreenMaterial; function Use(BTPC Controller) { if (ViewscreenMovie.RenderTexture != None) EnableKiosk(Controller); else DisableKiosk(Controller); }
So, what are we looking at, aside from a couple of very odd looking variables and variable types? This is probably one of the most complicated actors that any tutorial ill write has to offer. Lets take it one step at a time, so things are clear.
inside Kiosk.uc
simulated function PreBeginPlay() { super.PreBeginPlay(); InitializeKiosk(); } reliable client function InitializeKiosk() { if (WorldInfo.NetMode == NM_ListenServer || WorldInfo.NetMode == NM_DedicatedServer ) return; ViewscreenTexture = ScriptedTexture( class'ScriptedTexture'.static.Create( RenderTargetResolution, RenderTargetResolution)); ViewscreenMaterial = ViewscreenMesh. CreateAndSetMaterialInstanceConstant(0); ViewscreenMaterial.SetParent( Material'BTBanking.KioskSWF_mat'); ViewscreenMaterial.SetTextureParameterValue( 'SWFMovie', ViewscreenTexture); ViewscreenMovie = new ViewscreenMovieClass; ViewscreenMovie.RenderTexture = ViewscreenTexture; ViewscreenMovie.bIgnoreMouseInput = false; ViewscreenMovie.SetMovieInfo(ViewscreenSWF); ViewscreenMovie.setKiosk(self); ViewscreenMovie.Start(); DisableKiosk(none); }
I have removed my comments to help shorten this up, but ill take it from the top down. The first test is simply a confirmation that this is not the server. There are other ways to do this, you should play with them. We don’t want to have the following created on the server side.
Our kiosk screen mesh is going to need a texture, hence ViewscreenTexture. We create a ScriptedTexture, which allows for a number of options to us. the next three function calls are setting up our material with that texture and hook it into our base texture. This is when we are attaching our Kiosk to the texture, which is expecting an SWFMovie parameter.
RenderTargetResolution directly relates to the resolution of the texture, I would suggest you play with this, but know that render targets take up further memory, so any value you choose will be contextual. I have mine set to 256 in my config and use it for all of my kiosks.
ViewscreenMovieClass is the class that we use, deriving from GFxMoviePlayer. We have a reference to the Kiosk, so we can send signals both directions, and then hook in the render target. We set the SWF up and then start the movie. Then we call a magic function – DisableKiosk.
simulated function DisableKiosk(BTPC PC) { ViewscreenMovie.ClearCaptureKeys(); ViewscreenMovie.ClearFocusIgnoreKeys(); if (PC != None) { PC.MousePlayer.SetFilterer(none); PC.MousePlayer.DisableMouseInput(); } ViewscreenMovie.SetPause(true); } // Executed when the kiosk is "used" simulated function EnableKiosk(BTPC PC) { if (PC != None) { PC.MousePlayer.SetFilterer(ViewscreenMovie); PC.MousePlayer.EnableMouseInput(); } ViewscreenMovie.SetPause(false); ViewscreenMovie.AddCaptureKeys(); }
Note the SetFilterer call, which is what hooks our input filter into play. The way we get around starting and stopping is via the pause command, setting when we disable the kiosk. This keeps the render target from getting updates when it is not activated.
The following is only present for posterity, you will likely need to make your own modifications.
defaultproperties { // define here as lot of sub classes which have // moving parts will utilize this Begin Object Class=DynamicLightEnvironmentComponent Name=PickupLightEnvironment bDynamic=false bCastShadows=true End Object Begin Object Class=StaticMeshComponent Name=KioskMesh StaticMesh=StaticMesh'BTBanking.Kiosk' LightEnvironment=PickupLightEnvironment CollideActors=true BlockActors=true BlockZeroExtent=false BlockNonZeroExtent=true BlockRigidBody=true End Object Begin Object Class=StaticMeshComponent Name=Mesh StaticMesh=StaticMesh'BTPrimitives.Plane' LightEnvironment=PickupLightEnvironment Scale=31 Rotation=(Pitch=0,Yaw=-16383,Roll=14199) Translation=(X=11,Y=0,Z=76) End Object bCollideActors=true bBlockActors=true bCollideWorld=true bNoDelete=true bEdShouldSnap=true CollisionType=COLLIDE_BlockAll ViewscreenSWF = SwfMovie'BTFlash.Kiosk' ViewscreenMovieClass = class'BTGFxKioskMoviePlayer' ViewscreenMesh = Mesh Components.Add(Mesh) Components.Add(KioskMesh) Components.Add(PickupLightEnvironment) CollisionComponent=KioskMesh }
In the next installment ill walk through the movie player and address any questions regarding this section. We are nearly done.