How to detect a collision between one object and multiple objects in XNA 4.0 C#? - collision

I am new to XNA and CSharp programming so I want to learn to make a treasure hunting game as a beginning so I made a player(as a class) which can walk up, down, left and right. I made a Gem class also which the player can collide with and the gem disappears and a sound is played. But I want to make some walls that the player can collide with and stop so I made a class called Tile.cs (The wall class) and I made a void in it
public void CollideCheck(bool tWalk, bool bottomWalk, bool leftWalk, bool rightWalk, Rectangle topRect, Rectangle bottomRect, Rectangle rightRect, Rectangle leftRect)
{
colRect = new Rectangle((int)position.X, (int)position.Y, texture.Width, texture.Height);
if (this.colRect.Intersects(topRect))
{
tWalk = false;
}
else
tWalk = true;
if (this.colRect.Intersects(bottomRect))
{
bottomWalk = false;
}
else
bottomWalk = true;
if (this.colRect.Intersects(leftRect))
{
leftWalk = false;
}
else
leftWalk = true;
if (this.colRect.Intersects(rightRect))
{
rightWalk = false;
}
else
rightWalk = true;
}
Then, in the Game1.cs (The main Class) I made an array of "Tiles":
Tile[] tiles = new Tile[5];
And in the update void I made this:
foreach (Tile tile in tiles)
{
tile.CollideCheck(player.topWalk, player.bottomWalk, player.leftWalk, player.rightWalk,
new Rectangle((int)player.Position.X, (int)player.Position.Y - (int)player.Speed.Y, player.currentAnim.FrameWidth, player.currentAnim.FrameHeight),
new Rectangle((int)player.Position.X, (int)player.Position.Y + (int)player.Speed.Y, player.currentAnim.FrameWidth, player.currentAnim.FrameHeight),
new Rectangle((int)player.Position.X + (int)player.Speed.X, (int)player.Position.Y, player.currentAnim.FrameWidth, player.currentAnim.FrameHeight),
new Rectangle((int)player.Position.X - (int)player.Speed.X, (int)player.Position.Y, player.currentAnim.FrameWidth, player.currentAnim.FrameHeight));
}
All those rectangles are the borders of the player but when I run the game the player doesn't collide with it so is there any way to fix this?
I can post the project if I am not very clear.

Your parameters are in only, but you set their values inside the call. You have to declare them as out variables so that their value is sent back to the caller. Using out also makes sure you always set a value to them before exiting the function.
So change your function declaration to public void CollideCheck(out bool tWalk, out bool bottomWalk, out bool leftWalk, out bool rightWalk, Rectangle topRect, Rectangle bottomRect, Rectangle rightRect, Rectangle leftRect) and you get the values back.

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.

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.

Position and rotate animations in Libgdx

