Assigning different code to update() of different objects of the same class - processing

Would it be possible to load different code into the update function of different objects of the same class? Ie:
Button button = new Button();
class Button {
// constructor, variables, etc
void update() {
//load code specific to the object
}
}
Could I create a pointer to an external function (ie in a different file)? I know I can't point in java but is there anything similar?

A class is used to define certain behavior. Of course, not all instances of a class have to behave exactly the same (button1 displays red, button2 displays blue, for instance), but it is still the same basic behavior. A button would not act like a tree, and it doesn't make sense to have button1.func() do one thing and button2.func() do something completely different. Having said that, if you want some method of two buttons to do different things, you have two options: either split the behavior into two methods, or (and this is probably what you want) have the buttons contain an indentifier variable and have the method contain a conditional based on that variable. Here's an example:
class Button {
// ID is 1 for green and 2 for blue
int ID;
Button(int id){
ID = id;
}
void update(){
if(ID == 1){ //green
//do something
else if(ID == 2){
//do something else
}
}
}
To answer your question: dynamic code loading (eg from a text file) is a bad idea for lots of reasons. First, it's not clear what the code would do if you read over it (you'd have to go look at another file to find out), and second, it would be a huge security flaw because someone could replace your text file with something malicious and you'd have uncontrolled code execution.

Sample interface code
Button r = new RedButt(); // note Buton = new RedButt...
Button b = new BlueButt();
Button[] buttons = new Button[2];
void setup(){
size(200,200);
buttons[0] = r;
buttons[1] = b;
for(Button b : buttons){
b.display();
}
}
interface Button{
void display();
}
class RedButt implements Button{
RedButt(){
}
void display(){
fill(255,0,0);
ellipse(random(25, width-25), random(25, height -25), 50, 50);
}
}
class BlueButt implements Button{
BlueButt(){
}
void display(){
fill(0, 0, 255);
ellipse(random(25, width-25), random(25, height -25), 50, 50);
}
}

Related

Moving a Prefab in Unity

The purpose of the code is to
make a prefab of "Normal" and "Virus"
make them move in random directions
when they collide, change "Normal" into "Virus" Prefabs
However, I've got stucked on step 2.
I successfuly made "Normal" and "Virus" Prefabs get spawned at random places.
Btw, I have no idea what I should do to supply transform function to Prefabs.
Also, what codes should I use to replace "Normal" Prefabs into "Virus" Prefabs if they collide each other?
These are the codes and pics I used
using UnityEngine;
public class VirusSpawner : MonoBehaviour
{
[SerializeField]
private int objectSpawnCount = 5;
[SerializeField]
private GameObject[] prefabArray;
private void Awake()
{
for (int i = 0; i < objectSpawnCount; ++i)
{
int index = Random.Range(0, prefabArray.Length);
float x = Random.Range(-3, 3);
float y = Random.Range(-4, 4);
Vector3 position = new Vector3(x, y, 0);
Instantiate(prefabArray[index], position, Quaternion.identity);
}
}
}
As I understood what you need is a script for your objects, responsible for moving them and controlling all this "Normal" and "Virus" states.
2.1. Create a C# Script (i.e. "Virus") that moves the object as soon as it exists.
2.2. Right after instantiating your prefabs add this script to it:
GameObject newGO = Instantiate(prefabArray[index], position, Quaternion.identity);
newGO.AddComponent<Virus>();
On the new "Virus" script, add the collision detection and variable that holds the state of Normal or Virus
As #Caio Rocha said you can instantiate new Virus Object and add component to it but there can be one more way which can be faster :
Create a Empty Game Object.
Add both (Virus and Normal) prefabs as children of that prefab and drag and drop to project window to create new Prefab.
Attach this script to Parent:
public class ParentObject : MonoBehaviour
{
public GameObject VirusObject;
public GameObject NormalObject;
private bool isVirus;
private void Awake()
{
isVirus = (Random.Range(0, 2) == 1); //Randomly Make an object
//virus or normal on instantiation
if (isVirus)
{
TurnToVirus();
}
else
{
TurntoNormal();
}
}
private void OnCollisionEnter(Collision other)
{
ParentObject coll =
other.collider.gameObject.GetComponent<ParentObject>();
if (coll && coll.isVirus) //if other object is virus
{
TurnToVirus();
}
}
private void TurnToVirus()
{
VirusObject.SetActive(true);
NormalObject.SetActive(false);
}
private void TurntoNormal()
{
VirusObject.SetActive(false);
NormalObject.SetActive(true);
}
}
Now Inside prefab array move this Parent prefab instead of your own prefabs and it will randomly create normal and virus objects and when they interact normal turn to viruses. Make sure this script and collider is now on parent. This is much optimized over adding component individually just for single check.

