Unity 2D: NullReferenceException. How to add two manager scripts? - animation

I am quite new to Unity, so please bear with my horrible explanation. I have followed a tutorial on youtube about a 2D fighting game: https://www.youtube.com/watch?v=n8S3WgVoOmo&t=3319s
I will provide my code below.
In the linked video, the video-maker makes one PlayerManager script which controls both my player and duplicates of my player. Through a simple AI, the duplicate becomes my enemy and we fight. This much works as expected.
Now I wanted to change it a bit like a 2D platformer. I took the PlayerManager and split it into two. AIManager (same code as in tutorial overall) and a PlayerManager with some changes. This also worked well and I am able to move and the AI also recognises me. The problem comes from a script called DamageScript. It recognizes if and when I take damage, and triggers the relevant animation.
When the DamageScript was connected to just the PlayerManager, when I hit the AI or AI hit me, whoever got hit showed the Damage animation. But after the split I did, with the separate Managers for AI and Player, I have two options. Either I hit the AI, he does Damage animation and when he hits me I get and error. Or he hits me, I have the Damage animations and when I hit him I get an error. The error is this:
NullReferenceException: Object reference not set to an instance of an object
DamageScript.OnTriggerEnter2D (UnityEngine.Collider2D col) (at Assets/Scripts/DamageScript.cs:19)
This is the original DamageScript:
public class DamageScript : MonoBehaviour {
void OnTriggerEnter2D(Collider2D col)
{
if(col.transform.root != transform.root && col.tag != "Ground" && !col.isTrigger)
{
if (!col.transform.GetComponent<AIManager>().damage && !col.transform.GetComponent<PlayerManager>().blocking)
{
col.transform.GetComponent<AIManager>().damage = true;
col.transform.root.GetComponentInChildren<Animator>().SetTrigger("Damage");
}
}
}
}
This way my Player does the Damage animation, I get the error when I hit. So, I figured something like this would work, but I guess I don't really know how to code it:
void OnTriggerEnter2D(Collider2D col)
{
if(col.transform.root != transform.root && col.tag != "Ground" && !col.isTrigger)
{
line 11 if (!col.transform.GetComponent<AIManager>().damage && !col.transform.GetComponent<AIManager>().blocking)
{
col.transform.GetComponent<AIManager>().damage = true;
col.transform.root.GetComponentInChildren<Animator>().SetTrigger("Damage");
}
else
{
if (!col.transform.GetComponent<PlayerManager>().damage)
{
line 19 col.transform.GetComponent<PlayerManager>().damage = true;
col.transform.root.GetComponentInChildren<Animator>().SetTrigger("Damage");
}
}
}
}
}
Not surprisingly, it doesn't work and I still get crashes on some hits.
Any help would be appreciated, if possible.
Thank you!

From the situation you've described, I'll assume the error is being, primarily, by faulty refactoring of responsibility delegation.
More specifically, I believe it's sufficiently clear that the tutorial you've followed didn't follow single responsibility principle properly, and instead, implemented multiple responsibilities (player and AI) on a single class/file, to save time for the video, or to simplify the tutorial.
Later, when splitting the responsibilities to two scripts and two objects, you, as a beginner, was/is unaware of some details and/or pitfalls involved in reference management, and so are failing to assign references to both objects, or failing to handle missing references as the scripts are now split.
The problem arises because, if your player and AI now have different sets of scripts; one with each manager, but neither with both (unlike before, when both player and AI objects had "both"); then, either on the first or second if*manager.damage statement, the manager in question would not be found with GetComponent because it's not in that object, and upon trying to access fields/properties/methods on a null reference, the NullReferenceException would be thrown.
The solution is simply to do the proper null checks before accessing such fields/properties/methods, to guard from the exception and proceed to the second if statement if the first's manager is found to be null.
While at it, might as well cache the queries to make things better, as RetiredNinja recommended in the comments.
Code:
void OnTriggerEnter2D(Collider2D col) {
if(col.transform.root != transform.root && col.tag != "Ground" && !col.isTrigger) {
//Cache to avoid multiple queries and to simplify access
var playermanager = col.transform.GetComponent<PlayerManager>(); //One of these won't be found and will receive null instead
var aiManager = col.transform.GetComponent<AIManager>(); //One of these won't be found and will receive null instead
var animator = col.transform.root.GetComponentInChildren<Animator>();
if (aiManager != null //Null-check was missing
&& !aiManager.damage && !aiManager.blocking) { //Much nicer
aiManager.damage = true;
if(animator!=null)
animator.SetTrigger("Damage");
}
else {
if (playerManager != null && !playerManager.damage) {
playerManager.damage = true;
animator.SetTrigger("Damage");
}
}
}
}

Related

Processing - If statement and ids