I have 20 spites, I want them to be animated when a button is clicked.
Two types of animations 1) Position 2) Rotation.
Is there a recommended way to do this? Only way I can think of is recursively call setposition and angle with a delta value on Render method till the desired position and angle are reached.
When you have a start state and an end state, and you want to fill in the middle states, this is known as 'tweening (from inbetween). It comes from cartoon animation, but has come to be used more generally.
LibGDX makes use of Universal Tween Engine. You can start your journey to animating anything you want here. But, to give a bit more detail on how it works, here is an example from some of my own stuff. A similar usecase with regards to a sprite, but I have my sprites wrapped in a more generic class, a JJRenderNode. Here is how I make my class open to being tweened.
First you need a TweenAccessor for the class you want to tween.
public class RenderNodeTweenAccessor implements TweenAccessor<JJRenderNode> {
public static final int WIDTH = 1;
public static final int HEIGHT = 2;
public static final int WIDTH_HEIGHT = 3;
public static final int ALPHA = 4;
public static final int ALPHA_WIDTH_HEIGHT=5;
#Override
public int getValues(JJRenderNode target, int tweenType, float[] returnValues) {
switch (tweenType) {
case WIDTH:
returnValues[0] = target.getWidth();
return 1;
case HEIGHT:
returnValues[0] = target.getHeight();
return 1;
case WIDTH_HEIGHT:
returnValues[0] = target.getWidth();
returnValues[1] = target.getHeight();
return 2;
case ALPHA:
returnValues[0] = target.getColour().a;
return 1;
case ALPHA_WIDTH_HEIGHT:
returnValues[0] = target.getColour().a;
returnValues[1] = target.getWidth();
returnValues[2] = target.getHeight();
return 3;
default:
assert false;
return -1;
}
}
#Override
public void setValues(JJRenderNode target, int tweenType, float[] newValues) {
switch (tweenType) {
case WIDTH:
target.setWidth(newValues[0]);
break;
case HEIGHT:
target.setHeight(newValues[0]);
break;
case WIDTH_HEIGHT:
target.setWidth(newValues[0]);
target.setHeight(newValues[1]);
break;
case ALPHA:
target.getColour().a=newValues[0];
break;
case ALPHA_WIDTH_HEIGHT:
target.getColour().a=newValues[0];
target.setWidth(newValues[1]);
target.setHeight(newValues[2]);
default:
break;
}
}
}
The constant ints and the 'tweenType' in each of the get and set methods let you tween more than one combination of fields. In this case I have different combinations of width, height and alpha values for my JJRenderNode.
You have to register this TweenAccessor as follows:
Tween.registerAccessor(JJRenderNode.class, new RenderNodeTweenAccessor());
And then you are free to tween your class, for example:
Timeline.createSequence()
.push(Tween.set(node, RenderNodeTweenAccessor.WIDTH_HEIGHT).target(START_WIDTH, START_WIDTH))
.push(Tween.to(node, RenderNodeTweenAccessor.WIDTH_HEIGHT, 0.4f).target(PuzzleBlockCore.MAX_RENDER_WIDTH, PuzzleBlockCore.MAX_RENDER_WIDTH))
.start(JJ.tweenManager);
PS. You also need an instance of a TweenManger, and this needs to be updated with delta for each gameloop. I have a 'singleton' global instance that I use everywhere (JJ.TweenManager).
You might as well use spine 2d for animation. It costs, but it is worth it. For $60 I think, you get full libgdx support + bone rigging and animation.
http://esotericsoftware.com/
If you do want to animate with only libgdx however, you can create a list with all your sprite animations frames, loop through, and switch sprite texture to next frame in the animation.
private void render() {
sprite.set(aninationframes.get(currentFrame)
currentFrame = currentFrame + 1}
Though, you may want to add a delay per frame.
If(current time - time > some value) {
sprite.set(animationframes.get(currrntFrame)
currentFrame = currentFrame + 1
time = get current time here
}

Why in my unity project the chasing part is not working?

Could someone please maybe download and see my project? It is very simple, but not working as in the tutorial.
In my project, I set IsTrigger to true in either the ThirdPersonController or the AIThirdPersonController for one of the characters. This makes the character fall down from the Plane.
I also changed one of the characters to be tagged as Player and changed the state from PATROL to CHASE but that changed nothing. The other player never chases/follows the player I am controlling and moving around.
Why are the players falling down when I set IsTrigger to true in my project?
I see in the video that the instructor is using a Maze Plane. Is that a package I should import in the Assets or is it already somewhere in the Assets? I just added regular Plane for now because I could not find a Maze Plane.
Here is a link for my project from my OneDrive. The file name is Demo AI.rar:
Project in OneDrive
Here is a link for the video tutorial I am attempting to follow. It is supposes to be simple I suppose:
Tutorial
Here is the BasicAi class I'm using in my project, the same script from the tutorial video:
using System.Collections;
using UnityStandardAssets.Characters.ThirdPerson;
public class BasicAi : MonoBehaviour {
public NavMeshAgent agent;
public ThirdPersonCharacter character;
public enum State {
PATROL,
CHASE
}
public State state;
private bool alive;
// Variables for patrolling
public GameObject[] waypoints;
private int waypointInd = 0;
public float patrolSpeed = 0.5f;
// Variable for chasing
public float chaseSpeed = 1f;
public GameObject target;
// Use this for initialization
void Start () {
agent = GetComponent<NavMeshAgent> ();
character = GetComponent<ThirdPersonCharacter>();
agent.updatePosition = true;
agent.updateRotation = false;
state = BasicAi.State.PATROL;
alive = true;
StartCoroutine ("FSM");
}
IEnumerator FSM()
{
while (alive)
{
switch (state)
{
case State.PATROL:
Patrol ();
break;
case State.CHASE:
Chase ();
break;
}
yield return null;
}
}
void Patrol()
{
agent.speed = patrolSpeed;
if (Vector3.Distance (this.transform.position, waypoints [waypointInd].transform.position) >= 2) {
agent.SetDestination (waypoints [waypointInd].transform.position);
character.Move (agent.desiredVelocity, false, false);
} else if (Vector3.Distance (this.transform.position, waypoints [waypointInd].transform.position) <= 2) {
waypointInd += 1;
if (waypointInd > waypoints.Length) {
waypointInd = 0;
}
}
else
{
character.Move (Vector3.zero, false, false);
}
}
void Chase()
{
agent.speed = chaseSpeed;
agent.SetDestination (target.transform.position);
character.Move (agent.desiredVelocity, false, false);
}
void OnTriggerEnter(Collider coll)
{
if (coll.tag == "Player")
{
state = BasicAi.State.CHASE;
target = coll.gameObject;
}
}
}
Once a collider is a trigger it no longer collides with objects, your best bet is to place a child object that has a collider and setting that to the trigger, this way the original collider will still collide with your ground.
As for your other question how are you referencing your third person character, are you dragging it from the scene into the inspector, and you also have to bake your navmesh into your scene. I haven't looked at your project as that would take a lot of time, but maybe go through the tutorial again and see how they reference the character. With the inbuilt characters you normally have to access the namespace first.

Working Fine In Java Mode, Not So In Javascript (Also Trouble With Algorithm)

I'm currently teaching myself game programming and remaking some of the classic games to learn the different tricks and stuff(as suggested a fine article on gamedev.net).
I successfully coded PONG and now working on Snake(I assume that's what it meant by Worm). Now I figured out a lot of the stuff, except for two things I can't wrap my mind around.
My algorithm is simple enough: The player controls the head and the body follows. The head has it's own class and from there each segment is controlled as a separate object. The segments each control their own movement defined by a velocity vector. The first segment is independent from the array that controls the rest of the segments. So the head just sends orders to the first segment and the first segment transmits it to the rest of the segments.
The system is based on BendinPoints. Basically each segment has a variable for storing a BendingPoint coordinate and the velocity vector to take on when reaching that BendingPoint. A flag tells it whether it currently posses a BendingPoint or is free to accept a new coordinate.
So the head, when it turns, transmits that location where it turned and the direction(velocity vector) in which it turned. The first segment passes it on the second, the second passes it on to the third and so on. Each segment only passes an order to the next if it has one to pass. Each segment only receives new orders once the old one has been completed.
Now my problem is two-fold. One: this works fine in Java mode but not working in Javascript mode and I can't figure out why. And Two: Sometimes, when I'm changing direction too rapidly, the rest of the body apart from the head and first segment seem to lose track and wander off.
I hope the code comments will explain the rest. Forgive my newbishness.
String MODE;
Menu menu;
String[] menuItems={"START","INSTRUCTIONS","CREDITS","EXIT"};
/*# pjs font="data/waved.ttf" */
/*# pjs font="data/sixty.ttf" */
PFont sMenu=createFont("waved",72);
PFont sItem=createFont("sixty",35);
String gOverText="GAME OVER";
String hScoreText="Your score is: ";
String iControl="W,A,S,D turns the Snake in the respective direction.";
String iScore="Each Food increases Score by 1 and a segement is added.";
String iScore2="After every 10 points, number of segments added per Food increases by 1.";
String cBy="coded By";
String cName="Le Shaun";
MenuItem back;
Snake snk;
Food fd;
int hScore;
int dF;
float sWidth=800;
float sHeight=600;
PVector sLoc=new PVector(sWidth/2,sHeight/2);
PVector sVel=new PVector(0,-1);
float sRad=10;
color sCol=#9D6C0A;
PVector fLoc=new PVector(450,300);
float fRad=10;
color fCol=#FCF18C;
void setup(){
size(int(sWidth),int(sHeight));
snk=new Snake(sLoc,sVel,sRad,sCol);
fd=new Food(fLoc,fRad,fCol);
frameRate(60);
hScore=0;
dF=1;
menu=new Menu("SNAKE",menuItems,sMenu,sItem,color(#9D6C0A),color(#8CC610),color(#EDE724),color(#674707),color(255,0));
MODE="NIL";
back=new MenuItem("BACK",sItem,width/2,height/1.5,height/25,color(#8CC610),color(#EDE724),color(#674707),color(255,0)); //Common back button for some of the screens.
}
//Current screen is controlled by MODES. Each MODE defines which parts of the game will run, whether it be individual screens or the main gameplay itself.
void draw(){
background(#EDB824);
if(MODE.equals("NIL")){
menu.render();
MODE=menu.whichItem();
}
else if(MODE.equals("START")){
fd.render();
if(fd.isEaten(snk)){
for(int i=1;i<=dF;i++){
snk.sInc();
}
hScore++;
}
snk.render();
snk.update();
if(snk.isDead()){
MODE="GAMEOVER";
sLoc=new PVector(width/2,height/2);
fLoc=new PVector(width/2+100,height/2+100);
sVel=new PVector(+1,0);
snk=new Snake(sLoc,sVel,sRad,sCol);
}
dF=int(hScore/10)+1;
textFont(sItem);
textSize(height/25);
text(str(hScore),width-textWidth(str(hScore))*3,height-height/25);
}
else if(MODE.equals("GAMEOVER")){
stroke(0);
fill(#9D6C0A);
textFont(sMenu);
textSize(72);
text(gOverText,width/2-textWidth(gOverText)/2,height/3);
text(hScoreText+hScore,width/2-textWidth(gOverText)/2,height/2);
back.render();
back.update();
if(back.getClicked()){
back.unClick();
MODE="NIL";
hScore=0;
frameRate(60);
}
}
else if(MODE.equals("INSTRUCTIONS")){
stroke(0);
fill(#9D6C0A);
textFont(sMenu);
textSize(72);
text("INSTRUCTIONS",width/2-textWidth("INSTRUCTIONS")/2,height/3);
textFont(sItem);
textSize(20);
text(iControl,width/2-textWidth(iControl)/2,height/2);
text(iScore,width/2-textWidth(iScore)/2,height/2+35);
text(iScore2,width/2-textWidth(iScore2)/2,height/2+70);
back.render();
back.update();
if(back.getClicked()){
back.unClick();
MODE="NIL";
}
}
else if(MODE.equals("CREDITS")){
stroke(0);
fill(#9D6C0A);
textFont(sItem);
textSize(35);
text(cBy,width/2-textWidth(cBy)/2,height/2);
textSize(45);
text(cName,width/2-textWidth(cName)/2,height/1.7);
back.render();
back.update();
if(back.getClicked()){
back.unClick();
MODE="NIL";
}
}
//println(MODE);
}
void keyReleased(){
if(MODE.equals("START")){
String temp="";
temp+=key;
temp=temp.toUpperCase();
snk.changeDir(temp);
if(key=='v' || key=='V'){
frameRate(60);
}
}
}
void keyPressed(){
if(MODE.equals("START")){
if(key=='v' || key=='V'){
frameRate(180);
}
}
}
void mouseClicked(){
if(MODE.equals("NIL")){
menu.passTo(mouseX,mouseY);
}
if(MODE.equals("GAMEOVER") || MODE.equals("INSTRUCTIONS") || MODE.equals("CREDITS")){
back.mClicked(mouseX,mouseY);
}
}
//Menu class uses the objects from the MenuItem and forms a menu with a title and a list of MenuItem objects.
/*
Constructor: Str-MenuTitle, Str[]-MenuItems, PF-MenuFont, PF-MenuItemFont, c-TitleColor, c-ItemTextColor, c-ItemBackColor, c-ItemHoverTextColor, c-ItemHoverBackColor.
Methods:
void render() - Renders the MenuTitle and the MenuItems.
void passTo(float,float) - Passes the mouse coords to each MenuItem to check whether it has been clicked.
void passTo(int) - Resets the clickState of the specified MenuItem by calling the unClick() method on that MenuItem.
String whichItem() - Checks all the MenuItems for a their clickState and returns the one that's been clicked.
*/
class Menu{
String titleT;
PFont titleF;
PFont menuItem;
color titleC;
float spacer; //This is used to define the space between successive MenuItem objects.
float iniY=height/2.5;
MenuItem[] menuItems;
Menu(String titleT,String[] menuItemsNames,PFont titleF,PFont menuItemF,color titleC,color menuItemC,color menuBackC,color itemHoverC,color backHoverC){
this.titleT=titleT;
this.titleF=titleF;
this.titleC=titleC;
menuItems=new MenuItem[menuItemsNames.length]; //Initializes the MenuItem objects depending on the array passed to it. This makes the menu system very flexible.
spacer=48;
for(int i=0;i<menuItemsNames.length;i++){
menuItems[i]=new MenuItem(menuItemsNames[i],menuItemF,width/2,iniY+(spacer*i),height/25,menuItemC,menuBackC,itemHoverC,backHoverC);
}
}
void render(){ //Renders the menu.
textFont(titleF);
textSize(92);
fill(titleC);
text(titleT,width/2-(textWidth(titleT)/2),height/3.8);
for(int i=0;i<menuItems.length;i++){
menuItems[i].update();
menuItems[i].render();
}
}
void passTo(float mX,float mY){ //This accepts the X,Y mouse coords when the mouse is clicked and passes it to the relevant MenuItem object to check if the click occurs on that object.
for(int i=0;i<menuItems.length;i++){
menuItems[i].mClicked(mX,mY);
}
}
/*void passTo(int item){ //This accepts an ineteger value and resets that particular menu item's click state.
menuItems[item].unClick();
}*/
String whichItem(){ //Checks each time if the clickState of any MenuItem object is true. If it is, returns the array position of the relevant object.
for(int i=0;i<menuItems.length;i++){
if(menuItems[i].getClicked()){
menuItems[i].unClick();
return menuItems[i].menuItem;
}
}
return "NIL";
}
}
//MenuItem holds the attributes and methods relating to each single item on the menu. Thus each item is treated as a separate object.
//Each MenuItem object comprises mainly of a foreground text and a background object.
class MenuItem{
String menuItem;
PFont menuFont;
float itemX;
float itemY;
float itemSize;
color itemColor;
color backColor;
color pressedColor;
color pressedBack;
color presentItem;
color presentBack;
float textWidth;
boolean clickState=false; //This vairable is used to check the clickState of the menu item. If the mouse is clicked over the menu item, this variable becomes true.
MenuItem(String menuItem,PFont menuFont,float itemX,float itemY,float itemSize,color itemColor,color backColor,color pressedColor,color pressedBack){
this.menuItem=menuItem;
this.menuFont=menuFont;
this.itemX=itemX;
this.itemY=itemY;
this.itemSize=itemSize;
this.itemColor=itemColor;
this.backColor=backColor;
this.pressedColor=pressedColor;
this.pressedBack=pressedBack;
}
void render(){ //Handles the rendering for individual menu objects.
textFont(menuFont);
textSize(itemSize);
textWidth=textWidth(menuItem);
stroke(0);
fill(presentBack);
rectMode(CENTER);
rect(itemX,itemY,textWidth*1.3,itemSize*1.4,50);
fill(presentItem);
text(menuItem,itemX-textWidth/2,itemY+itemSize*.3);
}
void update(){ //Constatnly checks for the state of the object. If the mouse is over it a certain style is show and otherwise another style is shown.
if(mouseX<(itemX+(textWidth*1.3)/2) && mouseX>(itemX-(textWidth*1.3)/2) && mouseY<(itemY+(itemSize*1.4)/2) && mouseY>(itemY-(itemSize*1.4)/2)){
presentItem=pressedColor;
presentBack=pressedBack;
noStroke();
}
else{
presentItem=itemColor;
presentBack=backColor;
}
}
boolean getClicked(){ //Returns the clickState of the object.
return clickState;
}
void unClick(){ //Resets the click state after having been clicked once.
clickState=false;
}
void mClicked(float mX,float mY){ //Changes the clickState of the object depending on the position of the mouse as inputs.
if(mX<(itemX+(textWidth*1.3)/2) && mX>(itemX-(textWidth*1.3)/2) && mY<(itemY+(itemSize*1.4)/2) && mY>(itemY-(itemSize*1.4)/2)){
clickState=true;
println(menuItem);
}
}
}
/*
All control comes from the Snake's head. The head works directly with the first segment(SnakeBits object) and the first segement works with the rest of the body.
Each time a food is consumed, a new segment is created, it's position and velocity calculated as per the position of the last segment.
A loop checks whether each segment is open to receiving a new set of orders(BendingPoint and the velocity for that point), and passes on if so.
*/
class Snake{ //Controls the snake's head as well as the segment objects.
PVector sLoc; //Location and Velocity.
PVector sVel;
float sRad; //Radius and Color
float shRad;
color sCol;
float baseVel; //The base velocity of the snake.
SnakeBits[] sBits={}; //Array of SnakeBits objects that forms the segments.
PVector hold;
Snake(PVector sLoc,PVector sVel,float sRad,color sCol){
this.sLoc=sLoc;
this.sVel=sVel;
this.sRad=sRad;
this.shRad=sRad*1.;
this.sCol=sCol;
this.baseVel=abs(sVel.x>0 ? sVel.x : sVel.y); //The snake is initially given a vector in one of the cardinal directions. Whatever the value of velocity is in either direction is stored.
hold=PVector.mult(sVel,shRad+sRad);
hold=PVector.sub(sLoc,hold);
sBits=(SnakeBits[])append(sBits,new SnakeBits(hold,sVel,sRad,sCol));
}
void update(){ //Updates the movement of the head as well as the segments.
updateBP();
sLoc.add(sVel);
for(int i=0;i<sBits.length;i++){
sBits[i].update();
}
}
void render(){ //The display.
stroke(0);
fill(sCol);
ellipse(sLoc.x,sLoc.y,shRad*2.2,shRad*2.2);
for(int i=0;i<sBits.length;i++){
sBits[i].render();
}
}
void sInc(){ //Gets called each time a food item is eaten, and increases the size of the snake by adding segments based on the velocity vector of the last segment.
int lastInd=sBits.length-1;
hold=PVector.mult(sBits[lastInd].sbVel,sRad*2);
hold=PVector.sub(sBits[lastInd].sbLoc,hold);
PVector appVel=new PVector(sBits[lastInd].sbVel.x,sBits[lastInd].sbVel.y);
SnakeBits appBits=new SnakeBits(hold,appVel,sRad,sCol);
sBits=(SnakeBits[])append(sBits,appBits);
}
void changeDir(String dir){ //Gets called when a directional button is pressed.
PVector chng=new PVector(0,0); //Direction change can only occur perpendicular to the current direction. Uses baseVel to set the new direction.
if(!sBits[0].hasBP){
if(degrees(sVel.heading())==0 || degrees(sVel.heading())==180){
if(dir.equals("W")){
chng=new PVector(0,-baseVel);
sVel=chng;
updateFBP();
}
else if(dir.equals("S")){
chng=new PVector(0,baseVel);
sVel=chng;
updateFBP();
}
}
else if(degrees(sVel.heading())==90 || degrees(sVel.heading())==-90){
if(dir.equals("D")){
chng=new PVector(baseVel,0);
sVel=chng;
updateFBP();
}
else if(dir.equals("A")){
chng=new PVector(-baseVel,0);
sVel=chng;
updateFBP();
}
}
}
}
boolean isDead(){ //Checks for collision against the wall or it's own tail.
if((sLoc.x-shRad)<0 || (sLoc.x+shRad)>width || (sLoc.y-shRad)<0 || (sLoc.y+shRad)>height){
println("WALL");
return true;
}
PVector temp;
for(int i=0;i<sBits.length;i++){
if(dist(sLoc.x,sLoc.y,sBits[i].sbLoc.x,sBits[i].sbLoc.y)<(shRad+sRad-sRad*.6)){
println("TAIL");
println(sLoc.x+" "+sLoc.y+" "+sBits[i].sbLoc.x+" "+sBits[i].sbLoc.y+" "+dist(sLoc.x,sLoc.y,sBits[i].sbLoc.x,sBits[i].sbLoc.y)+" "+(shRad+sRad-sRad*.6));
return true;
}
}
return false;
}
void updateFBP(){ //Updates the first segment's BendingPoint.
sBits[0].takeNewBP(sLoc,sVel);
sBits[0].hasNewBP(true);
}
void updateBP(){ //Updates the rest of the segments as per the system of receiving new orders once the current orders have been executed.
for(int i=0;i<sBits.length-1;i++){
if(sBits[i].hasBP && !sBits[i+1].hasBP){
sBits[i+1].takeNewBP(sBits[i].newBP,sBits[i].newVel);
sBits[i+1].hasNewBP(true);
}
}
}
}
/*
Each SnakeBit has it's independent movement system. It holds a BendPoint(newBP) variable, a New BP Velocity(newVel) variable and a flag(hasBP) to show whether it has a new Bend Point.
When the SnakeBit already has a BP, it will wait till it reaches that BP and then take on the velocity from newVel. It's flag will be set to false.
In this state it will be open to receiving a new set of orders: a new BP and the velocity to take on for that BP. Thus new BP's are not taken on till the previous BP has been cleared.
*/
class SnakeBits{ //The individual bits of the snake that make up its body.
boolean hasBP;
PVector sbLoc; //Location and Velocity vectors.
PVector sbVel;
float sbRad; //Radius and color of the segment.
color sbCol;
PVector newBP; //This works with the changeDir() method. It holds the position at which the direction will be changed.
PVector newVel; //Stores the new Velocity vector that will be applied when the above position is reached.
SnakeBits(PVector sbLoc,PVector sbVel,float sbRad,color sbCol){
this.sbLoc=sbLoc;
this.sbVel=sbVel;
this.sbRad=sbRad;
this.sbCol=sbCol;
newVel=new PVector(sbVel.x,sbVel.y);
newBP=new PVector(width*2,height*2); //Initialized it as such to avoid problems during first run.
hasBP=false;
}
void render(){
stroke(0);
fill(sbCol);
ellipse(sbLoc.x,sbLoc.y,sbRad*2,sbRad*2);
}
void update(){
sbLoc.add(sbVel); //Both updates the Location and checks if it's time to change direction.
changeDir();
}
void changeDir(){
if(sbLoc.x==newBP.x && sbLoc.y==newBP.y && hasBP){ //As soon as the segment reaches the Location where a change in dir is needed, the Velocity is changed over to the new velocity vector.
println("FTRUE");
hasNewBP(false);
sbVel.x=newVel.x; sbVel.y=newVel.y;
newBP=new PVector(width*2,height*2);
}
}
void takeNewBP(PVector pos,PVector vel){ //Called externally by the Snake class. Takes where last segment changed direction and stores that location as well as the new velocity vector.
newBP.x=pos.x; newBP.y=pos.y;
newVel.x=vel.x; newVel.y=vel.y;
}
void hasNewBP(boolean dat){ //Updates the hasBP state by accepting a boolean and assigning it to hasBP.
hasBP=dat;
}
}
class Food{
PVector fLoc;
float fRad;
color fCol;
Food(PVector fLoc,float fRad,color fCol){
this.fLoc=fLoc;
this.fRad=fRad;
this.fCol=fCol;
}
void render(){
stroke(0);
fill(fCol);
ellipse(fLoc.x,fLoc.y,fRad*2,fRad*2);
}
boolean isEaten(Snake sn){
PVector temp;
temp=PVector.sub(fLoc,sn.sLoc);
if(temp.mag()<(sn.shRad+fRad)){
reset(sn);
return true;
}
return false;
}
void reset(Snake sn){
boolean set=false;
PVector tmp=new PVector();
while(!set){
tmp=new PVector(random(fRad,width-fRad),random(fRad,height-fRad));
set=true;
for(int i=0;i<sn.sBits.length;i++){
if(dist(tmp.x,tmp.y,sn.sBits[i].sbLoc.x,sn.sBits[i].sbLoc.y)<(fRad+sn.sRad) || dist(tmp.x,tmp.y,sn.sLoc.x,sn.sLoc.y)<(fRad+sn.shRad)){
set=false;
break;
}
}
}
fLoc=tmp;
}
}
Javascript mode seems to have a problem with textWidth(); To get over it you can change textWidth=textWidth(menuItem); in MenuItem.render() to textWidth=200; or find a working equivalent.
By the way, I noticed that when I run it in Javascript, it wouldn't take its full size and your size(); command is the culprit. It seems to require numbers instead of variables to function properly (setting it to size(800,600); worked)
Your other problem seems to stem from the fact that you change things when you press a button but you dont apply the change until the draw() function. Unfortunately I can't pinpoint it in your code by making small changes, which makes me believe that you need to refactor your turning system in a different fashion. My suggestion is to decouple where everyone has to go from where the previous one goes, opting instead to store the positions and directions he has to go in an ArrayList, which will grow essentially with each turn. Then, each bit of the snake has to go through its own ArrayList of directions and positions without regarding the momentary changes of its previous one. When it does reach one of those positions just turn it in the proper direction, delete the reached direction and position and start heading for the next one. The ArrayList essentially functions like a queue, where you add to the end but remove from the beginning.
Maybe it makes sense to post the second part of your question to https://gamedev.stackexchange.com/

Resources