Bläddra i källkod

Added Racer2D example

Alan Q 9 år sedan
förälder
incheckning
90e15d4d23
45 ändrade filer med 1088 tillägg och 0 borttagningar
  1. 4 0
      Racer2D/AtomicExample.json
  2. 0 0
      Racer2D/CSharp/Racer2D.atomic
  3. BIN
      Racer2D/CSharp/Resources/Characters/truck/head.png
  4. BIN
      Racer2D/CSharp/Resources/Characters/truck/suspension.png
  5. BIN
      Racer2D/CSharp/Resources/Characters/truck/vehicle.png
  6. BIN
      Racer2D/CSharp/Resources/Characters/truck/wheel.png
  7. 26 0
      Racer2D/CSharp/Resources/LICENSES.txt
  8. 3 0
      Racer2D/CSharp/Resources/Materials/Unlit.xml
  9. 3 0
      Racer2D/CSharp/Resources/Materials/UnlitAlpha.xml
  10. BIN
      Racer2D/CSharp/Resources/Music/Happy_Bee.ogg
  11. 40 0
      Racer2D/CSharp/Resources/Particles/dust.pex
  12. BIN
      Racer2D/CSharp/Resources/Particles/gblade.png
  13. 40 0
      Racer2D/CSharp/Resources/Particles/smoke.pex
  14. BIN
      Racer2D/CSharp/Resources/Particles/whitePuff07.png
  15. BIN
      Racer2D/CSharp/Resources/Scenarios/cloud1.png
  16. BIN
      Racer2D/CSharp/Resources/Scenarios/cloud2.png
  17. BIN
      Racer2D/CSharp/Resources/Scenarios/cloud3.png
  18. BIN
      Racer2D/CSharp/Resources/Scenarios/grasslands/BG.png
  19. BIN
      Racer2D/CSharp/Resources/Scenarios/grasslands/Object/Bush (1).png
  20. BIN
      Racer2D/CSharp/Resources/Scenarios/grasslands/Object/Bush (2).png
  21. BIN
      Racer2D/CSharp/Resources/Scenarios/grasslands/Object/Bush (3).png
  22. BIN
      Racer2D/CSharp/Resources/Scenarios/grasslands/Object/Bush (4).png
  23. BIN
      Racer2D/CSharp/Resources/Scenarios/grasslands/Object/Crate.png
  24. BIN
      Racer2D/CSharp/Resources/Scenarios/grasslands/Object/Mushroom_1.png
  25. BIN
      Racer2D/CSharp/Resources/Scenarios/grasslands/Object/Mushroom_2.png
  26. BIN
      Racer2D/CSharp/Resources/Scenarios/grasslands/Object/Sign_1.png
  27. BIN
      Racer2D/CSharp/Resources/Scenarios/grasslands/Object/Sign_2.png
  28. BIN
      Racer2D/CSharp/Resources/Scenarios/grasslands/Object/Stone.png
  29. BIN
      Racer2D/CSharp/Resources/Scenarios/grasslands/Object/Tree_1.png
  30. BIN
      Racer2D/CSharp/Resources/Scenarios/grasslands/Object/Tree_2.png
  31. BIN
      Racer2D/CSharp/Resources/Scenarios/grasslands/Object/Tree_3.png
  32. BIN
      Racer2D/CSharp/Resources/Scenarios/grasslands/ground.png
  33. BIN
      Racer2D/CSharp/Resources/Scenarios/grasslands/surface.png
  34. 182 0
      Racer2D/CSharp/Resources/Scripts/AtomicMain.cs
  35. 56 0
      Racer2D/CSharp/Resources/Scripts/Clouds.cs
  36. 154 0
      Racer2D/CSharp/Resources/Scripts/OpenSimplex.cs
  37. 273 0
      Racer2D/CSharp/Resources/Scripts/Terrain.cs
  38. 258 0
      Racer2D/CSharp/Resources/Scripts/Vehicle.cs
  39. BIN
      Racer2D/CSharp/Resources/Sounds/engine_sound_crop.wav
  40. BIN
      Racer2D/CSharp/Resources/Sounds/susp_1.wav
  41. BIN
      Racer2D/CSharp/Resources/Sounds/susp_2.wav
  42. BIN
      Racer2D/CSharp/Resources/Sounds/susp_3.wav
  43. BIN
      Racer2D/CSharp/Resources/Sounds/tires_squal_loop.wav
  44. 49 0
      Racer2D/CSharp/Settings/Engine.json
  45. BIN
      Racer2D/Screenshot.png

+ 4 - 0
Racer2D/AtomicExample.json

@@ -0,0 +1,4 @@
+{
+    "name": "Racer2D",
+    "desc" : "A simple 2D racing game with procedural terrain"
+}

+ 0 - 0
Racer2D/CSharp/Racer2D.atomic


BIN
Racer2D/CSharp/Resources/Characters/truck/head.png


BIN
Racer2D/CSharp/Resources/Characters/truck/suspension.png


BIN
Racer2D/CSharp/Resources/Characters/truck/vehicle.png


BIN
Racer2D/CSharp/Resources/Characters/truck/wheel.png


+ 26 - 0
Racer2D/CSharp/Resources/LICENSES.txt

@@ -0,0 +1,26 @@
+Vehicle Art
+License: Public Domain
+Link: http://www.gameart2d.com/hill-climb-racing---free-car-sprites.html
+
+Bushes, Trees, Stones, Crates, Background and other decorations
+License: Public Domain
+Link: http://www.gameart2d.com/free-platformer-game-tileset.html
+
+Ground Texture by Wildfire Games
+License: CC-BY-SA 3.0 http://creativecommons.org/licenses/by-sa/3.0/
+Link: http://opengameart.org/node/38489
+
+Music Happy Bee by Kevin MacLeod (incompetech.com)
+License: CC-BY 3.0 http://creativecommons.org/licenses/by/3.0/
+Link: http://incompetech.com/music/royalty-free/index.html?isrc=USUAN1300014
+
+Engine Sound
+License: Public Domain
+Link: http://opengameart.org/content/engine-sounds2
+
+Breaking Sound by audible-edge (Tom Haigh)
+License: CC-BY 3.0 http://creativecommons.org/licenses/by/3.0/
+Link: http://opengameart.org/content/car-tire-squeal-skid-loop
+
+Code, Clouds, Particles, Suspension Sounds and other assets by AlanGameDev for the Atomic Game Engine
+License: Public Domain

