A pain giver

BrandSpankingNewBrandSpankingNew Posts: 2,321Member, MPTester
edited May 19 in Scripts & Mods
I was looking around over at the "old" forum and someone there wanted to know how to give damage to the player when he entered a trigger. That led to some folks providing examples. Then someone asked for a simpler example and so I added my script into the mix. Perhaps someone here will find it useful at some time.

The idea here is that you have a collider acting as a trigger, let's say for example that you had a fireplace or a pool of acid or a radiation field, and you might want anything that goes into that trigger area to feel the pain, or maybe not. Maybe what entered there, such as a sturdy robot, shouldn't take any damage. Well, you get the idea.

This lets you set random damage amounts and also create a list of layers to ignore when it comes to giving damage. I mean, it's not much fun trying to hurt debris and pickups, etc. There's also a list for random sounds, so you can put in machine sounds or screams or fire sounds, etc. I have been having fun pushing exploding crates, and loot crates, into the trigger area and waiting for them to blow up. Doesn't take much to amuse me!

p.s. I wish someone would turn off word-wrap inside of the code tags.

Updated:
I noticed that someone actually used this the other day, so I stopped by and immediately saw a couple of improvements to make. Don't know why I didn't see the obvious before ... oh, well .... You'll notice where I have commented out some stuff.

Updated
May 18, 2017. Please see my addition to this thread, that I made on this date, that provides a much improved version of this old script. Thanks to @TryThis for his efforts in trying to provide a better version a couple of years ago. I'm sorry that I had not revisited this old script before this, but the new version works nicely for me and perhaps it will for you also.
// Inspired in its entirity by VisionPunks HitscanBullet and VenomUnity's
// initial script.
using UnityEngine;
using System.Collections.Generic;

[RequireComponent(typeof(AudioSource))]

public class BSN_PainGiver : MonoBehaviour

{
public float DamageHigh = 0.01f; // the max possible damage to send
public float DamageLow = 0.001f; // the min possible damage to send
private float Damage = 0f;
public string DamageMethodName = "Damage"; // name of damage method on target
public bool VaryDamage = true;
protected AudioSource m_Audio = null;
public List<AudioClip> m_DamageSounds = new List<AudioClip>(); // sounds to randomly play,fire,steam,screams,etc.
public Vector2 SoundDamagePitch = new Vector2(1.0f, 1.5f); // random pitch for sounds to add variety
// public List<string> DamageLayers = new List<string>(); // a list of layers to ignore. debris, pickups, water, etc.
// private LayerMask ignoreMask;
public LayerMask ignoreMask;


void Start ()
{
m_Audio = audio;
/*
// populate the layer mask with things to ignore
int aMask;
for (int i = 0; i < DamageLayers.Count; i++)
{
aMask = 1 << LayerMask.NameToLayer(DamageLayers[i]);
ignoreMask = ignoreMask | aMask;
}
*/
}

void OnTriggerEnter(Collider other)
{
// if it's not on the mask, hurt it!
if (!IsInLayerMask(other.gameObject, ignoreMask)) SendThePain(other);
}

void OnTriggerStay(Collider other)
{
if (!IsInLayerMask(other.gameObject, ignoreMask)) SendThePain(other);
}

void SendThePain(Collider other)
{

// vary the damage or default to highest damage
if(VaryDamage) Damage = Random.Range(DamageLow, DamageHigh);
else Damage = DamageHigh;

// Debug.Log(Damage);

if (m_DamageSounds.Count > 0)
{
m_Audio.pitch = Random.Range(SoundDamagePitch.x, SoundDamagePitch.y) * Time.timeScale;
m_Audio.PlayOneShot(m_DamageSounds[(int)Random.Range(0, (m_DamageSounds.Count))]);
}

// do damage on the target
//BAD--> //other.SendMessageUpwards(DamageMethodName, Damage, SendMessageOptions.DontRequireReceiver);
other.SendMessage(DamageMethodName, Damage, SendMessageOptions.DontRequireReceiver);
}

// Convert the object's layer to a bitfield for comparison
private bool IsInLayerMask(GameObject obj, LayerMask layerMask)
{
int objLayerMask = (1 << obj.layer);
if ((layerMask.value & objLayerMask) > 0) return true;
else return false;
}

}
Beautiful objects are wrought by study through effort, but ugly things are reaped automatically without toil.
Democritus
Tagged:

Comments

  • CTPEJIOK22CTPEJIOK22 Posts: 48Member
    thank. good example
    DeHR Multiplayer Mod
  • BrandSpankingNewBrandSpankingNew Posts: 2,321Member, MPTester
    Notice the change! I determined that SendMessageUpwards was a bad thing to use in the above code. I had a situation where if the player was carrying a crate, for example, and took the crate into the trigger, but did not enter himself (determined by debug log and precise positioning tests) the player was receiving damage anyhow. Changing to SendMessage has eliminated that behavior.
    Beautiful objects are wrought by study through effort, but ugly things are reaped automatically without toil.
    Democritus
  • bpendz Posts: 7Member
    This is very helpful thanks!
  • BrandSpankingNewBrandSpankingNew Posts: 2,321Member, MPTester
    @bpendz, thank you very much for saying that. I appreciate you taking the time to comment and I won't forget it. :o3
    Beautiful objects are wrought by study through effort, but ugly things are reaped automatically without toil.
    Democritus
  • gonzosan Posts: 105Member
    I just asked a question about something similar to this. I'll have to test this out and get back to you.
  • BrandSpankingNewBrandSpankingNew Posts: 2,321Member, MPTester
    Cool! Anything that I can do to help will be a pure pleasure.
    Beautiful objects are wrought by study through effort, but ugly things are reaped automatically without toil.
    Democritus
  • rchassereau Posts: 1Member
    Just wanted to say thank you for this script, it's working great for me, I appreciate you putting it up here.
  • ronaldomoonronaldomoon Posts: 18Member
    I was planning on getting some lava working in my project soon so when I saw this post I decided to go ahead and give it a try. It works pretty well and I like it quite a bit. I've only run into one issue so far and I'm curious if anyone else has encountered it.

    I want my lava to kill the player almost instantly. If you're lucky, you might get to quickly jump in and out if it will only require one quick jump to get out, otherwise you're toast. The problem I'm having is that every time I die in the lava, my player respawns with >100 health. I turned damage high (as I have vary damage turned off but I've tried it with it turned on at the default values as well) down to .01 and in that case I respawn with 99 health every time, higher values cause me to respawn with even less health. I tried going all the way down to .0001 and still respawned at 99, and it took waaaay too long to die anyway.

    If anyone can try to replicate this, or if anyone can even say they consistently respawn at 100 and can provide their settings/modifications I would greatly appreciate it.

    There is a possibility that my use of the Pixel Crushers Dialogue System asset is the problem. Perhaps the timing is a bit off when it syncs the Player info to LUA or vice versa. If no one else is having this issue, then I'd be willing to bet money that the problem lies in the Dialogue System's handling of storing persistent player info for saves and whatnot. But now that I think about it, I don't think it does anything when you die, only when you use a save button or object that you connect both UFPS and the Dialogue System to, so that may not be it at all.

    In spite of the issue I'm having, it is a really cool script and I appreciate you sharing it with everyone. It seems like it should be part of the official package, IMHO. Maybe they'll include it once multiplayer is finished and new gameplay features are added.
  • ronaldomoonronaldomoon Posts: 18Member
    I tried fiddling with the damage and respawn scripts to try to insure that the player respawns at 100 but I didn't have any luck. I did come up with a workaround though. I simply duplicated the PickupHealthLoot, took out the pickup messages, and set it as a Spawn FX prefab. I made a duplicate so I could erase the messages that pop-up and remove the pickup sound effect without affecting the normal PickupHealthLoot object. It seems like a really cheesy way to go about fixing the problem, but I don't have a lot of scripting experience and it works well enough that the player is none-the-wiser, so it's good enough for me, at least with this particular project.

    Thanks again for sharing!
  • TryThisTryThis Posts: 887Member
    edited July 2014
    @ronaldomoon‌ , I did test this earlier today and then I got side-tracked before giving you a reply. I can confirm that when I crank the damage in the paingiver way up, and kill the guy really quickly, that I respawn with less than 100% health. And it varies a lot for me (68%, 18%, 79%, etc.)

    That is SO bizarre! It is exactly the same damage message that an exploding cube sends to the player and that the AITurret sends to the player. And that little paingiver script is not doing anything even remotely tricky with the damage. I'm thinking that there is maybe something funky in the new respawner. Back in version 1.4.6, this kind of thing didn't happen. I have a place in a project there that kills you right-now if you step into it and he comes back at 100% every time.

    You know, one thing, I only tested using respawn points (3 different ones that the player cycles through) and I did not test using no spawn points. I'll do that. (... but I don't see what that could possibly have to do with it! ha!)

    But, I'm being lazy this evening and not opening up any Unity until morning. I'll check some junk then and see what I can discover with you. :-)

    Oh, and instead of all of that fun stuff that you did to get 100%, you could just add a line like this to the Reset method in vp_PlayerRespawner (I think) ... player.Health.Set(Mathf.Min(1, (player.Health.Get() + 1.0f)));
  • ronaldomoonronaldomoon Posts: 18Member
    I've already tried (probably should have mentioned this) using spawn points and no spawn point as well as adding the health via script, but the player still spawns at 99. I think you may be on to something in regard to the problem having to do with the respawner. I noticed that the respawner is also creating respawn FX that I assign at the position that I die rather than where I spawn.

    Thanks for replying. It looks like we may be on the way to identifying a bug in UFPS. I'm just glad to know that it wasn't something I did wrong. ;-)
  • TryThisTryThis Posts: 887Member
    edited March 2016
    @ronaldomoon‌ , I have modified the bsn_paingiver in a way that overcomes the issue. I will post it below for your inspection.

    If you want to kill him right away, in your lava, just go ahead and set Damage to 1 and turn off Vary Damage. Set the Use Timing to on and set a high time value to give the collider time to clear.... to avoid the race condition between the collider and the spawner. I tested with it set to 5 and it seems to work well.

    I know that bsn didn't foresee this dreadful circumstance, and I know that he would have rather been beaten than cause you extra work, ... but that's just tough luck. ha!

    If you want to use the paingiver in it's original form, just turn off the timing feature.

    This really is only a solution to your specific problem. It won't work well for all cases, or for all circumstances, as I am sure BSN knew.

    I wish you the best of luck, as I feel that he would, in pursuit of your goals. I wish that there were a better solution, and perhaps there is, but ... I haven't seen it, or maybe it's just that no one has shared it with us ... that is the most likely scenario, given my experiences with human beings, so I'm not really surprised.


    // Inspired in its entirity by VisionPunks HitscanBullet and VenomUnity's
    // initial script.

    // tt has made some modifications to this version, but it can still function exactly as the original
    // tt changed the class and file name so as not to overwrite the original work of bsn!
    using UnityEngine;
    using System.Collections.Generic;

    [RequireComponent(typeof(AudioSource))]

    public class tt_Modified_bsn_PainGiver : MonoBehaviour

    {
    public float DamageHigh = 0.01f; // the max possible damage to send
    public float DamageLow = 0.001f; // the min possible damage to send
    private float Damage = 1f;
    public string DamageMethodName = "Damage"; // name of damage method on target
    public bool VaryDamage = true;
    protected AudioSource m_Audio = null;
    public List<AudioClip> m_DamageSounds = new List<AudioClip>(); // sounds to randomly play,fire,steam,screams,etc.
    public Vector2 SoundDamagePitch = new Vector2(1.0f, 1.5f); // random pitch for sounds to add variety
    public LayerMask ignoreMask;

    // tt solving for an issue where a player continues to receive damage at respawn.
    // It appears that there is a "race" condition, where the "dead" player is still inside
    // the trigger when respawn occurs, and therefore, damage continues to be dealt to the player.
    // My solution is to use a combination of damage values and time delays to overcome the issue.

    // tt added these vars

    // set this to true to use timing feature for special cases

    public bool useTiming = false;

    // a timer for use in OnTriggerStay
    protected vp_Timer.Handle m_Timer = new vp_Timer.Handle();

    // if intending to kill the subject immediately, set a very high timer value
    // so that the trigger has a chance to clear properly
    public float PainInterval =5f;

    // triggered to true when subject enters the paingiver and false upon exit
    private bool inPainZone = false;

    void Start ()
    {
    m_Audio = audio;

    }

    void OnTriggerEnter(Collider other)
    {
    inPainZone = true;
    // if it's not on the mask, hurt it!
    if (!IsInLayerMask(other.gameObject, ignoreMask)) SendThePain(other);
    }

    void OnTriggerStay(Collider other)
    {
    // tt added this IF and the timing process
    if (useTiming)
    {
    if (!IsInLayerMask(other.gameObject, ignoreMask))
    {
    vp_Timer.In(PainInterval, delegate()
    { SendThePain(other); });
    }
    }
    else
    if (!IsInLayerMask(other.gameObject, ignoreMask)) SendThePain(other);
    }

    // tt added this exit
    void OnTriggerExit(Collider other)
    {
    inPainZone = false;
    m_Audio.Stop();
    }

    void SendThePain(Collider other)
    {
    // tt added this IF
    if (inPainZone)
    {
    // vary the damage or default to highest damage
    if(VaryDamage) Damage = Random.Range(DamageLow, DamageHigh);
    else Damage = DamageHigh;

    if (m_DamageSounds.Count > 0)
    {
    m_Audio.pitch = Random.Range(SoundDamagePitch.x, SoundDamagePitch.y) * Time.timeScale;
    m_Audio.PlayOneShot(m_DamageSounds[(int)Random.Range(0, (m_DamageSounds.Count))]);
    }
    if (other.transform.gameObject.activeSelf == true)
    // do damage on the target
    other.SendMessage(DamageMethodName, Damage, SendMessageOptions.DontRequireReceiver);
    }
    }

    // Convert the object's layer to a bitfield for comparison
    private bool IsInLayerMask(GameObject obj, LayerMask layerMask)
    {
    int objLayerMask = (1 << obj.layer);
    if ((layerMask.value & objLayerMask) > 0) return true;
    else return false;
    }

    }
  • ronaldomoonronaldomoon Posts: 18Member
    That works really well! Thanks, TT! =D>
  • radicalero Posts: 97Member, MPTester
    Works Perfect!!
  • KyleBriggs Posts: 250Member, MPTester
    Does anyone have a killzone that doesn't take damage off immediately, but gives you 10 seconds for example to get out, I know it can be done with Yield.WaitForSeconds but I am having trouble doing this, annoyingly I would also need a GUI countdown onscreen so people can see how much time they have left, this I have no idea how to do,
    any ideas or examples?
    Kyle Briggs
    3D Artist and Realistic Texture Artist
    http://kylebriggs.co.uk/
  • Cool! This can be useful so players won't go out of the map
  • Can't seem to work it out. Every time it respawns it either dies again or has less than 100 health
  • TryThisTryThis Posts: 887Member
    edited November 2014
    You know, I thought that thing about < 100% health got fixed before. I'm sorry about that. I don't get the "dies again" phenomenon, but I do get 99% health consistently. I'll work this out (maybe not today, because of other engagements, but right-quick ... hang tight), because that just won't do! ha! Might have to write some spaghetti code, but we'll get you your 100% health!
  • Right now I just have it to where you have 150 health and then when you die you get 100 every time (because it takes 50 every time) but I'd still like to have it to where I don't have to do that
  • jewjitsu Posts: 9Member
    I attempted to use the modified pain giver made by TryThis and when my player dies the script hurts the player, waits the 5 seconds, then spams the hurt sound killing the player instantly and lags the game out, any clue how to fix this?
  • BrandSpankingNewBrandSpankingNew Posts: 2,321Member, MPTester
    edited May 19
    @jewjitsu , Here is a version that will address your concerns. I will leave it to you to reincorporate anything that I have removed (spamming audio, etc.), but it works flawlessly for me (but naturally I expect someone to find an error or a flaw in some circumstance that I cannot foresee, and if they do then I will do my best to adapt or fix as needed.).

    Here is a short demo of this version.
    https://youtu.be/OgP4rivjnY8


    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;

    [RequireComponent(typeof(AudioSource))]

    public class BSN_PainGiver2 : MonoBehaviour
    {

    public float DamageHigh = 0.1f; // the max possible damage to send
    public float DamageLow = 0.03f; // the min possible damage to send
    public string DamageMethodName = "Damage"; // name of damage method on target
    public bool VaryDamage = true; // a variable damage toggle
    public AudioClip theScream; // one sound only in this version, but you can modify this to a list
    public LayerMask ignoreMask; // don't harm things on this list

    private bool okToPain = false; // assume we won't hurt things
    private float Damage = 0f;
    private AudioSource newAudio;

    // these 2 variables and their usage context are borrowed from the vp_KillZone script
    private vp_DamageHandler m_TargetDamageHandler = null;
    private vp_Respawner m_TargetRespawner = null;


    void Start ()
    {
    newAudio = GetComponent<AudioSource>();
    newAudio.clip = theScream;
    }

    void OnTriggerEnter(Collider other)
    {
    // let's assume that it is not okay to hurt "other"

    okToPain = false;

    // if "other" doesn't have a damage handler we won't waste our time

    m_TargetDamageHandler = vp_DamageHandler.GetDamageHandlerOfCollider(other);
    if (m_TargetDamageHandler == null)
    return;

    // if this "other" is already dead we won't waste our time

    if (m_TargetDamageHandler.CurrentHealth <= 0)
    return;


    // "other" seems to be something that we can hurt so let's check to see if it is respawning
    // before we try to give it any more pain.

    m_TargetRespawner = vp_Respawner.GetByCollider(other);
    if (m_TargetRespawner != null)
    {
    if (Time.time < m_TargetRespawner.LastRespawnTime + 1.0f)
    return;
    }

    // if it's not on the ignoreMask list, then hurt it!

    if (!IsInLayerMask(other.gameObject, ignoreMask))
    {
    okToPain = true;
    SendThePain(other);
    }
    }

    void OnTriggerStay(Collider other)
    {
    if (!okToPain) return;

    if (!IsInLayerMask(other.gameObject, ignoreMask)) SendThePain(other);
    }

    void SendThePain(Collider other)
    {
    // vary the damage or default to highest damage

    if(VaryDamage) Damage = Random.Range(DamageLow, DamageHigh);
    else Damage = DamageHigh;

    // Debug.Log(Damage);

    if (newAudio.clip != null)
    {
    if (!newAudio.isPlaying) newAudio.Play();
    }

    // do damage on the target

    other.SendMessage(DamageMethodName, Damage, SendMessageOptions.DontRequireReceiver);
    }

    // Convert the object's layer to a bitfield for comparison purposes

    private bool IsInLayerMask(GameObject obj, LayerMask layerMask)
    {
    int objLayerMask = (1 << obj.layer);
    if ((layerMask.value & objLayerMask) > 0) return true;
    else return false;
    }

    }


    Beautiful objects are wrought by study through effort, but ugly things are reaped automatically without toil.
    Democritus
Sign In or Register to comment.