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. Dismiss Notice

Game programming for experienced programmers - Design Patterns

Discussion in 'Scripting' started by scifresh, Dec 14, 2016.

  1. scifresh

    scifresh

    Joined:
    Oct 8, 2013
    Posts:
    23
    Hi guys, I am an experienced software developer with over 10 years of experience in business solutions. I have developed and designed complicated systems for automative industry and business intelligence. I ve changed my job and now I have extra 2 hours of free time every day. I thought it would be time to make my dream come true and I dove in to game programming.

    After a couple of research on the internet I ve come to the conclusion that unity tutorials are all for beginners and include really bad programming or at all "programming". I cant seem to wrap around my head how certain things should be implemented in Unity. I want to do things professionally (scalability, maintainability, testability are the things i mean professionally).

    My first question would be, how do you design your code in unity ? What are the "must have" components ? How do you make Splash screen, intro screen, main menu, loading screen without stopping the music, and the actual game screen. what i ve found on the internet was awfull. so i thought maybe some of you experienced game developers can show me a way to do all of this professionally. any code example or tutorial would be good.

    My second question is more like an example. Let me explain that:
    • I have a scene where I have a top-down camera, a terrain with static objects and navigation area.
    • a player with navagent and animations
    • a click to move script attached to the player (this is where it gets dirty)
    this all works fine but as I add things to the project, the code gets dirty. As unity does not have a starting point (main method) i am having problems to understand how should i approach this problem.

    Lets say I want to implement a logic for highlighting objects and characters, targeting besides click to move mechanism. Where should all of this code go ? Different game objects ? if yes, wouldnt it get processed alongside click to move ? I have all my current code in Update in click to move script. a raycast and a destination being sent to navagent. As I am trying to do an "RPG", there are more checks and controls for mouse and keyboard input. e.g. abilities, gui, items, stats and so on. I want to design the code once and focuse on game mechanics and features.

    I have thought about finite state machine, but I couldnt really implement it or i was not happy with the result. How would I approach this problem ? See it as a common problem that could apply to all systems. code examples would be amazing.

    @eisenpony i have read your posts and you seem to know your "sh!t" :) would you mind taking a look at my questions please ? thanks :)
     
  2. Brathnann

    Brathnann

    Joined:
    Aug 12, 2014
    Posts:
    7,144
    My best suggestion is find tutorials on youtube where someone has gone through several videos to produce some sort of game that may fit what you plan to do. Since you have experience, you should be able to apply your own programming skills to what is presented and if you think something can be done better, improve on it. But this should help you learn at least the Unity side of things.

    Unfortunately, you have a large amount of questions and there isn't just one answer. The good news is you already have the knowledge and should be able to understand anything thrown at you on the coding side.

    Brackeys, speedtutor, etc may be a starting point, but I'm guessing on the coding side you'll find them way to easy. (I haven't looked into their videos yet, I just know of them).

    You just may have better luck working at it, then coming back if you run into a question and asking for help on that. Then once you get that solved, ask another question.
     
    TaleOf4Gamers likes this.
  3. eisenpony

    eisenpony

    Joined:
    May 8, 2015
    Posts:
    971
    That's very kind but not really true .. If you read my posts you'll notice a common theme: I give general programming advice, advice about patterns, and advice about specific .net components. What I don't do is talk about Unity in any meaningful way. That's because, despite my best intentions, I've yet to step foot in it!

    That aside, I will do my best to help you, not by answering your specific questions (because I think I would lead you astray), but by sharing some of what I've learned about this forum.

    First of all, don't feel badly if you don't get the response you were looking for. There are a lot of different people on the forum at all different times. You might find asking exactly the same question at a different time of day will get you completely different answers. Secondly, if you're not happy with the results of a thread, just start a new one from a different angle. Ask your question in a different way, or simplify the problem you are trying to solve. Some people get annoyed if you start too many threads asking exactly the same thing but if you put a little thought into changing your questions and wait a bit of time before reposting, you'll get a lot of unique perspectives. This, I think, is the most valuable part of the forum so I wouldn't feel badly about asking, essentially, the same question a few times.

    Finally, there are some really bright people patrolling here on a regular basis, but due to the volume of questions they are often hesitant to get into a topic labeled "advanced programming" or "design patterns". For some reason, these threads tend to attract, and then annoy, a very strongly opinionated group of people. I've found the, in my opinion, really useful members typically just avoid them except to root out blatantly incorrect statements. Furthermore, being an abstract idea, patterns lead to a lot of hand waving and interpretive dances. I don't think it's impossible to have a meaningful conversation about patterns here, but you will find much more useful information when you can ask extremely specific questions.

    This brings me to your second question. This is a pretty good example of a specific problem. I'd suggest you think about how you could describe just this question and start a new thread about it. For instance, title it something like: How do you capture mouse clicks on terrain vs different types of objects?

    I don't spend a lot of time here anymore as I'm focusing on some other projects but if you're actually interested in what I have to say, I will point out one discussion I had almost exactly a year ago. It is probably my personal favorite contribution because it touches on something I see a lot of misunderstanding about. It also helped me to solidify my own understanding, which is one of the main reasons I started posting here to begin with.. Sadly, as with all forum posts, it has been long buried and forgotten.

    All that said, I wish you success as you chase your dreams. The truth is, nothing will teach you like trying (and occasionally failing) for yourself. Good luck!
     
    Last edited: Dec 14, 2016
    Ryiah and scifresh like this.
  4. Dameon_

    Dameon_

    Joined:
    Apr 11, 2014
    Posts:
    542
    Absolutely program for Unity like you would program for anything else. It's true that a lot of the example code out there (including Unity's own) is bad code and ignores basic principles like encapsulation. Unfortunately, a lot of the examples for Unity are written by people who taught themselves off of bad examples, and that whole problem just gets carried on down the line.

    Question one is actually at least three questions, and hours of explanations...

    Question two, don't just attach a "click to move" script to the player. I typically implement an InputManager class to intercept all input events and route them appropriately, which I do by having other objects register for state changes on certain virtual buttons/axes. This gives you a starting point for everything that implements input.

    Develop classes that do specific things for your game. Classes that handle GUI functionality, etc. Just like you would for any other application you're developing...

    Basically, if you have programming experience, just use that, and ignore all the horrible examples.
     
    scifresh and eisenpony like this.
  5. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,380
    If you're asking about Design Patterns, and come from 10+ years of business solutions... I assume you mean heavy OOP.

    So I'll come at you from that angle.

    First and foremost, Unity breaks OOP massively.

    It really likes its globals.

    So usually my first thing is to create object identity for some things.

    Example... its Random class is a static class. There is only one of them, and it can't be referenced. You can use System.Random instead as well. Personally, because I like to make my code work with both I wrote things like IRandom to give unity's class some identity.
    IRandom:
    https://github.com/lordofduct/spacepuppy-unity-framework/blob/master/SpacepuppyBase/IRandom.cs
    RandomUtil:
    https://github.com/lordofduct/space...lob/master/SpacepuppyBase/Utils/RandomUtil.cs

    Another is their 'Time' class... also static with no identity. If you want some code to run scaled vs unscaled, how can you wrap this into an object? So I created my ITimeSupplier interface.
    ITimeSupplier:
    https://github.com/lordofduct/spacepuppy-unity-framework/blob/master/SpacepuppyBase/ITimeSupplier.cs
    Various Time implementations:
    https://github.com/lordofduct/spacepuppy-unity-framework/blob/master/SpacepuppyBase/SPTime.cs

    Note with it now I can create various time identities. Not just scaled and unscaled. And I can stack time as well.

    I can now use them with my animation and tween scripts so that I can tell them which time scale to work on. For example if I have a player given a 'slow-mo' power up, I create a 'slow-mo' timesupplier and its animation/movement scripts use that instead of the global 'Time' class.

    Yeah, this was a problem I had when I first got into using 3rd party engines. Most of them don't have an entry point!

    You kinda have to just not think in that mindset.

    I like to think of it like multi-tasking (not multi-threading, because it's all on one thread). Each 'Component' (monobehaviour) is its own entry point into a multi-tasked system. Your mini-entry point is the 'Awake'/'Enable'/'Start' methods (oh dear god, mess around with these, you NEED to learn their mannerisms and order to one another... it is soooo weird).

    From there you can have several classes that are not components that can branch out from there if you need.

    And of course if you need that one script that's the first thing ever. I usually create a 'GameStartup' script, I order as first (-32000) in the execution order, and put it in my 'GameLoadScene' which is the first scene loaded when the game starts.

    I also create a 'Debug' script that uses the compiler symbol 'UNITY_EDITOR' to call on GameStartup behaviour for scenes that are loaded at editor time for debugging rather than in the proper order once built. It is of course timed to be just after the 'GameStartup'.

    That's a lot of stuff there. I'd first start organizing them into their related groups.

    I like to go with an 'entity' approach. Stuff that relates to the player, that's my 'Player' entity. Stuff related to UI, that's my 'UI' entity. So on, so forth.

    OK, I'll give you an example, I'm putting it behind spoiler tags cause there's a lot there to read.

    Over this last weekend my buddy and I did 'ludumdare 37' (a weekend long gamejam where you can 72 hours to make a game). We made a survival horror clone "Murder Mansion":
    http://ludumdare.com/compo/ludum-dare-37/?action=preview&uid=32909

    Nothing to big or special, can be beat in all of 5 minutes.

    Anyways, I'll show you my design for it (note I work with an artist, the scene is a bit messy).

    forum_player.png
    Here is our entity for the player.

    Note I give it a root gameobject, then inside several GameObjects to perform various jobs.

    PlayerF - its base

    Audio - artist put this here, it has audiosources that are played on events, like the hit soundfx

    Aspect - this has a single 'VisualAspect' component which can be found by 'VisualSensor' scripts I use

    Events (and children) - artist again, these are complicated event chains that he creates with a visual programming tool I created him that we call our 'T&I' system. It's like UnityEvent, but different, as well as predates it... I probably would have extended that if it existed, but alas... timing. See the classes for it.

    Hitbox - a trigger collider for determining hit area... this could be placed wherever, we just put it here.

    Rig - this is the actual model, all of its bones are underneath.

    AnimData - this is where my Animator classes go, it references the model of course.

    Motor - this is all my player logic. Movement script, so on so forth.

    DeathCam & AreaLight & GameObject - more artist crap slapped in willy nilly... he gets sloppy

    forum_player_base.png
    Here we have its base. On it we have a a couple scripts.
    IEntity - a script that represents the entity. This always gets placed on the base. I have a way to access any and all entities in the scene easily, see the implementation here:
    https://github.com/lordofduct/spacepuppy-unity-framework/blob/master/SpacepuppyBase/SPEntity.cs

    MultiTag - just lets multiple tags on an GameObject
    CharacterController - for movement
    MovementController - this wraps around CharacterController (or Rigidbody) to give a generalized interface no matter if you use CharacterController or Rigidbody for movement.
    HealthMeter - eh, I put it here... not sure why

    forum_player_AnimData.png
    Here is the AnimData.

    Basically it's just the PlayerAnimator script:
    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections.Generic;
    4.  
    5. using com.spacepuppy;
    6. using com.spacepuppy.Anim;
    7. using com.spacepuppy.Collections;
    8. using com.spacepuppy.Scenario;
    9. using com.spacepuppy.Utils;
    10.  
    11.  
    12. namespace com.mansion.Entities.Actors.Player
    13. {
    14.  
    15.     public class PlayerAnimator : SPComponent, IEntityAwakeHandler
    16.     {
    17.  
    18.         #region Anim Constants
    19.  
    20.         public const int ANIMLAYER_DEFAULT = 0;
    21.         public const int ANIMLAYER_WALK = 5;
    22.         public const int ANIMLAYER_ACTION = 10;
    23.         public const int ANIMLAYER_DEATH = 100;
    24.  
    25.         #endregion
    26.  
    27.  
    28.         #region Fields
    29.  
    30.         [SerializeField()]
    31.         [DefaultFromSelf(UseEntity = true)]
    32.         private SPAnimationController _controller;
    33.  
    34.         [SerializeField()]
    35.         private DefaultMovementAnimationInfo _defaultMovementAnimations;
    36.  
    37.         [SerializeField()]
    38.         private UndeadMovementAnimationInfo _undeadAnimations;
    39.  
    40.         [SerializeField()]
    41.         private RangeWeaponAnimationInfo _rangeWeaponAnimations;
    42.  
    43.         [SerializeField()]
    44.         private StruckAnimationInfo _struckAnimations;
    45.  
    46.         [Header("Event Triggers")]
    47.         [SerializeField()]
    48.         private Trigger _onFireWeapon;
    49.         [SerializeField()]
    50.         private Trigger _onReloadWeapon;
    51.  
    52.         [SerializeField()]
    53.         private Trigger _onStruck;
    54.         [SerializeField()]
    55.         private Trigger _onDeath;
    56.         [SerializeField()]
    57.         private Trigger _onRebirth;
    58.  
    59.         [SerializeField()]
    60.         private Trigger _onUndeadAttack;
    61.  
    62.  
    63.         [System.NonSerialized()]
    64.         private IEntity _entity;
    65.  
    66.         #endregion
    67.  
    68.         #region CONSTRUCTOR
    69.  
    70.         protected override void Awake()
    71.         {
    72.             base.Awake();
    73.  
    74.             var entity = SPEntity.Pool.GetFromSource<IEntity>(this);
    75.             if (entity != null && entity.IsAwake) this.OnAwake(entity);
    76.         }
    77.  
    78.         void IEntityAwakeHandler.OnEntityAwake(SPEntity entity)
    79.         {
    80.             this.OnAwake(entity as IEntity);
    81.         }
    82.  
    83.         private void OnAwake(IEntity entity)
    84.         {
    85.             _entity = entity;
    86.             this.InitAnims();
    87.         }
    88.  
    89.         #endregion
    90.  
    91.         #region Properties
    92.  
    93.         public IEntity Entity
    94.         {
    95.             get { return _entity; }
    96.         }
    97.  
    98.         public SPAnimationController Controller
    99.         {
    100.             get { return _controller; }
    101.         }
    102.  
    103.         public DefaultMovementAnimationInfo DefaultMovementAnimations
    104.         {
    105.             get { return _defaultMovementAnimations; }
    106.         }
    107.  
    108.         public UndeadMovementAnimationInfo UndeadAnimations
    109.         {
    110.             get { return _undeadAnimations; }
    111.         }
    112.  
    113.         public RangeWeaponAnimationInfo RangeWeaponAnimations
    114.         {
    115.             get { return _rangeWeaponAnimations; }
    116.         }
    117.  
    118.         public StruckAnimationInfo StruckAnimations
    119.         {
    120.             get { return _struckAnimations; }
    121.         }
    122.  
    123.         #endregion
    124.  
    125.         #region Methods
    126.  
    127.         private void InitAnims()
    128.         {
    129.             _defaultMovementAnimations.Init(this);
    130.             _undeadAnimations.Init(this);
    131.             _rangeWeaponAnimations.Init(this);
    132.             _struckAnimations.Init(this);
    133.         }
    134.  
    135.         #endregion
    136.  
    137.         #region Special Types
    138.  
    139.         [System.Serializable()]
    140.         public class DefaultMovementAnimationInfo
    141.         {
    142.  
    143.             public const string ANIM_MOVE_IDLE = "Idle";
    144.             public const string ANIM_MOVE_IDLEACTION = "IdleAction";
    145.             public const string ANIM_MOVE_WALK = "Walk";
    146.             public const string ANIM_MOVE_STRAFE_F = "StrafeForward";
    147.             public const string ANIM_MOVE_STRAFE_B = "StrafeBackward";
    148.             public const string ANIM_MOVE_STRAFE_L = "StrafeLeft";
    149.             public const string ANIM_MOVE_STRAFE_R = "StrafeRight";
    150.             public const string ANIM_MOVE_IDLE_CRIT = "Idle_Crit";
    151.             public const string ANIM_MOVE_IDLEACTION_CRIT = "IdleAction_Crit";
    152.             public const string ANIM_MOVE_WALK_CRIT = "Walk_Crit";
    153.  
    154.             public enum DefaultMovementState
    155.             {
    156.                 IdleAction = -1,
    157.                 Idle = 0,
    158.                 Walk = 1
    159.             }
    160.  
    161.             #region Fields
    162.  
    163.             [SerializeField()]
    164.             private float _moveAnimSpeedRatio = 1;
    165.             [SerializeField()]
    166.             private float _critMoveAnimSpeedRatio = 1;
    167.  
    168.             [SerializeField()]
    169.             [SPAnimClipCollection.Config(DefaultLayer = PlayerAnimator.ANIMLAYER_DEFAULT)]
    170.             [SPAnimClipCollection.StaticCollection(ANIM_MOVE_IDLE,
    171.                                                    ANIM_MOVE_IDLEACTION,
    172.                                                    ANIM_MOVE_WALK,
    173.                                                    ANIM_MOVE_STRAFE_F,
    174.                                                    ANIM_MOVE_STRAFE_B,
    175.                                                    ANIM_MOVE_STRAFE_L,
    176.                                                    ANIM_MOVE_STRAFE_R,
    177.                                                    ANIM_MOVE_IDLE_CRIT,
    178.                                                    ANIM_MOVE_IDLEACTION_CRIT,
    179.                                                    ANIM_MOVE_WALK_CRIT)]
    180.             private SPAnimClipCollection _animations;
    181.  
    182.             [System.NonSerialized()]
    183.             private PlayerAnimator _owner;
    184.  
    185.             [System.NonSerialized()]
    186.             private DefaultMovementState _state;
    187.             [System.NonSerialized()]
    188.             private SPAnimClip _currentWalk;
    189.  
    190.             #endregion
    191.  
    192.             #region CONSTRUCTOR
    193.  
    194.             #endregion
    195.  
    196.             #region Properties
    197.  
    198.             public DefaultMovementState State { get { return _state; } }
    199.  
    200.             #endregion
    201.  
    202.             #region Methods
    203.  
    204.             internal void Init(PlayerAnimator owner)
    205.             {
    206.                 _owner = owner;
    207.                 _animations.Init(_owner.Controller, "*move");
    208.                 this.PlayIdle();
    209.             }
    210.  
    211.             public SPAnim Play(string id, QueueMode queuMode = QueueMode.PlayNow, PlayMode playMode = PlayMode.StopSameLayer)
    212.             {
    213.                 return _animations.Play(id, queuMode, playMode);
    214.             }
    215.  
    216.             public void PlayIdle()
    217.             {
    218.                 if (_currentWalk != null)
    219.                 {
    220.                     _currentWalk.Stop();
    221.                     _currentWalk = null;
    222.                 }
    223.  
    224.                 SPAnimClip clip;
    225.                 if (_owner._entity.HealthMeter.Status == HealthMeter.StatusType.Critical)
    226.                     clip = _animations[ANIM_MOVE_IDLE_CRIT];
    227.                 else
    228.                     clip = _animations[ANIM_MOVE_IDLE];
    229.                 if (clip == null) return;
    230.  
    231.                 clip.Layer = ANIMLAYER_DEFAULT;
    232.                 clip.CrossFadeDirectly(Constants.DEFAULT_CROSSFADE_DUR);
    233.                 _state = DefaultMovementState.Idle;
    234.             }
    235.  
    236.             public void PlayIdleAction()
    237.             {
    238.                 SPAnimClip clip;
    239.                 if (_owner._entity.HealthMeter.Status == HealthMeter.StatusType.Critical)
    240.                     clip = _animations[ANIM_MOVE_IDLEACTION_CRIT];
    241.                 else
    242.                     clip = _animations[ANIM_MOVE_IDLEACTION];
    243.                 if (clip == null) return;
    244.  
    245.                 _state = DefaultMovementState.IdleAction;
    246.                 clip.WrapMode = WrapMode.Once;
    247.                 clip.Layer = ANIMLAYER_DEFAULT + 1;
    248.                 clip.CrossFade(Constants.DEFAULT_CROSSFADE_DUR).Schedule((a) =>
    249.                 {
    250.                     if (_state == DefaultMovementState.IdleAction)
    251.                     {
    252.                         _state = DefaultMovementState.Idle;
    253.                         this.PlayIdle();
    254.                     }
    255.                 });
    256.             }
    257.  
    258.             public void PlayWalk(float spd = 1f)
    259.             {
    260.                 SPAnimClip clip;
    261.                 float ratio;
    262.                 if (_owner._entity.HealthMeter.Status == HealthMeter.StatusType.Critical)
    263.                 {
    264.                     clip = _animations[ANIM_MOVE_WALK_CRIT];
    265.                     ratio = _critMoveAnimSpeedRatio;
    266.                 }
    267.                 else
    268.                 {
    269.                     clip = _animations[ANIM_MOVE_WALK];
    270.                     ratio = _moveAnimSpeedRatio;
    271.                 }
    272.                 if (clip == null) return;
    273.  
    274.                 clip.WrapMode = WrapMode.Loop;
    275.                 clip.Speed = spd * ratio;
    276.                 clip.Layer = ANIMLAYER_WALK;
    277.                 clip.CrossFadeDirectly(Constants.DEFAULT_CROSSFADE_DUR);
    278.                 _currentWalk = clip;
    279.                 _state = DefaultMovementState.Walk;
    280.             }
    281.  
    282.             public void PlayStrafe(float offAngle, float spd = 1f)
    283.             {
    284.                 SPAnimClip clip;
    285.                 if (Mathf.Abs(offAngle) >= 135f)
    286.                     clip = _animations[ANIM_MOVE_STRAFE_B];
    287.                 else if (offAngle < -45f)
    288.                     clip = _animations[ANIM_MOVE_STRAFE_L];
    289.                 else if (offAngle > 45f)
    290.                     clip = _animations[ANIM_MOVE_STRAFE_R];
    291.                 else
    292.                     clip = _animations[ANIM_MOVE_STRAFE_F];
    293.  
    294.                 if (clip == null) return;
    295.  
    296.                 clip.WrapMode = WrapMode.Loop;
    297.                 clip.Speed = spd * _moveAnimSpeedRatio;
    298.                 clip.Layer = ANIMLAYER_WALK;
    299.                 clip.CrossFadeDirectly(Constants.DEFAULT_CROSSFADE_DUR);
    300.                 _currentWalk = clip;
    301.                 _state = DefaultMovementState.Walk;
    302.             }
    303.        
    304.             #endregion
    305.  
    306.         }
    307.  
    308.         [System.Serializable()]
    309.         public class UndeadMovementAnimationInfo
    310.         {
    311.  
    312.             public const string ANIM_UNDEAD_IDLE = "Idle";
    313.             public const string ANIM_UNDEAD_IDLEACTION = "IdleAction";
    314.             public const string ANIM_UNDEAD_WALK = "Walk";
    315.             public const string ANIM_UNDEAD_MELEE = "Melee";
    316.  
    317.             public enum DefaultMovementState
    318.             {
    319.                 IdleAction = -1,
    320.                 Idle = 0,
    321.                 Walk = 1
    322.             }
    323.  
    324.             #region Fields
    325.  
    326.             [SerializeField()]
    327.             private float _moveAnimSpeedRatio = 1;
    328.  
    329.             [SerializeField()]
    330.             [SPAnimClipCollection.Config(DefaultLayer = PlayerAnimator.ANIMLAYER_DEFAULT)]
    331.             [SPAnimClipCollection.StaticCollection(ANIM_UNDEAD_IDLE,
    332.                                                    ANIM_UNDEAD_IDLEACTION,
    333.                                                    ANIM_UNDEAD_WALK,
    334.                                                    ANIM_UNDEAD_MELEE)]
    335.             private SPAnimClipCollection _animations;
    336.  
    337.             [System.NonSerialized()]
    338.             private PlayerAnimator _owner;
    339.  
    340.             [System.NonSerialized()]
    341.             private DefaultMovementState _state;
    342.             [System.NonSerialized()]
    343.             private SPAnimClip _currentWalk;
    344.  
    345.             #endregion
    346.  
    347.             #region CONSTRUCTOR
    348.  
    349.             #endregion
    350.  
    351.             #region Properties
    352.  
    353.             public DefaultMovementState State { get { return _state; } }
    354.  
    355.             #endregion
    356.  
    357.             #region Methods
    358.  
    359.             internal void Init(PlayerAnimator owner)
    360.             {
    361.                 _owner = owner;
    362.                 _animations.Init(_owner.Controller, "*undead");
    363.                 this.PlayIdle();
    364.             }
    365.  
    366.             public SPAnim Play(string id, QueueMode queuMode = QueueMode.PlayNow, PlayMode playMode = PlayMode.StopSameLayer)
    367.             {
    368.                 return _animations.Play(id, queuMode, playMode);
    369.             }
    370.  
    371.             public void PlayIdle()
    372.             {
    373.                 if (_currentWalk != null)
    374.                 {
    375.                     _currentWalk.Stop();
    376.                     _currentWalk = null;
    377.                 }
    378.  
    379.  
    380.                 var clip = _animations[ANIM_UNDEAD_IDLE];
    381.                 if (clip == null) return;
    382.  
    383.                 clip.Layer = ANIMLAYER_DEFAULT;
    384.                 clip.CrossFadeDirectly(Constants.DEFAULT_CROSSFADE_DUR);
    385.                 _state = DefaultMovementState.Idle;
    386.             }
    387.  
    388.             public void PlayIdleAction()
    389.             {
    390.                 var clip = _animations[ANIM_UNDEAD_IDLEACTION];
    391.                 if (clip == null) return;
    392.  
    393.                 _state = DefaultMovementState.IdleAction;
    394.                 clip.WrapMode = WrapMode.Once;
    395.                 clip.Layer = ANIMLAYER_DEFAULT + 1;
    396.                 clip.CrossFade(Constants.DEFAULT_CROSSFADE_DUR).Schedule((a) =>
    397.                 {
    398.                     if (_state == DefaultMovementState.IdleAction)
    399.                     {
    400.                         _state = DefaultMovementState.Idle;
    401.                         this.PlayIdle();
    402.                     }
    403.                 });
    404.             }
    405.  
    406.             public void PlayWalk(float spd = 1f)
    407.             {
    408.                 var clip = _animations[ANIM_UNDEAD_WALK];
    409.                 if (clip == null) return;
    410.  
    411.                 clip.WrapMode = WrapMode.Loop;
    412.                 clip.Speed = spd * _moveAnimSpeedRatio;
    413.                 clip.Layer = ANIMLAYER_WALK;
    414.                 clip.CrossFadeDirectly(Constants.DEFAULT_CROSSFADE_DUR);
    415.                 _currentWalk = clip;
    416.                 _state = DefaultMovementState.Walk;
    417.             }
    418.  
    419.             public IRadicalWaitHandle PlayMelee()
    420.             {
    421.                 var clip = _animations[ANIM_UNDEAD_MELEE];
    422.                 if (clip == null) return RadicalWaitHandle.Null;
    423.  
    424.                 _owner._onUndeadAttack.ActivateTrigger();
    425.  
    426.                 clip.WrapMode = WrapMode.Once;
    427.                 clip.Layer = ANIMLAYER_ACTION;
    428.                 var anim = clip.CrossFade(Constants.DEFAULT_CROSSFADE_DUR, QueueMode.PlayNow);
    429.                 return anim;
    430.             }
    431.  
    432.             #endregion
    433.  
    434.         }
    435.  
    436.         [System.Serializable()]
    437.         public class RangeWeaponAnimationInfo
    438.         {
    439.  
    440.             public const string ANIM_RANGE_DRAW = "Draw";
    441.             public const string ANIM_RANGE_AIM = "Aim";
    442.             public const string ANIM_RANGE_HOLSTER = "Holster";
    443.             public const string ANIM_RANGE_FIRE = "Fire";
    444.             public const string ANIM_RANGE_RELOAD = "Reload";
    445.  
    446.             public enum RangeWeaponState
    447.             {
    448.                 Holstering = -1,
    449.                 None = 0,
    450.                 Drawing = 1,
    451.                 Aim = 2,
    452.                 Firing = 3,
    453.                 Reload = 4
    454.             }
    455.  
    456.             #region Fields
    457.        
    458.             [SerializeField()]
    459.             [SPAnimClipCollection.Config(DefaultLayer = PlayerAnimator.ANIMLAYER_ACTION)]
    460.             [SPAnimClipCollection.StaticCollection(ANIM_RANGE_DRAW,
    461.                                                     ANIM_RANGE_AIM,
    462.                                                     ANIM_RANGE_HOLSTER,
    463.                                                     ANIM_RANGE_FIRE,
    464.                                                     ANIM_RANGE_RELOAD)]
    465.             private SPAnimClipCollection _animations;
    466.        
    467.             [System.NonSerialized()]
    468.             private PlayerAnimator _owner;
    469.  
    470.             [System.NonSerialized()]
    471.             private RangeWeaponState _state;
    472.  
    473.             [System.NonSerialized()]
    474.             private SPAnim _drawAnim;
    475.  
    476.             #endregion
    477.  
    478.             #region CONSTRUCTOR
    479.  
    480.             #endregion
    481.  
    482.             #region Properties
    483.  
    484.             public RangeWeaponState State
    485.             {
    486.                 get { return _state; }
    487.             }
    488.  
    489.             #endregion
    490.  
    491.             #region Methods
    492.  
    493.             internal void Init(PlayerAnimator owner)
    494.             {
    495.                 _owner = owner;
    496.                 _animations.Init(_owner.Controller, "*range");
    497.             }
    498.  
    499.             public SPAnim Play(string id, QueueMode queuMode = QueueMode.PlayNow, PlayMode playMode = PlayMode.StopSameLayer)
    500.             {
    501.                 return _animations.Play(id, queuMode, playMode);
    502.             }
    503.  
    504.             public bool DrawWeapon()
    505.             {
    506.                 switch (_state)
    507.                 {
    508.                     case RangeWeaponState.Holstering:
    509.                     case RangeWeaponState.None:
    510.                         {
    511.                             var clip1 = _animations[ANIM_RANGE_DRAW];
    512.                             var clip2 = _animations[ANIM_RANGE_AIM];
    513.  
    514.                             if (clip1 == null || clip2 == null) return false;
    515.  
    516.                             _state = RangeWeaponState.Drawing;
    517.                             clip1.WrapMode = WrapMode.Clamp;
    518.                             clip1.Layer = ANIMLAYER_ACTION;
    519.                             _drawAnim = clip1.Play(QueueMode.PlayNow);
    520.                             _drawAnim.Schedule((a) =>
    521.                             {
    522.                                 _drawAnim = null;
    523.                                 _state = RangeWeaponState.Aim;
    524.                             });
    525.  
    526.                             clip2.WrapMode = WrapMode.Loop;
    527.                             clip2.Layer = ANIMLAYER_ACTION;
    528.                             clip2.Play(QueueMode.CompleteOthers);
    529.  
    530.                             return true;
    531.                         }
    532.                     case RangeWeaponState.Drawing:
    533.                     case RangeWeaponState.Aim:
    534.                     case RangeWeaponState.Firing:
    535.                     case RangeWeaponState.Reload:
    536.                     default:
    537.                         return false;
    538.                 }
    539.             }
    540.  
    541.             public bool HolsterWeapon()
    542.             {
    543.                 switch(_state)
    544.                 {
    545.                     case RangeWeaponState.Holstering:
    546.                     case RangeWeaponState.None:
    547.                         return false;
    548.                     case RangeWeaponState.Drawing:
    549.                         {
    550.                             var clip = _animations[ANIM_RANGE_DRAW];
    551.  
    552.                             _state = RangeWeaponState.Holstering;
    553.                             clip.WrapMode = WrapMode.Clamp;
    554.                             clip.Layer = ANIMLAYER_ACTION;
    555.                             clip.Time = _drawAnim.Time;
    556.                             var anim = clip.Play(QueueMode.PlayNow);
    557.                             anim.Speed = -1f;
    558.                             anim.Schedule((a) =>
    559.                             {
    560.                                 _state = RangeWeaponState.None;
    561.                             });
    562.                             _drawAnim = null;
    563.  
    564.                             return true;
    565.                         }
    566.                     case RangeWeaponState.Aim:
    567.                         {
    568.                             var clip = _animations[ANIM_RANGE_HOLSTER];
    569.                             if (clip == null) return false;
    570.  
    571.                             _state = RangeWeaponState.Holstering;
    572.                             clip.WrapMode = WrapMode.Once;
    573.                             clip.Layer = ANIMLAYER_ACTION;
    574.                             clip.CrossFade(Constants.DEFAULT_CROSSFADE_DUR, QueueMode.PlayNow).Schedule((a) =>
    575.                             {
    576.                                 _state = RangeWeaponState.None;
    577.                             });
    578.  
    579.                             return true;
    580.                         }
    581.                     case RangeWeaponState.Firing:
    582.                     case RangeWeaponState.Reload:
    583.                     default:
    584.                         return false;
    585.                 }
    586.             }
    587.  
    588.             public bool FireWeapon()
    589.             {
    590.                 switch (_state)
    591.                 {
    592.                     case RangeWeaponState.Holstering:
    593.                     case RangeWeaponState.None:
    594.                     case RangeWeaponState.Drawing:
    595.                         return false;
    596.                     case RangeWeaponState.Aim:
    597.                         {
    598.                             var clip = _animations[ANIM_RANGE_FIRE];
    599.                             if (clip == null) return false;
    600.                        
    601.                             _state = RangeWeaponState.Firing;
    602.                             clip.WrapMode = WrapMode.Once;
    603.                             clip.Layer = ANIMLAYER_ACTION + 1;
    604.                             clip.Play(QueueMode.CompleteOthers).Schedule((a) =>
    605.                             {
    606.                                 _state = RangeWeaponState.Aim;
    607.                             });
    608.  
    609.                             _owner._onFireWeapon.ActivateTrigger();
    610.  
    611.                             return true;
    612.                         }
    613.                     case RangeWeaponState.Firing:
    614.                     case RangeWeaponState.Reload:
    615.                     default:
    616.                         return false;
    617.                 }
    618.             }
    619.  
    620.             public bool ReloadWeapon()
    621.             {
    622.                 switch (_state)
    623.                 {
    624.                     case RangeWeaponState.Holstering:
    625.                     case RangeWeaponState.None:
    626.                     case RangeWeaponState.Drawing:
    627.                         return false;
    628.                     case RangeWeaponState.Aim:
    629.                         {
    630.                             var clip = _animations[ANIM_RANGE_RELOAD];
    631.                             if (clip == null) return false;
    632.  
    633.                             _state = RangeWeaponState.Reload;
    634.                             clip.WrapMode = WrapMode.Once;
    635.                             clip.Layer = ANIMLAYER_ACTION + 1;
    636.                             clip.Play(QueueMode.CompleteOthers).Schedule((a) =>
    637.                             {
    638.                                 _state = RangeWeaponState.Aim;
    639.                             });
    640.  
    641.                             _owner._onReloadWeapon.ActivateTrigger();
    642.  
    643.                             return true;
    644.                         }
    645.                     case RangeWeaponState.Firing:
    646.                     case RangeWeaponState.Reload:
    647.                     default:
    648.                         return false;
    649.                 }
    650.             }
    651.  
    652.             #endregion
    653.  
    654.         }
    655.    
    656.  
    657.         [System.Serializable()]
    658.         public class StruckAnimationInfo
    659.         {
    660.  
    661.             public const string ANIM_STRUCK_DEATH = "Death";
    662.             public const string ANIM_STRUCK_REBIRTH = "Rebirth";
    663.  
    664.             #region Fields
    665.  
    666.             [SerializeField()]
    667.             [SPAnimClipCollection.Config(DefaultLayer = PlayerAnimator.ANIMLAYER_ACTION)]
    668.             private SPAnimClipCollection _struckAnimations;
    669.  
    670.             [SerializeField()]
    671.             [SPAnimClipCollection.Config(DefaultLayer = PlayerAnimator.ANIMLAYER_DEATH)]
    672.             [SPAnimClipCollection.StaticCollection(ANIM_STRUCK_DEATH,
    673.                                                     ANIM_STRUCK_REBIRTH)]
    674.             private SPAnimClipCollection _deathAnimations;
    675.  
    676.             [System.NonSerialized()]
    677.             private PlayerAnimator _owner;
    678.  
    679.             #endregion
    680.  
    681.             #region Methods
    682.  
    683.             internal void Init(PlayerAnimator owner)
    684.             {
    685.                 _owner = owner;
    686.                 _struckAnimations.Init(_owner.Controller, "*struck");
    687.                 _deathAnimations.Init(_owner.Controller, "*death");
    688.             }
    689.  
    690.             public SPAnim PlayStruck()
    691.             {
    692.                 var clip = _struckAnimations.PickRandom();
    693.                 if (clip == null) return null;
    694.  
    695.                 _owner._onStruck.ActivateTrigger();
    696.  
    697.                 return clip.Play();
    698.             }
    699.  
    700.             public SPAnim PlayDeath()
    701.             {
    702.                 var clip = _deathAnimations[ANIM_STRUCK_DEATH];
    703.                 if (clip == null) return null;
    704.  
    705.                 _owner._onDeath.ActivateTrigger();
    706.  
    707.                 clip.Layer = PlayerAnimator.ANIMLAYER_DEATH;
    708.                 clip.WrapMode = WrapMode.ClampForever;
    709.                 return clip.CrossFade(0.5f);
    710.             }
    711.  
    712.             public SPAnim PlayRebirth()
    713.             {
    714.                 var clip = _deathAnimations[ANIM_STRUCK_REBIRTH];
    715.                 if (clip == null) return null;
    716.  
    717.                 _owner._onRebirth.ActivateTrigger();
    718.  
    719.                 clip.Layer = PlayerAnimator.ANIMLAYER_DEATH;
    720.                 return clip.CrossFade(1f);
    721.             }
    722.  
    723.             #endregion
    724.  
    725.         }
    726.  
    727.         #endregion
    728.  
    729.     }
    730.  
    731. }
    732.  
    Note it references the 'Rig' so it can actually play the animations.

    It also has Event Triggers at the bottom. This is that 'com.spacepuppy.Scenario' stuff I linked earlier. My artist can hook into those events (the Events gameobject) and perform actions when they occur. As you can see here, the 'OnDeath' event references the 'e.Death' object. That object plays some soundfx, and prints some stuff to screen, and other fun stuff for on death.

    forum_player_motor.png
    Last one I'm showing is the Motor.

    This is the true guts of the player. The rest of that stuff is to look pretty, this is the gameplay here.

    MovementMotor is a finite state machine, I can have various 'MovementStyles' that can be used. In this case I have 'PlayeWalkMovementStyle' and 'PlayerUndeadWalkMovementStyle' (when you die, you come back to life as a zombie).

    Here is PlayerWalkMovementStyle:
    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections.Generic;
    4.  
    5. using com.spacepuppy;
    6. using com.spacepuppy.Cameras;
    7. using com.spacepuppy.Movement;
    8. using com.spacepuppy.UserInput;
    9. using com.spacepuppy.Utils;
    10.  
    11. using com.mansion.Entities.Cameras;
    12. using com.mansion.Entities.UI;
    13. using com.mansion.UserInput;
    14.  
    15. namespace com.mansion.Entities.Actors.Player
    16. {
    17.  
    18.     public class PlayerWalkMovementStyle : SPComponent, IMovementStyle
    19.     {
    20.  
    21.         #region Fields
    22.  
    23.         [SerializeField()]
    24.         private float _speed = 1f;
    25.         [SerializeField()]
    26.         private float _runSpeed = 2f;
    27.         [SerializeField()]
    28.         [Range(0f,1f)]
    29.         private float _turnSlerpRatio = 0.5f;
    30.         [Range(0f, 1f)]
    31.         private float _aimTurnSlerpRatio = 0.5f;
    32.         [SerializeField()]
    33.         [Range(0f, 1f)]
    34.         private float _injuredSpeedDamper = 0.75f;
    35.         [SerializeField()]
    36.         [Range(0f, 1f)]
    37.         private float _criticalSpeedDamper = 0.75f;
    38.  
    39.         [SerializeField()]
    40.         [DefaultFromSelf(UseEntity = true)]
    41.         private PlayerAnimator _animator;
    42.  
    43.         [System.NonSerialized()]
    44.         private IEntity _entity;
    45.         [System.NonSerialized()]
    46.         private MovementMotor _motor;
    47.    
    48.         #endregion
    49.  
    50.         #region CONSTRUCTOR
    51.  
    52.         protected override void Awake()
    53.         {
    54.             base.Awake();
    55.  
    56.             _entity = SPEntity.Pool.GetFromSource<IEntity>(this);
    57.             _motor = this.GetComponent<MovementMotor>();
    58.         }
    59.  
    60.         #endregion
    61.  
    62.         #region Properties
    63.  
    64.         public float Speed
    65.         {
    66.             get { return _speed; }
    67.             set { _speed = value; }
    68.         }
    69.  
    70.         public float RunSpeed
    71.         {
    72.             get { return _runSpeed; }
    73.             set { _runSpeed = value; }
    74.         }
    75.  
    76.         public float TurnSlerpRatio
    77.         {
    78.             get { return _turnSlerpRatio; }
    79.             set { _turnSlerpRatio = value; }
    80.         }
    81.  
    82.         public float AimTurnSlerpRatio
    83.         {
    84.             get { return _aimTurnSlerpRatio; }
    85.             set { _aimTurnSlerpRatio = value; }
    86.         }
    87.  
    88.         #endregion
    89.  
    90.         #region Methods
    91.  
    92.         private Vector3 GetCurrentCameraForward()
    93.         {
    94.             var cam = CameraManager.Main;
    95.             var forw = cam.transform.forward.SetY(0f).normalized;
    96.  
    97.             if(CameraZone.LastCamera != null && CameraZone.LastCamera != cam)
    98.             {
    99.                 const float LERP_TIME = 0.5f;
    100.                 var t = Time.time - CameraZone.LastCamerSwapTime;
    101.                 if(t < LERP_TIME)
    102.                 {
    103.                     var oldForw = CameraZone.LastCamera.transform.forward.SetY(0f).normalized;
    104.                     forw = Vector3.Slerp(oldForw, forw, t / LERP_TIME);
    105.                 }
    106.             }
    107.  
    108.             return forw;
    109.         }
    110.  
    111.         #endregion
    112.  
    113.         #region IMovementStyle Interface
    114.  
    115.         void IMovementStyle.OnActivate(IMovementStyle lastStyle, bool stateIsStacking)
    116.         {
    117.  
    118.         }
    119.  
    120.         void IMovementStyle.OnDeactivate(IMovementStyle nextStyle, bool stateIsStacking)
    121.         {
    122.  
    123.         }
    124.  
    125.         void IMovementStyle.OnPurgedFromStack()
    126.         {
    127.  
    128.         }
    129.  
    130.         void IMovementStyle.UpdateMovement()
    131.         {
    132.             if (Game.Paused) return;
    133.             if (_entity.Stalled || InGameUIController.Instance.MessageBox.IsShowing || _entity.HealthMeter.Health == 0f)
    134.             {
    135.                 //idle
    136.                 _motor.Controller.Move(Vector3.up * Game.GRAV * Time.deltaTime);
    137.                 _animator.DefaultMovementAnimations.PlayIdle();
    138.                 return;
    139.             }
    140.  
    141.             var input = Game.InputManager.GetDevice<MansionInputDevice>(Game.MAIN_INPUT);
    142.             if (input == null) return;
    143.  
    144.             var forw = this.GetCurrentCameraForward();
    145.             var right = Vector3.Cross(Vector3.up, forw);
    146.  
    147.             var dir = input.GetCurrentDualAxleState(MansionInputs.Move);
    148.             var strength = dir.magnitude;
    149.             float speed = 0f;
    150.             if (strength < 0.1f)
    151.                 speed = 0f;
    152.             else if (strength < 0.6f)
    153.                 speed = 0.5f * _speed;
    154.             else if (input.GetCurrentButtonState(MansionInputs.Run) <= ButtonState.None)
    155.                 speed = _speed;
    156.             else
    157.                 speed = _runSpeed;
    158.  
    159.             switch(_entity.HealthMeter.Status)
    160.             {
    161.                 case HealthMeter.StatusType.Injured:
    162.                     speed *= _injuredSpeedDamper;
    163.                     break;
    164.                 case HealthMeter.StatusType.Critical:
    165.                     speed *= _criticalSpeedDamper;
    166.                     break;
    167.             }
    168.  
    169.             dir.Normalize();
    170.             var walk = forw * dir.y + right * dir.x;
    171.  
    172.             var mv = walk * speed + Vector3.up * Game.GRAV;
    173.             _motor.Controller.Move(mv * Time.deltaTime);
    174.  
    175.             bool isAiming = (_animator.RangeWeaponAnimations.State > PlayerAnimator.RangeWeaponAnimationInfo.RangeWeaponState.None);
    176.             if (speed > 0f)
    177.             {
    178.                 if(isAiming)
    179.                 {
    180.                     var a = VectorUtil.AngleOffAroundAxis(walk, _motor.Controller.transform.forward.SetY(0f), Vector3.up);
    181.                     _animator.DefaultMovementAnimations.PlayStrafe(a, speed);
    182.                 }
    183.                 else
    184.                 {
    185.                     _motor.Controller.transform.rotation = Quaternion.Slerp(_motor.Controller.transform.rotation,
    186.                                                                             Quaternion.LookRotation(walk, Vector3.up),
    187.                                                                             _turnSlerpRatio);
    188.                     _animator.DefaultMovementAnimations.PlayWalk(speed);
    189.                 }
    190.             }
    191.             else
    192.             {
    193.                 _animator.DefaultMovementAnimations.PlayIdle();
    194.             }
    195.         }
    196.  
    197.         void IMovementStyle.OnUpdateMovementComplete()
    198.         {
    199.  
    200.         }
    201.  
    202.         #endregion
    203.  
    204.     }
    205.  
    206. }
    207.  
    It's a fairly simple movement script for player motion around the game world.

    And lastly, PlayerActionMotor:
    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections.Generic;
    4. using System.Linq;
    5.  
    6. using com.spacepuppy;
    7. using com.spacepuppy.AI.Sensors;
    8. using com.spacepuppy.Cameras;
    9. using com.spacepuppy.Movement;
    10. using com.spacepuppy.UserInput;
    11. using com.spacepuppy.Utils;
    12.  
    13. using com.mansion.Entities.GamePlay;
    14. using com.mansion.Entities.Weapons;
    15. using com.mansion.Entities.UI;
    16. using com.mansion.UserInput;
    17.  
    18. namespace com.mansion.Entities.Actors.Player
    19. {
    20.  
    21.     public class PlayerActionMotor : SPComponent
    22.     {
    23.  
    24.         #region Fields
    25.  
    26.         [SerializeField()]
    27.         private Sensor _actionSensor;
    28.  
    29.         [SerializeField()]
    30.         private Sensor _lockOnSensor;
    31.  
    32.         [SerializeField()]
    33.         private GunWeapon _gun;
    34.         [SerializeField()]
    35.         private GameObject _undeadAttackWeapon;
    36.  
    37.         [SerializeField()]
    38.         [DefaultFromSelf(UseEntity = true)]
    39.         private PlayerAnimator _animator;
    40.  
    41.         [SerializeField()]
    42.         private float _onStruckStallDuration = 1f;
    43.    
    44.         [System.NonSerialized()]
    45.         private IEntity _entity;
    46.         [System.NonSerialized()]
    47.         private PlayerWalkMovementStyle _walkMotor;
    48.  
    49.         [System.NonSerialized()]
    50.         private float _idleActionTicker;
    51.         [System.NonSerialized()]
    52.         private float _idleActionTimeout;
    53.  
    54.         [System.NonSerialized()]
    55.         private IEntity _lockedOnEntity;
    56.  
    57.         [System.NonSerialized()]
    58.         private bool _inUndeadAttack;
    59.    
    60.         #endregion
    61.  
    62.         #region CONSTRUCTOR
    63.  
    64.         protected override void Awake()
    65.         {
    66.             base.Awake();
    67.  
    68.             _entity = SPEntity.Pool.GetFromSource<IEntity>(this);
    69.             _walkMotor = this.GetComponent<PlayerWalkMovementStyle>();
    70.             _undeadAttackWeapon.SetActive(false);
    71.  
    72.             _entity.HealthMeter.OnStrike.TriggerActivated += this.OnStruck;
    73.             _entity.HealthMeter.OnDeath.TriggerActivated += this.OnDeath;
    74.         }
    75.  
    76.         protected override void Start()
    77.         {
    78.             base.Start();
    79.        
    80.             this.ResetIdleActionTicker();
    81.         }
    82.  
    83.         #endregion
    84.  
    85.         #region Properties
    86.  
    87.         public Sensor ActionSensor
    88.         {
    89.             get { return _actionSensor; }
    90.             set { _actionSensor = value; }
    91.         }
    92.  
    93.         public Sensor LockOnSensor
    94.         {
    95.             get { return _lockOnSensor; }
    96.             set { _lockOnSensor = value; }
    97.         }
    98.  
    99.         public GunWeapon Gun
    100.         {
    101.             get { return _gun; }
    102.             set { _gun = value; }
    103.         }
    104.  
    105.         public PlayerAnimator Animator
    106.         {
    107.             get { return _animator; }
    108.             set { _animator = value; }
    109.         }
    110.  
    111.         public float OnStruckStallDuration
    112.         {
    113.             get { return _onStruckStallDuration; }
    114.             set { _onStruckStallDuration = value; }
    115.         }
    116.    
    117.         #endregion
    118.  
    119.         #region Methods
    120.  
    121.         protected void Update()
    122.         {
    123.             var input = Game.InputManager.GetDevice<MansionInputDevice>(Game.MAIN_INPUT);
    124.             if (input == null) return;
    125.  
    126.             if(Game.Paused || _entity.Stalled || InGameUIController.Instance.MessageBox.IsShowing || _entity.HealthMeter.Health == 0f)
    127.             {
    128.                 //do nothing
    129.             }
    130.             else if(_entity.Type == IEntity.EntityType.UndeadPlayer)
    131.             {
    132.                 if (input.GetCurrentButtonState(MansionInputs.Action) == ButtonState.Down)
    133.                 {
    134.                     if (input.GetCurrentButtonState(MansionInputs.Aim) > ButtonState.None)
    135.                     {
    136.                         //do attack
    137.                         if(!_inUndeadAttack) this.StartCoroutine(this.DoUndeadMeleeAttack());
    138.                     }
    139.                     else
    140.                     {
    141.                         //attempt to activate whatever
    142.                         this.AttemptActivate();
    143.                         _idleActionTicker = 0f;
    144.                     }
    145.                 }
    146.  
    147.                 if (!_entity.Stalled &&
    148.                     _animator.DefaultMovementAnimations.State == PlayerAnimator.DefaultMovementAnimationInfo.DefaultMovementState.Idle)
    149.                 {
    150.                     _idleActionTicker += Time.deltaTime;
    151.                     if (_idleActionTicker >= _idleActionTimeout)
    152.                     {
    153.                         _animator.UndeadAnimations.PlayIdleAction();
    154.                         this.ResetIdleActionTicker();
    155.                     }
    156.                 }
    157.                 else
    158.                 {
    159.                     _idleActionTicker = 0f;
    160.                 }
    161.  
    162.                 //hack fix - make sure gun is always holstered
    163.                 if(_animator.RangeWeaponAnimations.State > PlayerAnimator.RangeWeaponAnimationInfo.RangeWeaponState.None)
    164.                 {
    165.                     _animator.RangeWeaponAnimations.HolsterWeapon();
    166.                 }
    167.             }
    168.             else if(_animator.RangeWeaponAnimations.State > PlayerAnimator.RangeWeaponAnimationInfo.RangeWeaponState.None)
    169.             {
    170.                 if(input.GetCurrentButtonState(MansionInputs.Aim) <= ButtonState.None)
    171.                 {
    172.                     _lockedOnEntity = null;
    173.                     _animator.RangeWeaponAnimations.HolsterWeapon();
    174.                 }
    175.                 else if(input.GetCurrentButtonState(MansionInputs.Action) == ButtonState.Down)
    176.                 {
    177.                     if(_gun.AmmoInClip <= 0)
    178.                     {
    179.                         if (_animator.RangeWeaponAnimations.ReloadWeapon())
    180.                             _gun.Reload();
    181.                     }
    182.                     else if(_animator.RangeWeaponAnimations.FireWeapon())
    183.                     {
    184.                         if(_gun.Fire(_entity.transform.forward.SetY(0f)))
    185.                         {
    186.                             _lockedOnEntity = null;
    187.                         }
    188.                     }
    189.                 }
    190.                 else if (input.GetCurrentButtonState(MansionInputs.Reload) == ButtonState.Down)
    191.                 {
    192.                     if (_animator.RangeWeaponAnimations.ReloadWeapon())
    193.                         _gun.Reload();
    194.                 }
    195.  
    196.                 if (_lockedOnEntity == null)
    197.                 {
    198.                     this.AttemptFindTarget();
    199.                 }
    200.                 else
    201.                 {
    202.                     var dir = (_lockedOnEntity.transform.position - _entity.transform.position).SetY(0f);
    203.                     _entity.transform.rotation = Quaternion.Slerp(_entity.transform.rotation,
    204.                                                                 Quaternion.LookRotation(dir, Vector3.up),
    205.                                                                 _walkMotor.AimTurnSlerpRatio);
    206.                 }
    207.            
    208.                 _idleActionTicker = 0f;
    209.             }
    210.             else
    211.             {
    212.                 if(input.GetCurrentButtonState(MansionInputs.Aim) > ButtonState.None)
    213.                 {
    214.                     //draw weapon
    215.                     _animator.RangeWeaponAnimations.DrawWeapon();
    216.                     this.ResetIdleActionTicker();
    217.                 }
    218.                 else if (input.GetCurrentButtonState(MansionInputs.Action) == ButtonState.Down)
    219.                 {
    220.                     //attempt to activate whatever
    221.                     this.AttemptActivate();
    222.                     _idleActionTicker = 0f;
    223.                 }
    224.                 else if (input.GetCurrentButtonState(MansionInputs.Reload) == ButtonState.Down)
    225.                 {
    226.                     //reload weapon
    227.                     if (_animator.RangeWeaponAnimations.ReloadWeapon())
    228.                     {
    229.                         if (_animator.RangeWeaponAnimations.ReloadWeapon())
    230.                             _gun.Reload();
    231.                     }
    232.                     _idleActionTicker = 0f;
    233.                 }
    234.  
    235.  
    236.  
    237.                 if (!_entity.Stalled &&
    238.                     _animator.DefaultMovementAnimations.State == PlayerAnimator.DefaultMovementAnimationInfo.DefaultMovementState.Idle)
    239.                 {
    240.                     _idleActionTicker += Time.deltaTime;
    241.                     if (_idleActionTicker >= _idleActionTimeout)
    242.                     {
    243.                         _animator.DefaultMovementAnimations.PlayIdleAction();
    244.                         this.ResetIdleActionTicker();
    245.                     }
    246.                 }
    247.                 else
    248.                 {
    249.                     _idleActionTicker = 0f;
    250.                 }
    251.             }
    252.  
    253.  
    254.  
    255.             if (input.GetCurrentButtonState(MansionInputs.Menu) == ButtonState.Down)
    256.             {
    257.                 //pause game
    258.                 Game.TogglePause();
    259.             }
    260.        
    261.         }
    262.  
    263.         private void AttemptActivate()
    264.         {
    265.             var trans = _entity.transform;
    266.             var pos = trans.position;
    267.             var forw = trans.forward.SetY(0f);
    268.             var aspect = (from a in _actionSensor.SenseAll()
    269.                           where a.gameObject.EntityHasComponent<IEntity>()
    270.                           let p = a.transform.position.SetY(0f)
    271.                           orderby VectorUtil.AngleBetween(p, forw), Vector3.Distance(p, pos) ascending
    272.                           select a).FirstOrDefault();
    273.  
    274.             if (aspect != null)
    275.             {
    276.                 var entity = SPEntity.Pool.GetFromSource(aspect);
    277.  
    278.                 if (entity.EntityHasComponent<PlayerInteractable>())
    279.                 {
    280.                     var comp = entity.FindComponent<PlayerInteractable>();
    281.                     comp.Trigger();
    282.                 }
    283.             }
    284.         }
    285.  
    286.  
    287.         private void ResetIdleActionTicker()
    288.         {
    289.             _idleActionTicker = 0f;
    290.             _idleActionTimeout = RandomUtil.Standard.Range(15f, 10f);
    291.         }
    292.  
    293.  
    294.  
    295.         private void AttemptFindTarget()
    296.         {
    297.             var forw = _entity.transform.forward.SetY(0f);
    298.             var pos = _entity.transform.position.SetY(0f);
    299.  
    300.             var aspect = (from a in _lockOnSensor.SenseAll()
    301.                           let e = SPEntity.Pool.GetFromSource<IEntity>(a)
    302.                           where e != null && e.Type == IEntity.EntityType.Mob && e.HealthMeter != null && e.HealthMeter.Health > 0f
    303.                           let p = a.transform.position.SetY(0f)
    304.                           orderby Vector3.Distance(p, pos), VectorUtil.AngleBetween(p, forw) ascending
    305.                           select a).FirstOrDefault();
    306.  
    307.             if(aspect != null)
    308.             {
    309.                 _lockedOnEntity = SPEntity.Pool.GetFromSource<IEntity>(aspect);
    310.             }
    311.         }
    312.  
    313.  
    314.         private System.Collections.IEnumerator DoUndeadMeleeAttack()
    315.         {
    316.             _inUndeadAttack = true;
    317.             _entity.Stalled = true;
    318.  
    319.             var a = _animator.UndeadAnimations.PlayMelee();
    320.             yield return WaitForDuration.Seconds(0.5f);
    321.             _undeadAttackWeapon.SetActive(true);
    322.             yield return WaitForDuration.Seconds(0.65f);
    323.             _undeadAttackWeapon.SetActive(false);
    324.             yield return a;
    325.  
    326.             _entity.Stalled = false;
    327.             _inUndeadAttack = false;
    328.         }
    329.  
    330.         #endregion
    331.  
    332.         #region Event Handlers
    333.  
    334.         private void OnStruck(object sender, TempEventArgs e)
    335.         {
    336.             this.StartRadicalCoroutine(this.PlayStruckRoutine());
    337.         }
    338.  
    339.         private void OnDeath(object sender, TempEventArgs e)
    340.         {
    341.             //if normal mode
    342.             this.StartRadicalCoroutine(this.PlayNormalDeathRoutine());
    343.  
    344.             //todo - if zombie
    345.         }
    346.  
    347.  
    348.  
    349.  
    350.  
    351.         private System.Collections.IEnumerator PlayStruckRoutine()
    352.         {
    353.             _idleActionTicker = 0f;
    354.             _animator.StruckAnimations.PlayStruck();
    355.             _entity.Stalled = true;
    356.             yield return WaitForDuration.Seconds(_onStruckStallDuration);
    357.             _entity.Stalled = false;
    358.         }
    359.  
    360.         private System.Collections.IEnumerator PlayNormalDeathRoutine()
    361.         {
    362.             _entity.Stalled = true;
    363.             _entity.Type = IEntity.EntityType.UndeadPlayer;
    364.  
    365.             //DUMMY - this is how we deal with zombies once you're undead, we make them chase stupid bait around
    366.             var go = new GameObject("PlayerBait");
    367.             go.transform.position = _entity.transform.position.SetY(0.5f);
    368.             go.AddComponent<PlayerBait>();
    369.             //END DUMMY
    370.  
    371.  
    372.             _animator.StruckAnimations.PlayDeath();
    373.  
    374.             //TODO - fade out camera with 'YOU DIED'
    375.             yield return WaitForDuration.Seconds(12f);
    376.             //TODO - fade camera back in
    377.  
    378.             yield return _animator.StruckAnimations.PlayRebirth();
    379.  
    380.             //TODO - change movement style
    381.  
    382.             _entity.HealthMeter.Health = float.PositiveInfinity;
    383.             _entity.FindComponent<MovementMotor>().States.ChangeState<PlayerUndeadWalkMovementStyle>();
    384.             _entity.Stalled = false;
    385.             _idleActionTicker = 0f;
    386.         }
    387.  
    388.         private System.Collections.IEnumerator PlayZombieDeathRoutine()
    389.         {
    390.             //TODO - need animations and what not for this!
    391.             yield break;
    392.         }
    393.  
    394.         #endregion
    395.  
    396.     }
    397.  
    398. }
    399.  
    Now, I ain't going to lie. This one is VERY SLOPPY.

    I had 72 hours to make a game, so I kind of just slapped stuff in here as we moved along to just get it working. If I had more time, I would have made this nicer.

    But you can see things in here like how I use the 'Sensor's I talked about earlier to lock onto zombies, or to activate things around the scene (see method 'AttemptActivate').

    Some of the logic could have been broken up better... especially the determining which state we're in, undead/paused/aiming/whatnot.


    forum_player_scripts.png
    And you can see here that I organize my scripts namespace wise together.

    So, now you can see how I organize my logic.

    This may not be the best option, but it's what works for me... and I too come from a business solutions type background.

    Finite state machines can be fine for certain things. For instance I use one for my movement styles.

    Anyways, hopefully what I showed is of some help.

    The links to my github is the opensource portion of my library (not all of it is released, my AI/Animation/Movement and what not stuff is stilled closed source). You're more than welcome to look around it. It's not perfect though... poorly commented and some of it a little slap-dash as I iron kinks out and what not.
     
    Last edited: Dec 14, 2016
  6. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,195
    Yup, there's a lot of bad Unity tutorials out there.

    A big issue is that a lot of them are geared towards teaching both Unity as the game engine, and basic C# scripting. Almost none of them assume that you know what a for-loop is when you start out. Unity's official tutorials have the same problem.

    I've been meaning to create Unity tutorials for programmers, but there's no time now. I've also just worked professionally in the field for 3 years, so idk if I'd be the best one for that.


    There's a bunch of things that are counter-intuitive to programmers that are never explained because non-programmers won't even notice. The ones I remember off the top of my head (random order) are:

    1: Almost all of the objects Unity uses inherit from UnityEngine.Object. This class has overridden the == method so that it returns true for obj == null if obj is an existing object that has been destroyed. Unity also throws faux nullReferences ("MissingReferenceException") if you try to access fields of a destroyed object.

    This is super-strange, and breaks everything you know about how OO languages with a GC works. There's been a bunch of talk about if it should be changed or not. I'll not go into that, but the practical implications are:

    - you can do if (x != null) to guard both against not initialized objects and against destroyed objects. There's almost no instances where you want to treat those two things differently, so it's a convenience
    - If you're working with an object with a reference to it's interface on the other hand, the == is statically dispatched to the interface's ==, which is System.Object, which doesn't do Unity's is-destroyed check. That's a caveat you'll have to be aware of.
    - similarly, ?? doesn't use the object's == override, so these two are not the same:

    Code (csharp):
    1. a = b != null ? b : c;
    2. //and
    3. a = b ?? c;
    - Finally, there's instances where (this == null) will return true. That's incredibly insane.
    - If you really need to know if an object is actually null or not, object.ReferenceEquals doesn't lie. I find this extension useful for when working with an interface reference:

    Code (csharp):
    1. public static bool IsNullOrUnityNull(this object obj) {
    2.     if (obj == null) { //C# null check
    3.         return true;
    4.     }
    5.  
    6.     if (obj is UnityEngine.Object) {
    7.         if (((UnityEngine.Object) obj) == null) { //Unity null check
    8.             return true;
    9.         }
    10.     }
    11.     return false;
    12. }
    1.5: Extension methods are super-useful since you don't have access to the engine's source.

    2: The Unity engine is a c++ program. You interface with it in C# code. This ties back to the UnityEngine.Object thing; everything that's a UnityEngine.Object also exists in c++ land.
    The practical effect of this is that you can't really work with Unity's objects (like MonoBehaviours) on other threads, otherwise the two "versions" of the thing would get disconnected. You can do multithreading just fine, but you'll want to gather up data as native C#-objects or primitives, and work on those.

    3: Unity's on a really old version of Mono. They're in the process of upgrading it as we speak (the first updates were rolled out in 5.5, which launched like a week ago), and they're planning on getting it up to date. In the meantime, be aware that you're on C# 4. 4.5? Something like that. You don't have async/await or the x?.y operator. The GC is an outdated piece of garbage.

    3: This isn't Unity-specific: Object Pooling is actually not horrible. The rest of the programming world moved away from that years ago, due to faster gc's and higher costs of managing pooled objects than instantiation,

    But game objects - not an OO object, but an actual thing in the game like a monster or a bullet - are huge things, with large attached 3d models and meshes and textures and whatnot. If you are rapidly spawning and destroying things like projectiles or enemies, you either have to solve them with particles or other effects, or pool them. Otherwise you're looking at GC spikes, which will cause lag spikes.

    4: Unity sucks at Unit testing. There's no good way to mock things like physics events or "wait for the next frame". There's integration tests where you set up a small test scene and check some condition after time, but my experience is that framerate differences and randomness wrecks that.

    This means that when you build the framework for your game, you should really, really, really think hard about making it unit-testable. I've not got a silver bullet for this, but moving to separate the game logic from Unity's messaging system is probably a good start.

    5: Editor scripting is incredibly powerful! It's easily the best feature of Unity, and Unity is from what I can tell from how they're advertising themselves completely unaware of the fact. The API for writing custom windows for your editor window is incredibly easy and fast to use, and you use exactly the same API for working with objects in the scene at edit time as at runtime. Making something like a button that replaces all the selected objects in the scene with a prefab (for things like replacing a bunch of props with a newer version or replacing the enemies in the scene with an other type) takes 2 minutes if you know the process.

    6: Unity's serializer does some really neat things, and is what makes the iteration time fast. This blog post sums it up nicely. There are some drawbacks, though:
    - It doesn't handle inheritance... at all. If you have an Animal[], and put a bunch of Cat's and Dog's in there, they're coming out as Animal instances when you come back from deserialization (ie. when you go from editing to playing). This drawback doesn't hold for Unity's own objects, so you can safely have a MonoBehaviour[] and put different MonoBehaviour subclasses in there. (This is because those are stored by object reference in the scene)
    - It doesn't handle collections very well. It stores List<T> and T[], but no multi-dimensional structures are supported. So no T[,] or Dictionary<T,V>.
    There's a built-in fix for this in the ISerializationCallbackReceiver interface, which allows you to hook into the serialization process and read/write data. Once you get into things, you'll end up using this a bunch.

    7: Coroutines are a really cool pattern, which abuses the hell out of C#'s already really cool IEnumerator yield-pattern. It's how you'll be doing things that need to happen over time - like a bar filling up or whatnot. Learn them!

    By all means, ask follow-up questions! I've you want immediate feedback, we've got a discord channel (see my sig). You'd be the one in the channel with the longest professional experience (I believe), but we know a thing or two about Unity. Of course, post questions in the forum as well!
     
    Last edited: Dec 14, 2016
    Remi_Tribia, eses, Ryiah and 9 others like this.
  7. scifresh

    scifresh

    Joined:
    Oct 8, 2013
    Posts:
    23
    thank you all for your comments and valuable information.

    i ended up using a state pattern from here http://gameprogrammingpatterns.com/state.html

    i am having problems on what to search on the internet. i dont know the terms, i dont know game programming. is there any book explaining the tricks or best practices on game programming ? how things work ?

    its really hard to dive into game programming without any knowledge.
     
  8. Brathnann

    Brathnann

    Joined:
    Aug 12, 2014
    Posts:
    7,144
    It's actually not hard at all. I had a few years of programming knowledge with c# and had played around with a few smaller game engines, but went to Unity when I discovered it used c#. Then I just started with some tutorials. Yeah, they were basic stuff, but they help you learn a bit how things are done. Working with components and gameobjects and such.

    Next step was just to start trying to make a game and when I got stuck, I'd google what it was I was trying to do. I now work for a small game company and have a few titles under by belt, so it did work out some how.

    Just find a starting point and work up from there. Experiment, practice and google or come here if you get stuck.
     
  9. scifresh

    scifresh

    Joined:
    Oct 8, 2013
    Posts:
    23
  10. ahmadreza909sarwari

    ahmadreza909sarwari

    Joined:
    Feb 7, 2019
    Posts:
    1
    Good answers from @lordofduct, its been years to answer but let me add something here:_
    1. In unity u dont need to worry about start point, the engine of unity can handle every classes which they inherits from MonoBehavior class and u can do handle ur code by methods like "Start() and Awake()".

    2. If u have experience with computer programming u should be able to manage bad codes and learn the basic of gaming programming from them and then implement ur own system for 2D or 3D games in unity.

    3. Unity visualized the boring staff like create animation or design ur scene, but if u think u need to work with really OOP then u can try "MonoGame, UrhoEngine, SlimDX" and they are just like unity but u have to manage all ur code by urself.