+ 3 - 0
Racer2D/CSharp/Resources/Materials/Unlit.xml

@@ -0,0 +1,3 @@
+<material>
+    <technique name="Techniques/DiffUnlit.xml" />
+</material>

+ 3 - 0
Racer2D/CSharp/Resources/Materials/UnlitAlpha.xml

@@ -0,0 +1,3 @@
+<material>
+    <technique name="Techniques/DiffUnlitAlpha.xml" />
+</material>

BIN
Racer2D/CSharp/Resources/Music/Happy_Bee.ogg


+ 40 - 0
Racer2D/CSharp/Resources/Particles/dust.pex

@@ -0,0 +1,40 @@
+<?xml version="1.0"?>
+<particleEmitterConfig>
+	<texture name="gblade.png" />
+	<sourcePosition x="0" y="0" />
+	<sourcePositionVariance x="0" y="0" />
+	<speed value="180" />
+	<speedVariance value="0" />
+	<particleLifeSpan value="2" />
+	<particleLifespanVariance value="0" />
+	<angle value="0" />
+	<angleVariance value="360" />
+	<gravity x="0" y="0" />
+	<radialAcceleration value="0" />
+	<tangentialAcceleration value="0" />
+	<radialAccelVariance value="0" />
+	<tangentialAccelVariance value="0" />
+	<startColor red="1" green="1" blue="1" alpha="1" />
+	<startColorVariance red="0" green="0" blue="0" alpha="0" />
+	<finishColor red="0" green="0" blue="0" alpha="0" />
+	<finishColorVariance red="0" green="0" blue="0" alpha="0" />
+	<maxParticles value="50" />
+	<startParticleSize value="100" />
+	<startParticleSizeVariance value="0" />
+	<finishParticleSize value="120" />
+	<FinishParticleSizeVariance value="0" />
+	<duration value="100" />
+	<emitterType value="1" />
+	<maxRadius value="20" />
+	<maxRadiusVariance value="0" />
+	<minRadius value="160" />
+	<minRadiusVariance value="0" />
+	<rotatePerSecond value="0" />
+	<rotatePerSecondVariance value="0" />
+	<blendFuncSource value="770" />
+	<blendFuncDestination value="771" />
+	<rotationStart value="0" />
+	<rotationStartVariance value="360" />
+	<rotationEnd value="0" />
+	<rotationEndVariance value="0" />
+</particleEmitterConfig>

BIN
Racer2D/CSharp/Resources/Particles/gblade.png


+ 40 - 0
Racer2D/CSharp/Resources/Particles/smoke.pex

@@ -0,0 +1,40 @@
+<?xml version="1.0"?>
+<particleEmitterConfig>
+	<texture name="whitePuff07.png" />
+	<sourcePosition x="0" y="0" />
+	<sourcePositionVariance x="0" y="0" />
+	<speed value="200" />
+	<speedVariance value="0" />
+	<particleLifeSpan value="2" />
+	<particleLifespanVariance value="0" />
+	<angle value="170" />
+	<angleVariance value="0" />
+	<gravity x="0" y="0" />
+	<radialAcceleration value="0" />
+	<tangentialAcceleration value="0" />
+	<radialAccelVariance value="0" />
+	<tangentialAccelVariance value="100" />
+	<startColor red="1" green="1" blue="1" alpha="0.5" />
+	<startColorVariance red="0.1" green="0.1" blue="0.1" alpha="0" />
+	<finishColor red="0" green="0" blue="0" alpha="0" />
+	<finishColorVariance red="0" green="0" blue="0" alpha="0" />
+	<maxParticles value="160" />
+	<startParticleSize value="50" />
+	<startParticleSizeVariance value="0" />
+	<finishParticleSize value="100" />
+	<FinishParticleSizeVariance value="0" />
+	<duration value="100" />
+	<emitterType value="0" />
+	<maxRadius value="0" />
+	<maxRadiusVariance value="313.13" />
+	<minRadius value="0" />
+	<minRadiusVariance value="0" />
+	<rotatePerSecond value="0" />
+	<rotatePerSecondVariance value="0" />
+	<blendFuncSource value="770" />
+	<blendFuncDestination value="771" />
+	<rotationStart value="0" />
+	<rotationStartVariance value="360" />
+	<rotationEnd value="0" />
+	<rotationEndVariance value="0" />
+</particleEmitterConfig>

BIN
Racer2D/CSharp/Resources/Particles/whitePuff07.png


BIN
Racer2D/CSharp/Resources/Scenarios/cloud1.png


BIN
Racer2D/CSharp/Resources/Scenarios/cloud2.png


BIN
Racer2D/CSharp/Resources/Scenarios/cloud3.png


BIN
Racer2D/CSharp/Resources/Scenarios/grasslands/BG.png


BIN
Racer2D/CSharp/Resources/Scenarios/grasslands/Object/Bush (1).png


BIN
Racer2D/CSharp/Resources/Scenarios/grasslands/Object/Bush (2).png


BIN
Racer2D/CSharp/Resources/Scenarios/grasslands/Object/Bush (3).png


BIN
Racer2D/CSharp/Resources/Scenarios/grasslands/Object/Bush (4).png


BIN
Racer2D/CSharp/Resources/Scenarios/grasslands/Object/Crate.png


BIN
Racer2D/CSharp/Resources/Scenarios/grasslands/Object/Mushroom_1.png


BIN
Racer2D/CSharp/Resources/Scenarios/grasslands/Object/Mushroom_2.png


BIN
Racer2D/CSharp/Resources/Scenarios/grasslands/Object/Sign_1.png


BIN
Racer2D/CSharp/Resources/Scenarios/grasslands/Object/Sign_2.png


BIN
Racer2D/CSharp/Resources/Scenarios/grasslands/Object/Stone.png


BIN
Racer2D/CSharp/Resources/Scenarios/grasslands/Object/Tree_1.png


BIN
Racer2D/CSharp/Resources/Scenarios/grasslands/Object/Tree_2.png


BIN
Racer2D/CSharp/Resources/Scenarios/grasslands/Object/Tree_3.png


