Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

Games [WIP] Raiders of the Lost Island

Discussion in 'Works In Progress - Archive' started by xelanoimis, Jul 23, 2017.

  1. xelanoimis

    xelanoimis

    Joined:
    Oct 29, 2013
    Posts:
    38


    Raiders of the Lost Island is a local co-op party game where up to 4 players compete in looting treasures from a sinking island, while building together a raft to survive the final wave.


    DESCRIPTION

    Four funny adventurers explore a sinking island under the constant threat of the rising waters. They must work together and collect resources quickly to build a raft and escape before the final wave washes them away. If they fail, they all lose together.

    However, the island is full of treasures: gold coins, gems and the most expensive diamonds. This hard to resist temptation will put their friendship to the test, because if they manage to survive, only the richest one will stand in front! The others just helped him win.

    Grab your friends and venture to the Lost Island! Seek your fortune, survive together and get rich in the process! Don’t let the greed get the best of you or you will all end up on the bottom of the ocean!

    FEATURES
    • local co-op party game (1-4 players)
    • lose together, but win alone
    • beautiful 3d isometric graphics
    • funny animated characters
    • basic fighting mechanic
    • simple controls scheme
    • gorgeous coins, gems and diamonds
    • diverse levels with different themes
    • dangers, traps and hidden passages
    • rules and environment modifiers
    • find who your friends really are
    • tons of laughs and good times

    SCREEN SHOTS





    AUTHOR


    Hi, I'm Alexandru Simion, the main developer of the Raiders of the Lost Island, a project that started with another 3 friends during the Global Game Jam 2017. I'm trying to build up a community of fans for the game so any feedback is welcomed.

    http://www.raidersofthelostisland.com/

    www.facebook.com/RaidersOfTheLostIsland
    twitter.com/xelanoimis
    www.youtube.com/channel/UC9DR3SKnlvL720fXvcnfJXA

    The game is in ALPHA stage and I'll try to use this thread to post constantly about the technical aspects of the game. So if you're interested, join me and follow the game's development. I'd love to hear from you!

    Thank you so much!
    Alex
     
    Karrzun and RavenOfCode like this.
  2. xelanoimis

    xelanoimis

    Joined:
    Oct 29, 2013
    Posts:
    38
    So, the first post in this series is about GlobalFog effect.

    I'm using the global fog but in my case there were some drawbacks. As you can see the game has an 3D isometric view with a 15 degrees FOV and my camera zooms in and out depending how far the 4 players are spread around the level.

    I was using the Distance Fog but I didn't want the players to see the level more foggy when camera was zooming away and less foggy when getting closer (camera moves in/out for zooming).

    Even more, I use the Height Fog property to make the water less transparent with the depth level (by constantly changing the height property to match the current water level that goes up and down). This is a gameplay aspect because the water is meant to hide treasures on lower level and challenge the players to remember and plan their strategies accordingly. Obviously I don't want the water transparency to variate with the camera zoom level.

    If I simply disable the distance fog, my water won't be opaque enough and the level would be too sharp (I do want just a bit of foggy feel).

    The solution was to use a negative value for startDistance which allowed me to disable the Distance Fog and get a similar feeling that stays the same for close and far camera zooming. I had to adjust the script GlobalFog.cs to accept negative values:

    Code (CSharp):
    1. // @A: allow negative start distance for fog
    2. // fogMaterial.SetVector ("_DistanceParams", new Vector4 (-Mathf.Max(startDistance,0.0f), excludeDepth, 0, 0));
    3. fogMaterial.SetVector("_DistanceParams", new Vector4(-startDistance, excludeDepth, 0, 0));
    Seems to work nicely and here are the results:

    global_fog.jpg

    You can see the difference between old version A (far) B (near): the underwater details were more visible when camera was close. In the new version the visibility is the same and so is the perceived level of fog over the whole level.

    Feel free to comment or post any questions. Any feedback is welcomed!
     
  3. taistelusopuli

    taistelusopuli

    Joined:
    Nov 6, 2014
    Posts:
    41
    Such a genius idea for a game! :) What platforms will you publish on? (Thought I saw XBox controllers in the video)

    I assume there will be several islands or will they generated for every new game?
     
  4. xelanoimis

    xelanoimis

    Joined:
    Oct 29, 2013
    Posts:
    38
    Thanks! Now the plan is to release on PC (steam, itch.io) but depending on interest I can see about other options. Yes, there will be more islands, each with random level modifiers for replayability (like shortcuts, stairs, bridges, etc). Alpha demo will come soon, then early access.
     
  5. Bakuda

    Bakuda

    Joined:
    Dec 7, 2015
    Posts:
    31
    This looks extremely fun! I love co-op games like this. Any chance you'll be releasing a Mac version?
     
  6. xelanoimis

    xelanoimis

    Joined:
    Oct 29, 2013
    Posts:
    38
    Thanks Bakuda! A Mac version is possible, depending on general interest. Unity makes it really easy, though I expect some work is needed, as always is. Fortunately I have a Mac and that should help. Focus is on the development platform which is PC. You can follow the game on Facebook for latest news, but I'll definitely keep track here too.
     
  7. xelanoimis

    xelanoimis

    Joined:
    Oct 29, 2013
    Posts:
    38
    Posted this WIP screenshot with my debug menus on FB and I thought to mention here a few things about the tech, in case someone needs a quick and easy solution.

    Raiders-debug.jpg
    I have a small wrapper over the Unity old GUILayout. The idea was to easily add new options from different managers, all in the same debug menu (one single OnGUI call). Not the most versatile system, but it takes advantage of the GUILayout. The old Unity GUI doesn't scale with resolutions which may be a problem of accessibility in some cases, but again, this is a quick and dirty debug support.

    Here is code, not a state of art, but it has a few tricks:

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class DebugMenu : MonoBehaviourSingleton<DebugMenu>
    6. {
    7.     public delegate void DebugMenuFunction();
    8.  
    9.     private static int m_frameCount;
    10.     private static bool m_active = false;
    11.     private static Dictionary<string, bool> m_groups = new Dictionary<string, bool>();
    12.     private static List<DebugMenuFunction> m_functions = new List<DebugMenuFunction>();
    13.  
    14.     public GUIStyle m_style;
    15.     public GUIStyle m_styleSlider;
    16.     public GUIStyle m_styleThumb;
    17.  
    18.     private void Update()
    19.     {
    20.         if (Input.GetKeyDown(KeyCode.F1))
    21.             m_active = !m_active;
    22.     }
    23.  
    24.     private void OnGUI()
    25.     {
    26.         if (m_active)
    27.         {
    28.             GUI.skin.button = m_style;
    29.             GUI.skin.horizontalSlider = m_styleSlider;
    30.             GUI.skin.horizontalSliderThumb = m_styleThumb;
    31.             GUI.skin.label = m_style;
    32.  
    33.             GUILayout.BeginArea(new Rect(0, 0, Screen.width / 3, Screen.height));
    34.             foreach (var f in m_functions)
    35.                 f();
    36.             GUILayout.EndArea();
    37.         }
    38.     }
    39.  
    40.     // call on Update or LateUpdates
    41.     public static void Add(DebugMenuFunction f)
    42.     {
    43.         if (m_frameCount != Time.frameCount)
    44.         {
    45.             m_frameCount = Time.frameCount;
    46.             m_functions.Clear();
    47.         }
    48.  
    49.         m_functions.Add(f);
    50.     }
    51.  
    52.     public static bool Open()
    53.     {
    54.         if (!m_active)
    55.             return false;
    56.  
    57.         return true;
    58.     }
    59.  
    60.     public static bool Group(string name)
    61.     {
    62.         bool on = false;
    63.         if(!m_groups.TryGetValue(name, out on))
    64.             m_groups.Add(name, on);
    65.  
    66.         GUI.color = Color.yellow;
    67.         if (GUILayout.Button(name))
    68.         {
    69.             on = !on;
    70.             m_groups[name] = on;
    71.         }
    72.         GUI.color = Color.white;
    73.  
    74.         return on;
    75.     }
    76.  
    77.     public static void Label(string name)
    78.     {
    79.         GUILayout.Label(name);
    80.     }
    81.  
    82.     public static bool Button(string name)
    83.     {
    84.         return GUILayout.Button(name);
    85.     }
    86.  
    87.     public static float Slider(float value, float min, float max, float step)
    88.     {
    89.         value = GUILayout.HorizontalSlider(value, min, max);
    90.         value = min + Mathf.Floor((value - min) / step) * step;
    91.         return value;
    92.     }
    93. }
    94.  
    It allows me to use it like this in various managers, where needed:

    Code (CSharp):
    1. void LateUpdate()
    2. {
    3.     DebugMenu.Add(OnDebugMenu);
    4. }
    5.  
    6. void OnDebugMenu()
    7. {
    8.     if (DebugMenu.Group("GAME"))
    9.     {
    10.         if (DebugMenu.Button("Level " + (m_progression.m_level + 1)))
    11.         {
    12.             m_progression.m_level = (m_progression.m_level + 1) % Progression.LevelsCount;
    13.         }
    14.         if (DebugMenu.Button("Finish with success"))
    15.         {
    16.             PageGame.instance.OnArkFinished();
    17.         }
    18.         if (DebugMenu.Button("Finish with failure"))
    19.         {
    20.             PageGame.instance.OnBigWaveFinished();
    21.         }
    22.        
    23.         DebugMenu.Label("Final wave time: " + Water.instance.m_finalWaveTime + " min");
    24.        
    25.         Water.instance.m_finalWaveTime = DebugMenu.Slider(Water.instance.m_finalWaveTime, 1.0f, 8.0f, 0.1f);
    26.     }
    27. }
     
  8. xelanoimis

    xelanoimis

    Joined:
    Oct 29, 2013
    Posts:
    38
    Saturday Challenge: How many resource sacks can you find in this screenshot?

    The sacks must be collected to build the raft, but during last night's playtest, Eddie wanted to see if he can brake the game and work against everyone, hiding sacks or throwing them off the island, so no one would survive. And he succeeded at first, until the rest of us started to work together and watched his every move closely. We didn't get out with much treasure, but we had a story to tell :)

    This unspeakable treason inspired this very challenge. Post a comment below with how many sacks can you spot in this screenshot (excluding the magnified sample sack in the red circle). All are visible, at least a bit.

    hidden sacks.jpg
     
  9. xelanoimis

    xelanoimis

    Joined:
    Oct 29, 2013
    Posts:
    38
    Getting close to release the alpha demo. Last night's playtest revealed a couple of things to be improved.
     
  10. xelanoimis

    xelanoimis

    Joined:
    Oct 29, 2013
    Posts:
    38
    Still iterating on the ending screen sequence.

    Here are some movies showing work in progress:
    V1

    V2


    What has been learned (latest playtest and feedback):

    1. Showing too much content in a single screen / sequence is confusing for a party game. I have:
    - round treasure items (cool to be seen to realize your the result of your effort in that round)
    - round points (wealth) got from these treasure items (silver=1, gold=3, diamond=10)
    - highlight round winner (spiky badge)
    - campaign points (total wealth) summed from each round's points
    - cool looking players avatars
    - highlight winning player (with most total wealth) by sorting avatars based on their total wealth
    - give cool "raider" badge to winning player to enforce it
    - show player colors to know who's who (I allow 2 players to take the same avatar)
    - chest models that are planned to level up

    2. Much confusion about who's who
    - players don't care about their colors (not enforced enough)
    - players don't like being reordered. They remember their places on the initial choose avatar screen and they know their places on the couch.
    - reordering avatars and their scores when someone else gets in front, totally screwed things up. Could improve it up to a point using animated transitions
    - will try to focus the winner "in-place", keeping him on his position, but having dedicated cameras and arrangements, win/lose anims

    3. Too many numbers at once
    - showing both round points and campaign total points... 8 numbers at once was too much for some, trying to disconnect their counting moments.
    - also having visual differences between them (some on badges, some on score bar) was also questionable.
    - use other measures than numbers (progress bars, etc.) could work

    4. Retention for everybody
    - find better ways to motivate people to play through the campaign
    - if one player is very good and gather lots of treasures, making lots of points each round, other players feel like not standing a chance and may sabotage the campaign, trying to prevent making the boat (hate)
    - find ways to allow cooperation in taking him down
    - find other ways to motivate (special titles like best builder, maybe fame or xp points gained differently)
    - personal tool items will help, but have to see how to give / lose them

    5. One saboteur is fun, two might be to much
    - if one player tries to prevent the others from building the boat, they have to really work together to take him down
    - tried this with 2 against 1 and finally managed to survive

    NEXT:
    Finish the demo, FAST. I might not include the campaign mode since there area more improvements needed for good retention and motivation. Still one round is fun enough and representative for this gameplay loop.
     
  11. xelanoimis

    xelanoimis

    Joined:
    Oct 29, 2013
    Posts:
    38
    Dev screenshot from an unusual perspective :)

    devshot.jpg
     
  12. xelanoimis

    xelanoimis

    Joined:
    Oct 29, 2013
    Posts:
    38
    After a some very busy weeks with Dev-Play Conference (won "Best Game of the Show" and "Best Design" awards), showing the game on TV and announcing the public Alpha Demo, I try to get back to work on game's features. Here are a few pictures from the last weeks:

    DevPlayMixS.jpg

    Don't forget to try the Alpha Demo with your friends and tell me about your experience with the game!

    http://www.raidersofthelostisland.com/tagged/play

    Have fun!
     
  13. xelanoimis

    xelanoimis

    Joined:
    Oct 29, 2013
    Posts:
    38
    Marian is back, working on new levels. So I've implemented a basic HeatMap system to analyze the game flow and to improve the levels. It tracks players movement and it can display multiple sets of data in the editor, for each level. It indicates areas that players don't visit and the paths they prefer. Only local data for now (no online support).
    lev1_w.jpg lev3_w.jpg lev3_q.jpg
     
  14. xelanoimis

    xelanoimis

    Joined:
    Oct 29, 2013
    Posts:
    38
    Here is the HeatMap source code. Not really polished and by far not optimized. It's more of a debug tool I put together quickly, to cover my current needs. Feel free to draw inspiration and reuse in your projects.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using System.IO;
    4. using System.Xml.Serialization;
    5. using UnityEngine;
    6.  
    7. [System.Serializable]
    8. public struct HeatmapNode
    9. {
    10.     public Vector3[] pos;
    11. }
    12.  
    13. [System.Serializable]
    14. public struct HeatmapQuad
    15. {
    16.     public Vector3 pos;
    17.     public Vector3 nor;
    18.     public int use;
    19.  
    20.     public void Use()
    21.     {
    22.         use++;
    23.     }
    24. }
    25.  
    26. [System.Serializable]
    27. public class HeatmapFile : StorageData
    28. {
    29.     public const int Version = 1;
    30.  
    31.     public int m_version;
    32.     public int m_level;
    33.     public int m_playersCount;
    34.     public float m_sampleFrequency;
    35.     public List<HeatmapNode> m_nodes;
    36.     public List<HeatmapQuad> m_quads;
    37.  
    38.     public HeatmapFile()
    39.     {
    40.         m_nodes = new List<HeatmapNode>();
    41.         m_quads = new List<HeatmapQuad>();
    42.     }
    43. }
    44.  
    45. public class Heatmap : MonoBehaviourSingleton<Heatmap>
    46. {
    47.     public enum Mode
    48.     {
    49.         Off,
    50.         Play,
    51.         Record
    52.     }
    53.  
    54.     public Mode m_mode;
    55.     public float m_playSpeed = 16;
    56.     public bool m_drawNodes = false;
    57.     public bool m_drawQuads = true;
    58.     public Color m_nodesColor = new Color(1.0f, 1.0f, 1.0f, 0.1f);
    59.     public Color m_quadsColor = new Color(1.0f, 1.0f, 1.0f, 0.1f);
    60.     public float m_quadsAlpha = 1.0f;
    61.  
    62.     public Mesh m_quadMesh;
    63.     public Material m_quadMaterial;
    64.  
    65.  
    66.     private bool m_active;
    67.  
    68.     private float m_timer;
    69.     private float m_sampleFrequency = 0.5f;
    70.  
    71.     private List<HeatmapFile> m_files;
    72.     private HeatmapFile m_current;
    73.  
    74.     private Dictionary<Vector3, HeatmapQuad> m_quadMap;
    75.  
    76.     public void OnUpdate()
    77.     {
    78.         if (m_mode == Mode.Play)
    79.         {
    80.             m_timer += Time.deltaTime * m_playSpeed;
    81.         }
    82.         else if (m_mode == Mode.Record)
    83.         {
    84.             m_timer += Time.deltaTime;
    85.             if (m_timer > m_sampleFrequency)
    86.             {
    87.                 m_timer -= m_sampleFrequency;
    88.  
    89.                 RecordQuad();
    90.                 RecordNode();
    91.             }
    92.         }
    93.     }
    94.  
    95.     void RecordQuad()
    96.     {
    97.         for (int i = 0; i < Game.PlayersCount; i++)
    98.         {
    99.             if (Game.instance.IsPlayerActive(i))
    100.             {
    101.                 Player p = Game.instance.GetPlayer(i);
    102.                 Vector3 pos = p.transform.position;
    103.  
    104.                 pos.x = Mathf.Floor(pos.x / 2) * 2;
    105.                 //pos.y = Mathf.Floor(pos.y); // / 0.1f) * 0.1f;
    106.                 pos.z = Mathf.Floor(pos.z / 2) * 2;
    107.  
    108.                 int layerMask = 1; // default bit 0
    109.                 RaycastHit hit;
    110.                 if (!Physics.Raycast(pos + Vector3.up, Vector3.down, out hit, 2.0f, layerMask))
    111.                     continue;
    112.  
    113.                 pos = hit.point;
    114.  
    115.                 pos.x = Mathf.Round(pos.x);
    116.                 pos.y = Mathf.Floor(pos.y / 0.1f) * 0.1f;
    117.                 pos.z = Mathf.Round(pos.z);
    118.                
    119.                 // Debug.Log(hit.collider.name + " at y=" + hit.point.y + " nor=" + hit.normal + " >> " + pos);
    120.                
    121.                 if (m_quadMap.ContainsKey(pos))
    122.                 {
    123.                     HeatmapQuad hq = m_quadMap[pos];
    124.                     hq.use++;
    125.                     m_quadMap[pos] = hq;
    126.                 }
    127.                 else
    128.                 {
    129.                     HeatmapQuad qv;
    130.                     qv.pos = pos;
    131.                     qv.nor = hit.normal;
    132.                     qv.use = 1;
    133.                     m_quadMap[pos] = qv;
    134.                 }
    135.             }
    136.         }
    137.     }
    138.  
    139.     void RecordNode()
    140.     {
    141.         HeatmapNode s;
    142.         s.pos = new Vector3[4];
    143.         int pi = 0;
    144.         for (int i = 0; i < Game.PlayersCount; i++)
    145.         {
    146.             if (Game.instance.IsPlayerActive(i))
    147.             {
    148.                 Player p = Game.instance.GetPlayer(i);
    149.                 s.pos[pi] = p.transform.position;
    150.                 pi++;
    151.             }
    152.         }
    153.         m_current.m_nodes.Add(s);
    154.     }
    155.  
    156.     void OnDrawGizmos()
    157.     {
    158.         if (m_active && m_mode == Mode.Play && m_files!=null)
    159.         {
    160.             if (m_drawNodes)
    161.                 DrawNodes();
    162.             if (m_drawQuads)
    163.                 DrawQuads();
    164.         }
    165.     }
    166.  
    167.     void DrawNodes()
    168.     {
    169.         int t = Mathf.RoundToInt(m_timer / m_sampleFrequency);
    170.         for (int i = 0; i < t; i++)
    171.         {
    172.             foreach (var f in m_files)
    173.             {
    174.                 if (i < f.m_nodes.Count && i > 0)
    175.                 {
    176.                     for (int p = 0; p < Game.PlayersCount; p++)
    177.                     {
    178.                         Vector3 b = f.m_nodes[i].pos[p] + Vector3.up * 0.25f;
    179.                         Vector3 a = f.m_nodes[i - 1].pos[p] + Vector3.up * 0.25f;
    180.                         if ((b - a).magnitude < 4)
    181.                             Debug.DrawLine(a, b, m_nodesColor);
    182.                     }
    183.                 }
    184.             }
    185.         }
    186.     }
    187.  
    188.     void DrawQuads()
    189.     {
    190.         m_quadMaterial.enableInstancing = true;
    191.         foreach (var f in m_files)
    192.         {
    193.             foreach (var q in f.m_quads)
    194.             {
    195.                 Color c = m_quadsColor;
    196.                 c.a *= (float)q.use / m_quadsAlpha;
    197.                 Quaternion rot = Quaternion.FromToRotation(Vector3.up, q.nor);
    198.  
    199.                 //Gizmos.color = c;
    200.                 //Gizmos.DrawMesh(m_quadMesh, q.pos + Vector3.up * 0.15f, rot, new Vector3(2.0f, 1.0f, 2.0f));
    201.  
    202.                 Matrix4x4 mat = Matrix4x4.TRS(q.pos + Vector3.up * 0.15f, rot, new Vector3(2.0f, 1.0f, 2.0f));
    203.                 m_quadMaterial.color = c;
    204.                 m_quadMaterial.SetPass(0);
    205.                 Graphics.DrawMeshNow(m_quadMesh, mat, 0);
    206.             }
    207.         }
    208.     }
    209.  
    210.     public void OnStartLevelSession()
    211.     {
    212.         m_active = true;
    213.         m_timer = 0.0f;
    214.  
    215.         if (m_mode == Mode.Play)
    216.         {
    217.             LoadAll(Game.instance.m_progression.m_level + 1);
    218.         }
    219.         else if (m_mode == Mode.Record)
    220.         {
    221.             m_current = new HeatmapFile();
    222.             m_quadMap = new Dictionary<Vector3, HeatmapQuad>();
    223.         }
    224.     }
    225.  
    226.     public void OnEndLevelSession()
    227.     {
    228.         m_active = false;
    229.  
    230.         if (m_mode == Mode.Record)
    231.         {
    232.             Save(Game.instance.m_progression.m_level + 1);
    233.         }
    234.  
    235.         // clean
    236.         m_files = null;
    237.         m_current = null;
    238.         m_quadMap = null;
    239.     }
    240.  
    241.     void Save(int level)
    242.     {
    243.         m_current.m_version = HeatmapFile.Version;
    244.         m_current.m_level = level;
    245.         m_current.m_playersCount = Game.instance.GetActivePlayersCount();
    246.         m_current.m_sampleFrequency = m_sampleFrequency;
    247.  
    248.         foreach (var q in m_quadMap)
    249.             m_current.m_quads.Add(q.Value);
    250.  
    251.         string date = System.DateTime.Now.ToString("yyMMdd_hhmmss");
    252.         string folder = "Heatmap/Level" + level.ToString("00");
    253.         Storage.Save<HeatmapFile>(ref m_current, (folder + "/heatmap_" + date + ".hmp"));
    254.  
    255.         Debug.Log("Saved heatmap.");
    256.     }
    257.  
    258.     void LoadAll(int level)
    259.     {
    260.         m_files = new List<HeatmapFile>();
    261.  
    262.         string[] files = Directory.GetFiles(Application.persistentDataPath + "/Heatmap/Level" + level.ToString("00"));
    263.  
    264.         foreach(var f in files)
    265.         {
    266.             string f2 = f.Replace('\\', '/');
    267.             f2 = f2.Replace(Application.persistentDataPath + "/", "");
    268.             HeatmapFile hf = new HeatmapFile();
    269.             if (Storage.Load<HeatmapFile>(ref hf, f2))
    270.             {
    271.                 if (hf.m_version == HeatmapFile.Version)
    272.                     m_files.Add(hf);
    273.                 else
    274.                     Debug.Log("Heatmap old version " + f);
    275.             }
    276.         }
    277.  
    278.         Debug.Log("Loaded " + m_files.Count + " heatmaps.");
    279.     }
    280.    
    281. }
    282.  
    And you will also need this storage utility class which I also use to store config settings.

    Code (CSharp):
    1. /////////////////////////////////////////////////////////////////////////////////////////////////
    2. // GF - Storage
    3. // Game data storage
    4. // Use:
    5. // - have the YourGameDataStructure class serializable ([System.Serializable]) and derived from StrageData
    6. // - implement override YourGameDataStructure.IsValid for versioning or validation
    7. // - call Storage.SaveSlot<YourGameDataStructure>(ref m_gameData, slot)
    8. // - call Storage.LoadSlot<YourGameDataStructure>(ref m_gameData, slot)
    9. // References:
    10. // http://wiki.unity3d.com/index.php?title=Saving_and_Loading_Data:_XmlSerializer
    11. /////////////////////////////////////////////////////////////////////////////////////////////////
    12. using UnityEngine;
    13. using System.Collections;
    14. using System.Collections.Generic;
    15. using System.Runtime.Serialization.Formatters.Binary;
    16. using System.IO;
    17. using System.Xml.Serialization;
    18.  
    19. [System.Serializable]
    20. public class StorageData
    21. {
    22.     public virtual bool IsValid() { return true; }
    23. }
    24.  
    25. public class Storage
    26. {
    27.     public static bool SaveSlot<T>(ref T data, int slot) where T : StorageData
    28.     {
    29.         return Save(ref data, "savedgame" + slot + ".sav");
    30.     }
    31.  
    32.     public static bool LoadSlot<T>(ref T data, int slot) where T : StorageData
    33.     {
    34.         return Load(ref data, "savedgame" + slot + ".sav");
    35.     }
    36.  
    37.     public static bool Save<T>(ref T data, string fileName) where T : StorageData
    38.     {
    39.         bool ok = false;
    40.         string pathName = Application.persistentDataPath + "/" + fileName;
    41.         System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(pathName));
    42.         FileStream file = File.Create(pathName);
    43.         if(file != null)
    44.         {
    45.             ok = true;
    46.             try
    47.             {
    48.                 // BinaryFormatter serializer = new BinaryFormatter();
    49.                 XmlSerializer serializer = new XmlSerializer(typeof(T));
    50.                 serializer.Serialize(file, data);
    51.                 // Debug.Log("save successful");
    52.             }
    53.             catch(System.Exception e)
    54.             {
    55.                 Debug.Log("SAVE to file '" + pathName + "' FAILED with EXCEPTION: " + e);
    56.                 ok = false;
    57.             }
    58.  
    59.             file.Close();
    60.         }
    61.  
    62.         return ok;
    63.     }  
    64.    
    65.     public static bool Load<T>(ref T data, string fileName) where T : StorageData /* , new() */
    66.     {
    67.         bool ok = false;
    68.         string pathName = Application.persistentDataPath + "/" + fileName;
    69.         if(File.Exists(pathName))
    70.         {
    71.             FileStream file = File.Open(pathName, FileMode.Open);
    72.             if(file != null)
    73.             {
    74.                 try
    75.                 {
    76.                     // BinaryFormatter serializer = new BinaryFormatter();
    77.                     XmlSerializer serializer = new XmlSerializer(typeof(T));
    78.                     data = (T)serializer.Deserialize(file);
    79.                     ok = (data != null) && data.IsValid();
    80.                 }
    81.                 catch(System.Exception e)
    82.                 {
    83.                     Debug.Log("LOAD from file '" + pathName + "' FAILED with EXCEPTION: " + e);
    84.                 }
    85.  
    86.                 file.Close();
    87.             }
    88.         }
    89.  
    90.         // @NOTE: optional re-construct
    91.         //if(!ok)
    92.         //    data = new T();
    93.         //    data = default(T);
    94.  
    95.         return ok;
    96.     }
    97.  
    98.     public static bool LoadFromText<T>(ref T data, string text) where T : StorageData /* , new() */
    99.      {
    100.         bool ok = false;
    101.         StringReader stream = new StringReader(text);
    102.         try
    103.         {
    104.              var serializer = new XmlSerializer(typeof(T));
    105.             data = (T)serializer.Deserialize(stream);
    106.             ok = (data != null) && data.IsValid();
    107.          }
    108.         catch(System.Exception e)
    109.         {
    110.             Debug.Log("LOAD from text '" + text + "' FAILED with EXCEPTION: " + e);
    111.         }
    112.  
    113.         // @NOTE: optional re-construct
    114.         //if(!ok)
    115.         //    data = new T();
    116.         //    data = default(T);
    117.  
    118.         return ok;
    119.      }
    120. }