Grandfather planted a turnip ... and tentacles came crawling out of it.UPD: Gentlemen, please accomplish your goal, what is wrong with the article and what can hurt someone. And then the spit flew into karma, but it is not clear what to fix.
Not for sake, but good deeds for writing ...Upd 2: forgot to warn ... In debug mode, UnityEditor displays hundreds of messages about invalid values ​​of quaternions to the console (apparently they are not normalized). Because of this, in debugging, it starts to slow down at some point. I haven’t yet understood whether my fault or bug is in the release of Unity. Hands will not reach the post in any way on the Unity3D forum.
Introduction
The article by Mr.
FrozmatGames prompted me to hurry up with this article, which deals with the implementation of a prototype for a game on genetics. I apologize in advance for the possible dampness of the text. Time is not enough for "combing". Send anger about ochepyatki and other textual flaws - I will try to fix everything.
In the article I will try to briefly describe the demo concept created with the simulation of organic growth. In the beginning I will describe the very idea on which the demo was created. Then I will describe what tools I began to use and on what I stopped in the end. Well, I’ll finish with a description of some points in implementation, a demonstration, and a list of things that I would like to implement further.
As always, the code is laid out on Github .
')
Game Idea and Prototype # 1
The main sources of inspiration for me are the worlds of the Strugatsky brothers (A., B. and J.;), as well as several articles on Habré (see list below).
The basic idea is a game world in which everything or almost everything is filled with life, is not built, is not going to, is not made, but is born, develops and grows. In general, a mixture of Terraria with Spore. This is if in brief about the idea of ​​the game. In more detail, while I will not tell - before that you need to bring your thoughts in order.
I decided to start with the implementation of the prototype, in which it will be possible to observe the birth and development of a certain abstract plant (the working title in the article title). The pictures below are sketches of the conceived prototype.

This sketch shows the initial stage of development of the object. At this stage, the core "grows" bones and strings connecting them. Bones and strings can be divided or added new from the core. As a part of a structure grows, its geometry and physical characteristics may change.