Unity - TextMeshPro text object doesn't update

I have been using TMP objects in several instances in my game, but all of a sudden it decides not to work on a certain object.
public class BeforeRoundTimer : MonoBehaviour
{
public TextMeshProUGUI timer;
private Timer oneSecondTimer;
private int time = 5;
public void StartCountdown()
{
Debug.Log("One second timer");
oneSecondTimer = new Timer(1000);
oneSecondTimer.Elapsed += UpdateTime;
oneSecondTimer.Enabled = true;
oneSecondTimer.AutoReset = true;
oneSecondTimer.Start();
}
private void UpdateTime(object source, ElapsedEventArgs e)
{
if(time == 0)
{
oneSecondTimer.Stop();
return;
}
timer.text = $"{time}";
time--;
}
}
I know the text is updating because I put debug statements (I have since removed them) and they fired when UpdateTime() is called. I also viewed the inspector when the game was playing, and the text value would update in front of my eyes. The text only changes when I make some stylistic change to it (i.e. making it bold, changing the font asset, including changing the text itself). I have looked back to my old code and it basically runs the exact same way, but it actually changes in game.
Ok so after taking a break, I decided to find another way to call my method every second. Instead of using a Timer, I decided to use Unity's InvokeRepeating() function.
public class BeforeRoundTimer : MonoBehaviour
{
public TextMeshProUGUI timer;
private int count = 0;
public void StartCountdown()
{
InvokeRepeating(nameof(UpdateTime), 0, 1f);
}
private void UpdateTime()
{
if(count == 5)
{
CancelInvoke("UpdateTime");
return;
}
Debug.Log("Update Time");
timer.text = $"{5 - count}";
count++;
}
}
One thing I noticed when trying to use the Timer in a different way is that it was only updating the text value every other second. It ran 10 times (I put a Debug.Log() in UpdateTime()) but only changed the value every other time while not actually updating the TMP. You could replace nameof(UpdateTime) with "UpdateTime", but Visual Studio recommended that I use the former so I went with that.
In short: don't use timers, use Unity's InvokeRepeating() function because it works perfectly. It is actually very similar to JavaScript's setInterval() which I found interesting.

RayCast not working as expected, help! (Unity 3D)

