I am creating a flappy bird like game, to learn more about game development, i realized that jumping to complex project ideas isnt a good idea. So i abandoned them, and started to create. Not much has passed and i already didnt know what to do, and that is to make my kinematic body flap like in flappy bird.
i wrote this code:
using Godot;
using System;
public class Bird : KinematicBody2D
{
public override void _Ready()
{
}
public override void _Process(float delta)
{
if (Input.IsActionPressed("ui_up"))
{
this.Position += new Vector2(0, -20);
}
else
{
this.Position += new Vector2(0, 20);
}
}
}
but this doesnt make the body flap, it just raises it up if an action is pressed and down if nothing else is happening
Related
Let's say I have two entities, a Player and an Enemy. Each of them would have its own C# script. On the Enemy, it has this basic health manager code:
[SerializeField] float health = 3;
public void TakeDamage(float damage)
{
health -= damage;
if (health <= 0) {
Destroy(gameObject);
// SOME EVENT HERE?
}
}
What I want is for the Player to know he's killed the Enemy (as well as knowing which enemy he destroyed). However, I'm not sure how to do this. I've looked at a few approaches:
Actions/delegates; they would require the Player to 'import' a public action/delegate from the Enemy (if I understand them correctly; I'm still new to C#), and I don't want a dependency between these two things... they're conceptually unrelated; I shouldn't have to 'import' from Enemy on the Player.
Using Unity events (like the ones you configure in UI, for example button OnClick()); this won't work either, because both Player and Enemy might be instantiated at runtime, not pre-defined.
In my head I'm imagining the Player script would have something like this listening to events:
void OnEnemyDestroyed(GameObject enemy) { ...do things in reaction to enemy death here... }
Is this possible?
For this specific situation my advice is using interface. For example:
public interface IUnit
{
void OnEnemyKilled(IUnit enemy);
}
Both player and enemy script will implement this interface, the TakeDamage method also need to append a new parameter with the type IUnit.
class Enemy : IUnit
{
public void TakeDamage(float damage, IUnit attacker = null)
{
health -= damage;
if (health <= 0) {
attacker?.OnEnemyKilled(this);
Destroy(gameObject);
}
}
public void OnEnemyKilled(IUnit enemy){}
}
This approach doesn't only solve the problem, the advantage is at some point, a single damage value is not enough, you may need information about who makes this attack, then you can add additional methods to the interface and execute them in TakeDamage.
Note that you'd better prepose the callback, so that in it you can keep accessing all the properties of the killed entity.
To answer the question in comment, there are many approaches to make sense, you can:
Pass null TakeDamage(10f); (Above method is updated)
Pass a default IUnit implementation
class NotAUnit : IUnit
{
public static readonly NotAUnit Instance = new();
public void OnEnemyKilled(IUnit enemy){}
}
TakeDamage(10f, NotAUnit.Instance);
An overloaded method. (As same as approach 1).
If it is about in general have a global event for listening on the destroying of objects you could have an additional component like
public class DestroyEvent : MonoBehaviour
{
public static event Action<GameObject> OnDestroyed;
private void OnDestroy()
{
OnDestroyed?.Invoke(gameObject);
}
}
So you could just globally attach a listener to
DestroyEvent.OnDestroyed += Listener;
...
private void Listener(GameObject destroyedObject)
{
Debug.Log(destroyedObject);
// e.g. list all attached components
foreach(var component in destroyedObject.GetComponents<Component>())
{
Debug.Log(component.GetType());
}
}
So as soon as an object has that component attached the moment it is destroyed you will receive the callback, regardless of whether it is spawned on runtime or even the component attached afterwards on runtime.
Have in mind that this is just a simple example - you will also get the event currently in case the scene is changed or you exit the app. But you can build on top of this and e.g. globally turn the event on and of with an additional flag etc
So in my game I have an object that I need to move smoothly from Vector3 fromPosition to Vector3 toPosition at speed float speed, and then go back to where it started. All very simple, but to try and make life easier when setting up levels I decided to make a custom inspector for this script with buttons that allow me to set the target positions to the current position of the object, so I can just move it to where it needs to be and click a button, rather that typing in all the coordinates. Thought I had it all working but then started seeing some very strange behaviour, after playing around it seems to be as follows: The first time a button is used all is well. Every time the button is used after that, the values change properly in the inspector, but upon hitting Play the values of toPosition and fromPosition are reverted to what they were the first time the button was used. (They don't revert back again on Stop). However if I type the values in manually, it works perfectly. Very strange, does anyone have any idea what might be happening here? The code for the script and custom inspector are bellow.
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
public class MovingGrapple : MonoBehaviour
{
public Vector3 fromPosition;
public Vector3 toPosition;
public float speed;
Rigidbody thisBody;
Grapple player;
// Start is called before the first frame update
void Start()
{
thisBody = GetComponent<Rigidbody>();
player = GameObject.Find("Head").GetComponent<Grapple>();
}
private void FixedUpdate()
{
thisBody.MovePosition(Vector3.MoveTowards(transform.position, toPosition, Time.fixedDeltaTime * speed));
if(transform.position == toPosition)
{
transform.position = fromPosition;
if (player.activeTarget != null && GetComponentsInChildren<Transform>().Contains(player.activeTarget.transform))
{
player.BreakGrapple();
GameObject.Destroy(player.activeTarget);
}
}
}
public void SetFromPosition()
{
fromPosition = transform.position;
}
public void SetToPosition()
{
toPosition = transform.position;
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
[CustomEditor(typeof(MovingGrapple))]
public class MovingGrappleInspector : Editor
{
public override void OnInspectorGUI()
{
DrawDefaultInspector();
MovingGrapple myTarget = (MovingGrapple)target;
if (GUILayout.Button("Set From position."))
{
myTarget.SetFromPosition();
}
if (GUILayout.Button("Set To position."))
{
myTarget.SetToPosition();
}
}
}
Thanks.
This will not only happen if you press play .. your changes are never saved!
If possible you should never mix editor scripts with direct access to the target except you know exactly what you're doing!
You would especially need to "manually" mark your changed object as dirty. Otherwise the changes are only temporary until your object is deserialized again (Enter/Exit PlayMode or reload of the Scene or asset).
You could before the changes add a Undo.RecordObject
if (GUILayout.Button("Set From position."))
{
Undo.RecordObject(myTarget, "SetFromPosition");
myTarget.SetFromPosition();
}
if (GUILayout.Button("Set To position."))
{
Undo.RecordObject(myTarget, "SetToPosition");
myTarget.SetToPosition();
}
Also (sounds unlikely in your use-case but)
Important: To correctly handle instances where objectToUndo is an instance of a Prefab, PrefabUtility.RecordPrefabInstancePropertyModifications must be called after RecordObject.
In general rather always go through SerializedProperty where possible like e.g.
[CustomEditor(typeof(MovingGrapple))]
public class MovingGrappleInspector : Editor
{
private SerializedProperty fromPosition;
private SerializedProperty toPosition;
private MovingGrapple myTarget
private void OnEnable()
{
fromPosition = serializedObject.FindProperty("fromPosition");
toPosition = serializedObject.FindProperty("toPosition");
myTarget = (MovingGrapple)target;
}
public override void OnInspectorGUI()
{
DrawDefaultInspector();
// This loads the current real values into the serialized properties
serializedObject.Update();
if (GUILayout.Button("Set From position."))
{
// Now go through the SerializedProperty
fromPosition.vector3Value = myTarget.transform.position;
}
if (GUILayout.Button("Set To position."))
{
toPosition.vector3Value = myTarget.transform.position;
}
// This writes back any changes properties into the actual component
// This also automatically handles all marking the scene and assets dirty -> saving
// And also handles proper Undo/Redo
serializedObject.ApplyModifiedProperties();
}
}
I am trying to get a conveyor belt to reverse its direction when the player presses a button.
Here's the code for the conveyor belt
using UnityEngine;
using System.Collections;
public class Conveyor : MonoBehaviour {
public float speed = 1.0f;
void OnTriggerStay(Collider col)
{
col.transform.position += transform.forward * speed * Time.deltaTime;
}
}
And the code for the button
public class PushButton : MonoBehaviour
{
public GameObject Button;
private Conveyor conveyor;
void Awake ()
{
conveyor = GetComponent<Conveyor>();
}
void OnTriggerStay(Collider entity)
{
if (entity.tag == "Player")
{
if (Input.GetKeyUp(KeyCode.E))
{
conveyor.speed = conveyor.speed * -1;
}
}
}
}
I'm getting an error saying "Object Reference not set to the instance of an object PushButton.OnTriggerStay (Unity Engine.Collider entity) (at Assests/PushButton.cs21)
I'm still not very familiar with using getComponent so I'm not sure how to fix this. Any help would be appreciated.
GetComponent will need a reference to an initialized object and a component or class that object has assigned. For what you are wanting, you will want to find a game object in the scene. Because you are already specifying that your GameObject has a Conveyor class assigned to it, find your GameObject and then specify the Conveyor component.
void Awake ()
{
conveyor = GameObject.FindWithTag("Conveyor").GetComponent<Conveyor>();
}
This should do the trick pending you have tagged your Conveyor game object with a 'Conveyor' tag.
However, there is an even easier way to quickly "grab" something like this. Be careful though!
void Awake()
{
conveyor = Object.FindObjectOfType<Conveyor>();
// ONLY DO THAT IF THERE IS ONLY >>ONE<< OF THE THING!
}
Here's a little essay on that. http://answers.unity3d.com/answers/46285/view.html
You often do that for example to find "boss" objects .. scene managers and the like.
Don't forget though, you can always just use a public variable, and drag inteh Inspector - that's always a sound idea for beginners working with Unity.
I read many pages on google and everything else I could find but nothing helped me, so I have to ask here: I made a script attached to 100 cubes it says:
using UnityEngine;
using System.Collections;
public class IfClose:MonoBehaviour{
public bool Gravitronned=false;
// Use this for initialization
void Start(){
}
// Update is called once per frame
void Update(){
}
void OnTriggerEnter(Collider col)
{
if(col.gameObject.name =="IfInScreenGrv"){
Gravitronned=true;
}
else
{
Gravitronned=false;
}
}
}
and then I have another script that says:
using UnityEngine;
using System.Collections;
public class TimeFreeze:MonoBehaviour{
static bool GravitronedTwice;
// Use this for initialization
void Start(){
}
void Update()
{
GravitronedTwice= gameObject.GetComponent<IfClose>().Gravitronned;
if(GravitronedTwice=true){
if(Input.GetKeyDown(KeyCode.V)){
Physics.gravity =newVector3(0,3.0F,0);
}
}
}
}
so when I press V I want the cube only in this area to get Physics.gravity = new Vector3 (0, 3.0F, 0);
You're calling the value of the bool variable Gravitronned in the second script using GetComponent<>() which is not the right way of going about this.
How about instead of having two bool variables, Gravitronned and GravitronnedTwice, you only have 1 variable in the first script, make it public [even though make it public a bad idea - but that's another topic altogether] and have it access to both the scripts, instead of what you're doing. That way you can change the bool value, as well as access it in both scripts.
Alternatively, you can make the bool Gravitronned private in Script 1, and access it in any other script via C# property.
Script 1
private bool Gravitronned;
public BOOL GRAVITY
{
get Gravitronned;
set Gravitronned;
}
I am writing a State Manager script for Unity in C#. Everything appears to be correct but when I test it out inside Unity, all the Debug.Log lines output twice. I'm following along with a book called Learning C# by Developing Games with Unity 3D Beginner's Guide. I have studied and studied the reference and I do not see what I am doing wrong. The script is far from finished, I think, but according to the text there should only be one output per Log.
This is my ISBase for the interface.
namespace Assets.Code.Interfaces
{
public interface IStateBase
{
void StateUpdate();
void ShowIt();
void StateFixedUpdate();
}
}
This my the BeginState, there is also a PlayState, WonState, and LostState. They're all pretty much identical except for the class name, the constructor name, the Debug.Log output, and the the new SwitchState.
using UnityEngine;
using Assets.Code.Interfaces;
namespace Assets.Code.States
{
public class BeginState : IStateBase
{
private StateManager manager;
public BeginState (StateManager managerRef) //Constructor
{
manager = managerRef;
Debug.Log ("Constructing BeginState");
}
public void StateUpdate()
{
if (Input.GetKeyUp (KeyCode.Space))
manager.SwitchState (new PlayState (manager));
}
public void ShowIt()
{
}
public void StateFixedUpdate()
{
}
}
}
And here is the actual StateManager.
using UnityEngine;
using Assets.Code.States;
using Assets.Code.Interfaces;
public class StateManager : MonoBehaviour
{
private IStateBase activeState;
void Start ()
{
activeState = new BeginState (this);
}
void Update ()
{
if (activeState != null)
activeState.StateUpdate ();
}
public void SwitchState(IStateBase newState)
{
activeState = newState;
}
}
IF it prints twice, it means that the call is done twice.
As obvious as it may sound, it is a matter to track it down, and I believe Unity will point you to the line of code called, if you double click on the console entry in the editor. This should open your IDE, or if it is open, just point the IDE to the line called.
Based on that, you may figure out that you have the same script on multiple objects; or you may have the same debug.log call in 2 different places of the script, but that are executed close to each other, ending up with you seeing the same statement.
Not much that can be done, without put a breakpoint and follow step by step
have you checked whether you have two same scripts attached to the same game object accidentally?
usually the duplicate log occurs for me when i got same script attached twice without me noticing it.
on the other hand, i would suggest guarding input from happening when switching state. anything to do with user input will need some guard from rapid repetition.
e.g.
if (!switchingStateCoolDown) {
Input.GetKeyUp (KeyCode.Space) { ... };
}
// hope you get the idea.