Access Static C# Variable from Javascript in Unity - javascript

I am trying to access C# static variable from javascript in unity 3D. How do i achieve it? I wanna get the score for me to use it in the javascript.
Score.cs
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
public class Score : MonoBehaviour {
public Text txt;
public static int score;
public GameObject gameobj;
void Start () {
txt = gameobj.GetComponent<Text>();
txt.text = "Score : " + score;
}
void Update () {
txt = gameobj.GetComponent<Text> ();
txt.text = "Score : " + score;
score = score + 1;
}
int GetScore(){
return score;
}
}
Collide.js
#pragma strict
function OnTriggerEnter(otherObj: Collider){
var scoreScript : Score;
scoreScript = GetComponent("Score");
int score = scoreScript.GetScore();
if (otherObj.tag == "Wall"){
Debug.Log(score);
Application.LoadLevel(2);
}
else if (otherObj.tag == "End"){
Application.LoadLevel(6);
}
}

Let me tell you the answer in points:
C# code is compiled before JS code
in general, JS code can access C# classes, the opposite is not possible.
However, you can affect the order of compilation by moving scripts into special folders which are compiled earlier. You could move your JS script to a folder called "Plugins" then it works.
You can also see it (1) for more clarification while for detail guide of all steps available here (2)

Related

Tanks! Unity currently trying to add a health pick up item

void OnTriggerEnter(Collider other)
{
if (other.gameObject.CompareTag ("HealthPickUp"))
{
m_CurrentHealth += amount;
other.gameObject.SetActive (false);
SetHealthUI ();
}
if (m_CurrentHealth <= 0f && !m_Dead)
{
OnDeath ();
}
}
trying to get it that if the tank hits the pick up item it will gain 20 pointa but as of right now ive got no clue what to do
To get collisions working in Unity, you need to do the following:
Attach a Collider component to each object you want to be part of the collision. This can be a BoxCollider, SphereCollider, etc. Whatever shape makes sense.
For trigger collisions (i.e. non-physics collisions), enable the Is Trigger value in the inspector on each collider component you created in step 1.
Attach a RigidBody component to the object you want to be colliding into other things. RigidBody components are expensive, and you don't want them all over the scene, so I would suggest you put it on your Tank. You may also have to turn on the Is Kinematic property, otherwise your Tank may behave improperly (it's hard to tell with such little information in your setup).
Attach a script to your Tank that has implemented the method OnTriggerEnter. An example is below.
(Optional, strongly recommended) I would suggest you create another script and attach it to your health pickup. I will demonstrate with an example below.
(Optional) You can use layer based collisions to filter what kinds of collisions you detect to make it more efficient.
public class Tank : MonoBehaviour
{
// Setup the initial health values in the editor.
[SerializeField] private int _initialHealth = 100;
[SerializeField] private int _maxHealth = 100;
private int _health = -1;
private void Awake()
{
_health = _initialHealth;
}
private void Update()
{
// You probably want to check this in Update, since you want the tank
// to die as soon as it happens, not just when you pick up a piece of health.
if (_health <= 0)
{
OnDeath();
}
}
private void OnDeath()
{
// Your death logic.
}
private void OnTriggerEnter(Collider other)
{
if (other.gameObject.CompareTag("HealthPickup"))
{
// Get the HealthPickup component and then add the amount to the tank's
// health. This is more robust because you can now create HealthPickup
// objects with a variable amount of health.
HealthPickup healthPickup = other.gameObject.GetComponent<HealthPickup>();
if (healthPickup != null)
{
_health = Mathf.Clamp(_health + healthPickup.amount, 0, _maxHealth);
healthPickup.OnPickup();
}
}
}
}
public class HealthPickup : MonoBehaviour
{
[SerializeField] private int _amount = default;
public int amount { get { return _amount; } }
public void OnPickup()
{
Destroy(gameObject);
}
}
If you follow these steps, you should have collisions working and a flexible system for giving your Tank health pickups.

Is there a workaround for Script Notify not working on UWP ms-appdata