Okay so I'm making a photography game where when you 'take a photo', Unity sends a few raycasts forward to check if certain tagged items are in the photo (all within the cameras FOV). My problem is, this seems to work intermittently! Sometimes it finds the tagged objects, other times it will be right in front of the view yet it will miss it completely! Can anyone advise about what I'm doing wrong?
public static Transform target;
public static GameObject[] targetName;
public static float length = 250f;
public static Transform thisObject;
// Start is called before the first frame update
void Start()
{
thisObject = GameObject.Find("Main Camera").GetComponent<Transform>();
//target = GameObject.FindGameObjectWithTag("Trees").transform;
}
// Update is called once per frame
void Update()
{
//InFront();
//HasLineOfSight("Trees");
}
public static bool InFront(Transform target1)
{
Vector3 directionToTarget = thisObject.position - target1.position;
float angleOnXAxis = Vector3.Angle(thisObject.right, directionToTarget);
float angleOnYAxis = Vector3.Angle(thisObject.up, directionToTarget);
//Debug.Log(angleOnYAxis);
if (Mathf.Abs(angleOnXAxis) < 130 && Mathf.Abs(angleOnXAxis) > 50
&& Mathf.Abs(angleOnYAxis) < 115 && Mathf.Abs(angleOnYAxis) > 62)
{
//Debug.DrawLine(transform.position, target.position, Color.green);
return true;
}
return false;
}
public static bool HasLineOfSight(string objectTag)
{
RaycastHit hit;
Vector3 direction = target.position - thisObject.position;
//Debug.Log(direction);
if (Physics.Raycast(thisObject.position, direction, out hit, length))
{
if (hit.transform.tag == objectTag)
{
Debug.DrawRay(thisObject.position, direction * 0.96f, Color.red);
return true;
}
else
{
return false;
}
}
else
{
return false;
}
}
public static GameObject SortObjects(string objectTag)
{
targetName = GameObject.FindGameObjectsWithTag(objectTag);
GameObject closestObject = null;
for (int i = 0; i < targetName.Length; i++)
{
if (Vector3.Distance(thisObject.position,
targetName[i].transform.position) <= length)
{
if (InFront(targetName[i].transform))
{
if (closestObject == null)
{
closestObject = targetName[i];
}
else
{
if (Vector3.Distance(targetName[i].transform.position, thisObject.position) <= Vector3.Distance(closestObject.transform.position, thisObject.position))
{
closestObject = targetName[i];
}
}
}
}
}
return closestObject;
}
public static bool ObjectCheck(string objectTag)
{
//Debug.Log(SortObjects(objectTag));
if (SortObjects(objectTag) != null)
{
target = SortObjects(objectTag).transform;
//Debug.Log(target);
if (InFront(target) && HasLineOfSight(objectTag))
{
return true;
}
else
{
return false;
}
}
else
{
return false;
}
}
I'm essentially calling ObjectCheck() with the tag I want to check for to get the closest, visible, object with that tag. What is wrong with this code??
In your script, only the closest object to the main camera gets checked. SortObjects() determines the closest tagged object, and then you only handle that single object in ObjectCheck(). - That object might be obstructed by something else, so the method returns false. And other tagged objects that are actually visible, are not picked up this way...
So, you could rename and change your SortObjects() function to check for both conditions right in the loop (InFront(target) && HasLineOfSight(objectTag)), and filter the objects out right in there, since only those objects are of interest.
Also, your HasLineOfSight() method checks the tag of the hit object, but what you probably wanted to do, is to check if the raycast actually hits that exact object. So it should instead compare the hit's gameObject to the target's gameObject, ignoring the tag, since a correct tag alone isn't enough. (Side note: it would make sense to place all "photographable objects" on a "photo layer", and set the layer mask in the Physics.Raycast() call accordingly, it's more efficient that way in larger scenes.)
The way the angles are calculated in the InFront() method is probably causing issues, because the direction vector to the target is really in 3D. To calculate the angles, you could try to use Vector3.Project() or Vector3.ProjectOnPlane(), but that will also be problematic, because of perspective camera issues.
This check is strongly related to the topic of "frustum culling", a technique usually used for rendering. But it's similar to what you need, to filter out all the (possibly) visible objects in the camera's field of view (frustum culling doesn't handle obstruction, it is just a geometric check to see if a point lies within the camera's frustum space). See:
https://en.wikipedia.org/wiki/Viewing_frustum
https://en.wikipedia.org/wiki/Hidden-surface_determination#Viewing-
http://www.lighthouse3d.com/tutorials/view-frustum-culling/
https://docs.unity3d.com/Manual/UnderstandingFrustum.html
If you want to dig deeper and optimize this, there are a couple of ways this can be done. But luckily, Unity comes with many useful related functions already built into the Camera class. So instead, you could use Camera.WorldToScreenPoint() (or Camera.WorldToViewportPoint()), and compare the resulting screen coordinates to the screen size or viewport, like discussed in Unity forum. (The frustum math is hidden behind these compact functions, but beware that this is probably not the optimal way to do this.)
Instead of calling FindGameObjectsWithTag() every time, you could do it only once in Start(), assuming objects do not get created/destroyed while the game is running.
I've tried to modify your script, since I'm also learning Unity again... The script can be dragged to the main camera, and it should show the "focus object" in the Scene view with the green debug line. I hope this helps:
using UnityEngine;
[RequireComponent(typeof(Camera))]
public class PhotoCast : MonoBehaviour
{
public float maxDistance = 250.0f;
public string objectTag = "photo";
protected GameObject[] objs;
protected GameObject objFocus;
protected Camera cam;
public void Start() {
objs = GameObject.FindGameObjectsWithTag(objectTag);
cam = GetComponent<Camera>();
}
public void Update() {
if (Input.GetButtonDown("Fire1")) {
objFocus = CheckObjects();
if (objFocus) {
Debug.Log("closest object in view: " + objFocus.name);
/* TODO: take actual photo here */
}
}
if (objFocus) {
Debug.DrawLine(transform.position,
objFocus.transform.position, Color.green);
}
}
GameObject CheckObjects() {
GameObject obj_closest = null;
float dist_closest = float.MaxValue;
foreach (GameObject o in objs) {
float dist = Vector3.Distance(
o.transform.position, transform.position);
if (dist < maxDistance && dist < dist_closest
&& InViewport(o.transform.position)
&& HasLineOfSight(o.transform)) {
dist_closest = dist;
obj_closest = o;
}
}
return obj_closest;
}
bool InViewport(Vector3 worldPos) {
Vector3 p = cam.WorldToViewportPoint(worldPos);
return (p.x > 0.0f && p.x <= 1.0f && p.y > 0.0f && p.y <= 1.0f
&& p.z > cam.nearClipPlane);
}
bool HasLineOfSight(Transform target) {
RaycastHit hit;
Vector3 dir = target.position - transform.position;
if (Physics.Raycast(transform.position, dir, out hit, maxDistance)) {
if (hit.collider.gameObject == target.gameObject) {
return true;
}
}
return false;
}
}
Side notes:
Another issue with this technique is, that there can be tagged objects right in front of the camera, but other tagged objects that are closer on the side will be picked up instead of the obvious one. Many small issues to fine-tune until the scripts fits the game, I guess. Instead of only using one Raycast per object, you could use multiple ones, and take the bounding box or the actual collider shape into account.
An improved version of the script could make use of the Physics.Overlap*() or Physics.*Cast*() functions, documented here.