BIN
Racer2D/CSharp/Resources/Scenarios/grasslands/ground.png


BIN
Racer2D/CSharp/Resources/Scenarios/grasslands/surface.png


+ 182 - 0
Racer2D/CSharp/Resources/Scripts/AtomicMain.cs

@@ -0,0 +1,182 @@
+using System.Collections.Generic;
+using AtomicEngine;
+
+internal static class Cache
+{
+    private static readonly ResourceCache _cache = AtomicNET.GetSubsystem<ResourceCache>();
+
+    public static T Get<T>(string path) where T : Resource
+    {
+        return _cache.Get<T>(path);
+    }
+}
+
+public class AtomicMain : AppDelegate
+{
+    private static Scene _scene;
+    private static Terrain _terrain;
+
+    private Viewport _viewport;
+    private Camera _camera;
+    private Vehicle _vehicle;
+    private Clouds _clouds;
+
+    public override void Start()
+    {
+        // We setup our scene, main camera and viewport
+        _viewport = GetSubsystem<Renderer>().GetViewport(0);
+        _scene = new Scene();
+        _scene.CreateComponent<Octree>().SetSize(new BoundingBox(1,100), 3);
+        _viewport.Scene = _scene;
+        _camera = _scene.CreateChild("Camera").CreateComponent<Camera>();
+        _camera.Node.Position = new Vector3(50, 10, -1);
+        _camera.Orthographic = true;
+        _camera.OrthoSize = 26;
+        _viewport.Camera = _camera;
+
+        // We create a sound source for the music and the music
+        SoundSource musicSource = _scene.CreateComponent<SoundSource>();
+        Sound music = Cache.Get<Sound>("music/Happy_Bee.ogg");
+        music.SetLooped(true);
+        musicSource.Play(music);
+        musicSource.SetSoundType("Music");
+
+        // We don't need a sound listener for the above, but we add one for the sounds and adjust the music gain
+        Audio audioSystem = GetSubsystem<Audio>();
+        audioSystem.SetListener(_camera.Node.CreateComponent<SoundListener>());
+        audioSystem.SetMasterGain("Music", 0.3f);
+
+        // We create a background node which is a child of the camera so it won't move relative to it
+        Node bg = _camera.Node.CreateChild("Background");
+        StaticSprite2D bgspr = bg.CreateComponent<StaticSprite2D>();
+        bgspr.SetSprite(Cache.Get<Sprite2D>("scenarios/grasslands/bg.png"));
+        bg.SetPosition(new Vector3(0,0,100));
+        bg.SetScale2D(Vector2.One*5.2f);
+        
+        // We add a physics world so we can simulate physics, and enable CCD
+        PhysicsWorld2D pw = _scene.CreateComponent<PhysicsWorld2D>();
+        pw.SetContinuousPhysics(true);
+
+        // We create a terrain, vehicle and cloud system
+        _terrain = new Terrain(_scene);
+        _vehicle = CreateVehicle(new Vector2(50,10));
+        _clouds = new Clouds(50, 5, 40, 16, 40);
+
+        // We subscribe to the PostUpdateEvent
+        SubscribeToEvent<PostUpdateEvent>(PostUpdate);
+        
+        // If we're building a debug release, we draw debug data
+        #if DEBUG
+        DebugRenderer dbr = _scene.CreateComponent<DebugRenderer>();
+        pw.SetDrawCenterOfMass(true); pw.SetDrawJoint(true); pw.SetDrawPair(true); pw.SetDrawShape(true);
+        SubscribeToEvent<PostRenderUpdateEvent>(e => {_scene.GetComponent<PhysicsWorld2D>().DrawDebugGeometry(dbr,false);});
+        #endif
+    }
+
+    // This function is called after all nodes positions were updated for the current frame (UpdateEvent)
+    void PostUpdate(PostUpdateEvent eventData)
+    {
+        // We lerp the camera so it follows the vehicle smoothly
+        _camera.Node.SetPosition(
+            new Vector3(LerpVector2(_camera.Node.Position2D, _vehicle.Node.Position2D+Vector2.UnitX*10, 5*eventData.TimeStep)) + 
+            Vector3.Back*10);
+        // We tick the cloud system
+        _clouds.Tick(eventData.TimeStep, _vehicle.Node.Position.X);
+    }
+
+    #region Static Utils
+
+    // Convenience function to create node with sprite and rigidbody (optional)
+    static Vehicle CreateVehicle(Vector2 position)
+    {
+        Node vehicleNode = CreateSpriteNode(Cache.Get<Sprite2D>("characters/truck/vehicle.png"), 1.4f);
+
+        // We create the vehicle and the chassis (CreateChassis returns the Vehicle for convenience)
+        Vehicle vehicle = vehicleNode.CreateComponent<Vehicle>().CreateChassis(
+            new Vector2(-0.1f, 0.4f), 1.4f, 5,
+            new Vector3(-2f, -1, 1), Cache.Get<ParticleEffect2D>("particles/smoke.pex"),
+            Cache.Get<Sound>("sounds/engine_sound_crop.wav"), Cache.Get<Sound>("sounds/tires_squal_loop.wav"),
+            new [] {Cache.Get<Sound>("sounds/susp_1.wav"), Cache.Get<Sound>("sounds/susp_3.wav")},
+            300, 50, 5, 500);
+
+        // We create the wheels
+        Sprite2D wspr = Cache.Get<Sprite2D>("characters/truck/wheel.png");
+        Node w1 = vehicle.CreateWheel(
+            wspr, new Vector2(1.5f,-1.5f), 1.25f, 4, 0.4f, Cache.Get<ParticleEffect2D>("particles/dust.pex"), 2.6f);
+        Node w2 = vehicle.CreateWheel(
+            wspr, new Vector2(-1.8f,-1.5f), 1.25f, 4, 0.4f, Cache.Get<ParticleEffect2D>("particles/dust.pex"), 2.6f);
+
+        // We create the head
+        Node head = vehicle.CreateHead(
+            Cache.Get<Sprite2D>("characters/truck/head.png"), new Vector3(-1,2.7f,-1), 1f, new Vector2(-1,1.8f));
+
+        // We position the vehicle
+        foreach (Node node in new []{vehicleNode, w1, w2, head})
+            node.Translate2D(position);
+
+        return vehicle;
+    }
+
+    // Create a node with a sprite and optionally set scale and add a RigidBody2D component
+    public static Node CreateSpriteNode(Sprite2D sprite, Node parent, float scale = 1f, bool addRigidBody = true)
+    {
+        Node n = parent.CreateChild();
+        n.SetScale2D(Vector2.One*scale);
+        n.CreateComponent<StaticSprite2D>().SetSprite(sprite);
+        if (addRigidBody) n.CreateComponent<RigidBody2D>().SetBodyType(BodyType2D.BT_DYNAMIC);
+        return n;
+    }
+
+    // Convenience overload
+    public static Node CreateSpriteNode(Sprite2D sprite, float scale = 1f, bool addRigidBody = true)
+    {
+        return CreateSpriteNode(sprite, _scene, scale, addRigidBody);
+    }
+
+    // Convenience function to add a collider to a given node
+    public static T AddCollider<T>(Node node, float fric = 1, float dens = 1, float elas = 0) where T: CollisionShape2D
+    {
+        CollisionShape2D s = node.CreateComponent<T>();
+        s.SetFriction(fric);
+        s.SetDensity(dens);
+        s.SetRestitution(elas);
+        return (T)s;
+    }
+
+    // This returns the surface point closest to wheel's center
+    public static Vector3 GetSurfacePointClosestToPoint(Node wheel)
+    {
+        // We sample various points near the wheel position
+        List<Vector2> points = new List<Vector2>();
+        for (float xOffset = -1; xOffset < 1; xOffset+=0.02f)
+        {
+            float y = _terrain.SampleSurface(wheel.Position.X + xOffset);
+            points.Add(new Vector2(wheel.Position.X + xOffset, y));
+        }
+
+        // We get the closest one
+        float lastDist = float.MaxValue;
+        Vector2 closestPoint = Vector2.Zero;
+        foreach (Vector2 point in points)
+        {
+            float pointDist = Vector2.Distance(wheel.Position2D, point);
+            if (pointDist < lastDist)
+            {
+                closestPoint = point;
+                lastDist = pointDist;
+            }
+        }
+
+        return new Vector3(closestPoint.X, closestPoint.Y, 2);
+    }
+
+    public static Vector2 LerpVector2(Vector2 vecA, Vector2 vecB, float t)
+    {
+        Vector2 ipo;
+        ipo.X = vecA.X + t * (vecB.X - vecA.X);
+        ipo.Y = vecA.Y + t * (vecB.Y - vecA.Y);
+        return ipo;
+    }
+
+    #endregion
+}

