11. History 2006 Dez – XNA Game Studio Express 1.0 2007 April – XNA Game Studio Express 1.0 Refresh 2007 Dez – XNA Game Studio 2.0 2008 Okt – XNA Game Studio 3.0 2009 März – XNA Game Studio 3.1
15. Entwicklung mit XNA Entwickelt wird in C# Keine DirectX Kenntnisse notwendig Einzige andere wichtige Sprache ist HLSL (High Level Shader Language) zur Shaderprogrammierung XNA Anwendungen Managed Code, alles wird von CLR ausgeführt rufen .NET und DirectX Funktionen auf
16.
17. Application Model Fenstermanagement Initialisieren des Graphics-Device Bereitstellen des Game-Loop // // init_d3d.cpp - Initializing Direct3D9 // // Copyright 2004 by Ken Paulson // // This program is free software; you can redistribute it and/or modify it // under the terms of the Drunken Hyena License. If a copy of the license was // not included with this software, you may get a copy from: // http://www.drunkenhyena.com/docs/DHLicense.txt // #defineWIN32_LEAN_AND_MEAN// Exclude rarely-used stuff from Windows headers #include<D3DX9.h> #include"../common/dhWindow.h" #include"../common/dhD3D.h" #include"../common/dhUtility.h" #include"../Common/dhUserPrefsDialog.h" // This is causes the required libraries to be linked in, the same thing can be accomplished by // adding it to your compiler's link list (Project->Settings->Link in VC++), // but I prefer this method. #pragmacomment(lib,"d3d9.lib") #pragmacomment(lib,"dxerr9.lib") // Forward declarations for all of our functions, see their definitions for more detail LRESULTCALLBACKdefault_window_proc(HWNDp_hwnd,UINTp_msg,WPARAMp_wparam,LPARAMp_lparam); HRESULTinit_scene(void); voidkill_scene(void); HRESULTrender(void); voidInitVolatileResources(void); voidFreeVolatileResources(void); // The name of our application. Used for window and MessageBox titles and error reporting constchar*g_app_name="Initializing Direct3D9"; // Our screen/window sizes and bit depth. A better app would allow the user to choose the // sizes. I'll do that in a later tutorial, for now this is good enough. constintg_width=640; constintg_height=480; constintg_depth=16;//16-bit colour // Our global flag to track whether we should quit or not. When it becomes true, we clean // up and exit. boolg_app_done=false; // Our main Direct3D interface, it doesn't do much on its own, but all the more commonly // used interfaces are created by it. It's the first D3D object you create, and the last // one you release. IDirect3D9*g_D3D=NULL; // The D3DDevice is your main rendering interface. It represents the display and all of its // capabilities. When you create, modify, or render any type of resource, you will likely // do it through this interface. IDirect3DDevice9*g_d3d_device=NULL; //Our presentation parameters. They get set in our call to dhInitDevice, and we need them //in case we need to reset our application. D3DPRESENT_PARAMETERSg_pp; } //****************************************************************************************** // Function:init_scene // Whazzit:Prepare any objects required for rendering. //****************************************************************************************** HRESULTinit_scene(void){ HRESULThr=D3D_OK; InitVolatileResources(); returnhr; } //****************************************************************************************** // Function:kill_scene // Whazzit:Clean up any objects we required for rendering. //****************************************************************************************** voidkill_scene(void){ FreeVolatileResources(); } //****************************************************************************************** // Function: render // Whazzit:Clears the screen and then presents the results. // If we were doing any real drawing, it would go in this function between // the BeginScene() & EndScene(). //****************************************************************************************** HRESULTrender(void){ HRESULThr; //Clear the buffer to our new colour. hr=g_d3d_device->Clear(0,//Number of rectangles to clear, we're clearing everything so set it to 0 NULL,//Pointer to the rectangles to clear, NULL to clear whole display D3DCLEAR_TARGET,//What to clear. We don't have a Z Buffer or Stencil Buffer 0x00000000,//Colour to clear to (AARRGGBB) 1.0f,//Value to clear ZBuffer to, doesn't matter since we don't have one 0);//Stencil clear value, again, we don't have one, this value doesn't matter if(FAILED(hr)){ returnhr; } //Notify the device that we're ready to render hr=g_d3d_device->BeginScene(); if(FAILED(hr)){ returnhr; //****************************************************************************************** // Function:WinMain // Whazzit:The entry point of our application //****************************************************************************************** intAPIENTRYWinMain(HINSTANCE,HINSTANCE,LPSTR,int){ boolfullscreen; HWNDwindow=NULL; D3DFORMATformat; HRESULThr; dhUserPrefsuser_prefs(g_app_name); // Prompt the user, Full Screen? Windowed? Cancel? // Prompt the user for their preferences if(!user_prefs.QueryUser()){ dhLog("Exiting"); return0; } fullscreen=user_prefs.GetFullscreen(); // Build our window. hr=dhInitWindow(fullscreen,g_app_name,g_width,g_height,default_window_proc,&window); if(FAILED(hr)){ dhLog("Failed to create Window",hr); return0; } //Build the D3D object hr=dhInitD3D(&g_D3D); if(FAILED(hr)){ dhKillWindow(&window); dhLog("Failed to create D3D",hr); return0; } //Find a good display/pixel format hr=dhGetFormat(g_D3D,fullscreen,g_depth,&format); if(FAILED(hr)){ dhKillWindow(&window); dhLog("Failed to get a display format",hr); return0; g_app_done=true; dhLog("Error rendering",hr); } } //Free all of our objects and other resources kill_scene(); //Clean up all of our Direct3D objects dhKillD3D(&g_D3D,&g_d3d_device); //Close down our window dhKillWindow(&window); //Exit happily return0; } //****************************************************************************************** // Function:InitVolatileResources // Whazzit:Prepare any objects that will not survive a device Reset. These are initialized // separately so they can easily be recreated when we Reset our device. //****************************************************************************************** voidInitVolatileResources(void){ //In this lesson there is nothing that needs to be done here. } //****************************************************************************************** // Function:FreeVolatileResources // Whazzit:Free any of our resources that need to be freed so that we can Reset our device, // also used to free these resources at the end of the program run. //****************************************************************************************** voidFreeVolatileResources(void){ //This sample has no resources that need to be freed here. } // //All rendering goes here // //Notify the device that we're finished rendering for this frame g_d3d_device->EndScene(); //Show the results hr=g_d3d_device->Present(NULL,//Source rectangle to display, NULL for all of it NULL,//Destination rectangle, NULL to fill whole display NULL,//Target window, if NULL uses device window set in CreateDevice NULL);//Unused parameter, set it to NULL returnhr; } //****************************************************************************************** // Function:default_window_proc // Whazzit:This handles any incoming Windows messages and sends any that aren't handled to // DefWindowProc for Windows to handle. //****************************************************************************************** LRESULTCALLBACKdefault_window_proc(HWNDp_hwnd,UINTp_msg,WPARAMp_wparam,LPARAMp_lparam){ switch(p_msg){ caseWM_KEYDOWN:// A key has been pressed, end the app caseWM_CLOSE://User hit the Close Window button, end the app caseWM_LBUTTONDOWN://user hit the left mouse button g_app_done=true; return0; } return(DefWindowProc(p_hwnd,p_msg,p_wparam,p_lparam)); } DWORDadapter=user_prefs.GetAdapter(); D3DDEVTYPEdev_type=user_prefs.GetDeviceType(); //Initialize our PresentParameters dhInitPresentParameters(fullscreen,window,g_width,g_height,format,D3DFMT_UNKNOWN,&g_pp); //Create our device hr=dhInitDevice(g_D3D,adapter,dev_type,window,&g_pp,&g_d3d_device); if(FAILED(hr)){ dhKillD3D(&g_D3D,&g_d3d_device); dhKillWindow(&window); dhLog("Failed to create the device",hr); return0; } //One-time preparation of objects and other stuff required for rendering init_scene(); //Loop until the user aborts (closes the window,presses the left mouse button or hits a key) while(!g_app_done){ dhMessagePump();//Check for window messages hr=g_d3d_device->TestCooperativeLevel(); if(SUCCEEDED(hr)){ hr=render();//Draw our incredibly cool graphics } //Our device is lost if(hr==D3DERR_DEVICELOST||hr==D3DERR_DEVICENOTRESET){ dhHandleLostDevice(g_d3d_device,&g_pp,hr); }elseif(FAILED(hr)){//Any other error
18. usingSystem; usingSystem.Collections.Generic; usingSystem.Linq; usingMicrosoft.Xna.Framework; usingMicrosoft.Xna.Framework.Audio; usingMicrosoft.Xna.Framework.Content; usingMicrosoft.Xna.Framework.GamerServices; usingMicrosoft.Xna.Framework.Graphics; usingMicrosoft.Xna.Framework.Input; usingMicrosoft.Xna.Framework.Media; usingMicrosoft.Xna.Framework.Net; usingMicrosoft.Xna.Framework.Storage; namespaceWindowsGame1 { publicclassGame1 : Microsoft.Xna.Framework.Game { GraphicsDeviceManagergraphics; SpriteBatchspriteBatch; publicGame1() { graphics=newGraphicsDeviceManager(this); Content.RootDirectory="Content"; } protectedoverridevoidInitialize() { base.Initialize(); } protectedoverridevoidLoadContent() { // Create a new SpriteBatch, which can be used to draw textures. spriteBatch=newSpriteBatch(GraphicsDevice); } protectedoverridevoidUnloadContent() { } protectedoverridevoidUpdate(GameTimegameTime) { base.Update(gameTime); } protectedoverridevoidDraw(GameTimegameTime) { GraphicsDevice.Clear(Color.CornflowerBlue); base.Draw(gameTime); } } }
21. Timing Default: Fixed-Timestep mit Ziel-Framerate 60 FPS Pro Sekunde wird 60 mal Update aufgerufen d.h. 16,67 Millisekunden pro Frame So oft wie möglich dazu noch Draw
22. Timing verwenden Update und Draw besitzen ein GameTime Objekt z.B. Zeitspanne zwischen zwei Aufrufen von Update() oder Draw() protected override void Update(GameTime gameTime) { int elapsed = gameTime.ElapsedGameTime.Milliseconds; }
23.
24. Content Pipeline Inhalte werden nicht im Original-Format geladen Inhalte werden mittels Content Pipeline in eigenes XNA Format übersetzt (XNA Binary, .xnb) Dabei werden diese für die Verwendung vorbereitet: 3D Modelle in XNA eigene Datenstruktur laden z.B. 3D-Modelle mit Textur zusammenfügen
29. 1. Inhalt laden Grafiken (Sprites) werden durch Klasse Texture2D repräsentiert Texture2D mySprite = Content.Load<Texture2D>(“Held”); KeineDateiendungangeben! Content in LoadContent laden
30. 2. Anzeigen - SpriteBatch 2D GrafikenwerdenmittelsSpriteBatchgezeichnet SpriteBatchkann 1 bis n Sprites mitgleichenEinstellungenzeichnen Umschlossen von SpriteBatch.Begin() und SpriteBatch.End()
31. 2. Anzeigen – SpriteBatch.Draw spriteBatch.Begin(); spriteBatch.Draw( Texture2D, Vector2, Color ); …spriteBatch.Draw( Texture2D, Vector2, Color ); spriteBatch.End();
32. Aufgabe 1 Sprite laden, anzeigen, in der Mitte des Bildschirmsrotieren (um Mittelpunkt der Grafik) und Rot färben XNA GamestudioProjekterstellen Grafik in Projekteinfügen (Unterpunkt Content) Laden mit Content.Load Mittels SpriteBatch und Draw Funktion anzeigen Rotation: SpriteBatch.Draw ist mehrfach überladen Mitte des Bildschirms: GraphicsDevice.Viewport.....
33. Lösung – Aufgabe 1 private Texture2D MySprite; private float RotationAngle = 0.0f; // Load our sprite through the content pipeline MySprite= Content.Load<Texture2D>("Controller"); // Update rotation according to elapsed time RotationAngle+= (float)gameTime.ElapsedGameTime.TotalSeconds; RotationAngle %= MathHelper.Pi * 2.0f;
34. Lösung – Aufgabe 1 // Begin sprite drawing spriteBatch.Begin(); // Position where the sprite should be displayed on the screen Vector2 pos = new Vector2(GraphicsDevice.Viewport.Width / 2, GraphicsDevice.Viewport.Height / 2); // Center point of rotation Vector2 origin = new Vector2(MySprite.Width / 2, MySprite.Height / 2); // Draw the sprite spriteBatch.Draw(MySprite, pos, null, Color.Red, RotationAngle, origin, 1.0f, SpriteEffects.None,0f); // End sprite drawing spriteBatch.End();
58. Aufgabe 2 Sprite mitTastatur/Mausbewegen Position des Sprites auf Bildschirmanzeigen
59. Lösung – Aufgabe 2 // Get new keyboard state KeyboardStatekeyboardState = Keyboard.GetState(); // Calculate the amount of moving with the elapsed time since the last frame, this gives us framerate independent movement float movement = (float)gameTime.ElapsedGameTime.TotalMilliseconds * 0.5f; // Left/Right if (keyboardState.IsKeyDown(Keys.Left)) { spritePosition.X-= movement; } if (keyboardState.IsKeyDown(Keys.Right)) { spritePosition.X+= movement; } // Up/Down if (keyboardState.IsKeyDown(Keys.Up)) { spritePosition.Y-= movement; } if (keyboardState.IsKeyDown(Keys.Down)) { spritePosition.Y+= movement; }
60. Lösung – Aufgabe 2 // Draw the sprite (White as color to disable any color effects) spriteBatch.Draw(MySprite, spritePosition, Color.White); // Text to display string displayText = "Position: " + spritePosition.X + " : " + spritePosition.Y; // Display text in upper left corner of viewport Vector2 displayPosition = new Vector2(10, 10); // Display text using our loaded Font and in red color spriteBatch.DrawString(MyFont, displayText, displayPosition, Color.Red);
62. Mathematische Funktionen: Vektoren Klassen Vector2, Vector3 und Vector4 Grundrechnenarten Add(), Multiply(), etc. Vector2.Distance() Abstand zwischen zwei Vektoren Vector2.Length() Länge eines Vektors Vector2.Reflect() Veränderter Vektor nach einer Kollision + =
63. Matrizen Klasse Matrix für Transformation Wird insbesondere bei 3D verwendet Hilfsfunktionen: Matrix.CreateRotation(...) Matrix.CreateTranslation(...) …
66. Aufgabe 3 Kollisionen mit einem anderen Sprite im 2D-Raum feststellen und diese behandeln Kollisionserkennung implementieren, die verhindert, dass ein Sprite den sichtbaren Teil des Bildschirmes verläßt
72. Musik MediaPlayer-Klasse Funktionen zum: Abspielen, Stoppen, Resumen von Songs Abspielen einer Datei MediaPlayer.Play(SongObject); Musik-Dateien werden in einer Endlosschleife abgespielt Kann durch eigene Musik von der Xbox360 „ersetzt“ werden
73. Aufgabe 4 Wiedergabe von Sounds Bei Bewegung eines Objektes Kollision eines Objektes Wiedergabe von Musik Permanente Hintergrund-Musik
75. Alternative XACT Cross-platform Audio Creation Tool Tool zum Erzeugen von Sound-Cues Erlaubt das Erstellen einer Vielzahl von Effekten und Sound-Kombinationen Erstellte Cues werden im Quelltext nur noch abgespielt, keine weitere Programmierung nötig
88. Und jetzt…?Wettbewerb! Um XX:XX kürt die fachkundige Jury das beste/interessanteste/spielbarste Spiel!
Notas do Editor
IchentwickeleSpieleseitrund 10 Jahren, wobeinebeneinigenkleinerenProjektenmomentanimmernocheinEchtzeitstrategiespielnamens “Die Verbotene Welt” aktuellist. Gestartet ca. 2001, basierten die erstenVersionennoch auf DirectX 6 bzw. spaeter auf DirectX 7, wobei wir dann spaeter auf OpenGL umgestiegen sind, da unsere Grafiker, die komischerweise fast alle Mac Fans sind, auch mitspielen wollten. WegendieserUmstellungenund diversen Hardware Abstraktionslayern, die wirselberentwickelnmussten, sindwirauchimmernochdran, peilenabereinen Release vermutlichimnaechstenJahr an.Wermehrdaruebererfahrenmoechte, kannsichunterwww.sechsta-sinn.dedarueberinformieren.
XNA ist in der Tradition von Akronymen wie GNU ein rekursives Akronym und steht für „XNA`s Not Acronymed“. XNA ist ein Framework von Microsoft, dass seit 2006 erhältlich ist und die Spieleentwicklung auf Windows, Xbox360 und dem Zune (inklusive HD) ermöglicht. Es ist als Nachfolger von Managed DirectX zu sehen, dass ca. 2005 eingestellt wurde und ermöglicht grafische Ausgabe, Wiedergabe von Sounds und das Abfragen von Eingabegeräten.
KommerziellverfuegbareMiddlewareswie Unity oder die Unreal Engine bringen oft direktfertigeEditoren, ein Entity Konzeptfuer die Spielwelt, eineeigeneSkriptsprache und aehnlichesmit. Diese Engines bewegensichschonziemlichweitRichtungSpiele-Editoren und muessen in vielenFaellennur um gewisse, fuer das eigene Spiel wichtige, Funktionenergaenztwerden. Das ist XNA nicht. Wiegesagtstellt XNA die GrundfunktionalitaetenzurVerfuegung um die Hardware komfortabelanzusprechen, und um
XNA istsozusagen der inoffizielleNachfolger von Managed DirectX, dessenEntwicklung ~2005 eingestelltwurde.
Das XNA Framework umfasstverschiedeneKomponenten. Zugrundeliegeneinige Kern-Microsoft Technologien, wie DirectX, als Hardware Abstraction Layer und Interface zuGrafikkarten und allgemein das .NET Framework unter Windows bzw. das .NET Compact Framework auf der Xbox und dem Zune.
Das XNA Application Model nimmtunsvieleAufgabenab, die ehermit der HardwareabstraktionsschichtoderdemBetriebssystemselbstzutunhaben. Beispielweisesieht die Fenster + Grafikinitialisierungmit der VerwendungeinigerHilfen so aus [Animation]. Das sindalles in allem ca. 600 Zeilen Code, die unsnichtsliefernalseinleeresFenster.
Die meiste Interaktion haben wir als Programmierer mit der Game-Loop. Ein Spiel verhaelt sich bei der Behandlung der Benutzereingaben etwas anders als “normale” Software. Bei einer normalen Windows Forms oder aehnliches GUI Applikation reagieren wir normalerweise nur auf Events, wie z.B. ein Kontextmenu, einen Button oder einen Tastendruck. Bei einem Spiel muessen durchgehend Aktionen durchgefuehrt werden, es muss beispielsweise das Verhalten von computer-gesteuerten Charakteren berechnet werden, es muss die Physik der Spielwelt aktualisiert werden – wenn ein Objekt faellt, faellt es nicht nur, wenn der Benutzer etwas tut sondern solange bis es irgendwie angekommen ist. Daher befinden sich Spiele waehrend ihrer Ausfuehrung konstant in einer Schleife, der sogenannten Gameloop.In der Gameloop werden also durchgehend Aktionen ausgeführt wird:Eingaben des Spielers abfragen (Tastatur, Gamepad, Maus etc.)Physik berechnen => KollisionenKünstliche IntelligenzWelt, Charaktere, Objekte anzeigenInterface anzeigenDiese Aktionen lassen sich also grob in zwei Kategorien einteilen, einmal Aktionen die den Stand der Spielwelt/Spiellogik verändern und einmal Aktionen die die Spielwelt nur anzeigen, also nichts verändern sondern nur lesen. Die eigentliche XNA Gameloop besteht daher für uns aus zwei Funktionen:Update()Draw()
TODO
Debugging: Breakpoints in verzögern Spiel, verändern Update/Draw Reihenfolge
GameTime.IsRunningSlowlz
Assets werden bei XNA Projekten nicht im Original geladen, sondern erst von einem Content Processor in ein XNA eigenes Binaerformat (.xnb)konvertiert. Beim Ausführen des Spiels werden die Assets dann direkt aus diesem XNB Format geladen. Die Vorteile bestehen darin, dass gewisse Arbeitsschritte bereits beim kompilieren des Projekts erledigt werden können und nicht erst zur Laufzeit ausgefuehrt werden.
Jetztwollenwirendlich mal konkretetwastun und zwarein Sprite anzeigen. ImGrunde benötigen wir dazu nur zwei Schritte:
Der erste Schritt ist das Laden der Grafik. Beispielsweise wollen wir hier eine 2D Grafik namens Held.bmp laden. Dazulegenwirunseinfachein Texture2D Objekt an und laden mittels der Content Pipeline die Grafik.Wichtigisthierbei, dassbeim Laden keineDateiendungangegebenwird, also anstatt Held.bmp nur Held da – wirerinnernuns – die Content Pipeline unser Original-Bitmap in das XNA eigene Format ueberfuehrt hat, und dabei die Endungverlorengegangenistbzw. nichtmehrgebrauchtwird.Ansonsten muss man nochdaraufachten, dass man den Content wirklicherst in LoadContentlaedt und nichtbereitsim Constructor oder in Initialize, da die Content Pipeline dortnochnichtinitialisiertist.
Der naechsteSchrittistdann das anzeigen. Um 2D Grafiken auf den Bildschirmzubringenstellt XNA eineKlassenamensSpriteBatchzurVerfuegung. In der Draw Methodebekommenwireinsolchesdirektvom Framework uebergeben, muessenuns also keineigeneserstellen.Wie der Name bereitssagt, kanneinSpriteBatchObjektmehrere Sprites anzeigen, dazugibteseineRahmenstruktur und zwarmuessenalleOperationenmitdemSpriteBatchObjekt von einem .Begin und einem .End Aufruf
Im Code sieht das Ganzefolgendermaßen aus. Wir haben die Rahmenstruktur und dazwischen beliebig viele .Draw Aufrufe. Was diese Rahmenstruktur, die im Grunde ein Batch – wie der Name schon sagt – liefert, bringt, werden wir spaeter noch sehen. Das SpriteBatchObjekte hat diverse überladene .Draw Methoden, wobei die einfachsteeineTextur, einen Vector mit der PositionsangabesowieeineFarbeerwartet.
Das Ganzesolltihrjetzteinfach mal in einerkleinenAufgabeausprobieren. Erstellteuch in Visual Studio einneues XNA GamestudioProjekt und fuegtdanneineGrafikeuremProjekthinzu. Alsnaechstes die Grafik in der Draw Methodeanzeigen. Wennihr das geschaffthabt, koennteicheuchnochmal die ueberladungen von SpriteBatch Draw angucken und einmalversuchen die Grafik um den Mittelpunkt des Bildschirmsrotierenzulassen.
Also, wie mehrfahch gesagt, die SpriteBatch Draw Funktion ist mehrfach ueberladen. Die wichtigsten Effekte die man damit erzielen kann habe ich hier mal dargestellt.Ausschnitte kann man z.B. nutzen, um bei einer Animation mit vielen Stufen nicht fuer jedes Frame eine eigene Textur laden zu muessen…Rotation koennte man beispielsweise in einem Spiel wie Asteroids nutzen, in dem man ein Raumschiff von oben sieht und in alle Richtungen drehen kann. Damit man nicht Bilder fuer alle Richtungen basteln muss, kann man das Schiff einfach drehen. Ebenso wenn man ein Sprite vielleicht nicht unbedingt um den Mittelpunkt/Ursprung drehen moechte, kann man den Referenzpunkt, den origin angeben. Des weiterengibtesnochEffekte, so kann man beispielsweise das Sprite horizontal odervertikalspiegelnoderueber die Farbe das Sprite einfaerben, oder transparent darstellenlassen. Das Einfaerbenkoennte man beispielsweisenutzen, um auf sehreinfache Art und Weise verschiedeneGegnertypendarzustellen.ZuletztistesnochmoeglicheinelayerTiefeanzugeben, die istbei der Stapelverarbeitung von Sprites wichtig. [Folienwechsel]
SpriteBlendModeAdditiveAlphaBlendingNoneSpriteSortModeDeferred – Sprites werden in Queue des jeweiligenSpriteBatchObjektsaufgenommen (FtB, BtF – genauso, abermitSortierungnach Z; Texture – SortierungnachTextur)Immediate – Sprites werdensofortgerendertSaveStateModeSaveState – RenderStates VOR Begin() werdengespeichert und nach End() wiederhergestelltNone – GenauumgekehrtMatrixMatrix – Translate, Scale, Rotation angewendet auf alle Sprites des Batch Objekts
Lizensierung von Fonts, kannproblematischsein
Mausnatuerlichnurunter Windows
Nachdemwirunsbisheruns die erstenWegeangeguckthaben um Grafiken auf demBildschirmanzuzeigen und zubewegen, sollnatuerlich
Mittels XACT istesmoeglich, die Zuordnung von Events zu Sounds auszulagern. Das heisstihrdefinierte in Eurem Spiel, dassbeieinerbestimmenAktionbeispielsweiseein