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

Revision 7638, 9.4 KB checked in by sieerinn, 3 years ago (diff)

Alkeellinen versio fysiikkamoottorista (jossa liikkuvat tasanteet ei vielä toimi)

Line 
1using System;
2using System.Collections.Generic;
3using Jypeli;
4using Jypeli.Widgets;
5
6namespace SimplePhysics
7{
8    /// <summary>
9    /// Yksinkertaisemmilla fysiikoilla varustettu peli.
10    /// </summary>
11    public class Game : Jypeli.Game
12    {
13        [Flags]
14        private enum Axis
15        {
16            Neither = 0,
17            Horizontal = 1,
18            Vertical = 2,
19            Both = Horizontal | Vertical
20        }
21
22        /// <summary>
23        /// Painovoima.
24        /// </summary>
25        public Vector Gravity { get; set; }
26
27        /// <summary>
28        /// Yhden tiilen koko (leveys tai korkeus).
29        /// </summary>
30        public int TileSize { get; }
31
32        // Taulukko pelissä olevista tiilistä.
33        private Object[,] tileMap;
34
35        // Lista pelissä olevista ei-staattisista fysiikkaobjekteista.
36        // TODO: Jos objektin lisäyksen jälkeen muuttaa IsStatic-ominaisuutta, niin objekti pitäisi lisätä tai poistaa listalta.
37        private readonly List<Object> objectList;
38
39        public Game(int tileSize)
40        {
41            TileSize = tileSize;
42            objectList = new List<Object>();
43        }
44
45        public void InitializeTileMap(int width, int height)
46        {
47            tileMap = new Object[width, height];
48        }
49
50        /// <summary>
51        /// Lisää tiilen peliin ja asettaa sen paikalleen pelimaailmaan.
52        /// </summary>
53        /// <param name="x">X-koordinaatti tiilikoordinaatistossa</param>
54        /// <param name="y">Y-koordinaatti tiilikoordinaatistossa</param>
55        /// <param name="tile">Paikalleen asetettava tiili</param>
56        public void SetTile(int x, int y, Object tile)
57        {
58            tile.Position = TileToWorldPosition(x, y);
59            tileMap[x, y] = tile;
60            tile.Destroyed += () => tileMap[x, y] = null;
61        }
62
63        /// <summary>
64        /// Poistaa tiilen pelistä.
65        /// </summary>
66        /// <param name="x">X-koordinaatti tiilikoordinaatistossa</param>
67        /// <param name="y">Y-koordinaatti tiilikoordinaatistossa</param>
68        public void RemoveTile(int x, int y)
69        {
70            if (tileMap[x, y] != null)
71                tileMap[x, y].Destroy();
72            tileMap[x, y] = null;
73        }
74
75        public override void ClearAll()
76        {
77            objectList.Clear();
78            base.ClearAll();
79        }
80
81        public override void Add(IGameObject o, int layer)
82        {
83            var obj = o as Object;
84            if (obj != null && !obj.IsStatic)
85            {
86                objectList.Add(obj);
87                obj.Destroyed += () => objectList.Remove(obj);
88            }
89            base.Add(o, layer);
90        }
91
92        /// <summary>
93        /// Tarkistaa voisiko objekti törmätä tiileen, jos se liikkuisi
94        /// offset-vektorin verran pelimaailmassa.
95        /// </summary>
96        public bool CanCollide(Object obj, Vector offset)
97        {
98            var originalPosition = obj.Position;
99            obj.Position += offset;
100
101            var bottomLeft = WorldToTilePosition(new Vector(obj.Left, obj.Bottom));
102            var bottomRight = WorldToTilePosition(new Vector(obj.Right, obj.Bottom));
103            var topLeft = WorldToTilePosition(new Vector(obj.Left, obj.Top));
104            var left = Math.Max(bottomLeft.X - 1, 0);
105            var right = Math.Min(bottomRight.X + 1, tileMap.GetLength(0) - 1);
106            var bottom = Math.Max(topLeft.Y - 1, 0);
107            var top = Math.Min(bottomLeft.Y + 1, tileMap.GetLength(1) - 1);
108
109            // Tarkistetaan törmäykset tiilien kanssa.
110            for (int x = left; x <= right; x++)
111            {
112                for (int y = bottom; y <= top; y++)
113                {
114                    if (tileMap[x, y] == null || !tileMap[x, y].IntersectsWith(obj))
115                        continue;
116
117                    if (tileMap[x, y].OneWayPlatform && !(obj.Bottom - offset.Y > tileMap[x, y].Top - 1))
118                        continue;
119
120                    obj.Position = originalPosition;
121                    return true;
122                }
123            }
124
125            // Tarkistetaan törmäykset muiden objektien kanssa.
126            for (int i = objectList.Count - 1; i >= 0; i--)
127            {
128                if (i >= objectList.Count) continue;
129                if (objectList[i] != obj && obj.IntersectsWith(objectList[i]))
130                {
131                    obj.Position = originalPosition;
132                    return true;
133                }
134            }
135
136            obj.Position = originalPosition;
137            return false;
138        }
139
140        public void UpdateObject(Object obj, Time time)
141        {
142            var dt = time.SinceLastUpdate.TotalSeconds;
143
144            // Lisätään objektin nopeuteen painovoima.
145            if (!obj.IgnoresGravity)
146                obj.Velocity += Gravity * dt;
147
148            // Lasketaan paljonko objektin pitäisi liikkua nopeutensa perusteella.
149            var requiredMovement = obj.Velocity * dt;
150
151            // Tarkistetaan törmäisikö objekti mihinkään, jos se liikkuisi
152            // haluamansa matkan verran.
153            if (CanCollide(obj, requiredMovement))
154            {
155                // Liikutetaan objekti törmäyksen kontaktipisteeseen.
156                var collisionAxis = MoveToContactPoint(obj, requiredMovement);
157
158                // Tarkistetaan tapahtuiko törmäys X- vai Y-akselilla ja annetaan
159                // kimmoisuuden vaikuttaa objektiin.
160                if (collisionAxis.HasFlag(Axis.Vertical))
161                {
162                    var newYVel = -obj.Velocity.Y * obj.RestitutionY;
163                    if (Math.Abs(newYVel * dt) <= 1) newYVel = 0;
164                    obj.Velocity = new Vector(obj.Velocity.X, newYVel);
165                }
166                if (collisionAxis.HasFlag(Axis.Horizontal))
167                {
168                    var newXVel = -obj.Velocity.X * obj.RestitutionX;
169                    if (Math.Abs(newXVel * dt) <= 1) newXVel = 0;
170                    obj.Velocity = new Vector(newXVel, obj.Velocity.Y);
171                }
172            }
173            else
174            {
175                // Objekti on vapaa liikkumaan, joten liikutetaan sitä.
176                obj.Position += requiredMovement;
177            }
178
179            // TODO: mitäs jos tiilelle halutaankin tägi ja handler?
180
181            // Tarkistetaan törmäykset muiden objektien kanssa.
182            // TODO: Tämä silmukka käydään nyt jo toisen kerran. Kerää törmäys tulokset CanCollide-aliohjelmasta?
183            for (int i = objectList.Count - 1; i >= 0; i--)
184            {
185                if (i >= objectList.Count) continue;
186                if (objectList[i] != obj && obj.IntersectsWith(objectList[i]))
187                {
188                    obj.CollidedWith(objectList[i]);
189                }
190            }
191
192            // Kitka maata vasten.
193            if (CanCollide(obj, new Vector(0, -2)))
194            {
195                obj.Velocity = new Vector(obj.Velocity.X * (1 - obj.Friction), obj.Velocity.Y);
196            }
197        }
198
199        private Axis MoveToContactPoint(Object obj, Vector movement)
200        {
201            var collisionAxis = Axis.Neither;
202            if (Math.Abs(movement.Y) >= 1.0 && StepAxis(obj, movement.Y, Axis.Vertical))
203            {
204                collisionAxis |= Axis.Vertical;
205            }
206            if (Math.Abs(movement.X) >= 1.0 && StepAxis(obj, movement.X, Axis.Horizontal))
207            {
208                collisionAxis |= Axis.Horizontal;
209            }
210            return collisionAxis;
211        }
212
213        private bool StepAxis(Object obj, double amount, Axis axis)
214        {
215            var unitStep = Math.Sign(amount) * 0.5;
216            var step = new Vector(axis.HasFlag(Axis.Horizontal) ? unitStep : 0,
217                                  axis.HasFlag(Axis.Vertical) ? unitStep : 0);
218
219            while (Math.Abs(amount) > Math.Abs(unitStep))
220            {
221                if (CanCollide(obj, step)) return true;
222                obj.Position += step;
223                amount -= unitStep;
224            }
225
226            if (Math.Abs(amount) > 0)
227            {
228                if (CanCollide(obj, step * amount)) return true;
229                obj.Position += step * amount;
230            }
231
232            return false;
233        }
234
235        public IntPoint WorldToTilePosition(Vector position)
236        {
237            var x = (int)((Level.Left - position.X) / TileSize);
238            var y = (int)((Level.Top + TileSize - position.Y) / TileSize);
239            return new IntPoint(-x, y);
240        }
241
242        public Vector TileToWorldPosition(int x, int y)
243        {
244            var worldX = Level.Left + (x * TileSize) + (TileSize / 2.0);
245            var worldY = Level.Top - (y * TileSize) - (TileSize / 2.0);
246            return new Vector(worldX, worldY);
247        }
248
249        public Vector TileToWorldPosition(IntPoint point)
250        {
251            return TileToWorldPosition(point.X, point.Y);
252        }
253       
254        public Object GetTileAt(int x, int y)
255        {
256            if (x > 0 && y > 0 && x < tileMap.GetLength(0) && y < tileMap.GetLength(1))
257            {
258                return tileMap[x, y];
259            }
260            return null;
261        }
262
263        public Object GetTileAt(IntPoint point)
264        {
265            return GetTileAt(point.X, point.Y);
266        }
267    }
268}
Note: See TracBrowser for help on using the repository browser.