source: 2016/27/SimoR/SimplePhysicsTest/SimplePhysics/Game.cs @ 7643

Revision 7643, 12.3 KB checked in by sieerinn, 3 years ago (diff)

Dokumentaatiota ja CollisionIgnoreGroup?-ominaisuus lisätty

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; }
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                || (obj.CollisionIgnoreGroup > 0 && obj.CollisionIgnoreGroup == other.CollisionIgnoreGroup);
123
124            // Tarkistetaan törmäykset tiilien kanssa.
125            for (int x = left; x <= right; x++)
126            {
127                for (int y = bottom; y <= top; y++)
128                {
129                    if (tileMap[x, y] == null || noCollision(tileMap[x, y]))
130                        continue;
131
132                    collisionList.Add(tileMap[x, y]);
133
134                    if (shortCircuit)
135                    {
136                        obj.Position = originalPosition;
137                        return collisionList;
138                    }
139                }
140            }
141
142            // Tarkistetaan törmäykset muiden objektien kanssa.
143            foreach (var other in objectList)
144            {
145                if (other != obj && !noCollision(other))
146                {
147                    collisionList.Add(other);
148
149                    if (shortCircuit)
150                    {
151                        obj.Position = originalPosition;
152                        return collisionList;
153                    }
154                }
155            }
156
157            obj.Position = originalPosition;
158            return collisionList;
159        }
160
161        /// <summary>
162        /// Päivittää liikkuvaa tasannetta.
163        /// </summary>
164        public void UpdateMovingPlatform(Object obj, Time time)
165        {
166            var dt = time.SinceLastUpdate.TotalSeconds;
167            var objectsOnPlatform = new List<Object>();
168
169            obj.Size += new Vector(4, 4);
170            foreach (var other in objectList)
171            {
172                if (other != obj && obj.IntersectsWith(other))
173                {
174                    objectsOnPlatform.Add(other);
175                }
176            }
177            obj.Size -= new Vector(4, 4);
178
179            foreach (var other in objectsOnPlatform)
180            {
181                var movement = obj.Velocity * dt;
182                MoveObject(other, movement, dt);
183
184                if (obj.IntersectsWith(other))
185                {
186                    other.OnCrushed();
187                }
188            }
189
190            obj.Position += obj.Velocity * dt;
191        }
192
193        /// <summary>
194        /// Päivittää tavallisen SimplePhysics.Object olion fysiikoita.
195        /// </summary>
196        public void UpdateObject(Object obj, Time time)
197        {
198            var dt = time.SinceLastUpdate.TotalSeconds;
199
200            var groundObjects = Collisions(obj, new Vector(0, -2));
201            if (groundObjects.Count > 0)
202            {
203                // Kitka maata vasten.
204                var highestFriction = groundObjects.Max(o => o.Friction);
205                var combinedFriction = highestFriction * obj.Friction;
206                obj.Velocity = new Vector(obj.Velocity.X * (1 - combinedFriction), obj.Velocity.Y);
207            }
208            else if (!obj.IgnoresGravity)
209            {
210                // Lisätään nopeuteen painovoima ilmassa ollessa.
211                obj.Velocity += Gravity * dt;
212            }
213
214            // Lasketaan paljonko objektin pitäisi liikkua nopeutensa perusteella.
215            var requiredMovement = obj.Velocity * dt;
216
217            // Yritetään liikuttaa objektia.
218            MoveObject(obj, requiredMovement, dt);
219        }
220
221        /// <summary>
222        /// Liikuttaa objektia, mutta huolehtii, ettei se voi joutua seinän sisään.
223        /// </summary>
224        /// <param name="obj">Liikutettava objekti</param>
225        /// <param name="requiredMovement">Liike</param>
226        /// <param name="dt">Viimeisestä päivityksestä kulunut aika</param>
227        private void MoveObject(Object obj, Vector requiredMovement, double dt)
228        {
229            // Tarkistetaan törmäisikö objekti mihinkään, jos se liikkuisi
230            // haluamansa matkan verran.
231            var collisions = Collisions(obj, requiredMovement);
232
233            if (collisions.Count > 0)
234            {
235                // Liikutetaan objekti törmäyksen kontaktipisteeseen.
236                var collisionAxis = MoveToContactPoint(obj, requiredMovement);
237
238                // Tarkistetaan tapahtuiko törmäys X- vai Y-akselilla ja annetaan
239                // kimmoisuuden vaikuttaa objektiin.
240                if (collisionAxis.HasFlag(Axis.Vertical))
241                {
242                    var newYVel = -obj.Velocity.Y * obj.RestitutionY;
243                    if (Math.Abs(newYVel * dt) <= 1) newYVel = 0;
244                    obj.Velocity = new Vector(obj.Velocity.X, newYVel);
245                }
246                if (collisionAxis.HasFlag(Axis.Horizontal))
247                {
248                    var newXVel = -obj.Velocity.X * obj.RestitutionX;
249                    if (Math.Abs(newXVel * dt) <= 1) newXVel = 0;
250                    obj.Velocity = new Vector(newXVel, obj.Velocity.Y);
251                }
252            }
253            else
254            {
255                // Objekti on vapaa liikkumaan, joten liikutetaan sitä.
256                obj.Position += requiredMovement;
257            }
258
259            // Kutsutaan törmäyskäsittelijöitä.
260            foreach (var other in collisions)
261            {
262                obj.CollidedWith(other);
263                other.CollidedWith(obj);
264            }
265        }
266
267        private Axis MoveToContactPoint(Object obj, Vector movement)
268        {
269            var collisionAxis = Axis.Neither;
270            if (Math.Abs(movement.Y) >= 1.0 && StepAxis(obj, movement.Y, Axis.Vertical))
271            {
272                collisionAxis |= Axis.Vertical;
273            }
274            if (Math.Abs(movement.X) >= 1.0 && StepAxis(obj, movement.X, Axis.Horizontal))
275            {
276                collisionAxis |= Axis.Horizontal;
277            }
278            return collisionAxis;
279        }
280
281        private bool StepAxis(Object obj, double amount, Axis axis)
282        {
283            var unitStep = Math.Sign(amount) * 0.5;
284            var step = new Vector(axis.HasFlag(Axis.Horizontal) ? unitStep : 0,
285                                  axis.HasFlag(Axis.Vertical) ? unitStep : 0);
286
287            while (Math.Abs(amount) > Math.Abs(unitStep))
288            {
289                if (Collisions(obj, step, true).Count > 0) return true;
290                obj.Position += step;
291                amount -= unitStep;
292            }
293
294            if (Math.Abs(amount) > 0)
295            {
296                if (Collisions(obj, step * amount, true).Count > 0) return true;
297                obj.Position += step * amount;
298            }
299
300            return false;
301        }
302
303        /// <summary>
304        /// Muuttaa Jypeli-sijainnin tiilikoordinaatistoon.
305        /// </summary>
306        public IntPoint WorldToTilePosition(Vector position)
307        {
308            var x = (int)((Level.Left - position.X) / TileSize);
309            var y = (int)((Level.Top + TileSize - position.Y) / TileSize);
310            return new IntPoint(-x, y);
311        }
312
313        /// <summary>
314        /// Muuttaa tiilikoordinaatiston ruudun Jypeli-koordinaatistoon.
315        /// </summary>
316        public Vector TileToWorldPosition(int x, int y)
317        {
318            var worldX = Level.Left + (x * TileSize) + (TileSize / 2.0);
319            var worldY = Level.Top - (y * TileSize) - (TileSize / 2.0);
320            return new Vector(worldX, worldY);
321        }
322
323        /// <summary>
324        /// Muuttaa tiilikoordinaatiston ruudun Jypeli-koordinaatistoon.
325        /// </summary>
326        public Vector TileToWorldPosition(IntPoint point)
327        {
328            return TileToWorldPosition(point.X, point.Y);
329        }
330
331        /// <summary>
332        /// Palauttaa tiilen, tai null jos koordinaatit ovat rajojen yli.
333        /// </summary>
334        public Object GetTileAt(int x, int y)
335        {
336            if (x > 0 && y > 0 && x < tileMap.GetLength(0) && y < tileMap.GetLength(1))
337            {
338                return tileMap[x, y];
339            }
340            return null;
341        }
342
343        /// <summary>
344        /// Palauttaa tiilen, tai null jos koordinaatit ovat rajojen yli.
345        /// </summary>
346        public Object GetTileAt(IntPoint point)
347        {
348            return GetTileAt(point.X, point.Y);
349        }
350    }
351}
Note: See TracBrowser for help on using the repository browser.