source: 2016/27/ohjaajat/VenienteFragore/SimplePhysics/Game.cs @ 7656

Revision 7656, 13.0 KB checked in by empaheik, 4 years ago (diff)

Testikenttä Tiledintegraatiolle.

Line 
1using System;
2using System.Collections.Concurrent;
3using System.Collections.Generic;
4using System.Linq;
5using System.Threading.Tasks;
6using Jypeli;
7using Jypeli.Widgets;
8
9namespace SimplePhysics
10{
11    /// <summary>
12    /// Yksinkertaisemmilla fysiikoilla varustettu peli.
13    /// </summary>
14    public class Game : Jypeli.Game
15    {
16        [Flags]
17        private enum Axis
18        {
19            Neither = 0,
20            Horizontal = 1,
21            Vertical = 2,
22            Both = Horizontal | Vertical
23        }
24
25        /// <summary>
26        /// Painovoima.
27        /// </summary>
28        public Vector Gravity { get; set; }
29
30        /// <summary>
31        /// Yhden tiilen koko (leveys tai korkeus).
32        /// </summary>
33        public int TileSize { get; set; }
34
35        // Taulukko pelissä olevista tiilistä.
36        private Object[,] tileMap;
37
38        // Lista pelissä olevista ei-staattisista fysiikkaobjekteista.
39        // TODO: Jos objektin lisäyksen jälkeen muuttaa IsStatic-ominaisuutta, niin objekti pitäisi lisätä tai poistaa listalta.
40        private readonly List<Object> objectList;
41
42        public Game(int tileSize)
43        {
44            TileSize = tileSize;
45            objectList = new List<Object>();
46        }
47
48        public void InitializeTileMap(int width, int height)
49        {
50            tileMap = new Object[width, height];
51        }
52
53        /// <summary>
54        /// Lisää tiilen peliin ja asettaa sen paikalleen pelimaailmaan.
55        /// </summary>
56        /// <param name="x">X-koordinaatti tiilikoordinaatistossa</param>
57        /// <param name="y">Y-koordinaatti tiilikoordinaatistossa</param>
58        /// <param name="tile">Paikalleen asetettava tiili</param>
59        public void SetTile(int x, int y, Object tile)
60        {
61            tile.Position = TileToWorldPosition(x, y);
62            tileMap[x, y] = tile;
63            tile.Destroyed += () => tileMap[x, y] = null;
64        }
65
66        /// <summary>
67        /// Poistaa tiilen pelistä.
68        /// </summary>
69        /// <param name="x">X-koordinaatti tiilikoordinaatistossa</param>
70        /// <param name="y">Y-koordinaatti tiilikoordinaatistossa</param>
71        public void RemoveTile(int x, int y)
72        {
73            if (tileMap[x, y] != null)
74                tileMap[x, y].Destroy();
75            tileMap[x, y] = null;
76        }
77
78        public override void ClearAll()
79        {
80            objectList.Clear();
81            base.ClearAll();
82        }
83
84        public override void Add(IGameObject o, int layer)
85        {
86            var obj = o as Object;
87            if (obj != null && !obj.IsStatic)
88            {
89                objectList.Add(obj);
90                obj.Destroyed += () => objectList.Remove(obj);
91            }
92            base.Add(o, layer);
93        }
94
95        /// <summary>
96        /// Palauttaa listan objekteista, jotka pystyvät törmäämään
97        /// objektiin, jos se liikkuisi offset-vektorin verran.
98        /// </summary>
99        /// <remarks>
100        /// Ei oikeasti liikuta objektia, offset-vektori on vain hypoteettinen liike.
101        /// </remarks>
102        public List<Object> Collisions(Object obj, Vector offset, bool shortCircuit = false)
103        {
104            var collisionList = new List<Object>();
105
106            var originalPosition = obj.Position;
107            obj.Position += offset;
108
109            var bottomLeft = WorldToTilePosition(new Vector(obj.Left, obj.Bottom));
110            var bottomRight = WorldToTilePosition(new Vector(obj.Right, obj.Bottom));
111            var topLeft = WorldToTilePosition(new Vector(obj.Left, obj.Top));
112            var left = Math.Max(bottomLeft.X - 1, 0);
113            var right = Math.Min(bottomRight.X + 1, tileMap.GetLength(0) - 1);
114            var bottom = Math.Max(topLeft.Y - 1, 0);
115            var top = Math.Min(bottomLeft.Y + 1, tileMap.GetLength(1) - 1);
116
117            Func<Object, bool> noCollision = (other) =>
118                !other.IntersectsWith(obj)
119                || other.OneWayPlatform && !(obj.Bottom - offset.Y > other.Top - 1)
120                || obj.IsDestroyed
121                || other.IsDestroyed
122                || other.IgnoresCollisionResponse
123                || obj.IgnoresCollisionResponse
124                || (obj.CollisionIgnoreGroup > 0 && obj.CollisionIgnoreGroup == other.CollisionIgnoreGroup);
125
126            // Tarkistetaan törmäykset tiilien kanssa.
127            for (int x = left; x <= right; x++)
128            {
129                for (int y = bottom; y <= top; y++)
130                {
131                    if (tileMap[x, y] == null || noCollision(tileMap[x, y]))
132                        continue;
133
134                    collisionList.Add(tileMap[x, y]);
135
136                    if (shortCircuit)
137                    {
138                        obj.Position = originalPosition;
139                        return collisionList;
140                    }
141                }
142            }
143
144            // Tarkistetaan törmäykset muiden objektien kanssa.
145            foreach (var other in objectList)
146            {
147                if (other != obj && !noCollision(other))
148                {
149                    collisionList.Add(other);
150
151                    if (shortCircuit)
152                    {
153                        obj.Position = originalPosition;
154                        return collisionList;
155                    }
156                }
157            }
158
159            obj.Position = originalPosition;
160            return collisionList;
161        }
162
163        /// <summary>
164        /// Päivittää liikkuvaa tasannetta.
165        /// </summary>
166        public void UpdateMovingPlatform(Object obj, Time time)
167        {
168            var dt = time.SinceLastUpdate.TotalSeconds;
169            var objectsOnPlatform = new List<Object>();
170
171            obj.Size += new Vector(4, 4);
172            foreach (var other in objectList)
173            {
174                if (other != obj && obj.IntersectsWith(other))
175                {
176                    objectsOnPlatform.Add(other);
177                }
178            }
179            obj.Size -= new Vector(4, 4);
180
181            foreach (var other in objectsOnPlatform)
182            {
183                var movement = obj.Velocity * dt;
184                MoveObject(other, movement, dt);
185
186                if (obj.IntersectsWith(other))
187                {
188                    other.OnCrushed();
189                }
190            }
191
192            obj.Position += obj.Velocity * dt;
193        }
194
195        /// <summary>
196        /// Päivittää tavallisen SimplePhysics.Object olion fysiikoita.
197        /// </summary>
198        public void UpdateObject(Object obj, Time time)
199        {
200            var dt = time.SinceLastUpdate.TotalSeconds;
201
202            var groundObjects = Collisions(obj, new Vector(0, -2));
203            if (groundObjects.Count > 0)
204            {
205                // Kitka maata vasten.
206                var highestFriction = groundObjects.Max(o => o.Friction);
207                var combinedFriction = highestFriction * obj.Friction;
208                obj.Velocity = new Vector(obj.Velocity.X * (1 - combinedFriction), obj.Velocity.Y);
209            }
210            else if (!obj.IgnoresGravity)
211            {
212                // Lisätään nopeuteen painovoima ilmassa ollessa.
213                obj.Velocity += Gravity * dt;
214            }
215
216            // Lasketaan paljonko objektin pitäisi liikkua nopeutensa perusteella.
217            var requiredMovement = obj.Velocity * dt;
218
219            // Yritetään liikuttaa objektia.
220            MoveObject(obj, requiredMovement, dt);
221
222            // Tarkistetaan törmättiinkö IgnoresCollisionResponse-ominaisuuden
223            // omaavien olioiden kanssa.
224            for (int i = objectList.Count; i >= 0; i--)
225            {
226                if (i >= objectList.Count) continue;
227                if (obj != objectList[i] && (obj.IgnoresCollisionResponse || objectList[i].IgnoresCollisionResponse))
228                {
229                    if (obj.IntersectsWith(objectList[i]))
230                    {
231                        obj.CollidedWith(objectList[i]);
232                        objectList[i].CollidedWith(obj);
233                    }
234                }
235            }
236        }
237
238        /// <summary>
239        /// Liikuttaa objektia, mutta huolehtii, ettei se voi joutua seinän sisään.
240        /// </summary>
241        /// <param name="obj">Liikutettava objekti</param>
242        /// <param name="requiredMovement">Liike</param>
243        /// <param name="dt">Viimeisestä päivityksestä kulunut aika</param>
244        private void MoveObject(Object obj, Vector requiredMovement, double dt)
245        {
246            // Tarkistetaan törmäisikö objekti mihinkään, jos se liikkuisi
247            // haluamansa matkan verran.
248            var collisions = Collisions(obj, requiredMovement);
249
250            if (collisions.Count > 0)
251            {
252                // Liikutetaan objekti törmäyksen kontaktipisteeseen.
253                var collisionAxis = MoveToContactPoint(obj, requiredMovement);
254
255                // Tarkistetaan tapahtuiko törmäys X- vai Y-akselilla ja annetaan
256                // kimmoisuuden vaikuttaa objektiin.
257                if (collisionAxis.HasFlag(Axis.Vertical))
258                {
259                    var newYVel = -obj.Velocity.Y * obj.RestitutionY;
260                    if (Math.Abs(newYVel * dt) <= 1) newYVel = 0;
261                    obj.Velocity = new Vector(obj.Velocity.X, newYVel);
262                }
263                if (collisionAxis.HasFlag(Axis.Horizontal))
264                {
265                    var newXVel = -obj.Velocity.X * obj.RestitutionX;
266                    if (Math.Abs(newXVel * dt) <= 1) newXVel = 0;
267                    obj.Velocity = new Vector(newXVel, obj.Velocity.Y);
268                }
269            }
270            else
271            {
272                // Objekti on vapaa liikkumaan, joten liikutetaan sitä.
273                obj.Position += requiredMovement;
274            }
275
276            // Kutsutaan törmäyskäsittelijöitä.
277            foreach (var other in collisions)
278            {
279                obj.CollidedWith(other);
280                other.CollidedWith(obj);
281            }
282        }
283
284        private Axis MoveToContactPoint(Object obj, Vector movement)
285        {
286            var collisionAxis = Axis.Neither;
287            if (Math.Abs(movement.Y) >= 1.0 && StepAxis(obj, movement.Y, Axis.Vertical))
288            {
289                collisionAxis |= Axis.Vertical;
290            }
291            if (Math.Abs(movement.X) >= 1.0 && StepAxis(obj, movement.X, Axis.Horizontal))
292            {
293                collisionAxis |= Axis.Horizontal;
294            }
295            return collisionAxis;
296        }
297
298        private bool StepAxis(Object obj, double amount, Axis axis)
299        {
300            var unitStep = Math.Sign(amount) * 0.5;
301            var step = new Vector(axis.HasFlag(Axis.Horizontal) ? unitStep : 0,
302                                  axis.HasFlag(Axis.Vertical) ? unitStep : 0);
303
304            while (Math.Abs(amount) > Math.Abs(unitStep))
305            {
306                if (Collisions(obj, step, true).Count > 0) return true;
307                obj.Position += step;
308                amount -= unitStep;
309            }
310
311            if (Math.Abs(amount) > 0)
312            {
313                if (Collisions(obj, step * amount, true).Count > 0) return true;
314                obj.Position += step * amount;
315            }
316
317            return false;
318        }
319
320        /// <summary>
321        /// Muuttaa Jypeli-sijainnin tiilikoordinaatistoon.
322        /// </summary>
323        public IntPoint WorldToTilePosition(Vector position)
324        {
325            var x = (int)((Level.Left - position.X) / TileSize);
326            var y = (int)((Level.Top + TileSize - position.Y) / TileSize);
327            return new IntPoint(-x, y);
328        }
329
330        /// <summary>
331        /// Muuttaa tiilikoordinaatiston ruudun Jypeli-koordinaatistoon.
332        /// </summary>
333        public Vector TileToWorldPosition(int x, int y)
334        {
335            var worldX = Level.Left + (x * TileSize) + (TileSize / 2.0);
336            var worldY = Level.Top - (y * TileSize) - (TileSize / 2.0);
337            return new Vector(worldX, worldY);
338        }
339
340        /// <summary>
341        /// Muuttaa tiilikoordinaatiston ruudun Jypeli-koordinaatistoon.
342        /// </summary>
343        public Vector TileToWorldPosition(IntPoint point)
344        {
345            return TileToWorldPosition(point.X, point.Y);
346        }
347
348        /// <summary>
349        /// Palauttaa tiilen, tai null jos koordinaatit ovat rajojen yli.
350        /// </summary>
351        public Object GetTileAt(int x, int y)
352        {
353            if (x > 0 && y > 0 && x < tileMap.GetLength(0) && y < tileMap.GetLength(1))
354            {
355                return tileMap[x, y];
356            }
357            return null;
358        }
359
360        /// <summary>
361        /// Palauttaa tiilen, tai null jos koordinaatit ovat rajojen yli.
362        /// </summary>
363        public Object GetTileAt(IntPoint point)
364        {
365            return GetTileAt(point.X, point.Y);
366        }
367    }
368}
Note: See TracBrowser for help on using the repository browser.