source: 2014/30/MitjaK/Attack to Agora/Attack to Agora/Muokattu Jypeli/Game/Game.cs @ 6472

Revision 6472, 57.2 KB checked in by anlakane, 4 years ago (diff)
Line 
1#region MIT License
2/*
3 * Copyright (c) 2009 University of Jyväskylä, Department of Mathematical
4 * Information Technology.
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 * THE SOFTWARE.
23 */
24#endregion
25
26/*
27 * Authors: Tero Jäntti, Tomi Karppinen, Janne Nikkanen.
28 */
29
30using System;
31using System.ComponentModel;
32using System.Xml;
33using System.Xml.Serialization;
34using Microsoft.Xna.Framework;
35using Microsoft.Xna.Framework.Graphics;
36using System.Collections.Generic;
37using System.Linq;
38using Microsoft.Xna.Framework.Content;
39
40#if WINDOWS
41using System.Runtime.InteropServices;
42#endif
43
44#if XBOX
45using Microsoft.Xna.Framework.GamerServices;
46#endif
47
48using Jypeli.Widgets;
49using Jypeli.Effects;
50using Jypeli.Controls;
51using Jypeli.WP7;
52
53using XnaColor = Microsoft.Xna.Framework.Color;
54using XnaSoundEffect = Microsoft.Xna.Framework.Audio.SoundEffect;
55using XnaRectangle = Microsoft.Xna.Framework.Rectangle;
56using System.Reflection;
57using System.Diagnostics;
58
59
60namespace Jypeli
61{
62    /// <summary>
63    /// Peliluokka reaaliaikaisille peleille.
64    /// </summary>
65    [Save]
66    public class Game : Microsoft.Xna.Framework.Game, ControlContexted, IDisposable, GameObjectContainer
67    {
68#if WINDOWS
69        [DllImport( "user32.dll" )]
70        private static extern int GetSystemMetrics( int smIndex );
71#endif
72
73        Queue<Action> PendingActions = new Queue<Action>();
74
75        /// <summary>
76        /// Onko peli pysähdyksissä.
77        /// </summary>
78        public bool IsPaused { get; set; }
79
80        /// <summary>
81        /// Kerrokset, joilla pelioliot viihtyvät.
82        /// </summary>
83        public SynchronousList<Layer> Layers { get; private set; }
84
85        /// <summary>
86        /// Kerrokset, joilla olevat pelioliot eivät liiku kameran mukana.
87        /// </summary>
88        public IList<Layer> StaticLayers
89        {
90            get { return Layers.FindAll( l => l.IgnoresZoom && l.RelativeTransition == Vector.Zero ).AsReadOnly(); }
91        }
92
93        /// <summary>
94        /// Kerrokset, joilla olevat pelioliot liikkuvat kameran mukana.
95        /// </summary>
96        public IList<Layer> DynamicLayers
97        {
98            get { return Layers.FindAll( l => !l.IgnoresZoom || l.RelativeTransition != Vector.Zero ).AsReadOnly(); }
99        }
100
101        /// <summary>
102        /// Pienin mahdollinen kerros.
103        /// </summary>
104        public int MinLayer
105        {
106            get { return Layers.FirstIndex; }
107        }
108
109        /// <summary>
110        /// Suurin mahdollinen kerros.
111        /// </summary>
112        public int MaxLayer
113        {
114            get { return Layers.LastIndex; }
115        }
116
117        /// <summary>
118        /// Kerrosten määrä.
119        /// </summary>
120        public int LayerCount
121        {
122            get { return Layers.Count; }
123        }
124
125        /// <summary>
126        /// Pelin nimi.
127        /// </summary>
128        public static string Name { get; private set; }
129
130        // TJ: Since there is only one isntance of the
131        // game class in practice, its useful to have it available as
132        // a static member. This way, we avoid passing a reference to the
133        // game in lots of places.
134        //
135        // In addition, some often-used things, such as Time or GraphicsDevice,
136        // are available as static properties (public or internal) as well.
137        public static Game Instance { get; private set; }
138
139        /// <summary>
140        /// Tapahtuu kun Game.Instance on alustettu.
141        /// </summary>
142        public static event Action InstanceInitialized;
143
144        /// <summary>
145        /// Tapahtuu kun peli lopetetaan.
146        /// </summary>
147        public static new event Action Exiting;
148
149        /// <summary>
150        /// Kamera, joka näyttää ruudulla näkyvän osan kentästä.
151        /// Kameraa voidaan siirtää, zoomata tai asettaa seuraamaan tiettyä oliota.
152        /// </summary>
153        [Save]
154        public Camera Camera { get; set; }
155
156        /// <summary>
157        /// Kentän reunat näkyvissä tai pois näkyvistä.
158        /// Huomaa, että tämä ominaisuus ei vaikuta reunojen törmäyskäsittelyyn.
159        /// </summary>
160        public bool DrawPerimeter { get; set; }
161
162        /// <summary>
163        /// Väri, jolla kentän reunat piirretään.
164        /// </summary>
165        public Color PerimeterColor { get; set; }
166
167        /// <summary>
168        /// Tekstuurien (kuvien) reunanpehmennys skaalattaessa (oletus päällä).
169        /// </summary>
170        public static bool SmoothTextures { get; set; }
171
172        /// <summary>
173        /// Kirjaston mukana tuleva sisältö.
174        /// Voidaan käyttää esimerkiksi tekstuurien lataamiseen.
175        /// </summary>
176        internal static ResourceContentManager ResourceContent { get; private set; }
177
178        /// <summary>
179        /// Näytön dimensiot, eli koko ja reunat.
180        /// </summary>
181        public static ScreenView Screen
182        {
183            get { return Instance.screen; }
184        }
185
186        /// <summary>
187        /// Ikkuna.
188        /// </summary>
189        public static new JypeliWindow Window { get; private set; }
190
191        /// <summary>
192        /// Pelin kontrollit.
193        /// </summary>
194        public static Jypeli.Controls.Controls Controls
195        {
196            get { return Instance.controls; }
197        }
198
199        private ListenContext _context = new ListenContext() { Active = true };
200
201        /// <summary>
202        /// Pelin pääohjainkonteksti.
203        /// </summary>
204        public ListenContext ControlContext
205        {
206            get { return Instance._context; }
207        }
208
209        public bool IsModal
210        {
211            get { return false; }
212        }
213
214        /// <summary>
215        /// Viestinäyttö, johon voi laittaa viestejä.
216        /// </summary>
217        /// <value>Viestinäyttö.</value>
218        public MessageDisplay MessageDisplay { get; set; }
219
220        /// <summary>
221        /// Tietovarasto, johon voi tallentaa tiedostoja pidempiaikaisesti.
222        /// Sopii esimerkiksi pelitilanteen lataamiseen ja tallentamiseen.
223        /// </summary>
224        public static FileManager DataStorage { get { return Instance.dataStorage; } }
225
226        /// <summary>
227        /// Onko olio valittavissa.
228        /// Vain valittu (fokusoitu) olio voii kuunnella näppäimistöä ja muita ohjainlaitteita.
229        /// Peliolio on aina valittavissa.
230        /// </summary>
231        public bool AcceptsFocus { get { return true; } }
232
233        /// <summary>
234        /// Näppäimistö.
235        /// </summary>
236        public Keyboard Keyboard { get { return controls.Keyboard; } }
237
238        /// <summary>
239        /// Hiiri.
240        /// </summary>
241        public Mouse Mouse { get { return controls.Mouse; } }
242
243        /// <summary>
244        /// Kosketusnäyttö. Vain kännykässä.
245        /// </summary>
246        public TouchPanel TouchPanel { get { return controls.TouchPanel; } }
247
248        public PhoneBackButton PhoneBackButton { get { return controls.PhoneBackButton; } }
249
250        /// <summary>
251        /// Peliohjain yksi.
252        /// </summary>
253        public GamePad ControllerOne { get { return controls.GameControllers[0]; } }
254
255        /// <summary>
256        /// Peliohjain kaksi.
257        /// </summary>
258        public GamePad ControllerTwo { get { return controls.GameControllers[1]; } }
259
260        /// <summary>
261        /// Peliohjain kolme.
262        /// </summary>
263        public GamePad ControllerThree { get { return controls.GameControllers[2]; } }
264
265        /// <summary>
266        /// Peliohjain neljä.
267        /// </summary>
268        public GamePad ControllerFour { get { return controls.GameControllers[3]; } }
269
270        /// <summary>
271        /// Kiihtyvyysanturi. Vain kännykässä.
272        /// </summary>
273        public Accelerometer Accelerometer { get { return controls.Accelerometer; } }
274
275        private Phone phone;
276
277        /// <summary>
278        /// Phone-olio esim. puhelimen tärisyttämiseen.
279        /// </summary>
280        public Phone Phone { get { return phone; } }
281
282
283        /// <summary>
284        /// Aktiivinen kenttä.
285        /// </summary>
286        public Level Level
287        {
288            get { return theLevel; }
289        }
290
291        /// <summary>
292        /// Peliaika. Sisältää tiedon siitä, kuinka kauan peliä on pelattu (Time.SinceStartOfGame)
293        /// ja kuinka kauan on viimeisestä pelin päivityksestä (Time.SinceLastUpdate).
294        /// Tätä päivitetään noin 30 kertaa sekunnissa kun peli ei ole pause-tilassa.
295        /// </summary>
296        public static Time Time
297        {
298            get { return currentTime; }
299        }
300
301        /// <summary>
302        /// Todellinen peliaika. Sisältää tiedon siitä, kuinka kauan peliä on pelattu (Time.SinceStartOfGame)
303        /// ja kuinka kauan on viimeisestä pelin päivityksestä (Time.SinceLastUpdate).
304        /// Tätä päivitetään noin 30 kertaa sekunnissa, myös pause-tilassa.
305        /// </summary>
306        public static Time RealTime
307        {
308            get { return currentTime; }
309        }
310
311        /// <summary>
312        /// Tuuli. Vaikuttaa vain efekteihin
313        /// </summary>
314        public static Vector Wind { get; set; }
315
316        /// <summary>
317        /// Teksti, joka näkyy pelin ikkunassa (jos peli ei ole koko ruudun tilassa).
318        /// </summary>
319        public string Title
320        {
321            get { return Window.Title; }
322            set { Window.Title = value; }
323        }
324
325        /// <summary>
326        /// Kuinka monta pelioliota pelissä on (ei laske widgettejä).
327        /// </summary>
328        internal int ObjectCount
329        {
330            get
331            {
332                return Layers.Sum<Layer>( l => l.Objects.Count );
333            }
334        }
335
336        /// <summary>
337        /// Onko peli kokoruututilassa.
338        /// </summary>
339        public bool IsFullScreen
340        {
341            get { return Window.Fullscreen; }
342            set { Window.Fullscreen = value; }
343        }
344
345        /// <summary>
346        /// Mediasoitin.
347        /// </summary>
348        public MediaPlayer MediaPlayer { get; private set; }
349
350        private Jypeli.Controls.Controls controls;
351        private FileManager dataStorage;
352        private ScreenView screen;
353
354        [EditorBrowsable( EditorBrowsableState.Never )]
355        public static new GraphicsDevice GraphicsDevice
356        {
357            get { return ( (Microsoft.Xna.Framework.Game)Instance ).GraphicsDevice; }
358        }
359
360        internal static GraphicsDeviceManager GraphicsDeviceManager { get; private set; }
361
362#if !WINDOWS_PHONE
363        internal static List<Light> Lights { get { return lights; } }
364#endif
365
366#if DEBUG
367        private BarGauge[] objectCountDisplays;
368        private Label fpsDisplay;
369        private string fpsText = "00";
370        private int fpsSkipCounter;
371        private bool isDebugScreenShown = false;
372#endif
373
374#if !WINDOWS_PHONE
375        private static List<Light> lights = new List<Light>();
376#endif
377
378        private Level theLevel;
379
380        private bool loadContentHasBeenCalled = false;
381        private bool beginHasBeenCalled = false;
382
383        // Real time passed, including paused time
384        private static Time currentRealTime = new Time();
385
386        // Game time passed
387        private static Time currentTime = new Time();
388
389
390        /// <summary>
391        /// Alustaa uuden peliluokan.
392        /// </summary>
393        public Game()
394            : this( 1 )
395        {
396        }
397
398        /// <summary>
399        /// Alustaa uuden peliluokan.
400        /// </summary>
401        /// <param name="device">Mikä monitori käytössä, 1=ensimmäinen</param>
402        public Game( int device )
403        {
404            if ( Instance != null )
405                throw new Exception( "Only one instance of the Game class can be created." );
406
407            Instance = this;
408            Name = this.GetType().Assembly.FullName.Split( ',' )[0];
409
410            InitializeLayers();
411            InitializeContent();
412
413            Camera = new Camera();
414            controls = new Jypeli.Controls.Controls();
415
416            InitializeGraphics( device );
417            Window = new JypeliWindow( base.Window, Game.GraphicsDeviceManager );
418            Window.Resized += new JypeliWindow.ResizeEvent( WindowResized );
419
420#if WINDOWS
421            this.dataStorage = new WindowsFileManager( WindowsLocation.DataPath, WindowsLocation.MyDocuments ); ;
422#elif WINDOWS_PHONE
423            dataStorage = new IsolatedStorageManager();
424#elif XBOX
425            Components.Add( new GamerServicesComponent( this ) );
426            dataStorage = new XboxFileManager();
427#endif
428
429            dataStorage.ReadAccessDenied += delegate (Exception ex)
430            {
431                ShowErrorMessage("Could not read from data directory. " + ex.Message);
432            };
433            dataStorage.WriteAccessDenied += delegate (Exception ex)
434            {
435                ShowErrorMessage("Could not write to data directory. " + ex.Message);
436            };
437
438            DrawPerimeter = false;
439            PerimeterColor = Color.DarkGray;
440
441            phone = new Phone();
442            Phone.Tombstoning = true;
443        }
444
445        private void WindowResized( int oldWidth, int oldHeight, int newWidth, int newHeight )
446        {
447            if ( GraphicsDevice == null )
448                return;
449
450            if ( Mouse != null )
451                Mouse.Viewport = GraphicsDevice.Viewport;
452
453            if ( screen != null )
454                screen.viewPort = GraphicsDevice.Viewport;
455        }
456
457        private void ShowErrorMessage(string message)
458        {
459            MessageDisplay.Add( "ERROR: " + message, Color.Red );
460
461            /*bool mouseVisible = IsMouseVisible;
462            IsMouseVisible = true;
463
464            MessageWindow errWindow = new MessageWindow( message );
465            errWindow.Color = Color.Red;
466            errWindow.Message.TextColor = Color.White;
467            errWindow.Closed += delegate { IsMouseVisible = mouseVisible; };
468            Add( errWindow );*/
469        }
470
471        /// <summary>
472        /// Näyttää viesti-ikkunan.
473        /// </summary>
474        /// <param name="message">Viesti</param>
475        public void ShowMessageWindow( string message )
476        {
477            MessageWindow w = new MessageWindow( message );
478            Add( w );
479        }
480
481        private void InitializeLayers()
482        {
483            Layers = new SynchronousList<Layer>( -3 );
484            Layers.ItemAdded += OnLayerAdded;
485            Layers.ItemRemoved += OnLayerRemoved;
486
487            for ( int i = 0; i < 7; i++ )
488            {
489                Layers.Add( new Layer() );
490            }
491
492            // This is the widget layer
493            Layers.Add( Layer.CreateStaticLayer() );
494
495            Layers.UpdateChanges();
496        }
497
498        private void InitializeContent()
499        {
500#if WINDOWS_PHONE
501           
502            ResourceContent = new ResourceContentManager( this.Services, WindowsPhoneResources.ResourceManager );
503#elif XBOX
504            ResourceContent = new ResourceContentManager( this.Services, XBox360Resources.ResourceManager );
505#else
506            ResourceContent = new ResourceContentManager( this.Services, Resources.ResourceManager );
507#endif
508
509            Content.RootDirectory = "Content";
510        }
511
512        private void InitializeGraphics( int device )
513        {
514#if WINDOWS
515            if ( device == 1 )
516                GraphicsDeviceManager = new GraphicsDeviceManager( this );
517            else
518                GraphicsDeviceManager = new TargetedGraphicsDeviceManager( this, device );
519#else
520            GraphicsDeviceManager = new GraphicsDeviceManager( this );
521#endif
522            GraphicsDeviceManager.PreferredDepthStencilFormat = Jypeli.Graphics.SelectStencilMode();
523            SmoothTextures = true;
524        }
525
526        private void ActivateObject( ControlContexted obj )
527        {
528            obj.ControlContext.Active = true;
529
530            if ( obj.IsModal )
531            {
532                Game.Instance.ControlContext.SaveFocus();
533                Game.Instance.ControlContext.Active = false;
534
535                foreach ( Layer l in Layers )
536                {
537                    foreach ( IGameObject lo in l.Objects )
538                    {
539                        ControlContexted co = lo as ControlContexted;
540                        if ( lo == obj || co == null )
541                            continue;
542
543                        co.ControlContext.SaveFocus();
544                        co.ControlContext.Active = false;
545                    }
546                }
547            }
548        }
549
550        private void DeactivateObject( ControlContexted obj )
551        {
552            obj.ControlContext.Active = false;
553
554            if ( obj.IsModal )
555            {
556                Game.Instance.ControlContext.RestoreFocus();
557
558                foreach ( Layer l in Layers )
559                {
560                    foreach ( IGameObject lo in l.Objects )
561                    {
562                        ControlContexted co = lo as ControlContexted;
563                        if ( lo == obj || co == null )
564                            continue;
565
566                        co.ControlContext.RestoreFocus();
567                    }
568                }
569            }
570        }
571
572        protected virtual void OnObjectAdded( IGameObject obj )
573        {
574            IGameObjectInternal iObj = obj as IGameObjectInternal;
575            if ( iObj == null ) return;
576            iObj.IsAddedToGame = true;
577            iObj.OnAddedToGame();
578
579            ControlContexted cObj = obj as ControlContexted;
580            if ( cObj != null ) ActivateObject( cObj );
581        }
582
583        protected virtual void OnObjectRemoved( IGameObject obj )
584        {
585            IGameObjectInternal iObj = obj as IGameObjectInternal;
586            if ( iObj == null ) return;
587            iObj.IsAddedToGame = false;
588            iObj.OnRemoved();
589
590            ControlContexted cObj = obj as ControlContexted;
591            if ( cObj != null ) DeactivateObject( cObj );
592        }
593
594        internal static void OnAddObject( IGameObject obj )
595        {
596            Debug.Assert( Instance != null );
597            Instance.OnObjectAdded( obj );
598        }
599
600        internal static void OnRemoveObject( IGameObject obj )
601        {
602            Debug.Assert( Instance != null );
603            Instance.OnObjectRemoved( obj );
604        }
605
606        private void OnLayerAdded( Layer l )
607        {
608            l.Objects.ItemAdded += this.OnObjectAdded;
609            l.Objects.ItemRemoved += this.OnObjectRemoved;
610        }
611
612        private void OnLayerRemoved( Layer l )
613        {
614            l.Objects.ItemAdded -= this.OnObjectAdded;
615            l.Objects.ItemRemoved -= this.OnObjectRemoved;
616        }
617
618        /// <summary>
619        /// Suorittaa aliohjelman kun peli on varmasti alustettu.
620        /// </summary>
621        /// <param name="actionMethod">Suoritettava aliohjelma.</param>
622        public static void AssertInitialized( Action actionMethod )
623        {
624            if ( Instance != null )
625                actionMethod();
626            else
627                InstanceInitialized += actionMethod;
628        }
629
630        /// <summary>
631        /// Suorittaa aliohjelman seuraavalla päivityksellä.
632        /// </summary>
633        /// <param name="action"></param>
634        public static void DoNextUpdate( Action action )
635        {
636            if ( Instance != null )
637                Instance.PendingActions.Enqueue( action );
638            else
639                InstanceInitialized += action;
640        }
641
642        /// <summary>
643        /// Suorittaa aliohjelman seuraavalla päivityksellä.
644        /// </summary>
645        /// <typeparam name="T1"></typeparam>
646        /// <param name="action"></param>
647        /// <param name="p1"></param>
648        public static void DoNextUpdate<T1>( Action<T1> action, T1 p1 )
649        {
650            DoNextUpdate( delegate { action( p1 ); } );
651        }
652
653        /// <summary>
654        /// Suorittaa aliohjelman seuraavalla päivityksellä.
655        /// </summary>
656        /// <typeparam name="T1"></typeparam>
657        /// <typeparam name="T2"></typeparam>
658        /// <param name="action"></param>
659        /// <param name="p1"></param>
660        /// <param name="p2"></param>
661        public static void DoNextUpdate<T1, T2>( Action<T1, T2> action, T1 p1, T2 p2 )
662        {
663            DoNextUpdate( delegate { action( p1, p2 ); } );
664        }
665
666        /// <summary>
667        /// Suorittaa aliohjelman kun peli on varmasti alustettu.
668        /// </summary>
669        /// <param name="actionMethod">Suoritettava aliohjelma.</param>
670        public static void AssertInitialized<T1>( Action<T1> actionMethod, T1 o1 )
671        {
672            if ( Instance != null )
673                actionMethod( o1 );
674            else
675                InstanceInitialized += delegate { actionMethod( o1 ); };
676        }
677
678        /// <summary>
679        /// Lisää olion peliin.
680        /// Tavalliset oliot tulevat automaattisesti kerrokselle 0
681        /// ja ruutuoliot päällimmäiselle kerrokselle.
682        /// </summary>
683        public void Add( IGameObject o )
684        {
685            if ( o.Layer != null && o.Layer.Objects.WillContain( o ) )
686            {
687                if ( o.Layer == Layers[0] )
688                {
689                    throw new NotSupportedException( "Object cannot be added twice" );
690                }
691                else
692                    throw new NotSupportedException( "Object cannot be added to multiple layers" );
693            }
694
695            if ( o is Widget ) Add( o, MaxLayer );
696            else Add( o, 0 );
697        }
698
699#if !WINDOWS_PHONE
700        /// <summary>
701        /// Lisää valon peliin. Nykyisellään valoja voi olla ainoastaan
702        /// yksi kappale.
703        /// </summary>
704        public void Add( Light light )
705        {
706            if ( light == null ) throw new NullReferenceException( "Tried to add a null light to game" );
707
708            if ( lights.Count >= 1 )
709                throw new NotSupportedException( "Only one light is supported" );
710
711            lights.Add( light );
712        }
713#endif
714
715        /// <summary>
716        /// Lisää peliolion peliin, tiettyyn kerrokseen.
717        /// </summary>
718        /// <param name="o">Lisättävä olio.</param>
719        /// <param name="layer">Kerros, luku väliltä [-3, 3].</param>
720        public virtual void Add( IGameObject o, int layer )
721        {
722            if ( o == null ) throw new NullReferenceException( "Tried to add a null object to game" );
723            Layers[layer].Add( o );
724        }
725
726        internal static IList<IGameObject> GetObjectsAboutToBeAdded()
727        {
728            List<IGameObject> result = new List<IGameObject>();
729
730            foreach ( Layer layer in Game.Instance.Layers )
731            {
732                layer.GetObjectsAboutToBeAdded( result );
733            }
734
735            return result;
736        }
737
738        /// <summary>
739        /// Lisää oliokerroksen peliin.
740        /// </summary>
741        /// <param name="l"></param>
742        public void Add( Layer l )
743        {
744            Layers.Add( l );
745            Layers.UpdateChanges();
746        }
747
748        /// <summary>
749        /// Poistaa olion pelistä. Jos haluat tuhota olion,
750        /// kutsu mielummin olion <c>Destroy</c>-metodia.
751        /// </summary>
752        /// <remarks>
753        /// Oliota ei poisteta välittömästi, vaan viimeistään seuraavan
754        /// päivityksen jälkeen.
755        /// </remarks>
756        public void Remove( IGameObject o )
757        {
758            if ( !o.IsAddedToGame )
759                return;
760
761            foreach ( Layer l in Layers )
762                l.Remove( o );
763        }
764         
765        /// <summary>
766        /// Poistaa oliokerroksen pelistä.
767        /// </summary>
768        /// <param name="l"></param>
769        public void Remove( Layer l )
770        {
771            Layers.Remove( l );
772            Layers.UpdateChanges();
773        }
774
775        /// <summary>
776        /// Palauttaa listan kaikista peliolioista jotka toteuttavat ehdon.
777        /// Lista on järjestetty päällimmäisestä alimmaiseen.
778        /// </summary>
779        /// <param name="condition">Ehto</param>
780        /// <returns>Lista olioista</returns>
781        public List<GameObject> GetObjects( Predicate<GameObject> condition )
782        {
783            List<GameObject> objs = new List<GameObject>();
784
785            for ( int i = MaxLayer; i >= MinLayer; i-- )
786            {
787                foreach ( var obj in Layers[i].Objects )
788                {
789                    GameObject gobj = obj as GameObject;
790
791                    if ( gobj != null && condition( gobj ) )
792                        objs.Add( gobj );
793                }
794            }
795
796            return objs;
797        }
798
799        /// <summary>
800        /// Palauttaa listan kaikista peliolioista joilla on tietty tagi.
801        /// Lista on järjestetty päällimmäisestä alimmaiseen.
802        /// </summary>
803        /// <param name="tags">Tagi(t)</param>
804        /// <returns>Lista olioista</returns>
805        public List<GameObject> GetObjectsWithTag( params string[] tags )
806        {
807            return GetObjects( o => tags.Contains<string>( o.Tag as string ) );
808        }
809
810        /// <summary>
811        /// Palauttaa ensimmäisen peliolion joka toteuttaa ehdon (null jos mikään ei toteuta).
812        /// </summary>
813        /// <param name="condition">Ehto</param>
814        /// <returns>Olio</returns>
815        public GameObject GetFirstObject( Predicate<GameObject> condition )
816        {
817            for ( int i = MaxLayer; i >= MinLayer; i-- )
818            {
819                foreach ( var obj in Layers[i].Objects )
820                {
821                    GameObject gobj = obj as GameObject;
822
823                    if ( gobj != null && condition( gobj ) )
824                        return gobj;
825                }
826            }
827
828            return null;
829        }
830
831        /// <summary>
832        /// Palauttaa ensimmäisen ruutuolion joka toteuttaa ehdon (null jos mikään ei toteuta).
833        /// </summary>
834        /// <param name="condition">Ehto</param>
835        /// <returns>Lista olioista</returns>
836        public Widget GetFirstWidget( Predicate<Widget> condition )
837        {
838            return (Widget)GetFirstObject( obj => obj is Widget && condition( (Widget)obj ) );
839        }
840
841        /// <summary>
842        /// Palauttaa listan peliolioista, jotka ovat annetussa paikassa.
843        /// Jos paikassa ei ole mitään pelioliota, palautetaan tyhjä lista.
844        /// Lista on järjestetty päällimmäisestä alimmaiseen.
845        /// </summary>
846        /// <param name="position">Paikkakoordinaatit</param>
847        /// <returns>Lista olioista</returns>
848        public List<GameObject> GetObjectsAt( Vector position )
849        {
850            return GetObjects( obj => obj.IsInside( position ) );
851        }
852
853        /// <summary>
854        /// Palauttaa peliolion, joka on annetussa paikassa.
855        /// Jos paikassa ei ole mitään pelioliota, palautetaan null.
856        /// Jos olioita on useampia, palautetaan päällimmäinen.
857        /// </summary>
858        /// <param name="position">Paikkakoordinaatit</param>
859        /// <returns>Mahdollinen olio</returns>
860        public GameObject GetObjectAt( Vector position )
861        {
862            return GetFirstObject( obj => obj.IsInside( position ) && !(obj is MessageDisplay) );
863        }
864
865        /// <summary>
866        /// Palauttaa ruutuolion, joka on annetussa paikassa.
867        /// Jos paikassa ei ole mitään oliota, palautetaan null.
868        /// Jos olioita on useampia, palautetaan päällimmäinen.
869        /// </summary>
870        /// <param name="position">Paikkakoordinaatit</param>
871        /// <returns>Mahdollinen ruutuolio</returns>
872        public Widget GetWidgetAt( Vector position )
873        {
874            return (Widget)GetFirstObject( obj => obj is Widget && obj.IsInside( position ) && !( obj is MessageDisplay ) );
875        }
876
877        /// <summary>
878        /// Palauttaa listan peliolioista, jotka ovat annetussa paikassa tietyllä säteellä.
879        /// Jos paikassa ei ole mitään pelioliota, palautetaan tyhjä lista.
880        /// Lista on järjestetty päällimmäisestä alimmaiseen.
881        /// </summary>
882        /// <param name="position">Paikkakoordinaatit</param>
883        /// <param name="radius">Säde jolla etsitään</param>
884        /// <returns>Lista olioista</returns>
885        public List<GameObject> GetObjectsAt( Vector position, double radius )
886        {
887            Predicate<GameObject> isInsideRadius = delegate ( GameObject obj )
888            {
889                if ( obj is MessageDisplay ) return false;
890
891                Vector positionUp = new Vector( position.X, position.Y + radius );
892                Vector positionDown = new Vector( position.X, position.Y - radius );
893                Vector positionLeft = new Vector( position.X - radius, position.Y );
894                Vector positionRight = new Vector( position.X + radius, position.Y );
895
896                if ( obj.IsInside( position ) ) return true;
897                if ( obj.IsInside( positionUp ) ) return true;
898                if ( obj.IsInside( positionDown ) ) return true;
899                if ( obj.IsInside( positionLeft ) ) return true;
900                if ( obj.IsInside( positionRight ) ) return true;
901
902                return false;
903            };
904
905            return GetObjects( isInsideRadius );
906        }
907
908        /// <summary>
909        /// Palauttaa peliolion, joka on annetussa paikassa tietyllä säteellä.
910        /// Jos paikassa ei ole mitään pelioliota, palautetaan null.
911        /// Jos olioita on useampia, palautetaan ensin lisätty.
912        /// </summary>
913        /// <param name="position">Paikkakoordinaatit</param>
914        /// <param name="radius">Säde jolla etsitään</param>
915        /// <returns>Mahdollinen olio</returns>
916        public GameObject GetObjectAt( Vector position, double radius )
917        {
918            var objs = GetObjectsAt( position, radius );
919            return objs.Count > 0 ? objs[0] : null;
920        }
921
922        /// <summary>
923        /// Palauttaa listan peliolioista, jotka ovat annetussa paikassa tietyllä säteellä.
924        /// Jos paikassa ei ole mitään pelioliota, palautetaan tyhjä lista.
925        /// Lista on järjestetty päällimmäisestä alimmaiseen.
926        /// Vain annetulla tagilla varustetut oliot huomioidaan.
927        /// </summary>
928        /// <param name="position">Paikkakoordinaatit</param>
929        /// <param name="tag">Etsittävän olion tagi.</param>
930        /// <returns>Lista olioista</returns>
931        public List<GameObject> GetObjectsAt( Vector position, object tag )
932        {
933            return GetObjectsAt( position ).FindAll( obj => obj.Tag == tag );
934        }
935
936        /// <summary>
937        /// Palauttaa peliolion, joka on annetussa paikassa.
938        /// Vain annetulla tagilla varustetut oliot huomioidaan.
939        /// Jos paikassa ei ole mitään pelioliota, palautetaan null.
940        /// Jos olioita on useampia, palautetaan ensin lisätty.
941        /// </summary>
942        /// <param name="position">Paikkakoordinaatit</param>
943        /// <param name="tag">Etsittävän olion tagi.</param>
944        /// <returns>Mahdollinen olio</returns>
945        public GameObject GetObjectAt( Vector position, object tag )
946        {
947            return GetObjectsAt( position ).Find( obj => obj.Tag == tag );
948        }
949
950        /// <summary>
951        /// Palauttaa listan peliolioista, jotka ovat annetussa paikassa tietyllä säteellä.
952        /// Jos paikassa ei ole mitään pelioliota, palautetaan tyhjä lista.
953        /// Lista on järjestetty päällimmäisestä alimmaiseen.
954        /// Vain annetulla tagilla varustetut oliot huomioidaan.
955        /// </summary>
956        /// <param name="position">Paikkakoordinaatit</param>
957        /// <param name="tag">Etsittävän olion tagi.</param>
958        /// <param name="radius">Säde jolla etsitään</param>
959        /// <returns>Lista olioista</returns>
960        public List<GameObject> GetObjectsAt( Vector position, object tag, double radius )
961        {
962            return GetObjectsAt( position, radius ).FindAll<GameObject>( obj => obj.Tag == tag );
963        }
964
965        /// <summary>
966        /// Palauttaa peliolion, joka on annetussa paikassa tietyllä säteellä.
967        /// Vain annetulla tagilla varustetut oliot huomioidaan.
968        /// Jos paikassa ei ole mitään pelioliota, palautetaan null.
969        /// Jos olioita on useampia, palautetaan ensin lisätty.
970        /// </summary>
971        /// <param name="position">Paikkakoordinaatit</param>
972        /// <param name="tag">Etsittävän olion tagi.</param>
973        /// <param name="radius">Säde jolla etsitään</param>
974        /// <returns>Mahdollinen olio</returns>
975        public GameObject GetObjectAt( Vector position, object tag, double radius )
976        {
977            return GetObjectsAt( position, radius ).Find( obj => obj.Tag == tag );
978        }
979
980        /// <summary>
981        /// Palauttaa pelioliot kahden pisteen välillä.
982        /// </summary>
983        /// <param name="pos1"></param>
984        /// <param name="pos2"></param>
985        /// <returns></returns>
986        public List<GameObject> GetObjectsBetween( Vector pos1, Vector pos2 )
987        {
988            return GetObjects( obstacle => !( obstacle is Widget ) && obstacle.IsBlocking( pos1, pos2 ) );
989        }
990
991        /// <summary>
992        /// Lataa animaation contentista.
993        /// </summary>
994        /// <param name="name">Animaation nimi (ei tarkennetta)</param>
995        /// <returns>Animation-olio</returns>
996        public static Animation LoadAnimation( string name )
997        {
998            return Instance.Content.Load<Animation>( name );
999        }
1000
1001        /// <summary>
1002        /// Lataa kuvan contentista.
1003        /// </summary>
1004        /// <param name="name">Kuvan nimi (ei tarkennetta)</param>
1005        /// <returns>Image-olio</returns>
1006        public static Image LoadImage( string name )
1007        {
1008            return new Image( name );
1009        }
1010
1011        /// <summary>
1012        /// Lataa taulukon kuvia contentista.
1013        /// </summary>
1014        /// <param name="name">Kuvien nimet ilman tarkennetta pilkuin eroiteltuna</param>
1015        /// <returns>Taulukko Image-olioita</returns>
1016        public static Image[] LoadImages( params string[] names )
1017        {
1018            Image[] result = new Image[names.Length];
1019            for ( int i = 0; i < names.Length; i++ )
1020                result[i] = LoadImage( names[i] );
1021            return result;
1022        }
1023
1024        /// <summary>
1025        /// Lataa taulukon kuvia contentista.
1026        /// </summary>
1027        /// <param name="baseName">Ennen numeroa tuleva osa nimestä.</param>
1028        /// <param name="startIndex">Ensimmäisen kuvan numero.</param>
1029        /// <param name="endIndex">Viimeisen kuvan numero.</param>
1030        /// <param name="zeroPad">Onko numeron edessä täytenollia.</param>
1031        /// <returns></returns>
1032        public static Image[] LoadImages( string baseName, int startIndex, int endIndex, bool zeroPad = false )
1033        {
1034            if ( startIndex > endIndex ) throw new ArgumentException("Starting index must be smaller than ending index.");
1035
1036            Image[] result = new Image[endIndex - startIndex];
1037            string format;
1038
1039            if ( zeroPad )
1040            {
1041                int digits = endIndex.ToString().Length;
1042                format = "{0}{1:" + "0".Repeat( digits ) + "}";
1043            }
1044            else
1045            {
1046                format = "{0}{1}";
1047            }
1048
1049            for ( int i = startIndex; i < endIndex; i++ )
1050            {
1051                string imgName = String.Format( format, baseName, i );
1052                result[i - startIndex] = LoadImage( imgName );
1053            }
1054
1055            return result;
1056        }
1057
1058        /// <summary>
1059        /// Soittaa ääniefektin.
1060        /// </summary>
1061        /// <param name="name">Äänen nimi (ei tarkennetta)</param>
1062        public static void PlaySound( string name )
1063        {
1064            LoadSoundEffect( name ).Play();
1065        }
1066
1067        /// <summary>
1068        /// Lataa ääniefektin contentista.
1069        /// </summary>
1070        /// <param name="name">Äänen nimi (ei tarkennetta)</param>
1071        /// <returns>SoundEffect-olio</returns>
1072        public static SoundEffect LoadSoundEffect( string name )
1073        {
1074            return new SoundEffect( name );
1075        }
1076
1077        /// <summary>
1078        /// Lataa taulukon ääniefektejä contentista.
1079        /// </summary>
1080        /// <param name="names">Äänien nimet ilman tarkennetta pilkuin eroiteltuna</param>
1081        /// <returns>Taulukko SoundEffect-olioita</returns>
1082        public static SoundEffect[] LoadSoundEffects( params string[] names )
1083        {
1084            SoundEffect[] result = new SoundEffect[names.Length];
1085            for ( int i = 0; i < names.Length; i++ )
1086                result[i] = LoadSoundEffect( names[i] );
1087            return result;
1088        }
1089
1090        /// <summary>
1091        /// Poistaa kaikki ajastimet.
1092        /// </summary>
1093        public void ClearTimers()
1094        {
1095            Timer.ClearAll();
1096        }
1097
1098        /// <summary>
1099        /// Nollaa kaiken.
1100        /// </summary>
1101        public virtual void ClearAll()
1102        {
1103            Level.Clear();
1104            ResetLayers();
1105            ClearTimers();
1106#if !WINDOWS_PHONE
1107            ClearLights();
1108#endif
1109            ClearControls();
1110            GC.Collect();
1111            addMessageDisplay();
1112            ControlContext.Enable();
1113            Camera.Reset();
1114        }
1115
1116        /// <summary>
1117        /// Nollaa oliokerrokset. Huom. tuhoaa kaikki pelioliot!
1118        /// </summary>
1119        /// <param name="l"></param>
1120        public void ResetLayers()
1121        {
1122            ClearGameObjects();
1123            InitializeLayers();
1124        }
1125
1126        /// <summary>
1127        /// Poistaa kaikki oliokerrokset. Huom. tuhoaa kaikki pelioliot!
1128        /// </summary>
1129        /// <param name="l"></param>
1130        public void RemoveAllLayers()
1131        {
1132            ClearGameObjects();
1133            Layers.Clear();
1134        }
1135
1136        /// <summary>
1137        /// Palauttaa kontrollit alkutilaansa.
1138        /// </summary>
1139        public void ClearControls()
1140        {
1141            controls.Clear();
1142            TouchPanel.Clear();
1143#if DEBUG && !WINDOWS_PHONE
1144            Keyboard.Listen( Key.F12, ButtonState.Pressed, ToggleDebugScreen, null );
1145#endif
1146        }
1147
1148#if DEBUG
1149        private void ToggleDebugScreen()
1150        {
1151            if ( isDebugScreenShown )
1152                isDebugScreenShown = false;
1153            else
1154                isDebugScreenShown = true;
1155        }
1156#endif
1157
1158        /// <summary>
1159        /// Tuhoaa ja poistaa pelistä kaikki pelioliot (ml. fysiikkaoliot).
1160        /// </summary>
1161        public void ClearGameObjects()
1162        {
1163            foreach ( var layer in Layers )
1164                layer.Clear();
1165
1166            addMessageDisplay();
1167        }
1168
1169#if !WINDOWS_PHONE
1170        public void ClearLights()
1171        {
1172            lights.Clear();
1173        }
1174#endif
1175
1176        /// <summary>
1177        /// Ajetaan Updaten sijaan kun peli on pysähdyksissä.
1178        /// </summary>
1179        protected virtual void PausedUpdate( Time time )
1180        {
1181            foreach ( var layer in Layers )
1182            {
1183                // Update the UI components only
1184                layer.Objects.Update( time, o => o is Widget );
1185            }
1186
1187            Timer.UpdateAll( time, t => t.IgnorePause );
1188        }
1189
1190        /// <summary>
1191        /// Ajetaan kun pelin tilannetta päivitetään. Päivittämisen voi toteuttaa perityssä luokassa
1192        /// toteuttamalla tämän metodin. Perityn luokan metodissa tulee kutsua kantaluokan metodia.
1193        /// </summary>
1194        protected virtual void Update( Time time )
1195        {
1196            this.Camera.Update( time );
1197            Layers.Update( time );
1198            Timer.UpdateAll( time );
1199
1200            while (PendingActions.Count > 0)
1201            {
1202                PendingActions.Dequeue()();
1203            }
1204        }
1205
1206        /// <summary>
1207        /// This gets called after the GraphicsDevice has been created. So, this is
1208        /// the place to initialize the resources needed in the game. Except the graphics content,
1209        /// which should be called int LoadContent(), according to the XNA docs.
1210        /// </summary>
1211        [EditorBrowsable( EditorBrowsableState.Never )]
1212        protected override void Initialize()
1213        {
1214            Jypeli.Graphics.Initialize();
1215            screen = new ScreenView( GraphicsDevice.Viewport );
1216
1217#if WINDOWS_PHONE
1218            Phone.ResetScreen();
1219#else
1220            Point defaultSize = GetDefaultWindowSize();
1221            Window.Width = defaultSize.X;
1222            Window.Height = defaultSize.Y;
1223            Window.Update();
1224#endif
1225
1226            theLevel = new Level( this );
1227
1228            MediaPlayer = new MediaPlayer( Content );
1229
1230            addMessageDisplay();
1231
1232#if DEBUG
1233            double barWidth = 20;
1234            double barHeight = Screen.Height;
1235            fpsDisplay = new Label( "00" );
1236            fpsDisplay.Color = Color.Gray;
1237            fpsDisplay.X = Level.Right - barWidth / 2 - fpsDisplay.Width;
1238            fpsDisplay.Y = Screen.Top - fpsDisplay.Height / 2;
1239
1240            double left = Screen.Right - Layers.Count * barWidth;
1241
1242            objectCountDisplays = new BarGauge[Layers.Count];
1243
1244            for ( int i = 0; i < Layers.Count; i++ )
1245            {
1246                var gauge = new BarGauge( barWidth, Screen.Height );
1247                gauge.X = left + i * barWidth + barWidth / 2;
1248                gauge.Y = Screen.Center.Y;
1249                gauge.BarColor = Color.DarkGreen;
1250                gauge.BindTo( Layers[i + Layers.FirstIndex].ObjectCount );
1251                objectCountDisplays[i] = gauge;
1252            }
1253
1254            Keyboard.Listen( Key.F12, ButtonState.Pressed, ToggleDebugScreen, null );
1255#endif
1256
1257            base.Initialize();
1258        }
1259
1260        /// <summary>
1261        /// XNA calls this when graphics resources need to be loaded.
1262        /// Note that this can be called multiple times (whenever the graphics device is reset).
1263        /// </summary>
1264        [EditorBrowsable( EditorBrowsableState.Never )]
1265        protected override void LoadContent()
1266        {
1267            if ( !loadContentHasBeenCalled )
1268            {
1269                if ( InstanceInitialized != null )
1270                    InstanceInitialized();
1271
1272#if !WINDOWS_PHONE
1273                CallBegin();
1274#endif
1275                loadContentHasBeenCalled = true;
1276            }
1277
1278#if DEBUG && !WINDOWS_PHONE
1279            MessageDisplay.Add( "F12 - Debug view" );
1280#endif
1281            base.LoadContent();
1282
1283            GC.Collect();
1284        }
1285
1286        /// <summary>
1287        /// Aloittaa pelin kutsumalla Begin-metodia.
1288        /// Tärkeää: kutsu tätä, älä Beginiä suoraan, sillä muuten peli ei päivity!
1289        /// </summary>
1290        internal void CallBegin()
1291        {
1292            Begin();
1293            beginHasBeenCalled = true;
1294        }
1295
1296        /// <summary>
1297        /// Tässä alustetaan peli.
1298        /// </summary>
1299        public virtual void Begin()
1300        {
1301        }
1302
1303        /// <summary>
1304        /// Tässä alustetaan peli tombstoning-tilasta.
1305        /// Jos metodia ei ole määritelty, kutsutaan Begin.
1306        /// </summary>
1307        public virtual void Continue()
1308        {
1309        }
1310
1311        protected override void OnExiting( object sender, EventArgs args )
1312        {
1313            if ( Exiting != null )
1314                Exiting();
1315
1316            base.OnExiting( sender, args );
1317        }
1318
1319        private void addMessageDisplay()
1320        {
1321            MessageDisplay = new MessageDisplay();
1322            MessageDisplay.BackgroundColor = Color.LightGray;
1323            Add( MessageDisplay );
1324        }
1325
1326        [EditorBrowsable( EditorBrowsableState.Never )]
1327        protected override void Update( GameTime gameTime )
1328        {
1329            if ( !loadContentHasBeenCalled || !beginHasBeenCalled )
1330            {
1331                // No updates until both LoadContent and Begin have been called
1332                base.Update( gameTime );
1333                return;
1334            }
1335
1336            Window.Update();
1337            currentRealTime.Advance( gameTime );
1338
1339            if ( this.IsActive ) controls.Update();
1340            if ( DataStorage.IsUpdated )
1341                DataStorage.Update( currentRealTime );
1342
1343            // The update in derived classes.
1344            if ( !IsPaused )
1345            {
1346                currentTime.Advance( gameTime );
1347                this.Update( currentTime );
1348            }
1349            else
1350            {
1351                this.PausedUpdate( currentRealTime );
1352            }
1353
1354            base.Update( gameTime );
1355        }
1356
1357        protected virtual void Paint( Canvas canvas )
1358        {
1359        }
1360
1361        [EditorBrowsable( EditorBrowsableState.Never )]
1362        protected override void Draw( GameTime gameTime )
1363        {
1364            Time time = new Time( gameTime );
1365
1366            GraphicsDevice.Clear( ClearOptions.Target, Level.Background.Color.AsXnaColor(), 1.0f, 0 );
1367
1368            if ( Level.Background.Image != null && !Level.Background.MovesWithCamera )
1369            {
1370                SpriteBatch spriteBatch = Jypeli.Graphics.SpriteBatch;
1371                spriteBatch.Begin( SpriteSortMode.Deferred, BlendState.AlphaBlend );
1372                spriteBatch.Draw( Level.Background.Image.XNATexture, new XnaRectangle( 0, 0, (int)screen.Width, (int)screen.Height ), XnaColor.White );
1373                spriteBatch.End();
1374            }
1375
1376            // The world matrix adjusts the position and size of objects according to the camera angle.
1377            var worldMatrix =
1378                Matrix.CreateTranslation( (float)-Camera.Position.X, (float)-Camera.Position.Y, 0 )
1379                * Matrix.CreateScale( (float)Camera.ZoomFactor, (float)Camera.ZoomFactor, 1f );
1380
1381            Renderer.LightingEnabled = true;
1382            // If the background should move with camera, draw it here.
1383            Level.Background.Draw( worldMatrix, Matrix.Identity );
1384            Renderer.LightingEnabled = false;
1385
1386            if ( DrawPerimeter )
1387            {
1388                Matrix m = Matrix.CreateScale( (float)Level.Width, (float)Level.Height, 1 ) * worldMatrix;
1389                Renderer.DrawRectangle( ref m, PerimeterColor );
1390            }
1391
1392            foreach ( var layer in Layers )
1393            {
1394                layer.Draw( Camera );
1395            }
1396
1397            Graphics.LineBatch.Begin( ref worldMatrix );
1398            Graphics.Canvas.Reset( Level );
1399            Paint( Graphics.Canvas );
1400            Graphics.LineBatch.End();
1401
1402#if DEBUG
1403            if ( isDebugScreenShown )
1404            {
1405                DrawDebugView();
1406            }
1407#endif
1408
1409            base.Draw( gameTime );
1410        }
1411
1412#if DEBUG
1413        private void DrawDebugView()
1414        {
1415            for ( int i = 0; i < Layers.Count; i++ )
1416            {
1417                objectCountDisplays[i].Draw( Matrix.Identity );
1418            }
1419
1420            if ( fpsSkipCounter++ > 10 )
1421            {
1422                fpsSkipCounter = 0;
1423                fpsText = ( 1.0 / Time.SinceLastUpdate.TotalSeconds ).ToString( "F2" );
1424            }
1425
1426            fpsDisplay.Text = fpsText;
1427            fpsDisplay.Draw( Matrix.Identity );
1428        }
1429#endif
1430
1431        /// <summary>
1432        /// Asettaa ikkunan koon.
1433        /// </summary>
1434        /// <param name="width">Leveys.</param>
1435        /// <param name="height">Korkeus.</param>
1436        /// <param name="height">Käyttääkö peli koko ruutua.</param>
1437        [Obsolete( "Please set Window.Width, Window.Height and Window.Fullscreen instead." )]
1438        public bool SetWindowSize( int width, int height, bool fullscreen )
1439        {
1440            Window.Width = width;
1441            Window.Height = height;
1442            Window.Fullscreen = fullscreen;
1443            return true;
1444        }
1445
1446        /// <summary>
1447        /// Asettaa ikkunan koon.
1448        /// </summary>
1449        /// <param name="width">Leveys.</param>
1450        /// <param name="height">Korkeus.</param>
1451        [Obsolete("Please set Window.Width and Window.Height instead.")]
1452        public bool SetWindowSize( int width, int height )
1453        {
1454            Window.Width = width;
1455            Window.Height = height;
1456            return true;
1457        }
1458
1459        private Point GetDefaultWindowSize()
1460        {
1461            int xres = GraphicsDevice.DisplayMode.Width;
1462            int yres = GraphicsDevice.DisplayMode.Height;
1463
1464#if WINDOWS
1465            int borderwidth = GetSystemMetrics( 32 ); // SM_CXFRAME
1466            int titleheight = GetSystemMetrics( 30 ); // SM_CXSIZE
1467            yres -= borderwidth + titleheight;
1468#endif
1469
1470            /*if ( xres > 1024 && yres > 768 ) return new Point( 1024, 768 );
1471            if ( xres > 800 && yres > 600 ) return new Point( 800, 600 );
1472
1473            return new Point( 640, 480 );*/
1474
1475            return new Point( xres, yres );
1476        }
1477
1478        /// <summary>
1479        /// Pysäyttää pelin tai jatkaa sitä, jos se on jo pysäytetty.
1480        /// </summary>
1481        public void Pause()
1482        {
1483            IsPaused = !IsPaused;
1484        }
1485
1486        /// <summary>
1487        /// Lopettaa pelin.
1488        /// </summary>
1489        public void Exit()
1490        {
1491            Phone.StopVibrating();
1492            base.Exit();
1493        }
1494
1495        /// <summary>
1496        /// Kysyy haluaako lopettaa pelin ja lopettaa jos vastataan kyllä.
1497        /// </summary>
1498        public void ConfirmExit()
1499        {
1500            bool cursorVisible = IsMouseVisible;
1501            IsMouseVisible = true;
1502
1503            YesNoWindow kyselyIkkuna = new YesNoWindow( "Do you want to quit?" );
1504            kyselyIkkuna.Yes += Exit;
1505            kyselyIkkuna.Closed += delegate { IsMouseVisible = cursorVisible; IsPaused = false; };
1506            Add( kyselyIkkuna );
1507
1508            IsPaused = true;
1509        }
1510
1511        /// <summary>
1512        /// Tallentaa pelin.
1513        /// </summary>
1514        /// <param name="tagName">Pelitilanteen nimi.</param>
1515        public void SaveGame( string tagName )
1516        {
1517            Type gameType = this.GetType();
1518            SaveState state = DataStorage.BeginSave( tagName );
1519
1520            foreach ( PropertyInfo property in gameType.GetProperties( BindingFlags.GetProperty | StorageFile.AllOfInstance ) )
1521            {
1522                if ( property.GetCustomAttributes( typeof( SaveAttribute ), true ).Length == 0 )
1523                    continue;
1524
1525                object propValue = property.GetValue( this, null );
1526                Type propType = property.PropertyType;
1527                //DataStorage.Save( propValue, propType, tagName + FileManager.SanitizeFileName( property.Name ) );
1528                state.Save( propValue, propType, FileManager.SanitizeFileName( property.Name ) + "Property" );
1529            }
1530
1531            foreach ( FieldInfo field in gameType.GetFields( BindingFlags.GetField | StorageFile.AllOfInstance ) )
1532            {
1533                if ( field.GetCustomAttributes( typeof( SaveAttribute ), true ).Length == 0 )
1534                    continue;
1535
1536                object fieldValue = field.GetValue( this );
1537                Type fieldType = field.FieldType;
1538                //DataStorage.Save( fieldValue, fieldType, tagName + FileManager.SanitizeFileName( field.Name ) );
1539                state.Save( fieldValue, fieldType, FileManager.SanitizeFileName( field.Name ) + "Field" );
1540            }
1541
1542            state.EndSave();
1543        }
1544
1545        /// <summary>
1546        /// Lataa pelin.
1547        /// </summary>
1548        /// <param name="tagName">Pelitilanteen nimi.</param>
1549        public void LoadGame( string tagName )
1550        {
1551            if ( !DataStorage.Exists( tagName ) )
1552                return;
1553
1554            Type gameType = this.GetType();
1555
1556            LoadState state = DataStorage.BeginLoad( tagName );
1557
1558            foreach ( PropertyInfo property in gameType.GetProperties( BindingFlags.GetProperty | StorageFile.AllOfInstance ) )
1559            {
1560                if ( property.GetCustomAttributes( typeof( SaveAttribute ), true ).Length == 0 )
1561                    continue;
1562
1563                object oldValue = property.GetValue( this, null );
1564                Type propType = property.PropertyType;
1565                //object newValue = DataStorage.Load( oldValue, propType, tagName + FileManager.SanitizeFileName( property.Name ) );
1566                object newValue = state.Load( oldValue, propType, FileManager.SanitizeFileName( property.Name ) + "Property" );
1567                property.SetValue( this, newValue, null );
1568            }
1569
1570            foreach ( FieldInfo field in gameType.GetFields( BindingFlags.GetField | StorageFile.AllOfInstance ) )
1571            {
1572                if ( field.GetCustomAttributes( typeof( SaveAttribute ), true ).Length == 0 )
1573                    continue;
1574
1575                object oldValue = field.GetValue( this );
1576                Type fieldType = field.FieldType;
1577                //object newValue = DataStorage.Load( oldValue, fieldType, tagName + FileManager.SanitizeFileName( field.Name ) );
1578                object newValue = state.Load( oldValue, fieldType, FileManager.SanitizeFileName( field.Name ) + "Field" );
1579                field.SetValue( this, newValue );
1580            }
1581
1582            state.EndLoad();
1583        }
1584
1585        public void AddFactory<T>( string tag, Factory.FactoryMethod method )
1586        {
1587            Factory.AddFactory<T>( tag, method );
1588        }
1589
1590        public void RemoveFactory<T>( string tag, Factory.FactoryMethod method )
1591        {
1592            Factory.RemoveFactory<T>( tag, method );
1593        }
1594
1595        public T FactoryCreate<T>( string tag )
1596        {
1597            return Factory.FactoryCreate<T>( tag );
1598        }
1599
1600        /// <summary>
1601        /// Näyttää kontrollien ohjetekstit.
1602        /// </summary>
1603        public void ShowControlHelp()
1604        {
1605            MessageDisplay.Add( "Ohjeet:" );
1606
1607            foreach ( String message in controls.GetHelpTexts() )
1608            {
1609                MessageDisplay.Add( message );
1610            }
1611        }
1612
1613        [EditorBrowsable( EditorBrowsableState.Never )]
1614        public void Dispose()
1615        {
1616        }
1617
1618        /// <summary>
1619        /// Sitoo kontrollien ohjeet viestinäyttöön ja haluttuihin nappeihin.
1620        /// Tämän jälkeen nappeja painamalla pelaaja saa automaattisesti ohjeen esille.
1621        /// </summary>
1622        /// <param name="keysOrButtons">Napit, joita painamalla ohjeen saa näkyviin.</param>
1623        private void BindControlHelp( params object[] keysOrButtons )
1624        {
1625            String nappaimet = "";
1626
1627            foreach ( object o in keysOrButtons )
1628            {
1629                if ( o is Key )
1630                {
1631                    Key k = (Key)o;
1632                    controls.Keyboard.Listen( k, ButtonState.Pressed, ShowControlHelp, null );
1633
1634                    nappaimet += k.ToString();
1635                }
1636
1637                if ( o is Button )
1638                {
1639                    Button b = (Button)o;
1640                    for ( int i = 0; i < controls.GameControllers.Length; i++ )
1641                    {
1642                        controls.GameControllers[i].Listen( b, ButtonState.Pressed, ShowControlHelp, null );
1643                    }
1644
1645                    nappaimet += b.ToString();
1646                }
1647
1648                nappaimet += " / ";
1649            }
1650
1651            MessageDisplay.Add( "Katso näppäinohje painamalla " + nappaimet.Substring( 0, nappaimet.Length - 3 ) );
1652        }
1653
1654        public static Image LoadImageFromResources( string name )
1655        {
1656            return new Image( ResourceContent.Load<Texture2D>( name ) );
1657        }
1658
1659        public static SoundEffect LoadSoundEffectFromResources( string name )
1660        {
1661            return new SoundEffect( ResourceContent.Load<XnaSoundEffect>( name ) );
1662        }
1663
1664        /// <summary>
1665        /// Lataa fontin. Fontin tulee olla lisätty content-hakemistoon.
1666        /// </summary>
1667        /// <param name="name">Fontin tiedoston nimi, ilman päätettä.</param>
1668        public static Font LoadFont( string name )
1669        {
1670            return new Font( name, ContentSource.GameContent );
1671        }
1672    }
1673}
1674
Note: See TracBrowser for help on using the repository browser.