+ 56 - 0
Racer2D/CSharp/Resources/Scripts/Clouds.cs

@@ -0,0 +1,56 @@
+using System;
+using AtomicEngine;
+
+public class Clouds
+{
+    private int _currentIndex;
+    private readonly int _range;
+    private readonly int _deviation;
+    private readonly int _minY;
+    private readonly Node[] _clouds;
+    private readonly Random rng = new Random();
+
+    public Clouds(float startX, int minY, int deviation, int amount, int range)
+    {
+        _minY = minY;
+        _deviation = deviation;
+        _range = range;
+
+        Sprite2D[] cloudSprites = {
+            Cache.Get<Sprite2D>("scenarios/cloud1.png"),
+            Cache.Get<Sprite2D>("scenarios/cloud2.png"),
+            Cache.Get<Sprite2D>("scenarios/cloud3.png")};
+
+        // We pre-fill the screen with clouds
+        float cloudSpacing = range*2/amount;
+        _clouds = new Node[amount];
+        for (int i = 0; i < amount; i++)
+        {
+            _clouds[i] = AtomicMain.CreateSpriteNode(cloudSprites[rng.Next(cloudSprites.Length)], 4, false);
+            RecycleCloud(startX-=cloudSpacing, _clouds[i]);
+        }
+    }
+
+    private void RecycleCloud(float currentX, Node cloud)
+    {
+        // Position cloud at rightmost edge of the range
+        cloud.SetPosition(new Vector3(currentX+_range, rng.Next(_deviation) + _minY, 15));
+    }
+
+    public void Tick(float dt, float currentX)
+    {
+        // We lazily check clouds and recycle them
+        _currentIndex++;
+        Node currentCloud = _clouds[_currentIndex%_clouds.Length];
+        if (currentCloud.Position.X < currentX - _range)
+        {
+            RecycleCloud(currentX, currentCloud);
+        }
+
+        // We translate all clouds according to their height
+        foreach (Node cloud in _clouds)
+        {
+            cloud.Translate2D(-Vector2.UnitX*dt*((cloud.Position2D.Y-_minY)*0.3f+1));
+        }
+    }
+}

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 154 - 0
Racer2D/CSharp/Resources/Scripts/OpenSimplex.cs


+ 273 - 0
Racer2D/CSharp/Resources/Scripts/Terrain.cs

