source: 2015/26/MikkoL/JRPG/JRPG/Jypeli/Game/Game.cs @ 6401

Revision 6401, 57.1 KB checked in by mijoliim, 5 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
418            Window = new JypeliWindow( base.Window, Game.GraphicsDeviceManager );
419            Window.Resized += new JypeliWindow.ResizeEvent( WindowResized );
420
421#if WINDOWS
422            this.dataStorage = new WindowsFileManager( WindowsLocation.DataPath, WindowsLocation.MyDocuments ); ;
423#elif WINDOWS_PHONE
424            dataStorage = new IsolatedStorageManager();
425#elif XBOX
426            Components.Add( new GamerServicesComponent( this ) );
427            dataStorage = new XboxFileManager();
428#endif
429
430            dataStorage.ReadAccessDenied += delegate (Exception ex)
431            {
432                ShowErrorMessage("Could not read from data directory. " + ex.Message);
433            };
434            dataStorage.WriteAccessDenied += delegate (Exception ex)
435            {
436                ShowErrorMessage("Could not write to data directory. " + ex.Message);
437            };
438
439            DrawPerimeter = false;
440            PerimeterColor = Color.DarkGray;
441
442            phone = new Phone();
443            Phone.Tombstoning = true;
444        }
445
446        private void WindowResized( int oldWidth, int oldHeight, int newWidth, int newHeight )
447        {
448            if ( GraphicsDevice == null )
449                return;
450
451            if ( Mouse != null )
452                Mouse.Viewport = GraphicsDevice.Viewport;
453
454            if ( screen != null )
455                screen.viewPort = GraphicsDevice.Viewport;
456        }
457
458        private void ShowErrorMessage(string message)
459        {
460            MessageDisplay.Add( "ERROR: " + message, Color.Red );
461
462            /*bool mouseVisible = IsMouseVisible;
463            IsMouseVisible = true;
464
465            MessageWindow errWindow = new MessageWindow( message );
466            errWindow.Color = Color.Red;
467            errWindow.Message.TextColor = Color.White;
468            errWindow.Closed += delegate { IsMouseVisible = mouseVisible; };
469            Add( errWindow );*/
470        }
471
472        /// <summary>
473        /// Näyttää viesti-ikkunan.
474        /// </summary>
475        /// <param name="message">Viesti</param>
476        public void ShowMessageWindow( string message )
477        {
478            MessageWindow w = new MessageWindow( message );
479            Add( w );
480        }
481
482        private void InitializeLayers()
483        {
484            Layers = new SynchronousList<Layer>( -3 );
485            Layers.ItemAdded += OnLayerAdded;
486            Layers.ItemRemoved += OnLayerRemoved;
487
488            for ( int i = 0; i < 7; i++ )
489            {
490                Layers.Add( new Layer() );
491            }
492
493            // This is the widget layer
494            Layers.Add( Layer.CreateStaticLayer() );
495
496            Layers.UpdateChanges();
497        }
498
499        private void InitializeContent()
500        {
501#if WINDOWS_PHONE
502           
503            ResourceContent = new ResourceContentManager( this.Services, WindowsPhoneResources.ResourceManager );
504#elif XBOX
505            ResourceContent = new ResourceContentManager( this.Services, XBox360Resources.ResourceManager );
506#else
507            ResourceContent = new ResourceContentManager( this.Services, Resources.ResourceManager );
508#endif
509
510            Content.RootDirectory = "Content";
511        }
512
513        private void InitializeGraphics( int device )
514        {
515#if WINDOWS
516            if ( device == 1 )
517                GraphicsDeviceManager = new GraphicsDeviceManager( this );
518            else
519                GraphicsDeviceManager = new TargetedGraphicsDeviceManager( this, device );
520#else
521            GraphicsDeviceManager = new GraphicsDeviceManager( this );
522#endif
523            GraphicsDeviceManager.PreferredDepthStencilFormat = Jypeli.Graphics.SelectStencilMode();
524            GraphicsDeviceManager.SynchronizeWithVerticalRetrace = false;
525            SmoothTextures = true;
526        }
527
528        private void ActivateObject( ControlContexted obj )
529        {
530            obj.ControlContext.Active = true;
531
532            if ( obj.IsModal )
533            {
534                Game.Instance.ControlContext.SaveFocus();
535                Game.Instance.ControlContext.Active = false;
536
537                foreach ( Layer l in Layers )
538                {
539                    foreach ( IGameObject lo in l.Objects )
540                    {
541                        ControlContexted co = lo as ControlContexted;
542                        if ( lo == obj || co == null )
543                            continue;
544
545                        co.ControlContext.SaveFocus();
546                        co.ControlContext.Active = false;
547                    }
548                }
549            }
550        }
551
552        private void DeactivateObject( ControlContexted obj )
553        {
554            obj.ControlContext.Active = false;
555
556            if ( obj.IsModal )
557            {
558                Game.Instance.ControlContext.RestoreFocus();
559
560                foreach ( Layer l in Layers )
561                {
562                    foreach ( IGameObject lo in l.Objects )
563                    {
564                        ControlContexted co = lo as ControlContexted;
565                        if ( lo == obj || co == null )
566                            continue;
567
568                        co.ControlContext.RestoreFocus();
569                    }
570                }
571            }
572        }
573
574        protected virtual void OnObjectAdded( IGameObject obj )
575        {
576            IGameObjectInternal iObj = obj as IGameObjectInternal;
577            if ( iObj == null ) return;
578            iObj.IsAddedToGame = true;
579            iObj.OnAddedToGame();
580
581            ControlContexted cObj = obj as ControlContexted;
582            if ( cObj != null ) ActivateObject( cObj );
583        }
584
585        protected virtual void OnObjectRemoved( IGameObject obj )
586        {
587            IGameObjectInternal iObj = obj as IGameObjectInternal;
588            if ( iObj == null ) return;
589            iObj.IsAddedToGame = false;
590            iObj.OnRemoved();
591
592            ControlContexted cObj = obj as ControlContexted;
593            if ( cObj != null ) DeactivateObject( cObj );
594        }
595
596        internal static void OnAddObject( IGameObject obj )
597        {
598            Debug.Assert( Instance != null );
599            Instance.OnObjectAdded( obj );
600        }
601
602        internal static void OnRemoveObject( IGameObject obj )
603        {
604            Debug.Assert( Instance != null );
605            Instance.OnObjectRemoved( obj );
606        }
607
608        private void OnLayerAdded( Layer l )
609        {
610            l.Objects.ItemAdded += this.OnObjectAdded;
611            l.Objects.ItemRemoved += this.OnObjectRemoved;
612        }
613
614        private void OnLayerRemoved( Layer l )
615        {
616            l.Objects.ItemAdded -= this.OnObjectAdded;
617            l.Objects.ItemRemoved -= this.OnObjectRemoved;
618        }
619
620        /// <summary>
621        /// Suorittaa aliohjelman kun peli on varmasti alustettu.
622        /// </summary>
623        /// <param name="actionMethod">Suoritettava aliohjelma.</param>
624        public static void AssertInitialized( Action actionMethod )
625        {
626            if ( Instance != null )
627                actionMethod();
628            else
629                InstanceInitialized += actionMethod;
630        }
631
632        /// <summary>
633        /// Suorittaa aliohjelman seuraavalla päivityksellä.
634        /// </summary>
635        /// <param name="action"></param>
636        public static void DoNextUpdate( Action action )
637        {
638            if ( Instance != null )
639                Instance.PendingActions.Enqueue( action );
640            else
641                InstanceInitialized += action;
642        }
643
644        /// <summary>
645        /// Suorittaa aliohjelman seuraavalla päivityksellä.
646        /// </summary>
647        /// <typeparam name="T1"></typeparam>
648        /// <param name="action"></param>
649        /// <param name="p1"></param>
650        public static void DoNextUpdate<T1>( Action<T1> action, T1 p1 )
651        {
652            DoNextUpdate( delegate { action( p1 ); } );
653        }
654
655        /// <summary>
656        /// Suorittaa aliohjelman seuraavalla päivityksellä.
657        /// </summary>
658        /// <typeparam name="T1"></typeparam>
659        /// <typeparam name="T2"></typeparam>
660        /// <param name="action"></param>
661        /// <param name="p1"></param>
662        /// <param name="p2"></param>
663        public static void DoNextUpdate<T1, T2>( Action<T1, T2> action, T1 p1, T2 p2 )
664        {
665            DoNextUpdate( delegate { action( p1, p2 ); } );
666        }
667
668        /// <summary>
669        /// Suorittaa aliohjelman kun peli on varmasti alustettu.
670        /// </summary>
671        /// <param name="actionMethod">Suoritettava aliohjelma.</param>
672        public static void AssertInitialized<T1>( Action<T1> actionMethod, T1 o1 )
673        {
674            if ( Instance != null )
675                actionMethod( o1 );
676            else
677                InstanceInitialized += delegate { actionMethod( o1 ); };
678        }
679
680        /// <summary>
681        /// Lisää olion peliin.
682        /// Tavalliset oliot tulevat automaattisesti kerrokselle 0
683        /// ja ruutuoliot päällimmäiselle kerrokselle.
684        /// </summary>
685        public void Add( IGameObject o )
686        {
687            if ( o.Layer != null && o.Layer.Objects.WillContain( o ) )
688            {
689                if ( o.Layer == Layers[0] )
690                {
691                    throw new NotSupportedException( "Object cannot be added twice" );
692                }
693                else
694                    throw new NotSupportedException( "Object cannot be added to multiple layers" );
695            }
696
697            if ( o is Widget ) Add( o, MaxLayer );
698            else Add( o, 0 );
699        }
700
701#if !WINDOWS_PHONE
702        /// <summary>
703        /// Lisää valon peliin. Nykyisellään valoja voi olla ainoastaan
704        /// yksi kappale.
705        /// </summary>
706        public void Add( Light light )
707        {
708            if ( light == null ) throw new NullReferenceException( "Tried to add a null light to game" );
709
710            if ( lights.Count >= 1 )
711                throw new NotSupportedException( "Only one light is supported" );
712
713            lights.Add( light );
714        }
715#endif
716
717        /// <summary>
718        /// Lisää peliolion peliin, tiettyyn kerrokseen.
719        /// </summary>
720        /// <param name="o">Lisättävä olio.</param>
721        /// <param name="layer">Kerros, luku väliltä [-3, 3].</param>
722        public virtual void Add( IGameObject o, int layer )
723        {
724            if ( o == null ) throw new NullReferenceException( "Tried to add a null object to game" );
725            Layers[layer].Add( o );
726        }
727
728        internal static IList<IGameObject> GetObjectsAboutToBeAdded()
729        {
730            List<IGameObject> result = new List<IGameObject>();
731
732            foreach ( Layer layer in Game.Instance.Layers )
733            {
734                layer.GetObjectsAboutToBeAdded( result );
735            }
736
737            return result;
738        }
739
740        /// <summary>
741        /// Lisää oliokerroksen peliin.
742        /// </summary>
743        /// <param name="l"></param>
744        public void Add( Layer l )
745        {
746            Layers.Add( l );
747            Layers.UpdateChanges();
748        }
749
750        /// <summary>
751        /// Poistaa olion pelistä. Jos haluat tuhota olion,
752        /// kutsu mielummin olion <c>Destroy</c>-metodia.
753        /// </summary>
754        /// <remarks>
755        /// Oliota ei poisteta välittömästi, vaan viimeistään seuraavan
756        /// päivityksen jälkeen.
757        /// </remarks>
758        public void Remove( IGameObject o )
759        {
760            if ( !o.IsAddedToGame )
761                return;
762
763            foreach ( Layer l in Layers )
764                l.Remove( o );
765        }
766         
767        /// <summary>
768        /// Poistaa oliokerroksen pelistä.
769        /// </summary>
770        /// <param name="l"></param>
771        public void Remove( Layer l )
772        {
773            Layers.Remove( l );
774            Layers.UpdateChanges();
775        }
776
777        /// <summary>
778        /// Palauttaa listan kaikista peliolioista jotka toteuttavat ehdon.
779        /// Lista on järjestetty päällimmäisestä alimmaiseen.
780        /// </summary>
781        /// <param name="condition">Ehto</param>
782        /// <returns>Lista olioista</returns>
783        public List<GameObject> GetObjects( Predicate<GameObject> condition )
784        {
785            List<GameObject> objs = new List<GameObject>();
786
787            for ( int i = MaxLayer; i >= MinLayer; i-- )
788            {
789                foreach ( var obj in Layers[i].Objects )
790                {
791                    GameObject gobj = obj as GameObject;
792
793                    if ( gobj != null && condition( gobj ) )
794                        objs.Add( gobj );
795                }
796            }
797
798            return objs;
799        }
800
801        /// <summary>
802        /// Palauttaa listan kaikista peliolioista joilla on tietty tagi.
803        /// Lista on järjestetty päällimmäisestä alimmaiseen.
804        /// </summary>
805        /// <param name="tags">Tagi(t)</param>
806        /// <returns>Lista olioista</returns>
807        public List<GameObject> GetObjectsWithTag( params string[] tags )
808        {
809            return GetObjects( o => tags.Contains<string>( o.Tag as string ) );
810        }
811
812        /// <summary>
813        /// Palauttaa ensimmäisen peliolion joka toteuttaa ehdon (null jos mikään ei toteuta).
814        /// </summary>
815        /// <param name="condition">Ehto</param>
816        /// <returns>Olio</returns>
817        public GameObject GetFirstObject( Predicate<GameObject> condition )
818        {
819            for ( int i = MaxLayer; i >= MinLayer; i-- )
820            {
821                foreach ( var obj in Layers[i].Objects )
822                {
823                    GameObject gobj = obj as GameObject;
824
825                    if ( gobj != null && condition( gobj ) )
826                        return gobj;
827                }
828            }
829
830            return null;
831        }
832
833        /// <summary>
834        /// Palauttaa ensimmäisen ruutuolion joka toteuttaa ehdon (null jos mikään ei toteuta).
835        /// </summary>
836        /// <param name="condition">Ehto</param>
837        /// <returns>Lista olioista</returns>
838        public Widget GetFirstWidget( Predicate<Widget> condition )
839        {
840            return (Widget)GetFirstObject( obj => obj is Widget && condition( (Widget)obj ) );
841        }
842
843        /// <summary>
844        /// Palauttaa listan peliolioista, jotka ovat annetussa paikassa.
845        /// Jos paikassa ei ole mitään pelioliota, palautetaan tyhjä lista.
846        /// Lista on järjestetty päällimmäisestä alimmaiseen.
847        /// </summary>
848        /// <param name="position">Paikkakoordinaatit</param>
849        /// <returns>Lista olioista</returns>
850        public List<GameObject> GetObjectsAt( Vector position )
851        {
852            return GetObjects( obj => obj.IsInside( position ) );
853        }
854
855        /// <summary>
856        /// Palauttaa peliolion, joka on annetussa paikassa.
857        /// Jos paikassa ei ole mitään pelioliota, palautetaan null.
858        /// Jos olioita on useampia, palautetaan päällimmäinen.
859        /// </summary>
860        /// <param name="position">Paikkakoordinaatit</param>
861        /// <returns>Mahdollinen olio</returns>
862        public GameObject GetObjectAt( Vector position )
863        {
864            return GetFirstObject( obj => obj.IsInside( position ) && !(obj is MessageDisplay) );
865        }
866
867        /// <summary>
868        /// Palauttaa ruutuolion, joka on annetussa paikassa.
869        /// Jos paikassa ei ole mitään oliota, palautetaan null.
870        /// Jos olioita on useampia, palautetaan päällimmäinen.
871        /// </summary>
872        /// <param name="position">Paikkakoordinaatit</param>
873        /// <returns>Mahdollinen ruutuolio</returns>
874        public Widget GetWidgetAt( Vector position )
875        {
876            return (Widget)GetFirstObject( obj => obj is Widget && obj.IsInside( position ) && !( obj is MessageDisplay ) );
877        }
878
879        /// <summary>
880        /// Palauttaa listan peliolioista, jotka ovat annetussa paikassa tietyllä säteellä.
881        /// Jos paikassa ei ole mitään pelioliota, palautetaan tyhjä lista.
882        /// Lista on järjestetty päällimmäisestä alimmaiseen.
883        /// </summary>
884        /// <param name="position">Paikkakoordinaatit</param>
885        /// <param name="radius">Säde jolla etsitään</param>
886        /// <returns>Lista olioista</returns>
887        public List<GameObject> GetObjectsAt( Vector position, double radius )
888        {
889            Predicate<GameObject> isInsideRadius = delegate ( GameObject obj )
890            {
891                if ( obj is MessageDisplay ) return false;
892
893                Vector positionUp = new Vector( position.X, position.Y + radius );
894                Vector positionDown = new Vector( position.X, position.Y - radius );
895                Vector positionLeft = new Vector( position.X - radius, position.Y );
896                Vector positionRight = new Vector( position.X + radius, position.Y );
897
898                if ( obj.IsInside( position ) ) return true;
899                if ( obj.IsInside( positionUp ) ) return true;
900                if ( obj.IsInside( positionDown ) ) return true;
901                if ( obj.IsInside( positionLeft ) ) return true;
902                if ( obj.IsInside( positionRight ) ) return true;
903
904                return false;
905            };
906
907            return GetObjects( isInsideRadius );
908        }
909
910        /// <summary>
911        /// Palauttaa peliolion, joka on annetussa paikassa tietyllä säteellä.
912        /// Jos paikassa ei ole mitään pelioliota, palautetaan null.
913        /// Jos olioita on useampia, palautetaan ensin lisätty.
914        /// </summary>
915        /// <param name="position">Paikkakoordinaatit</param>
916        /// <param name="radius">Säde jolla etsitään</param>
917        /// <returns>Mahdollinen olio</returns>
918        public GameObject GetObjectAt( Vector position, double radius )
919        {
920            var objs = GetObjectsAt( position, radius );
921            return objs.Count > 0 ? objs[0] : null;
922        }
923
924        /// <summary>
925        /// Palauttaa listan peliolioista, jotka ovat annetussa paikassa tietyllä säteellä.
926        /// Jos paikassa ei ole mitään pelioliota, palautetaan tyhjä lista.
927        /// Lista on järjestetty päällimmäisestä alimmaiseen.
928        /// Vain annetulla tagilla varustetut oliot huomioidaan.
929        /// </summary>
930        /// <param name="position">Paikkakoordinaatit</param>
931        /// <param name="tag">Etsittävän olion tagi.</param>
932        /// <returns>Lista olioista</returns>
933        public List<GameObject> GetObjectsAt( Vector position, object tag )
934        {
935            return GetObjectsAt( position ).FindAll( obj => obj.Tag == tag );
936        }
937
938        /// <summary>
939        /// Palauttaa peliolion, joka on annetussa paikassa.
940        /// Vain annetulla tagilla varustetut oliot huomioidaan.
941        /// Jos paikassa ei ole mitään pelioliota, palautetaan null.
942        /// Jos olioita on useampia, palautetaan ensin lisätty.
943        /// </summary>
944        /// <param name="position">Paikkakoordinaatit</param>
945        /// <param name="tag">Etsittävän olion tagi.</param>
946        /// <returns>Mahdollinen olio</returns>
947        public GameObject GetObjectAt( Vector position, object tag )
948        {
949            return GetObjectsAt( position ).Find( obj => obj.Tag == tag );
950        }
951
952        /// <summary>
953        /// Palauttaa listan peliolioista, jotka ovat annetussa paikassa tietyllä säteellä.
954        /// Jos paikassa ei ole mitään pelioliota, palautetaan tyhjä lista.
955        /// Lista on järjestetty päällimmäisestä alimmaiseen.
956        /// Vain annetulla tagilla varustetut oliot huomioidaan.
957        /// </summary>
958        /// <param name="position">Paikkakoordinaatit</param>
959        /// <param name="tag">Etsittävän olion tagi.</param>
960        /// <param name="radius">Säde jolla etsitään</param>
961        /// <returns>Lista olioista</returns>
962        public List<GameObject> GetObjectsAt( Vector position, object tag, double radius )
963        {
964            return GetObjectsAt( position, radius ).FindAll<GameObject>( obj => obj.Tag == tag );
965        }
966
967        /// <summary>
968        /// Palauttaa peliolion, joka on annetussa paikassa tietyllä säteellä.
969        /// Vain annetulla tagilla varustetut oliot huomioidaan.
970        /// Jos paikassa ei ole mitään pelioliota, palautetaan null.
971        /// Jos olioita on useampia, palautetaan ensin lisätty.
972        /// </summary>
973        /// <param name="position">Paikkakoordinaatit</param>
974        /// <param name="tag">Etsittävän olion tagi.</param>
975        /// <param name="radius">Säde jolla etsitään</param>
976        /// <returns>Mahdollinen olio</returns>
977        public GameObject GetObjectAt( Vector position, object tag, double radius )
978        {
979            return GetObjectsAt( position, radius ).Find( obj => obj.Tag == tag );
980        }
981
982        /// <summary>
983        /// Palauttaa pelioliot kahden pisteen välillä.
984        /// </summary>
985        /// <param name="pos1"></param>
986        /// <param name="pos2"></param>
987        /// <returns></returns>
988        public List<GameObject> GetObjectsBetween( Vector pos1, Vector pos2 )
989        {
990            return GetObjects( obstacle => !( obstacle is Widget ) && obstacle.IsBlocking( pos1, pos2 ) );
991        }
992
993        /// <summary>
994        /// Lataa animaation contentista.
995        /// </summary>
996        /// <param name="name">Animaation nimi (ei tarkennetta)</param>
997        /// <returns>Animation-olio</returns>
998        public static Animation LoadAnimation( string name )
999        {
1000            return Instance.Content.Load<Animation>( name );
1001        }
1002
1003        /// <summary>
1004        /// Lataa kuvan contentista.
1005        /// </summary>
1006        /// <param name="name">Kuvan nimi (ei tarkennetta)</param>
1007        /// <returns>Image-olio</returns>
1008        public static Image LoadImage( string name )
1009        {
1010            return new Image( name );
1011        }
1012
1013        /// <summary>
1014        /// Lataa taulukon kuvia contentista.
1015        /// </summary>
1016        /// <param name="name">Kuvien nimet ilman tarkennetta pilkuin eroiteltuna</param>
1017        /// <returns>Taulukko Image-olioita</returns>
1018        public static Image[] LoadImages( params string[] names )
1019        {
1020            Image[] result = new Image[names.Length];
1021            for ( int i = 0; i < names.Length; i++ )
1022                result[i] = LoadImage( names[i] );
1023            return result;
1024        }
1025
1026        /// <summary>
1027        /// Lataa taulukon kuvia contentista.
1028        /// </summary>
1029        /// <param name="baseName">Ennen numeroa tuleva osa nimestä.</param>
1030        /// <param name="startIndex">Ensimmäisen kuvan numero.</param>
1031        /// <param name="endIndex">Viimeisen kuvan numero.</param>
1032        /// <param name="zeroPad">Onko numeron edessä täytenollia.</param>
1033        /// <returns></returns>
1034        public static Image[] LoadImages( string baseName, int startIndex, int endIndex, bool zeroPad = false )
1035        {
1036            if ( startIndex > endIndex ) throw new ArgumentException("Starting index must be smaller than ending index.");
1037
1038            Image[] result = new Image[endIndex - startIndex];
1039            string format;
1040
1041            if ( zeroPad )
1042            {
1043                int digits = endIndex.ToString().Length;
1044                format = "{0}{1:" + "0".Repeat( digits ) + "}";
1045            }
1046            else
1047            {
1048                format = "{0}{1}";
1049            }
1050
1051            for ( int i = startIndex; i < endIndex; i++ )
1052            {
1053                string imgName = String.Format( format, baseName, i );
1054                result[i - startIndex] = LoadImage( imgName );
1055            }
1056
1057            return result;
1058        }
1059
1060        /// <summary>
1061        /// Soittaa ääniefektin.
1062        /// </summary>
1063        /// <param name="name">Äänen nimi (ei tarkennetta)</param>
1064        public static void PlaySound( string name )
1065        {
1066            LoadSoundEffect( name ).Play();
1067        }
1068
1069        /// <summary>
1070        /// Lataa ääniefektin contentista.
1071        /// </summary>
1072        /// <param name="name">Äänen nimi (ei tarkennetta)</param>
1073        /// <returns>SoundEffect-olio</returns>
1074        public static SoundEffect LoadSoundEffect( string name )
1075        {
1076            return new SoundEffect( name );
1077        }
1078
1079        /// <summary>
1080        /// Lataa taulukon ääniefektejä contentista.
1081        /// </summary>
1082        /// <param name="names">Äänien nimet ilman tarkennetta pilkuin eroiteltuna</param>
1083        /// <returns>Taulukko SoundEffect-olioita</returns>
1084        public static SoundEffect[] LoadSoundEffects( params string[] names )
1085        {
1086            SoundEffect[] result = new SoundEffect[names.Length];
1087            for ( int i = 0; i < names.Length; i++ )
1088                result[i] = LoadSoundEffect( names[i] );
1089            return result;
1090        }
1091
1092        /// <summary>
1093        /// Poistaa kaikki ajastimet.
1094        /// </summary>
1095        public void ClearTimers()
1096        {
1097            Timer.ClearAll();
1098        }
1099
1100        /// <summary>
1101        /// Nollaa kaiken.
1102        /// </summary>
1103        public virtual void ClearAll()
1104        {
1105            Level.Clear();
1106            ResetLayers();
1107            ClearTimers();
1108#if !WINDOWS_PHONE
1109            ClearLights();
1110#endif
1111            ClearControls();
1112            GC.Collect();
1113            addMessageDisplay();
1114            ControlContext.Enable();
1115            Camera.Reset();
1116        }
1117
1118        /// <summary>
1119        /// Nollaa oliokerrokset. Huom. tuhoaa kaikki pelioliot!
1120        /// </summary>
1121        /// <param name="l"></param>
1122        public void ResetLayers()
1123        {
1124            ClearGameObjects();
1125            InitializeLayers();
1126        }
1127
1128        /// <summary>
1129        /// Poistaa kaikki oliokerrokset. Huom. tuhoaa kaikki pelioliot!
1130        /// </summary>
1131        /// <param name="l"></param>
1132        public void RemoveAllLayers()
1133        {
1134            ClearGameObjects();
1135            Layers.Clear();
1136        }
1137
1138        /// <summary>
1139        /// Palauttaa kontrollit alkutilaansa.
1140        /// </summary>
1141        public void ClearControls()
1142        {
1143            controls.Clear();
1144            TouchPanel.Clear();
1145#if DEBUG && !WINDOWS_PHONE
1146            Keyboard.Listen( Key.F12, ButtonState.Pressed, ToggleDebugScreen, null );
1147#endif
1148        }
1149
1150#if DEBUG
1151        private void ToggleDebugScreen()
1152        {
1153            if ( isDebugScreenShown )
1154                isDebugScreenShown = false;
1155            else
1156                isDebugScreenShown = true;
1157        }
1158#endif
1159
1160        /// <summary>
1161        /// Tuhoaa ja poistaa pelistä kaikki pelioliot (ml. fysiikkaoliot).
1162        /// </summary>
1163        public void ClearGameObjects()
1164        {
1165            foreach ( var layer in Layers )
1166                layer.Clear();
1167
1168            addMessageDisplay();
1169        }
1170
1171#if !WINDOWS_PHONE
1172        public void ClearLights()
1173        {
1174            lights.Clear();
1175        }
1176#endif
1177
1178        /// <summary>
1179        /// Ajetaan Updaten sijaan kun peli on pysähdyksissä.
1180        /// </summary>
1181        protected virtual void PausedUpdate( Time time )
1182        {
1183            foreach ( var layer in Layers )
1184            {
1185                // Update the UI components only
1186                layer.Objects.Update( time, o => o is Widget );
1187            }
1188
1189            Timer.UpdateAll( time, t => t.IgnorePause );
1190        }
1191
1192        /// <summary>
1193        /// Ajetaan kun pelin tilannetta päivitetään. Päivittämisen voi toteuttaa perityssä luokassa
1194        /// toteuttamalla tämän metodin. Perityn luokan metodissa tulee kutsua kantaluokan metodia.
1195        /// </summary>
1196        protected virtual void Update( Time time )
1197        {
1198            this.Camera.Update( time );
1199            Layers.Update( time );
1200            Timer.UpdateAll( time );
1201
1202            while (PendingActions.Count > 0)
1203            {
1204                PendingActions.Dequeue()();
1205            }
1206        }
1207
1208        /// <summary>
1209        /// This gets called after the GraphicsDevice has been created. So, this is
1210        /// the place to initialize the resources needed in the game. Except the graphics content,
1211        /// which should be called int LoadContent(), according to the XNA docs.
1212        /// </summary>
1213        [EditorBrowsable( EditorBrowsableState.Never )]
1214        protected override void Initialize()
1215        {
1216            Jypeli.Graphics.Initialize();
1217            screen = new ScreenView( GraphicsDevice.Viewport );
1218
1219#if WINDOWS_PHONE
1220            Phone.ResetScreen();
1221#else
1222            Point defaultSize = GetDefaultWindowSize();
1223            Window.Width = defaultSize.X;
1224            Window.Height = defaultSize.Y;
1225            Window.Update();
1226#endif
1227
1228            theLevel = new Level( this );
1229
1230            MediaPlayer = new MediaPlayer( Content );
1231
1232            addMessageDisplay();
1233
1234#if DEBUG
1235            double barWidth = 20;
1236            double barHeight = Screen.Height;
1237            fpsDisplay = new Label( "00" );
1238            fpsDisplay.Color = Color.Gray;
1239            fpsDisplay.X = Level.Right - barWidth / 2 - fpsDisplay.Width;
1240            fpsDisplay.Y = Screen.Top - fpsDisplay.Height / 2;
1241
1242            double left = Screen.Right - Layers.Count * barWidth;
1243
1244            objectCountDisplays = new BarGauge[Layers.Count];
1245
1246            for ( int i = 0; i < Layers.Count; i++ )
1247            {
1248                var gauge = new BarGauge( barWidth, Screen.Height );
1249                gauge.X = left + i * barWidth + barWidth / 2;
1250                gauge.Y = Screen.Center.Y;
1251                gauge.BarColor = Color.DarkGreen;
1252                gauge.BindTo( Layers[i + Layers.FirstIndex].ObjectCount );
1253                objectCountDisplays[i] = gauge;
1254            }
1255
1256            Keyboard.Listen( Key.F12, ButtonState.Pressed, ToggleDebugScreen, null );
1257#endif
1258
1259            base.Initialize();
1260        }
1261
1262        /// <summary>
1263        /// XNA calls this when graphics resources need to be loaded.
1264        /// Note that this can be called multiple times (whenever the graphics device is reset).
1265        /// </summary>
1266        [EditorBrowsable( EditorBrowsableState.Never )]
1267        protected override void LoadContent()
1268        {
1269            if ( !loadContentHasBeenCalled )
1270            {
1271                if ( InstanceInitialized != null )
1272                    InstanceInitialized();
1273
1274#if !WINDOWS_PHONE
1275                CallBegin();
1276#endif
1277                loadContentHasBeenCalled = true;
1278            }
1279
1280#if DEBUG && !WINDOWS_PHONE
1281            MessageDisplay.Add( "F12 - Debug view" );
1282#endif
1283            base.LoadContent();
1284
1285            GC.Collect();
1286        }
1287
1288        /// <summary>
1289        /// Aloittaa pelin kutsumalla Begin-metodia.
1290        /// Tärkeää: kutsu tätä, älä Beginiä suoraan, sillä muuten peli ei päivity!
1291        /// </summary>
1292        internal void CallBegin()
1293        {
1294            Begin();
1295            beginHasBeenCalled = true;
1296        }
1297
1298        /// <summary>
1299        /// Tässä alustetaan peli.
1300        /// </summary>
1301        public virtual void Begin()
1302        {
1303        }
1304
1305        /// <summary>
1306        /// Tässä alustetaan peli tombstoning-tilasta.
1307        /// Jos metodia ei ole määritelty, kutsutaan Begin.
1308        /// </summary>
1309        public virtual void Continue()
1310        {
1311        }
1312
1313        protected override void OnExiting( object sender, EventArgs args )
1314        {
1315            if ( Exiting != null )
1316                Exiting();
1317
1318            base.OnExiting( sender, args );
1319        }
1320
1321        private void addMessageDisplay()
1322        {
1323            MessageDisplay = new MessageDisplay();
1324            MessageDisplay.BackgroundColor = Color.LightGray;
1325            Add( MessageDisplay );
1326        }
1327
1328        [EditorBrowsable( EditorBrowsableState.Never )]
1329        protected override void Update( GameTime gameTime )
1330        {
1331            if ( !loadContentHasBeenCalled || !beginHasBeenCalled )
1332            {
1333                // No updates until both LoadContent and Begin have been called
1334                base.Update( gameTime );
1335                return;
1336            }
1337
1338            Window.Update();
1339            currentRealTime.Advance( gameTime );
1340
1341            if ( this.IsActive ) controls.Update();
1342            if ( DataStorage.IsUpdated )
1343                DataStorage.Update( currentRealTime );
1344
1345            // The update in derived classes.
1346            if ( !IsPaused )
1347            {
1348                currentTime.Advance( gameTime );
1349                this.Update( currentTime );
1350            }
1351            else
1352            {
1353                this.PausedUpdate( currentRealTime );
1354            }
1355
1356            base.Update( gameTime );
1357        }
1358
1359        protected virtual void Paint( Canvas canvas )
1360        {
1361        }
1362
1363        [EditorBrowsable( EditorBrowsableState.Never )]
1364        protected override void Draw( GameTime gameTime )
1365        {
1366            Time time = new Time( gameTime );
1367
1368            GraphicsDevice.Clear( ClearOptions.Target, Level.Background.Color.AsXnaColor(), 1.0f, 0 );
1369
1370            if ( Level.Background.Image != null && !Level.Background.MovesWithCamera )
1371            {
1372                SpriteBatch spriteBatch = Jypeli.Graphics.SpriteBatch;
1373                spriteBatch.Begin( SpriteSortMode.Deferred, BlendState.AlphaBlend );
1374                spriteBatch.Draw( Level.Background.Image.XNATexture, new XnaRectangle( 0, 0, (int)screen.Width, (int)screen.Height ), XnaColor.White );
1375                spriteBatch.End();
1376            }
1377
1378            // The world matrix adjusts the position and size of objects according to the camera angle.
1379            var worldMatrix =
1380                Matrix.CreateTranslation( (float)-Camera.Position.X, (float)-Camera.Position.Y, 0 )
1381                * Matrix.CreateScale( (float)Camera.ZoomFactor, (float)Camera.ZoomFactor, 1f );
1382
1383            // If the background should move with camera, draw it here.
1384            Level.Background.Draw( worldMatrix, Matrix.Identity );
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        public 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.