I'm an Italian student and I'm new in programming. I need your help for a school project.
I'm making a blob tracking program using Daniel Shiffman's tutorials. Currently I have 2 blobs on the screen. I am identifying them with 2 IDs: number 0 and number 1.
I need to put some conditions on those blobs: if one blob is in a certain part of the screen and the other one is in another part, I need to call a function.
I don't know how to put the if conditions separately for the two ids. Below is some pseudo code of what I would like to achieve:
for (id==0)
if (...) and
for (id==1)
if(...) then {
void()
}
I would really appreciate any help!
I don't really know where you want the blobs to be when the desired function fires, but I can try to give you an example...
Blob
Assign some sort of position variable, in this case PVector, to your blob object.
class Blob {
PVector position;
Blob (PVector position) {
this.position = position;
}
void update() {
*random movements, etc...*
}
}
Create two blob objects
Create two objects and assign a position to each of them.
Blob[] blobs = new Blob[2];
void setup() {
size(400, 400);
blobs[0] = new Blob(5, new PVector(40, 40));
blobs[1] = new Blob(13, new PVector(100, 100));
}
Check if blobs is at left or right side of the screen
I check if blob[0] is at the left side of the screen and if blob[1] is at right side of the screen. If they are, at the same time, the desiredFunction(); will fire.
void draw() {
for (int i = 0; i < blobs.length; i++) {
blobs.update();
}
if (blobs[0].position.x < (width / 2) && blobs[1].position.x < (width / 2) {
desiredFunction();
}
}
Remember
This is just an example. You could of course check other parts of the screen instead of the left and right parts. You can also use IDs on your blobs instead of an array, I just thought it was better to just use an array in this case.
PS: I wrote this answer without having processing started. The code has certainly a couple of typing errors.
For the example you have described, you can achieve this using the && operator in one if statement.
First assign the conditions you want to test to boolean variables. For example, create the boolean variables id0IsThere, and id1IsThere, and set them to true if the blobs are in the locations you want them to be in. Then use the following if statement:
if (id0IsThere && id1IsThere) {
yourFunction();
}
The && operator means that the code inside the if statement that executes yourFunction() is only executed if both conditions are true. In this case, if both blobs are in the positions you want them to be in. Hope that helps. Read more about if statements and the && operator here:
https://processing.org/reference/if.html
https://processing.org/reference/logicalAND.html

Check of checkmate in chess

I am using this object oriented design of chess. I have implemented generating of valid moves for all pieces. Now I am trying implement check of checkmate.
I tried to make a method, which if player have moves, which cancel the checkmate. But the program end with StackOverflowError.
I delete the method. But the pseudoalgorithm of the method was something like that
boolean isGameOver(arg){
if(playerIsInCheck){
if(!hasValidMoves){
print("checkmate");
return true;
}
else{
return false;
}
}
else{
if(!hasValidMoves){
print("stalemate");
return true;
}
else{
return false;
}
}
}
I don't know how to check if the move cancel the checkmate. Can anyone advise me? I do not need all the code written in any programming language. Pseudoalgorithm will be sufficient.
The algorithm for checking for checkmate is as follows:
public boolean checkmated(Player player) {
if (!player.getKing().inCheck() || player.isStalemated()) {
return false; //not checkmate if we are not
//in check at all or we are stalemated.
}
//therefore if we get here on out, we are currently in check...
Pieces myPieces = player.getPieces();
for (Piece each : myPieces) {
each.doMove(); //modify the state of the board
if (!player.getKing().inCheck()) { //now we can check the modified board
each.undoMove(); //undo, we dont want to change the board
return false;
//not checkmate, we can make a move,
//that results in our escape from checkmate.
}
each.undoMove();
}
return true;
//all pieces have been examined and none can make a move and we have
//confimred earlier that we have been previously checked by the opponent
//and that we are not in stalemate.
}
I can't tell you why you are getting a stack overflow without seeing your method definitions, but I can explain how you check for mate-cancelling moves ( no pseudocode, sorry).
Basically, you generate a List of all possible Moves (Pseudolegals) and you let your programm try each of them. If the players king is no longer hittable in the resulting position (in your case you use the IsInCheck method), the current move is cancelling the mate.
If you do need the pseudocode, write a comment and I'll see what I can do.

Flixel Game Over Screen

I am new to game development but familiar with programming languages. I have started using Flixel and have a working Breakout game with score and lives.
I am just stuck on how I can create a new screen/game over screen if a player runs out of lives. I would like the process to be like following:
Check IF lives are equal to 0
Pause the game and display a new screen (probably transparent) that says 'Game Over'
When a user clicks or hits ENTER restart the level
Here is the function I currently have to update the lives:
private function loseLive(_ball:FlxObject, _bottomWall:FlxObject):void
{
// check for game over
if (lives_count == 0)
{
}
else
{
FlxG:lives_count -= 1;
lives.text = 'Lives: ' + lives_count.toString()
}
}
Here is my main game.as:
package
{
import org.flixel.*;
public class Game extends FlxGame
{
private const resolution:FlxPoint = new FlxPoint(640, 480);
private const zoom:uint = 2;
private const fps:uint = 60;
public function Game()
{
super(resolution.x / zoom, resolution.y / zoom, PlayState, zoom);
FlxG.flashFramerate = fps;
}
}
}
There are multiple ways to go about doing this...
You could use different FlxStates, like I described in the answer to your other post: Creating user UI using Flixel, although you'll have to get smart with passing the score or whatever around, or use a Registry-type setup
If you want it to actually work like you described above, with a transparent-overlay screen, you can try something like this (keep in mind, the exact details may differ for your project, I'm just trying to give you an idea):
First, make sure you have good logic for starting a level, lets say it's a function called StartLevel.
You'll want to define a flag - just a Boolean - that tracks whether or not the game is still going on or not: private var _isGameOver:Boolean; At the very end of StartLevel(), set this to false.
In your create() function for your PlayState, build a new FlxGroup which has all the things you want on your Game Over screen - some text, an image, and something that says "Press ENTER to Restart" (or whatever). Then set it to visible = false. The code for that might look something like:
grpGameOver = new FlxGroup();
grpGameOver.add(new FlxSprite(10,10).makeGraphic(FlxG.Width-20,FlxG.Height-20,0x66000000)); // just a semi-transparent black box to cover your game screen.
grpGameOver.add(new FlxText(...)); // whatever you want to add to the group...
grpGameOver.visible = false;
add(grpGameOver); // add the group to your State.
Depending on how your game is setup, you may also want to set the objects in your group's scrollFactor to 0 - if your game screen scrolls at all:
grpGameOver.setAll("scrollFactor", new FlxPoint(0,0));
In your update() function, you'll need to split it into 2 parts: one for when the game is over, and one for if the game is still going on:
if (_isGameOver)
{
if (FlxG.keys.justReleased("ENTER"))
{
grpGameOver.visible = false;
StartLevel();
}
}
else
{
... the rest of your game logic that you already have ...
}
super.update();
Keep in mind, if you have things that respond to user input anywhere else - like a player object or something, you might need to change their update() functions to check for that flag as well.
Then, the last thing you need to do is in your loseLive() logic:
if (lives_count == 0)
{
_isGameOver = true;
grpGameOver.visible = true;
}
else
...
That should do it!
I would highly recommend spending some time with different tutorials and sample projects to kind of get a better feel for Flixel in general. Photon Storm has some great material to play with (even though he's jumped over to HTML5 games)
I also want to note that if you get comfortable with the way Flixel handles updates, you can get really smart with your state's update() function and have it only call update on the grpGameOver objects, instead of having to change all your other objects updates individually. Pretty advanced stuff, but can be worth it to learn it.

CTMLoader does not correctly assign the instanceof the geometry

if I modify the sample webgl_loader_ctm.html and in the routine callbackModel() add these lines of code:
if (geometry instanceof THREE.Object3D) alert("THREE.Object3D");
else if (geometry instanceof THREE.Geometry) alert("THREE.Geometry");
else alert("Unknown instanceof geometry");
I was expecting the second alert to be activated but instead I got the third. As my code depends on the instanceof to be correct, does anybody know what do I have to change in the loader in order to fix this?
There are two main loading methods built into CTMLoader: Depending on the useBuffers parameter, it creates BufferGeometry or Geometry. You might want to first check against BufferGeometry (which is not a subclass of Geometry) too, or set the useBuffers parameter to false (I'm not familiar with CTM, so I don't know if the file formats are different for buffered vs normal).
Anyway, Three.js handling of classes can be a bit hard to keep track of, and some related classes may not share a parent class. You may or may not agree, but I would maybe do the instanceof checking "if it looks like a duck, it is a duck" -style. So checking for some property that only exists in Object3D or similar objects:
if (geometry.lookAt) {
alert("Looks like Object3D, I know what to do with this");
} else if (geometry.vertices) {
alert("Looks like Geometry with some vertices, I know what to do with this");
} else {
alert("I dont know how to handle this object");
}

jsplumb library getconnection function is not returning values

Hi all I am working in JS plumb library for making connections.I am stuck at one point and need help from experts.
Here is my scenario.
I have many connections and what I want is that when I click on one connection a certain label appears on it to show that it is selected.When I click one some other connection previously clicked connection disappears and new connection get selected.
What I have done so far is that
jsPlumbInst.bind('click', function(c) {
c.showOverlay('selected');
var previously_active = jsPlumbInst.getConnections({scope:"active"});//this function not returning me values
if(previously_active.length != 0) {
/*So never go in this statement*/
previously_active[0].hideOverlay('selected');
previously_active.scope("jsPlumb_DefaultScope");
}
c.scope = "active";
});
Here the problem is that my connection scope is set to "active"
jsPlumbInst.getConnections({scope:"active"})
is not returning anything.
So can any one kindly guide me that whether I am doing right?
Or is there any other way to achieve this?
var sourcecon = jsPlumb.getConnections({source: e}) ;
for(i=0; i<sourcecon.length; i++)
{
var target = getName(sourcecon[i].targetId) ;
var source = getName(sourcecon[i].sourceId) ;
removefrommatrix(source, target,sourcecon[i].sourceId,sourcecon[i].targetId) ;
}
This is code snippet which I am using. It works fine. Your code looks fine except just one difference that you have used jsPlumbInst rather than jsPlumb. I guess that could be the problem. For me its like static class in Java.Not sure about that. But try and see if it could help you.Seems like I am almost a year late in replying. All the best :-)

Resources