@@ -0,0 +1,273 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using AtomicEngine;
+using OpenSimplex;
+
+public class Terrain
+{
+    private readonly Scene _scene;
+    private readonly Material _surfaceMaterial;
+    private readonly Material _chunkMaterial;
+    private readonly DecorationLibrary<Sprite2D> _decorationLib;
+    private readonly List<CollisionChain2D> _chunks = new List<CollisionChain2D>();
+
+    // Generator configuration
+    private const int Chunksize = 20;
+    private const int Chunkheight = -100;
+    private int chunksToGenerate = 200;
+    private const float NoiseScaleX = .05f;
+    private const float NoiseScaleY = 6;
+    private const int SurfaceRepeatPerChunk = 6;
+    private const float SurfaceSegmentSize = 0.5f;
+    private const int PercentageOfChunksWithCrates = 4;
+
+    // Generation working variables
+    private OpenSimplexNoise _noise = new OpenSimplexNoise();
+    private Random _rng = new Random();
+    private Vector3 _lastSurfaceExtrusion = Vector3.Right*Chunksize;
+    private int _discard = 0;
+
+    private class DecorationLibrary<T>
+    {
+        private T[][] _groups;
+        private Random _rng = new Random();
+
+        public DecorationLibrary(params T[][] spritesGroups)
+        {
+            _groups = spritesGroups;
+        }
+
+        public T GetRandomFromGroup(int group)
+        {
+            if (group >= _groups.Length)
+                throw new Exception($"There's no group with index {group} in this library");
+            return _groups[group][_rng.Next(_groups[group].Length)];
+        }
+
+        public List<T> GetAllResources()
+        {
+            List <T> allList = new List<T>();
+            foreach (T[] group in _groups)
+            {
+                allList = allList.Concat(group).ToList();
+            }
+            return allList;
+        }
+
+    }
+
+    public Terrain(Scene scene)
+    {
+        _scene = scene;
+
+        // We load the materials, the ones in this example only use built in techniques, we use diffuse for the ground for performance
+        _surfaceMaterial = Cache.Get<Material>("materials/UnlitAlpha.xml");
+        _surfaceMaterial.SetTexture(0, Cache.Get<Texture2D>("scenarios/grasslands/surface.png"));
+        _chunkMaterial = Cache.Get<Material>("materials/Unlit.xml");
+        _chunkMaterial.SetTexture(0,Cache.Get<Texture2D>("scenarios/grasslands/ground.png"));
+
+        // We create and populate a library with the decorations
+        Func<string, Sprite2D> GetSprite = file => Cache.Get<Sprite2D>($"scenarios/grasslands/Object/{file}");
+        _decorationLib = new DecorationLibrary<Sprite2D>(
+            new [] {GetSprite("Bush (1).png"), GetSprite("Bush (2).png"), GetSprite("Bush (3).png"), GetSprite("Bush (4).png")}, 
+            new [] {GetSprite("Mushroom_1.png"), GetSprite("Mushroom_2.png"), GetSprite("Stone.png"), GetSprite("Sign_2.png")}, 
+            new [] {GetSprite("Tree_1.png"), GetSprite("Tree_2.png"), GetSprite("Tree_3.png")}
+            );
+
+        // We setup the hotstop/origin of each sprite to be next to its bottom
+        foreach (Sprite2D sprite in _decorationLib.GetAllResources())
+        {
+            sprite.SetHotSpot(new Vector2(0.5f, 0.1f));
+        }
+
+        // We generate the chunks and add some boxes randomly
+        Sprite2D crateSprite = Cache.Get<Sprite2D>("scenarios/grasslands/Object/Crate.png");
+        for (int i = 0; i < Chunksize*chunksToGenerate; i+=Chunksize)
+        {
+            GenerateChunk(i);
+
+            // Crates
+            if (_rng.Next(100) < PercentageOfChunksWithCrates)
+            {
+                Node crateNode = AtomicMain.CreateSpriteNode(crateSprite, 3);
+                crateNode.SetPosition(new Vector3(i + _rng.Next(8), 20, -5));
+                CollisionBox2D crateCollider = crateNode.CreateComponent<CollisionBox2D>();
+                crateCollider.SetSize(0.76f, 0.76f);
+                crateCollider.SetDensity(1.0f);
+                crateCollider.SetRestitution(0.6f);
+                crateCollider.SetFriction(0.4f);
+            }
+        }
+    }
+
+    public float SampleSurface(float posX)
+    {
+        return (float)_noise.Evaluate(posX*NoiseScaleX, 0)*NoiseScaleY;
+    }
+
+    void GenerateChunk(int startx)
+    {
+        // We create a node and position where the chunk starts
+        Node node = _scene.CreateChild();
+        node.SetPosition2D(startx, 0);
+
+        // We create components to render the geometries of the surface and the ground
+        var groundComponent = node.CreateComponent<CustomGeometry>();
+        groundComponent.SetMaterial(_chunkMaterial);
+        groundComponent.BeginGeometry(0, PrimitiveType.TRIANGLE_LIST);
+
+        var surfaceComponent = node.CreateComponent<CustomGeometry>();
+        surfaceComponent.SetMaterial(_surfaceMaterial);
+        surfaceComponent.BeginGeometry(0, PrimitiveType.TRIANGLE_LIST);
+        
+        // We initialize and add a single entry to the surface collider points list
+        List<Vector2> surfacePoints = new List<Vector2>() {new Vector2(0,
+            (float)_noise.Evaluate(startx*NoiseScaleX, 0)*NoiseScaleY
+            )
+        };
+
+        // We translate the last surface extrusion point so it's local relative to the chunk we're creating
+        _lastSurfaceExtrusion += Vector3.Left*Chunksize;
+
+        // We loop all the segments in this chunk
+        float incr = SurfaceSegmentSize;
+        for (float x = 0; x < Chunksize-float.Epsilon*8; x+=incr)
+        {
+            // We store vars for the position for the current segment end point x position and for the y position of the 4 points
+            float xEnd = x+incr;
+            float tlY = SampleSurface(startx + x);
+            float trY = SampleSurface(startx + xEnd);
+            float blY = tlY + Chunkheight;
+            float brY = trY + Chunkheight;
+
+            // We create vectors that represent 
+            Vector3 bl = new Vector3(x, blY, -10);
+            Vector3 tl = new Vector3(x, tlY, -10);
+            Vector3 br = new Vector3(xEnd, brY, -10);
+            Vector3 tr = new Vector3(xEnd, trY, -10);
+
+            // We add the top right point to the surface points list (remember we added the first point in the list init)
+            surfacePoints.Add(new Vector2(tr));
+
+            // We call the CreateDecor function passing the global position of the current segment end point and the segment angle
+            CreateDecor(tr+Vector3.Right*startx, tl-tr);
+
+            // We create the geometry of the surface (UV os oriented according to the surface)
+            Vector2 startV = Vector2.UnitX*(x/Chunksize)*SurfaceRepeatPerChunk;
+            Vector2 endV = Vector2.UnitX*(xEnd/Chunksize*SurfaceRepeatPerChunk);
+            //bl
+            surfaceComponent.DefineVertex(_lastSurfaceExtrusion);
+            surfaceComponent.DefineTexCoord(startV);
+            //tl
+            surfaceComponent.DefineVertex(tl);
+            surfaceComponent.DefineTexCoord(startV-Vector2.UnitY);
+            //tr
+            surfaceComponent.DefineVertex(tr);
+            surfaceComponent.DefineTexCoord(-Vector2.UnitY+endV);
+            //bl
+            surfaceComponent.DefineVertex(_lastSurfaceExtrusion);
+            surfaceComponent.DefineTexCoord(startV);
+            //tr
+            surfaceComponent.DefineVertex(tr);
+            surfaceComponent.DefineTexCoord(-Vector2.UnitY+endV);
+            //br - We store the last point to use for continuity
+            _lastSurfaceExtrusion = tr + Quaternion.FromAxisAngle(Vector3.Back, 90)*Vector3.NormalizeFast(tr - tl);
+            surfaceComponent.DefineVertex(_lastSurfaceExtrusion);
+            surfaceComponent.DefineTexCoord(endV);
+
+            // We create the geometry of the ground (UV is in world coordinates)
+            //bl
+            groundComponent.DefineVertex(bl);
+            groundComponent.DefineTexCoord(new Vector2(bl/Chunksize));
+            //tl
+            groundComponent.DefineVertex(tl);
+            groundComponent.DefineTexCoord(new Vector2(tl/Chunksize));
+            //tr
+            groundComponent.DefineVertex(tr);
+            groundComponent.DefineTexCoord(new Vector2(tr/Chunksize));
+            //bl
+            groundComponent.DefineVertex(bl);
+            groundComponent.DefineTexCoord(new Vector2(bl/Chunksize));
+            //tr
+            groundComponent.DefineVertex(tr);
+            groundComponent.DefineTexCoord(new Vector2(tr/Chunksize));
+            //br
+            groundComponent.DefineVertex(br);
+            groundComponent.DefineTexCoord(new Vector2(br/Chunksize));
+        }
+        // We commit the geometry data we just created
+        surfaceComponent.Commit();
+        groundComponent.Commit();
+        
+        // We create the collider component for the chunk surface
+        CollisionChain2D surfaceCollider = node.CreateComponent<CollisionChain2D>();
+        surfaceCollider.SetLoop(false);
+        surfaceCollider.SetFriction(1);
+        surfaceCollider.SetVertexCount((uint) surfacePoints.Count+1);
+        _chunks.Add(surfaceCollider);
+
+        // We add a small overlapping segment with a bit of negative offset in y so the wheel passes smoothly over chunk seams
+        Vector2 smoother = new Vector2(-incr*.5f, (float) _noise.Evaluate((startx-incr*.5f)*NoiseScaleX, 0)*NoiseScaleY-.005f);
+        surfaceCollider.SetVertex(0,smoother);
+
+        // Finally, we set the vertex of the surface collider
+        for (int c = 0; c < surfacePoints.Count; c++)
+        {
+            Vector2 surfacePoint = surfacePoints[c];
+            surfaceCollider.SetVertex((uint)c+1, surfacePoint);
+        }
+        
+        // A collider must have a rigid body to interact with other bodies, even if static which is the case
+        node.CreateComponent<RigidBody2D>().SetBodyType(BodyType2D.BT_STATIC);
+    }
+
+    void CreateDecor(Vector3 position, Vector3 leftVector)
+    {
+        // We discard a few calls
+        _discard++;
+        if (_discard % 3 != 0)
+            return;
+
+        // We use the simplex noise to evaluate chances with smooth transition
+        double chance = _noise.Evaluate(position.X * 0.2f, 0)+1;
+        Node node = null;
+        bool isTree = false;
+
+        // Common decorations (grasses)
+        if (chance < 1.2f)
+        {
+            // We discard a few of these too
+            if (_rng.Next(6) < 4)
+                node = AtomicMain.CreateSpriteNode(_decorationLib.GetRandomFromGroup(0), 3, false);
+        }
+        else if (chance < 1.3f)
+            node = AtomicMain.CreateSpriteNode(_decorationLib.GetRandomFromGroup(1), 4, false);
+        else if (chance < 1.7f)
+        {
+            // We only spawn trees if the surface isn't too steep
+            if (Math.Abs(leftVector.Y) < 0.1f)
+            {
+                node = AtomicMain.CreateSpriteNode(_decorationLib.GetRandomFromGroup(2), 4, false);
+                node.Scale2D = node.Scale2D *= 1 + _rng.Next(30)/100f;
+                isTree = true;
+            }
+        }
+
+        if (node != null)
+        {
+            // We position the node on the surface, if the node is a tree we don't rotate it AND offset it a bit
+            node.SetPosition(position+Vector3.Forward*15);
+            if (!isTree)
+            {
+                Quaternion transformation = Quaternion.FromRotationTo(Vector3.Left, leftVector);
+                node.SetRotation(transformation);
+            }
+            else
+                node.Translate(-node.Up*0.1f);
+        }
+    }
+}
+

