650 likes | 782 Views
Sega 500. Observers in UT. Jeff “Ezeikeil” Giles jgiles@artschool.com http://gamestudies.cdis.org/~jgiles. Today. Today’s lesson came about after much frustration in playing with SquadAi for the first time. So I decided to come up with something useful.
E N D
Sega 500 Observers in UT Jeff “Ezeikeil” Giles jgiles@artschool.com http://gamestudies.cdis.org/~jgiles
Today • Today’s lesson came about after much frustration in playing with SquadAi for the first time. So I decided to come up with something useful. • We’re going to take a side bar into how to implement a useful design pattern in UT for our own nefarious purposes.
Introducing • The weird and wonderful observer design pattern.
What is it? • Some of you have no doubt already come across design patterns before. • Items such as singletons and factories. • If your not familiar with the observer pattern yet…well it does just that… observers.
What is it? • Now, we all know the strangeness that is inherent to UScript, which is why it is SO cool to be able to bring some sense of “order” back into it. • But an observer… What is it?...more to the point…why should we care?
What is it? • Well, I’ll answer the what is it first, then the “Why do we care” should be come pretty self evident. • The Observer let’s us monitor (observe) several values, objects, classes, states…yada…yada…yada from a single object.
What is it? • In other words, it allows us to define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.
What is it? • Now, these bad boys are used all the time in the “real” programming world where there are items that need to be updates simultaneously. • Think any multi windowed app…kind of like the editor. But with instant updates.
What is it? • When might you use them? • When an abstraction has two aspects, one dependent on the other. Encapsulating these aspects in separate objects lets you vary and reuse them independently.
What is it? • When a change to one object requires changing others, and you don't know how many objects need to be changed. • When an object should be able to notify other objects without making assumptions about who these objects are. In other words, you don't want these objects tightly coupled.
What is it? • An Observer is comprised of 2 elements. • The observer itself: That which does the watching…think stalker. • And the subject:The item being watched…the stalkee.
What is it? The UML for a C++ Observer
And we care because?... • Hey this is C++, how does this relate? • Trust me, there are many applications for an observer in UT. • Think about just how often you may want to track specific data in specific (multiple) objects throughout the course of the game.
And we care because?... • Say, for example, we need to know the position of a number of bots or switch positions…quickly…cheaply… • ‘Nother example? Ok. Triggered events <points frantically at the observer>
And we care because?... • Starting to see the uses yet? • But here’s today’s juicy goodness. How to access any debug info & splash it to the HUD…quickly…cheaply…easily.
Observers in UT • But first, understand that due to the nature of UScript, we have to break away from the above UML • …but only a bit. The functionality is still, fundamentally very similar. • The main difference comes from UT’s rules on inheritance…
Observers in UT • The plan: we’re going to create an observer class, create it once and only once ( can’t do singletons in UScript ) and register data to it. • On a call to the observer from the HUD, draw it to the screen.
Observers in UT • And here’s what we’re after…
Observers in UT • A Closer look: • Understand what this is. Is a listing of data from different classes and objects which I can add to at any point I wish.
Observers in UT • Register bots on the fly…no problem…I’ll just do a few addbots calls.
Observers in UT • No boys and girls, this is not smoke and mirrors, nor is it a trick of light. • So how did we do this?... • Starting with the observer class, we derive from actor, we don’t need any other functionality. class Observer extends Actor;
Observers in UT • At it’s core, it’s a linked list in it functionality. • So we need the list elements and a method to add them. var Observer NextObserver; function AddObserver(observer watch) { if ( NextObserver == None ) NextObserver = watch; else NextObserver.AddObserver(watch); //append to end of list }
Observers in UT • But here’s the magic that makes it work. I indroduce you to a new friend, the delegate… //function pointer to obj returns a data string delegate string RegisterSubject();
And what’s a delegate? • A delegates is a reference to a function bound to an object. • Their main use is to provide a callback mechanism, for example to provide event notification in a user interface system .
And what’s a delegate? • In a nutshell, they are very similar to function pointers in C++…but there are some considerable restrictions. We’ll talk about them in a bit… • Their primary purpose in life is to work with the GUI, but as you are about to see, this is not their only application.
Observers in UT • Now, there is some overhead to use this, it’s not totally free…but the pay off can be big! • What we ask the user to do is create a function that returns a string and has no signature. • We point our delegate at this.
Observers in UT • How do we us it? • Well, in the game type I created & stored the observer…just because its an easy place to get at from just about anywhere (server only). function PreBeginPlay() { class'xGame.xPawn'.default.ControllerClass=class'Eze.EzeAI'; super.PreBeginPlay(); if( watcher==none) watcher=spawn(class'Observer'); }
Observers in UT • Now, in the HUD I created this: function string getRange() { local string end; temp+=0.5; end=" Range"; end=temp$ end; return end; } • Which is a function for the delegate to point at… • Note: the function signatures have to be the same.
Observers in UT • And then register it with the observer function PostBeginPlay() { customDM(level.Game).watcher.AddObserver(spawn(class'Observer')); customDM(level.Game).watcher.RegisterSubject=getRange; //point delegate at function } • …and it works…well almost…got to splash it to the screen still.
Observers in UT function DrawDebug(Canvas C) { local int xpos,ypos, ctr; local observer watch; Ypos=50; Xpos=20; c.Font=c.TinyFont; for(watch=self; watch!=none; watch=watch.NextObserver) { if(watch.RegisterSubject()!="") { c.SetPos(xpos, ypos); c.DrawText(watch.RegisterSubject()); ypos+=10; } } } • Which looks a little something like this…
Observers in UT • In a nutshell, it simple itereates over all the delegates in the list, calling the delegate which gets us a string with all the information we want.
Observers in UT • No sifting through code to find the log entries or debug code. • Create your functions, register the objects in the postbeginplay()…and your off to the races. • Best part, anywhere you can access the game type, you can access the observer.
Observers in UT • Are we convinced yet? • Delegates are powerful toys, but as mentioned, there are some restrictions in their use. • Can’t pass a function as a variable for one • Two, UT has no generic data types a-la void pointer …my kingdom for a void pointer.
Observers in UT • Also the base data types can’t be passed into a class as a object…no worky • But even so, using the delegate we got around this and this was quickly pulled together…1 afternoon.
Observers in UT • Think about what we could get away with if we poured more time into it! • Say I created a data class (e.g. a Node) with a delegate, struct and all the other data…like a handle to the class object for example…I could story whatever I wanted!
Observers in UT • Ok, so they’re not totally free…but really, is anything? • But what we have now is a reasonably generic data storage container of sorts. For cheap too…
Cleaning up • One thing about object management as I’m sure you know from your C++ days is that you have to be judicious about cleaning up after yourself. • Yes, it’s true that UT comes with automatic garbage collection, but relying in it is bad practice for one, and two not always reliable.
Cleaning up • Especially when we start talking about creation methods other than the Spawn function….which don’t get added to the actor list.
Cleaning up • So, that being said, time to get back to the code. • What I’m after is a way to get the object to remove stuff from the list if it becomes invalid.
Cleaning up • So I need to grab a hold of the object somehow. • Now, at 1st glance I could just pass it in with the delegate…But that’s a baaaad idea.
Cleaning up • Remember where I’m calling the delegate from…the Observer itself. • The Observe is calling a function which belongs to another class. • Which means I’d have to call the specific object every render…uhg!
Cleaning up • So, better idea…Pass it in as a handle when we call AddObserver. function AddObserver(observer watch, object handle) { if ( NextObserver == None ) { NextObserver = watch; objHandle=handle; } else NextObserver.AddObserver(watch,handle); //append to end of list }
Cleaning up • Now real newness there… • However, there is this. var private Object objHandle;
Cleaning up • Private? Can I do that? • Sure can…data encapsulation here we come. • private: The variable is private, and may only be accessed by the class's script; no other classes (including subclasses) may access it.
Cleaning up • Yup, quite similar to C++. • It protects the data nicely and causes up to create some assessor functions. • Why am I showing you this?...it comes with a subtle headach which you’ll see in a moment.
Cleaning up • So to clean as you go…I added this to the DrawDebug function. if(watch.objHandle==none) { lastVisited.NextObserver= watch.NextObserver; toRemove=watch; toRemove.Destroyed(); log("--->Killed Node"@ctr@"COUNT"$ctr@"numElements:"@numElements); } else if(watch.RegisterSubject()!="")
Cleaning up • Simply tracks the last visited observer & redirects it around the one we are about to delete.
Cleaning up • If you run it now you should be able to addbots and killbots in the list no problem. Start Killed 2 Added 4
Right, cool…things get added and removed from the list. Now it’s a good idea to have some sorts of purge method to dump the whole thing at once. Here’s mine: Cleaning up
Cleaning up function DestroyObserver() { local int numOB; local Observer toDestroy, ob; ob=self.NextObserver; //kill ourselves last so we don't loose the next link while(ob!=none) { todestroy=ob; ob=ob.NextObserver; log("DESTROYED"$todestroy.objHandle); todestroy.Destroyed(); log("NONE!-->"$todestroy.objHandle); numOB++; } log("DESTROYED<----------------"@numOB); Destroyed(); //destroy self