Division and inset are two operations that are well described in "
Neuro Evolution of Augmenting Topologies ". They can be encrypted with genes, which will allow me to use genetic algorithms in the future to evolve the world.
Physics2D.Net + WPF Render vs Unity3D 4.3.0
At first, it was decided to implement the prototype using Physics2D.Net. It seems to be easy to work with him (you can read a simple tutorial on a
wiki ):
PhysicsEngine engine = new PhysicsEngine(); engine.BroadPhase = new Physics2DDotNet.Detectors.SelectiveSweepDetector(); engine.Solver = new Physics2DDotNet.Solvers.SequentialImpulsesSolver();
Simulation is started and stopped through a timer object (
PhysicsTimer ):
PhysicsTimer timer = new PhysicsTimer(engine.Update, .01f); timer.IsRunning = true;
But the possibilities, of course, are not enough. In addition, the problem was the rendering of the scene. As the simplest solution, the rendering on WriteableBitmap was used (see the code
in the repository ). Later I refused this approach, because I suspected that there would be difficulties in replacing rendering with something more productive. It’s better to learn something more immediately, I decided. If it is interesting to someone to read about Physics2D.Net, then write in the comments - I will try to find time for it.
After the article about the release of Unity3D 4.3.0, which implements native 2D mode, it was decided to try to implement the demo on it. Projects on Unity3D differ from the OOP architecture that I am used to by their massive use of the component code organization approach. Because the code from the branch with Physics2D decided not to use, but to start the implementation from scratch.
As a basis for my concept, I took a 2D demo project (
this one here ). Created your sprites for the main parts of the game object and behavior scenarios. Also in the project were added to the implementation of various helperov.
I suspected that with the traditional OOP approach in Unity3D it would be difficult. Polymorphism in behavior scenarios does not work. But in general, there were not so many problems with the traditional PLO ... maybe for now. When working with a solution, I use not only Mono Develop, but also Visual Studio 2012, because C # editor is better and more stable. Faced Mono Develop's problems with auto-indents, dragging pieces of code, auto-closing parentheses and other trifles, which are quite a lot and they strain. But debugging through VS, I have not yet learned. By the way speaking, with debag and Mono Develop problems. For some reason, the namespaces with
enum are unloaded in the debug, which is why the types of game objects are not shown in the debug, they have to be duplicated in string variables, which I really don't like. Maybe I just do not know any tricks or in vain I endured enums to a separate namespace.
Implementation
What is implemented at the moment:
- Three types of prefabs are added: core (Core), node (Node) and bone (Bone)
- Nodes and pits are several species, the core is of the same type
- Implemented loading, decoding and replication of DNA, as well as genes in its composition. See the Seed, DnaProcessor, GeneProcessor scripts in the repository
- Decrypted genes are applied to the current part of the game object while satisfying the conditions of applicability.
- The following conditions of gene applicability have been implemented: the current position in the object element tree (± random tolerance); subtype of parent element; gene activation time.
- For genes of bones and nodes the addition of child elements is realized.
- Scripts that implement various growth stages communicate with each other by sending broadcast messages (GameObject.SendMessage) for components of the current GameObject.
- Implemented a simple animation when adding nodes.
- Implemented two single-helper to work with genes (GenesManager) and prefabs in resources (PrefabsManager).
Now a little more life cycle in the demo.
The root of the object to be grown is the core (Prefab Core). This object has a Seed script that loads from dna.txt resources.
using UnityEngine; using System.Collections; using Fukami.Helpers; public class Seed : MonoBehaviour { public string Dna; void Start(){ var dnaAsset = Resources.Load<TextAsset>("dna"); Dna = dnaAsset.text.Replace("\r\n","*"); } void OnSeedDnaStringRequested (Wrap<string> dna) { dna.Value = Dna; dna.ValueSource = "Seed"; } }
This file contains the complete DNA of the "turnip".
// node, [Node type], [Bone Type], [Base depth], [Depth tolerance], [Grow Time]
node 1,0,0,0,07A
node 1,0,0,0,080
node 2,0,0,0,100
bone 1,0,4,2,190
bone 1,0,4,2,15E
node 3,0,3,2,96
bone, 1,0,0,5, C8
bone, 1,0,2,1, FA
bone, 1,0,3,1,12C
node, 2,0,3,2, Fa
node 1,0,7,3,80
bone 1,0,8,2,128
Also, a DnaProcessor script is attached to the kernel, which at the start checks whether the current element has a parent.
void Start() { _age = 0.0f; var dna = new Wrap<string>(); if (gameObject.transform.parent != null) { var gen = new Wrap<int>(); gameObject.transform.parent.SendMessage("OnDnaGenRequested", gen); _generation = gen.IsSet ? gen.Value + 1 : 0; gameObject.transform.parent.SendMessage("OnDnaStringRequested", dna); } else { SendMessage("OnSeedDnaStringRequested", dna); } if (dna.IsSet) { DnaString = dna.Value; } }
If there is no parent, then DnaProcessor sends an “OnSeedDnaStringRequested” message requesting DNA from the Seed script. Otherwise, the message “OnDnaStringRequested” will be sent to receive DNA from the same DnaProcessor in the parent element. DnaProcessor also asks the parent for the current position in the tree (depth, generation) to transfer this value to the genes.
The life cycle of a gene is controlled by the “GeneProcessor” script. This script is very simple, it counts the activation time and then sends the message “OnApplyGene”.
void Update () { if (Gene != null) { _age += Time.deltaTime; } if (_age >= Gene.GrowTime) { enabled = false; SendMessage("OnApplyGene", Gene); return; } }
The message will be accepted either by “BonesApplicant” (if the current element is a node), or “NodesApplicant” in the case of a bone. Both of these scripts in the “OnApplyGene” message handler perform the check of conditions and rules of gene applicability.
bool GetIsApplicable(GeneData gene) { // Part Type check if (gene.GeneType != "node" && gene.GeneType != "stats") { return false; } // Subtype check if (gene.ApplicantSubtype != 0 && gene.ApplicantSubtype != Subtype) { return false; } // Generation check var gen = Generation; if (gen != gene.BaseDepth) { var actDistance = Mathf.Abs(gen - gene.BaseDepth); if (UnityEngine.Random.Range(0f, 1f) < actDistance / (gene.DepthTolerance + 1)) { return false; } } return true; }
void OnApplyGene(GeneData gene) { if (!GetIsApplicable(gene)) return; switch (gene.GeneType.ToLower()) { case "bone": AddBone(gene); break; case "joint": AddJoint(gene); break; case "stats": AddStats(gene); break; default: break; } }
If everything is in order and there are free slots (
ChildSlot list ), then a child element is created for the gene.
private void AddBone(GeneData gene) { var slot = Slots.FirstOrDefault(s => !s.IsOccupied); if (slot == null) { return; } slot.IsOccupied = true; var bonePrefab = PrefabsManager.Instance.LoadPrefab(string.Format("{0}{1}", gene.GeneType, gene.Subtype)); var newBody = (GameObject)Instantiate(bonePrefab, gameObject.transform.position, gameObject.transform.rotation * Quaternion.AngleAxis(slot.Angle, Vector3.forward)); newBody.transform.parent = gameObject.transform; newBody.SetActive(true); //var slide = newBody.AddComponent<SliderJoint2D>(); var slide = newBody.AddComponent<HingeJoint2D>(); slide.connectedBody = gameObject.GetComponent<Rigidbody2D>(); slide.connectedAnchor = new Vector2(slot.X, slot.Y); //slide.limits = new JointTranslationLimits2D { min = -0.1f, max = 0.1f }; slide.limits = new JointAngleLimits2D { min = -1.0f, max = 1.0f }; slide.useLimits = true; gameObject.SendMessage("OnChildAdded", newBody, SendMessageOptions.DontRequireReceiver); } bool GetIsApplicable(GeneData gene) { // Part Type check if (gene.GeneType != "bone" && gene.GeneType != "stats" && gene.GeneType != "joint") { return false; } // Subtype check if (gene.ApplicantSubtype != 0 && gene.ApplicantSubtype != Subtype) { return false; } // Generation check var gen = Generation; if (gen != gene.BaseDepth) { var actDistance = Mathf.Abs(gen - gene.BaseDepth); if (UnityEngine.Random.Range(0f, 1f) < actDistance / (gene.DepthTolerance + 1)) { return false; } } return true; }
private void AddNode(GeneData gene) { var slot = Slots.FirstOrDefault(s => !s.IsOccupied); if (slot == null) return; slot.IsOccupied = true; var nodePrefab = PrefabsManager.Instance.LoadPrefab(string.Format("{0}{1}", gene.GeneType, gene.Subtype)); var newBody = (GameObject)Instantiate(nodePrefab, gameObject.transform.position, Quaternion.FromToRotation(Vector3.right, Vector3.up) * //Quaternion.AngleAxis(Random.Range(-slot.Angle, slot.Angle), Vector3.forward)); Quaternion.AngleAxis(slot.Angle, Vector3.forward)); newBody.transform.parent = gameObject.transform; newBody.SetActive(true); var hinge = newBody.AddComponent<HingeJoint2D>(); hinge.connectedBody = gameObject.GetComponent<Rigidbody2D>(); hinge.connectedAnchor = new Vector2(slot.X, slot.Y); hinge.limits = new JointAngleLimits2D { min = -1.0f, max = 1.0f }; hinge.useLimits = true; newBody.AddComponent<HingeSmoothPos>(); gameObject.SendMessage("OnChildAdded", newBody, SendMessageOptions.DontRequireReceiver); }
The child element is instantiated via the PrefabsManager, which selects the prefab by the string type (
bone or
node ) and the postfix subtype prefab. It turns out, for example, "node1", "node2", etc. The DieIfNoChildren script is additionally attached to the nodes, which simply destroys the node if a child element is not attached to it after a certain time.
About many moments there is something to tell separately. Ask questions - I will try to answer in detail in the comments or even write a separate post. For now, here's a better demo animation. Compiled EXE can be
downloaded in the repository .
Someone tell me pliz convenient and free way to capture video from the screen to send to YouTubeWhat's next
Next, I want to play around with DNA in order to get a less beautiful simulation. You also need to implement the processing of genes of joints (Joint, an element connecting two nodes), as well as a characteristics modifier (Stats, mass, and other physical coefficients, hit points, etc.). The next thing that is planned to be implemented is additional elements of bones and nodes (protective cover, some attacking elements, etc.), as well as the ability to destroy bones or replace them with portals. In the end, I want to get something like a somosbranny house “a-la Terraria” or something like a bunker.
! THANKS FOR ATTENTION !