+ 258 - 0
Racer2D/CSharp/Resources/Scripts/Vehicle.cs

@@ -0,0 +1,258 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using AtomicEngine;
+
+public class Vehicle : CSComponent
+{
+    private List<Wheel> _wheels = new List<Wheel>();
+    private Input _input = AtomicNET.GetSubsystem<Input>();
+    private RigidBody2D _rigidBody;
+    private ParticleEmitter2D _exhaustParticles;
+    private SoundSource3D _soundSource;
+    private SoundSource3D _suspensionSoundSource;
+    private Sound _accelSound;
+    private Sound _brakeSound;
+    private Sound[] _suspensionSounds;
+    private int _horsePower;
+    private int _maxSpdFwd;
+    private int _maxSpdBwd;
+    private int _rollForce;
+
+    //private float lastSuspensionSound; //TODO there's no Time subsystem?
+    private DateTime lastSuspensionSound = DateTime.Now;
+
+    // We use this for the wheel logic
+    private class Wheel
+    {
+        private readonly RigidBody2D _rigidBody;
+        private readonly ConstraintWheel2D _constraint;
+        private readonly ParticleEmitter2D _particleEmitter;
+        private readonly float _particlesDistance;
+
+        // This struct is used to pass wheel dynamics data
+        public struct DynamicsData
+        {
+            public bool IsInContact;
+            public float Resistance;
+            public float AngularVelocity;
+        }
+
+        public Wheel(RigidBody2D rigidBody, ConstraintWheel2D constraint, ParticleEmitter2D particleEmitter, float particlesDistance)
+        {
+            _rigidBody = rigidBody; _constraint = constraint; _particleEmitter = particleEmitter; _particlesDistance = particlesDistance;
+        }
+
+        // Applies force and returns the work (normalized) necessary to reach the target speed
+        public DynamicsData ApplyNonLinearTorque(int power, int targetSpeed, bool onlyInContact = false)
+        {
+            bool inContact = EmitSurfaceParticles();
+            float fraction = _rigidBody.AngularVelocity/targetSpeed;
+            float mult = fraction > 0 ? 1-fraction : 1;
+            if (!onlyInContact || inContact)
+                _rigidBody.ApplyTorque(mult*power, true);
+            return new DynamicsData
+            {
+                Resistance = fraction*mult, AngularVelocity = _rigidBody.AngularVelocity, IsInContact = inContact
+            };
+        }
+        // Emit particles if close to a surface point and returns true if positive
+        public bool EmitSurfaceParticles()
+        {
+            Vector3 nearestSurfPoint = AtomicMain.GetSurfacePointClosestToPoint(_rigidBody.Node);
+            float contactDistance = Vector3.Distance(_rigidBody.Node.Position, nearestSurfPoint);
+            if (contactDistance > _particlesDistance)
+            {
+                _particleEmitter.Effect.StartColor = new Color(0, 0, 0, 0);
+                return false;
+            }
+            _particleEmitter.Effect.StartColor = Color.White;
+            _particleEmitter.Node.Position = nearestSurfPoint;
+            return true;
+        }
+        // Returns current suspension compression for the wheel
+        public float CurrentSuspensionCompression()
+        {
+            Vector2 anchorPosition = new Vector2(_constraint.OwnerBody.Node.WorldPosition +
+                                     _constraint.OwnerBody.Node.WorldRotation*new Vector3(_constraint.Anchor));
+            return Vector2.Distance(_constraint.OtherBody.Node.Position2D, anchorPosition);
+        }
+    }
+    
+    public Vehicle CreateChassis(Vector2 colliderCenter, float colliderRadius, int massDensity, Vector3 exhaustPosition,
+        ParticleEffect2D exhaustParticles, Sound engineSound, Sound tireSound, Sound[] suspensionSounds,
+        int horsePower, int maxSpeedFwd, int maxSpeedBwd, int rollForce)
+    {
+        // We set out private fields
+        _horsePower = horsePower;
+        _maxSpdFwd = maxSpeedFwd;
+        _maxSpdBwd = maxSpeedBwd;
+        _rollForce = rollForce;
+        _rigidBody = GetComponent<RigidBody2D>();
+
+        // We add the collider (circle collider at the moment)
+        var col = AtomicMain.AddCollider<CollisionCircle2D>(Node, dens:massDensity, fric:0);
+        col.SetRadius(colliderRadius);
+        col.SetCenter(colliderCenter);
+        
+        // We create the exhaust particle system
+        var exhaustParticlesNode = Node.CreateChild();
+        exhaustParticlesNode.SetPosition(exhaustPosition);
+        _exhaustParticles = exhaustParticlesNode.CreateComponent<ParticleEmitter2D>();
+        _exhaustParticles.SetEffect(exhaustParticles);
+        
+        // We setup the engine sound and other sound effect
+        engineSound.SetLooped(true);
+        _soundSource = Node.CreateComponent<SoundSource3D>();
+        _soundSource.SetNearDistance(10);
+        _soundSource.SetFarDistance(50);
+        _accelSound = engineSound;
+        _brakeSound = tireSound;
+        _suspensionSoundSource = Node.CreateComponent<SoundSource3D>();
+        _suspensionSounds = suspensionSounds;
+
+        // We return the Vehicle for convenience, since this function is intended to be the vehicle's init function
+        return this;
+    }
+
+    public Node CreateWheel(Sprite2D sprite, Vector2 relativePosition, float radius, int suspensionFrequency, float suspensionDamping,
+        ParticleEffect2D particles, float distanceToEmitParticles)
+    {
+        Node wheelNode = AtomicMain.CreateSpriteNode(sprite);
+        wheelNode.SetPosition2D(relativePosition);
+
+        // CreateSpriteNode adds a RigidBody for us, so we get it here
+        RigidBody2D wheelRigidBody = wheelNode.GetComponent<RigidBody2D>();
+        // We activate CCD
+        wheelRigidBody.SetBullet(true);
+
+        AtomicMain.AddCollider<CollisionCircle2D>(wheelNode).SetRadius(radius);
+        
+        // The Box2D wheel joint provides spring for simulating suspension
+        ConstraintWheel2D wheelJoint = Node.CreateComponent<ConstraintWheel2D>();
+        wheelJoint.SetOtherBody(wheelRigidBody);
+        wheelJoint.SetAnchor(relativePosition);
+        wheelJoint.SetAxis(Vector2.UnitY);
+        wheelJoint.SetFrequencyHz(suspensionFrequency);
+        wheelJoint.SetDampingRatio(suspensionDamping);
+
+        // Each wheel has a particle emitter to emit particles when it's in contact with the surface
+        Node particlesNode = Node.Scene.CreateChild();
+        particlesNode.SetPosition(new Vector3(relativePosition.X, relativePosition.Y, 14));
+        ParticleEmitter2D particleEmitter = particlesNode.CreateComponent<ParticleEmitter2D>();
+        particleEmitter.SetEffect(particles);
+
+        // We create a new Wheel struct and add to the _wheels list
+        _wheels.Add(new Wheel(wheelRigidBody, wheelJoint, particleEmitter, distanceToEmitParticles));
+
+        return wheelNode;
+    }
+
+    public Node CreateHead(Sprite2D sprite, Vector3 relativePosition, float colliderRadius, Vector2 neckAnchor)
+    {
+        Node head = AtomicMain.CreateSpriteNode(sprite);
+        head.SetPosition(relativePosition);
+        AtomicMain.AddCollider<CollisionCircle2D>(head).SetRadius(colliderRadius);
+
+        // This is the actual neck joint
+        ConstraintRevolute2D joint = head.CreateComponent<ConstraintRevolute2D>();
+        joint.SetOtherBody(_rigidBody);
+        joint.SetAnchor(neckAnchor);
+        
+        // This is the spring, it's attached to the body with an offset
+        ConstraintDistance2D spring = head.CreateComponent<ConstraintDistance2D>();
+        spring.SetOtherBody(_rigidBody);
+        spring.SetOwnerBodyAnchor(-Vector2.UnitY*2);
+        spring.SetOtherBodyAnchor(Node.WorldToLocal2D(head.WorldPosition2D-Vector2.UnitY*2));
+        spring.SetFrequencyHz(3);
+        spring.SetDampingRatio(0.4f);
+
+        return head;
+    }
+
+    // Update is called once per frame
+    private void Update(float dt)
+    {
+        // Wheel controls
+        bool isBraking = _input.GetKeyDown((int) SDL.SDL_Keycode.SDLK_DOWN);
+        bool isAccelerating = _input.GetKeyDown((int) SDL.SDL_Keycode.SDLK_UP);
+        
+        foreach (Wheel wheel in _wheels)
+        {
+            // We give priority to braking
+            if (isBraking)
+            {
+                // This function also emit particles
+                Wheel.DynamicsData wheelDynamics = wheel.ApplyNonLinearTorque(_horsePower, _maxSpdBwd, true);
+                if (wheelDynamics.IsInContact)
+                {
+                    // If the wheel is in contact, we play the braking sound according to the current work in the wheel
+                    if (_soundSource.Sound != _brakeSound || !_soundSource.IsPlaying())
+                        _soundSource.Play(_brakeSound);
+                    _soundSource.SetFrequency(48000);
+                    _soundSource.Gain = wheelDynamics.Resistance*-1;
+                    if (_soundSource.Gain > 1) _soundSource.Gain = 1;
+                }
+                else
+                {
+                    _soundSource.Stop();
+                }
+            }
+            else if (isAccelerating)
+            {
+                Wheel.DynamicsData wheelDynamics = wheel.ApplyNonLinearTorque(-_horsePower, -_maxSpdFwd);
+                // We set the sound frequency according to the speed and gain according to the work being done
+                _soundSource.SetFrequency((wheelDynamics.AngularVelocity/-40+1)*48000);
+                _soundSource.Gain += 0.03f;
+                if (_soundSource.Gain > 1) _soundSource.Gain = 1;
+                _soundSource.Gain += Math.Abs(1-wheelDynamics.Resistance*5);
+                if (_soundSource.Sound != _accelSound || !_soundSource.IsPlaying())
+                    _soundSource.Play(_accelSound);
+            }
+            else
+            {
+                // If it's not receiving any input, fades out engine sound or stop other sounds
+                if (_soundSource.Sound == _accelSound)
+                {
+                    _soundSource.SetFrequency(48000);
+                    _soundSource.Gain -= 0.01f;
+                    if (_soundSource.Gain < 0.3f) _soundSource.Gain = 0.3f;
+                }
+                else
+                    _soundSource.Stop();
+
+                // We emit surface particles anyway
+                wheel.EmitSurfaceParticles();
+            }
+        }
+
+        // Roll controls
+        if (_input.GetKeyDown((int) SDL.SDL_Keycode.SDLK_LEFT))
+            _rigidBody.ApplyTorque(_rollForce,true);
+        if (_input.GetKeyDown((int) SDL.SDL_Keycode.SDLK_RIGHT))
+            _rigidBody.ApplyTorque(_rollForce*-1,true);
+        // Debug control for when you rolled over
+        if (_input.GetKeyDown((int) SDL.SDL_Keycode.SDLK_SPACE))
+            _rigidBody.SetAngularVelocity(2);
+
+        // We apply bit of the vehicle's velocity to the exhaust particles so they look OK
+        Vector2 currentVelocity = _rigidBody.GetLinearVelocity();
+        _exhaustParticles.Effect.Speed = Math.Abs(100 + currentVelocity.LengthFast * 20);
+        _exhaustParticles.Effect.SpeedVariance = currentVelocity.LengthFast * 10;
+        _exhaustParticles.Effect.Angle = -Node.Rotation2D + 
+            (float)(Math.Atan2(currentVelocity.X - 5, -currentVelocity.Y) * (360 / (Math.PI * 2))) - 90;
+
+        // Suspension sounds
+        foreach (Wheel wheel in _wheels)
+        {
+            if (wheel.CurrentSuspensionCompression() > 0.7f)
+            {
+                if (DateTime.Now - lastSuspensionSound > TimeSpan.FromMilliseconds(500))
+                {
+                    lastSuspensionSound=DateTime.Now;
+                    _suspensionSoundSource.Play(_suspensionSounds[new Random().Next(_suspensionSounds.Length)]);
+                }
+            }
+        }
+    }
+}