I am developing an application for Xamarin.UWP which is trying to inject Javascript into a local html file (uri: ms-appdata:///local/index.html) like so:
async void OnWebViewNavigationCompleted(WebView sender, WebViewNavigationCompletedEventArgs args)
{
if (args.IsSuccess)
{
// Inject JS script
if (Control != null && Element != null)
{
foreach (var f in Element.RegisteredFunctions.Where(ff => !ff.IsInjected))
{
await Control.InvokeScriptAsync("eval", new[] { string.Format(JavaScriptFunctionTemplate, f.Name) });
f.Injected();
}
}
}
}
Then when the Javascript method is called this will call the OnWebViewScriptNotify method so that I can proccess the request in my application.
The trouble is this doesnt work for some kind of security reasons:
This was a policy decision we made that we have had feedback on so we
re-evaluate it. The same restriction doesn't apply if you use
NavigateToStreamUri together with a resolver object. Internally that's
what happens with ms-appdata:/// anyway.
I then tried what is advised in this case which was to use a resolver as mentioned here: https://stackoverflow.com/a/18979635/2987066
But this has a massive affect on performance, because it is constantly converting all files to a stream to load in, as well as certain pages loading incorrectly.
I then looked at using the AddWebAllowedObject method like so:
private void Control_NavigationStarting(WebView sender, WebViewNavigationStartingEventArgs args)
{
if (Control != null && Element != null)
{
foreach (var f in Element.RegisteredFunctions)
{
var communicator = new HtmlCommunicator(f);
Control.AddWebAllowedObject("HtmlCommunicator", communicator);
}
}
}
Where HtmlCommunicator is:
[AllowForWeb]
public sealed class HtmlCommunicator
{
public JSFunctionInjection Function { get; set; }
public HtmlCommunicator(JSFunctionInjection function)
{
Function = function;
}
public void Fred()
{
var d = 2;
//Do something with Function
}
}
and in my html it is like so:
try { window.HtmlCommunicator.Fred(); } catch (err) { }
But this doesn't work either.
So is there a way to work around this rediculous limitation?
So I found this answer: C# class attributes not accessible in Javascript
It says:
I believe you need to define the method name starting with a lower
case character.
For example: change GetIPAddress to getIPAddress.
I tested it on my side and found if I use the upper case name
'GetIPAddress', it won't work. But if I use getIPAddress, it works.
So I tried this:
I created a new project of type Windows Runtime Component as suggested here and I changed my method names to lower case so I had:
[AllowForWeb]
public sealed class HtmlCommunicator
{
public HtmlCommunicator()
{
}
public void fred()
{
var d = 2;
//Do something with Function
}
}
In my javascript I then had:
try { window.HtmlCommunicator.fred(); } catch (err) { }
and in my main UWP project I referenced the new Windows Runtime Component library and had the following:
public HtmlCommunicator communicator { get; set; }
private void Control_NavigationStarting(WebView sender, WebViewNavigationStartingEventArgs args)
{
if (Control != null && Element != null)
{
communicator = new HtmlCommunicator();
Control.AddWebAllowedObject("HtmlCommunicator", communicator);
}
}
And this worked!

QML reports ReferenceError: XYZ is not defined on C++ object added to context

I've started learning QML recently (after trying it out a long time ago) and I'm stuck at the way Qt C++ code interacts with QML and vice versa.
I have a Counter which has the following header:
#include <QObject>
#include <QTimer>
class Counter : public QObject
{
Q_OBJECT
Q_PROPERTY(int count
READ getCount
WRITE setCount
NOTIFY signalCountChanged)
public:
Counter(QObject *parent = Q_NULLPTR);
int getCount();
void setCount(int count);
signals:
void signalCountChanged(int);
public slots:
void slotStart();
private slots:
void slotTimeout();
private:
int count;
QTimer *timer;
};
My main.cpp is as follows:
#include <QtGui/QGuiApplication>
#include <QtQml/QQmlContext>
#include <QtGui/QGuiApplication>
#include <QtQuick/QQuickItem>
#include <QtQuick/QQuickView>
#include "counter.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
qmlRegisterType<Counter>("org.qmlplayground.counter", 0, 1, "Counter");
QQuickView view;
view.setSource(QUrl(QStringLiteral("qrc:/main.qml")));
QObject *viewO = dynamic_cast<QObject*>(view.rootObject());
Counter c;
// Register Counter instance as "counter" property of top level context so that it can be accessed from within the QML code (for example: set the value count)
view.rootContext()->setContextProperty("counter", &c);
QObject::connect(viewO, SIGNAL(signalStartCounter()),
&c, SLOT(slotStart()));
QObject::connect(viewO, SIGNAL(signalQuit()), &app, SLOT(quit()));
view.show();
return app.exec();
}
and finally part of my QML (the main.qml which is loaded in the QQuickView while the rest being an UI form):
import QtQuick 2.7
import QtQuick.Window 2.2
// Importing some JavaScript files
import "qrc:/loggingFunctions.js" as LOG
import "qrc:/parseFunctions.js" as PARSE
// Importing a Qt C++ custom module
import org.qmlplayground.counter 0.1
MainForm {
property int countState: counter.count // ERROR HERE
signal signalStartCounter()
signal signalQuit()
anchors.fill: parent
textInputMouseArea.onClicked: {
LOG.logger("Clicked! Selecting all text in text input field", "N")
textInput.selectAll()
}
textInput.onAccepted: {
if(textInput.text === "quit") signalQuit()//Qt.quit();
if(textInput.text === "help") textInput.text = LOG.logger("Displaying help", "H");
var res = PARSE.parseInput(textInput.text);
if(res && (typeof res === 'object') && res.constructor === Array) {
switch(res[0]) {
case "fact":
labelOutput.text = res[1];
break;
case "count":
counter.count = res[1];
signalStartCounter();
break;
}
}
}
onCountStateChanged:
console.log("Hello")
textInput.onTextChanged:
console.log("Text changed");
}
As you can see I've already tested two signals sent from my QML to my C++ code one being connected to my QGuiApplication's slot quit() and the other being connected to my counter's slot slotStart(). It works fine. It seems that the line
counter.count = res[1];
doesn't cause any issues (perhaps because it's JS and not QML?). Now I want to read the count value of my Counter instance and update my UI accordingly. If I'm not mistaken every QML property automatically gets a couple of things one of these being the onChanged method (event handler or whatever it's called).
When I run my code however I get
`qrc:/main.qml:21: ReferenceError: counter is not defined
I thought that doing view->rootContext()->setContextProperty("counter", &c); would be enough but it seems that I'm missing something. So the more general question would be how do I properly make a C++ object visible in QML context.
This took me perhaps 2 hours to figure out (I posted my question when I was on the verge of commiting a suicide :D) but the answer was really obvious: how can I call for a property which hasn't been initialized yet? The solution to my problem is basically to move the setContextProperty() BEFORE I load the QML file:
// ...
QQuickView view;
Counter c;
view.rootContext()->setContextProperty("counter", &c);
view.setSource(QUrl(QStringLiteral("qrc:/main.qml")));
// ...
By doing so the property is first added to the root context of the view and after that the additional QML stuff is loaded but the counter property is still present). With the previous version of my code I was basically trying to access counter inside my QML file BEFORE I have added it as a property.

Unity Scripting global variable

I have a script called Death which re spawns the player at the beginning location when the collision is true. I am trying to make a score count that when this collision is true it will minus 100 points but have been unsuccessful. The script bellow if from the score and death script. Any help would be much appreciated.
Score script:
var gui : GameObject;
static var score : int;
Death.death = false;
function Start ()
{
gui.GetComponent ("GUIText").text = "Score: 0";
}
function Update ()
{
gui.GetComponent ("GUIText").text = "Score: " + score;
if (death)
{
score = score - 100;
}
}
Death Script:
#pragma strict
var Ball : Transform;
public var death : boolean = false;
function OnCollisionEnter (b : Collision)
{
if (b.gameObject.tag == "Ball")
{
death = true;
Ball.transform.position.x = 1.6;
Ball.transform.position.y = 1.5;
Ball.transform.position.z = 1.1;
Ball.GetComponent.<Rigidbody>().velocity.y = 0;
Ball.GetComponent.<Rigidbody>().velocity.x = 0;
Ball.GetComponent.<Rigidbody>().velocity.z = 0;
}
}
I hope, that I can help you even though I'm using C#. It should be very easy to translate this to UnityScript.
using UnityEngine;
public class Score : MonoBehaviour
{
public GUIText guiText;
int score;
void Update()
{
if(DeathTrigger.wasTriggered)
{
DeathTrigger.wasTriggered = false;
score -= 100;
}
guiText.text = string.Format("Score: {0}", score);
}
}
public class DeathTrigger : MonoBehaviour
{
public static bool wasTriggered;
void OnCollisionEnter(Collision other)
{
if (other.gameObject.CompareTag("Ball"))
{
wasTriggered = true;
// ...
}
}
}
I assume this is a beginner's questions, so I won't say anything about how static variables are evil and so on, but I still want to post an example of where to go next for a better approach:
using System;
using UnityEngine;
using UnityEngine.UI;
public class BetterDeathTrigger : MonoBehaviour
{
// This event will be called when death is triggered.
public static event Action wasTriggered;
void OnCollisionEnter(Collision other)
{
if (other.gameObject.CompareTag("Ball"))
{
// Call the event.
if (wasTriggered != null)
wasTriggered();
// ...
}
}
}
public class BetterScore : MonoBehaviour
{
public Text uiText; // Unity 4.6 UI system
int score;
void Start()
{
// Subscribe to the event.
BetterDeathTrigger.wasTriggered += WasTriggered_Handler;
}
// This method will now be called everytime the event is called from the DeathTrigger.
private void WasTriggered_Handler()
{
score -= 100;
uiText.text = string.Format("Score: {0}", score);
}
}
A couple of things:
GUIText is pretty old and was already replaced by the new UI system since Unity version 4.6
Static variables are not smart in the long run, prefer instances of objects unless you are very sure how statics work
This is good example of where to use events. Again, it being static might lead to problems but for the first example it's the easiest.
The Unity Learn site offers a lot of tutorials about programming concepts such as "Communicating between scripts", they also have basic game examples where you can follow along with a complete project.
It's definitely worth trying what Xarbrough suggested to go with instead. Statics can be confusing and get in the way in the long run. But here's how you can do it, written in Javascript.
public class Death { // you can change the class name to something that is broad enough to hold several public static variables
public static var death : boolean;
}//This will end this class. When you make public classes like this, the script doesnt even need to be attached to a object, because it doesn't use Mono behavior
//This would be the actual DeathScript, whats above is technically not part of the Death script
var Ball : Transform;
function OnCollisionEnter (b : Collision) {
if (b.gameObject.tag == "Ball"){
Death.death = true;
Ball.transform.position.x = 1.6;
Ball.transform.position.y = 1.5;
Ball.transform.position.z = 1.1;
Ball.GetComponent.<Rigidbody>().velocity.y = 0;
Ball.GetComponent.<Rigidbody>().velocity.x = 0;
Ball.GetComponent.<Rigidbody>().velocity.z = 0;
} }
From there, anytime you want to access the static variable, just tell it where to look. Death.death.
Hope this helps!

Pass data by hostpage error:JavaScriptObject$ cannot be cast to com.pkg.model

I followed this article to use hostpage to pass an array to client:
https://developers.google.com/web-toolkit/articles/dynamic_host_page
Currently,I can see follow content in firebug
<html style="overflow: hidden;">
<head>
......
<script type="text/javascript">
var rcmdFriends=[{"Name":"Friend-0","Image":"url"}];
</script>
</head>
......
</html>
Then I tried to use these code to get js variable(a json array actually) from hostpage and print it to user:
//get array from host page
private native JsArrayExt<People> getRecommendedFriends()/*-{
return $wnd.rcmdFriends;
}-*/;
#Override
public void onModuleLoad()
{
final FlowPanel fPanel = new FlowPanel();
JsArrayExt<People> channels = getRecommendedFriends();
for (int i = 0, len = channels.length(); i < len; i++)
{
//"print" name to user
fPanel.add(new Label(channels.get(i).getName()));
}
RootPanel.get().add(fPanel);
}
//model definition
#SingleJsoImpl(PeopleImpl.class)
public interface People extends HasName
{
String getImage();
void setImage(String Image);
}
But got this eror:
java.lang.ClassCastException: com.google.gwt.core.client.JavaScriptObject$ cannot be cast to com.pkg.People
Strangely,I can already see the length of "channels" is 1,and why do I get this casting error?How to solove this problem?
You cannot cast to an ordinary Java pojo. You must implement an overlay type
public class PersonJSON extends JavaScriptObject {
protected PersonJSON() {
}
public final native String getName() /*-{
return this.Name;
}-*/;
public final native String getImage() /*-{
return this.Image;
}-*/;
}
Then you can call
JsArray<PersonJSON> channels = getRecommendedFriends();
and read out the values from the PersonJSON elements;
Assuming JsArrayExt is the interface from Why can't I define interface for overlay type lightweight collections?, I suppose that the fact you do not use an explicit JSO subclass confuses the DevMode.
Because you directly call a JSNI method, I don't understand why you don't use a JsArrayExtImpl<PersonImpl> which I believe would Just Work™; there's no point in using the interfaces here.
If you really can't make it work, I'd suggest using AutoBeans instead (it unfortunately requires a small serialize/parse dance in DevMode: AutoBeanCodex.decode(factory, Person.class, new JSONObject(rawJso).toString()), whereas in prod mode you can simply use AutoBeanCodex.decode(factory, Person.class, (JsoSplittable) rawJso)). In your case, it'd require another dance because you're using an array as the root object; see GWT Autobean - how to handle lists?

Categories