Unity3D Draggable GUI.Box with GUI controls

I'm constructing a custom node editor window in Unity, and I've had a look at various resources such as this one, which uses GUI.Box to construct node windows.
This works great, and I'm able to drag these windows around the way I want, however once I add controls to the GUI.Box, I want them to override the Drag() function I've written.
Here's an example of the issue - When I move the vertical slider up, the entire box drags with it.
Is there a way to fix this behavior using GUI.Box, or should I go back to GUI.Window with its built-in GUI.DragWindow()?
Here's a simplified version of the code I'm using:
EditorMouseInput.cs:
private bool ActionLeftMouseDown()
{
mouseDownNode = editor.GetSelectedNode(Input.current.mousePosition);
if (mouseDownNode == null)
editor.StartMovingEditorCanvas();
else
mouseDownNode.IsSelected = true;
}
BaseNodeEditor.cs:
public BaseNode GetSelectedNode(Vector2 mousePos)
{
foreach (BaseNode node in Nodes)
{
if (node.WindowRect.Contains(mousePos))
return node;
}
return null;
}
public void Drag(Vector2 delta)
{
if (!MoveEditorMode && !ConnectionMode)
{
foreach (BaseNode node in Nodes)
{
node.Drag(delta);
}
}
BaseNode.cs:
public void Drag(Vector2 delta)
{
if (IsSelected)
draggedDistance += delta;
}
The vertical slider is added in the derived JumpNode class. Extract of the helper class that constructs the slider:
Vector2 pos = node.WindowRect.position + rect.position * GridSpacing;
value = GUI.VerticalSlider(new Rect(pos, rect.size * GridSpacing), value, maxValue, minValue);
I can see why this doesn't do what I want, but I don't know how to go about it given the GUI controls aren't part of the GUI.Box.
Any help or suggestions, even a nudge towards another source would be greatly appreciated - I feel I've used all the search terms that exist in my head!
Edit - Solved: Thanks to Kleber for solving this one for me. In case anyone else runs into this or a similar issue, the solution for me was in realising that GUI controls consume left mousedown events automatically, so clicking a slider means there's no propagation to the Box to check if it was clicked.
What I needed to do was separate the IsSelected and IsDragged flags in the Node class, and clear IsDragged on mouseUp. I originally used IsSelected to flag both drag enabled, and selected (multiple nodes could be selected and dragged at once).
It's quite a complex tutorial so I didn't read it entirely, but the problem seems to be the MouseDrag detection. Well, basically you want to stop the event propagation when you click on a GUI element inside the Box, right? To do so, you call:
Event.current.Use()
every time the user drags the mouse on one of your components.
Using the resource you've mentioned, I altered the Node class and added a slider inside the Draw() method, ending like this:
public void Draw() {
inPoint.Draw();
outPoint.Draw();
GUI.Box(rect, title, style);
GUI.BeginGroup(rect);
_value = GUI.HorizontalSlider(new Rect(20, 0, 50, 20), _value, 100, -100);
GUI.EndGroup();
}
Another thing you can do is change how you draw your window. Here it's a simple example that I've tested on the latest Unity version (5.6):
private void OnGUI() {
GUI.Box(_rect, string.Empty);
GUI.BeginGroup(_rect);
_value = GUI.VerticalSlider(new Rect(145, 100, 20, 100), _value, 100, -100);
GUI.EndGroup();
var e = Event.current;
if (e.type == EventType.MouseDrag && _rect.Contains(e.mousePosition)) {
_rect.x += e.delta.x;
_rect.y += e.delta.y;
Repaint();
}
}
As you can see, this example doesn't need an Event.current.Use() to work properly.

Use parameters for variable animation

I'd like to animate my Score-GUI text counting up to a variable value but there are two things in my way:
1: How can I animate to a variable instead of a fixed value?
2: Why can't I add own properties (like int) to my script and animate them?
For #2 I created a property in my script. Yet the editor won't show it in the AddProperty-dialog (as shown below):
public int currentScore = 0;
public int score {
get { return currentScore; }
set { this.currentScore += value; }
}
EDIT: The animator is set up in the most basic way:
Since you only have 1 Animation. An Animator is irrelevant to the solution. This is tested and working. Now you need to make the Animation a Legacy type to get this working because we are not going to use the Animator.
Click the Animation on the Project -> look at the upper right section of the Inspector view, there is a little button there which will drop down a selection. "Debug" then Check the Legacy.
Set your Animation to whatever you want. I force the WrapMode in the script to be wrap mode once. So it will only play once.
Now in the Animation Component make sure you select the Animation that you want by default or it wont work. Cause we only use anim.Play(); Without parameters meaning, run the default animation that is set.
I created a Text UI and added an Animation that alpha is 0 from the start and at the end point making it 1. You have to do that on your own.
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
public class MyScore : MonoBehaviour {
// Use this for initialization
public int currentScore = 0;
public GameObject Myscore; // Drag the GameObject that has the Animation for your score.
public Text myScoreText; //Drag in the Inspector the Text object to reference
public Animation anim;
public int score
{
get { return currentScore; }
set { this.currentScore += value; }
}
void Start()
{
anim = Myscore.GetComponent<Animation>(); // Reference the Animation Component.
anim.wrapMode = WrapMode.Once; // Legacy animation Set to play once
AddScore();
}
public void AddScore()
{
score += 10;
myScoreText.text = score.ToString();
anim.Play();
Debug.Log("Current Score is "+ score);
Invoke("AddScore", 2);
}
}
Good luck.

Resources