BIN
Racer2D/CSharp/Resources/Sounds/engine_sound_crop.wav


BIN
Racer2D/CSharp/Resources/Sounds/susp_1.wav


BIN
Racer2D/CSharp/Resources/Sounds/susp_2.wav


BIN
Racer2D/CSharp/Resources/Sounds/susp_3.wav


BIN
Racer2D/CSharp/Resources/Sounds/tires_squal_loop.wav


+ 49 - 0
Racer2D/CSharp/Settings/Engine.json

@@ -0,0 +1,49 @@
+{
+  "desktop": {
+
+    "engine" : {
+      "workerThreads" : true,
+      "logQuiet" : false,
+      "logLevel" : 1
+    },
+
+    "graphics": {
+      "headless": false,
+      "frameLimiter" : true,
+      "flushGPU" : false,
+      "forceGL2" : false,
+      "orientations" : "LandscapeLeft LandscapeRight",
+      "vsync" : false,
+      "tripleBuffer" : false,
+      "multiSample" : 1,
+      "renderPath" : "forward",
+      "shadows" : true,
+      "lowQualityShadows" : false,
+      "materialQuality" : "high",
+      "textureQuality" : "high",
+      "textureFilterMode" : "trilinear",
+      "textureAnisotropy" : 4
+    },
+
+    "window" : {
+      "title" : "AtomicMutant",
+      "fullscreen" : false,
+      "borderless" : false,
+      "resizable" : false
+    },
+
+    "sound": {
+      "enabled" : true,
+      "interpolation" : true,
+      "stereo" : true,
+      "bufferMS": 100,
+      "mixRate" : 44100
+    },
+
+    "input" : {
+      "touchEmulation" : false
+    }
+
+  }
+
+}

BIN
Racer2D/Screenshot.png


Vissa filer visades inte eftersom för många filer har ändrats