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

Revision 7639, 10.6 KB checked in by sieerinn, 3 years ago (diff)

Liikkuvat tasanteet toimii

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        public List<Object> Collisions(Object obj, Vector offset, bool shortCircuit = false)
96        {
97            var collisionList = new List<Object>();
98
99            var originalPosition = obj.Position;
100            obj.Position += offset;
101
102            var bottomLeft = WorldToTilePosition(new Vector(obj.Left, obj.Bottom));
103            var bottomRight = WorldToTilePosition(new Vector(obj.Right, obj.Bottom));
104            var topLeft = WorldToTilePosition(new Vector(obj.Left, obj.Top));
105            var left = Math.Max(bottomLeft.X - 1, 0);
106            var right = Math.Min(bottomRight.X + 1, tileMap.GetLength(0) - 1);
107            var bottom = Math.Max(topLeft.Y - 1, 0);
108            var top = Math.Min(bottomLeft.Y + 1, tileMap.GetLength(1) - 1);
109
110            Func<Object, bool> noCollision = (other) =>
111                !other.IntersectsWith(obj) ||
112                other.OneWayPlatform && !(obj.Bottom - offset.Y > other.Top - 1);
113
114            // Tarkistetaan törmäykset tiilien kanssa.
115            for (int x = left; x <= right; x++)
116            {
117                for (int y = bottom; y <= top; y++)
118                {
119                    if (tileMap[x, y] == null || noCollision(tileMap[x, y]))
120                        continue;
121
122                    collisionList.Add(tileMap[x, y]);
123
124                    if (shortCircuit)
125                    {
126                        obj.Position = originalPosition;
127                        return collisionList;
128                    }
129                }
130            }
131
132            // Tarkistetaan törmäykset muiden objektien kanssa.
133            foreach (var other in objectList)
134            {
135                if (other != obj && !noCollision(other))
136                {
137                    collisionList.Add(other);
138
139                    if (shortCircuit)
140                    {
141                        obj.Position = originalPosition;
142                        return collisionList;
143                    }
144                }
145            }
146
147            obj.Position = originalPosition;
148            return collisionList;
149        }
150
151        public void UpdateMovingPlatform(Object obj, Time time)
152        {
153            var dt = time.SinceLastUpdate.TotalSeconds;
154            var objectsOnPlatform = new List<Object>();
155
156            obj.Size += new Vector(4, 4);
157            foreach (var other in objectList)
158            {
159                if (other != obj && obj.IntersectsWith(other))
160                {
161                    objectsOnPlatform.Add(other);
162                }
163            }
164            obj.Size -= new Vector(4, 4);
165
166            foreach (var other in objectsOnPlatform)
167            {
168                var movement = obj.Velocity * dt;
169                MoveObject(other, movement, dt);
170
171                if (obj.IntersectsWith(other))
172                {
173                    other.OnCrushed();
174                }
175            }
176
177            obj.Position += obj.Velocity * dt;
178        }
179
180        public void UpdateObject(Object obj, Time time)
181        {
182            var dt = time.SinceLastUpdate.TotalSeconds;
183
184            var groundObjects = Collisions(obj, new Vector(0, -2));
185            if (groundObjects.Count > 0)
186            {
187                // Kitka maata vasten.
188                var highestFriction = groundObjects.Max(o => o.Friction);
189                var combinedFriction = highestFriction * obj.Friction;
190                obj.Velocity = new Vector(obj.Velocity.X * (1 - combinedFriction), obj.Velocity.Y);
191            }
192            else if (!obj.IgnoresGravity)
193            {
194                // Lisätään nopeuteen painovoima ilmassa ollessa.
195                obj.Velocity += Gravity * dt;
196            }
197
198            // Lasketaan paljonko objektin pitäisi liikkua nopeutensa perusteella.
199            var requiredMovement = obj.Velocity * dt;
200
201            // Yritetään liikuttaa objektia.
202            MoveObject(obj, requiredMovement, dt);
203        }
204
205        private void MoveObject(Object obj, Vector requiredMovement, double dt)
206        {
207            // Tarkistetaan törmäisikö objekti mihinkään, jos se liikkuisi
208            // haluamansa matkan verran.
209            var collisions = Collisions(obj, requiredMovement);
210
211            if (collisions.Count > 0)
212            {
213                // Liikutetaan objekti törmäyksen kontaktipisteeseen.
214                var collisionAxis = MoveToContactPoint(obj, requiredMovement);
215
216                // Tarkistetaan tapahtuiko törmäys X- vai Y-akselilla ja annetaan
217                // kimmoisuuden vaikuttaa objektiin.
218                if (collisionAxis.HasFlag(Axis.Vertical))
219                {
220                    var newYVel = -obj.Velocity.Y * obj.RestitutionY;
221                    if (Math.Abs(newYVel * dt) <= 1) newYVel = 0;
222                    obj.Velocity = new Vector(obj.Velocity.X, newYVel);
223                }
224                if (collisionAxis.HasFlag(Axis.Horizontal))
225                {
226                    var newXVel = -obj.Velocity.X * obj.RestitutionX;
227                    if (Math.Abs(newXVel * dt) <= 1) newXVel = 0;
228                    obj.Velocity = new Vector(newXVel, obj.Velocity.Y);
229                }
230            }
231            else
232            {
233                // Objekti on vapaa liikkumaan, joten liikutetaan sitä.
234                obj.Position += requiredMovement;
235            }
236
237            // Kutsutaan törmäyskäsittelijöitä.
238            foreach (var other in collisions)
239            {
240                obj.CollidedWith(other);
241            }
242        }
243
244        private Axis MoveToContactPoint(Object obj, Vector movement)
245        {
246            var collisionAxis = Axis.Neither;
247            if (Math.Abs(movement.Y) >= 1.0 && StepAxis(obj, movement.Y, Axis.Vertical))
248            {
249                collisionAxis |= Axis.Vertical;
250            }
251            if (Math.Abs(movement.X) >= 1.0 && StepAxis(obj, movement.X, Axis.Horizontal))
252            {
253                collisionAxis |= Axis.Horizontal;
254            }
255            return collisionAxis;
256        }
257
258        private bool StepAxis(Object obj, double amount, Axis axis)
259        {
260            var unitStep = Math.Sign(amount) * 0.5;
261            var step = new Vector(axis.HasFlag(Axis.Horizontal) ? unitStep : 0,
262                                  axis.HasFlag(Axis.Vertical) ? unitStep : 0);
263
264            while (Math.Abs(amount) > Math.Abs(unitStep))
265            {
266                if (Collisions(obj, step, true).Count > 0) return true;
267                obj.Position += step;
268                amount -= unitStep;
269            }
270
271            if (Math.Abs(amount) > 0)
272            {
273                if (Collisions(obj, step * amount, true).Count > 0) return true;
274                obj.Position += step * amount;
275            }
276
277            return false;
278        }
279
280        public IntPoint WorldToTilePosition(Vector position)
281        {
282            var x = (int)((Level.Left - position.X) / TileSize);
283            var y = (int)((Level.Top + TileSize - position.Y) / TileSize);
284            return new IntPoint(-x, y);
285        }
286
287        public Vector TileToWorldPosition(int x, int y)
288        {
289            var worldX = Level.Left + (x * TileSize) + (TileSize / 2.0);
290            var worldY = Level.Top - (y * TileSize) - (TileSize / 2.0);
291            return new Vector(worldX, worldY);
292        }
293
294        public Vector TileToWorldPosition(IntPoint point)
295        {
296            return TileToWorldPosition(point.X, point.Y);
297        }
298
299        public Object GetTileAt(int x, int y)
300        {
301            if (x > 0 && y > 0 && x < tileMap.GetLength(0) && y < tileMap.GetLength(1))
302            {
303                return tileMap[x, y];
304            }
305            return null;
306        }
307
308        public Object GetTileAt(IntPoint point)
309        {
310            return GetTileAt(point.X, point.Y);
311        }
312    }
313}
Note: See TracBrowser for help on using the repository browser.