碰撞检测仅在物体正好撞击中心时才起作用
Collision detection only works when object hits right in the centre
你好,我这里有一个 'falling' 风格的游戏,我让荷马辛普森一家的头从天而降,避开沙拉并收集汉堡来增加分数。我已经实施了碰撞检测,所以当荷马击中沙拉时,他应该重新生成并扣除生命,这很好。但是,如果本垒打的头撞到沙拉的侧面而不是直接撞到沙拉的中央,比赛将冻结片刻,然后像什么都没发生一样继续进行。我不确定为什么会发生这种情况,并想知道我这样做是否有问题。下面是我的代码:
是碰撞检测不够准确还是我遗漏了其他问题?
PImage background;
PImage MenuBackground;
int y=0;//global variable background location
final int End = 0;
final int Active = 1;
final int Menu = 2;
int gameMode = Menu;
int score = 0;
int lives = 3;
Boolean BurgerCollisionInProgress = false;
Boolean BurgerCollisionInProgress2 = false;
Salad salad1;
Salad salad2;
Salad salad3;
Homer user1;
Burger Burger;
public void settings()
{
size(500,1000); //setup size of canvas
}
void menu()
{
background = loadImage("spaceBackground.jpg"); //image used for background
background.resize(500,1000); //resizes the background
gameMode = Active;
float rand = random(25,475);
int intRand = int(rand);
float rand2 = random(25,475);
int intRand2 = int(rand2);
float rand3 = random(25,475);
int intRand3 = int(rand3);
float rand4 = random(25,475);
int intRand4 = int(rand4);
user1 = new Homer(250,100); //declares new defender as user1
Burger = new Burger(intRand,900,2);
salad1 = new Salad(intRand2,900,3);
salad2 = new Salad(intRand3,900,3);
salad3 = new Salad(intRand4,900,3); //3 aliens declared with their x and y position and their speed they move at
draw();
}
void setup()
{
if(gameMode == 2)
{
MenuBackground = loadImage("simpMenu.png");
MenuBackground.resize(540,1000);
image(MenuBackground, 0, y);
textAlign(CENTER);
textSize(40);
fill(252, 3, 3);
text("Press 'p' to play", 250,500);
}
}
void draw ()
{
if (gameMode == Active)
{
if(crash() == false)
{
drawBackground();//calls the drawBackground method
textSize(32);
fill(22,100,8);
text("Score: " + score,75,40);
text("Lives: " + lives,75,80);
salad1.update();//calls the update method which holds the move and render methods for alien
salad2.update();
salad3.update();
user1.render();//calls the update method which holds the move and render methods for user
Burger.update();//calls the update method which holds the move and render methods for burger
if(Bcrash() == true && BurgerCollisionInProgress == false)
{
score = score+1;
BurgerCollisionInProgress = true;
Burger.y = 900;
float rand = random(25,475);
int intRand = int(rand);
Burger.x = intRand;
}
if(Bcrash() == false)
{
BurgerCollisionInProgress = false;
}
if(crash() == true && BurgerCollisionInProgress2 == false)
{
if (lives < 1)
{ gameMode = End;
textSize(28);
fill(22,100,8);
text("Game Over, press 'r' to restart",200,200);
}
else
{
lives = lives - 1;
BurgerCollisionInProgress2 = true;
menu();
}
if(crash() == false)
{
BurgerCollisionInProgress2 = false;
}
}
}
}
}
void drawBackground()
{
image(background, 0, y); //draw background twice adjacent
image(background, 0, y-background.width);
y -=2;
if(y == -background.width)
y=0; //wrap background
}
boolean crash()
{
if(user1.crash(salad1))
{
return true;
}
if(user1.crash(salad2))
{
return true;
}
if(user1.crash(salad3))
{
return true;
}
return false;
}
boolean Bcrash()
{
if(user1.crash(Burger))
{
return true;
}
return false;
}
荷马 class:
class Homer
{
PImage UserImage;
int x,y; //declaring variables
Homer(int x, int y)
{
this.x = x;
this.y = y;
UserImage = loadImage("homer.png");
UserImage.resize (60, 52);
} // end of Homer
void render()
{
//draw a Homer
image(UserImage,x,y);
} //end of void render
boolean crash(Salad A)
{
if((abs(x-A.x)<=30) && abs(y-A.y)<=30)
{
return true;
}
return false;
}// end of crash
boolean crash(Burger A)
{
if((abs(x-A.x)<=30) && abs(y-A.y)<=30)
{
return true;
}
return false;
}
} // end of class
汉堡Class:
class Burger
{
PImage burgerImage;
int x,y, speedX;
int speedY = 0;
Burger(int x, int y, int speedY)
{
this.x = x;
this.y = y;
this.speedY= speedY;
burgerImage = loadImage("food.png");
burgerImage.resize (60, 52);
}
void render()
{
image(burgerImage,x,y);
}
void move()
{
y = y - speedY;
float rand = random(25,475);
int intRand = int(rand);
if(this.y < 0)
{
this.y = 900;
this.x = intRand;
}
}
void update()
{
move();
render();
}
}
沙拉Class:
class Salad
{
float x,y;
float speedX, speedY; //declaring variables
PImage saladImage;
Salad(int x, int y, int speedY)
{
this.x = x;
this.y = y;
this.speedY = speedY;
saladImage = loadImage("salad.png");
saladImage.resize (60, 52);
} //end of salad
void move()
{
y=y-speedY;
float stepY = random(-5,5);
y = y + (int)stepY;
float rand = random(25,475);
int intRand = int(rand);
if(this.y < 0)
{
this.y = 900; // once the salads y is less than 0 they restart at 900
this.x = intRand;
speedY = speedY + 0.5;
}
} //end of void move
//draw a salad
void render()
{
image(saladImage,x,y);
} //end of void render
void update()
{
move();
render();
}
}// end of alien class
有几件小事使这比它可能更难。首先,您的相交方法不太正确。然后,可以改进处理坐标的方式。
我首先要做的是向您展示如何使矩形相交。之后,我将向您展示如何处理可绘制对象,使它们易于操作。然后,我将向您展示一些框架代码,用于一个简短、简单的游戏,其中包含物品掉落和碰撞,我会专门为您添加一些帮助,以便您可以将这些建议应用到您的游戏环境中。
1。碰撞
有很多方法可以处理冲突。其中大部分是应用数学,其中一些是利用颜色或不可见精灵的巧妙算法。可能还有一些方法我忘记了。
我们只会在矩形之间进行碰撞,因为您的程序看起来很 rectangle-friendly 而且这是更简单的方法。所以我们要写一个交叉点检测算法。
写算法首先要做的就是伪代码。我不是在开玩笑。用键盘输入 clakety-clak 并点击编译很容易。它大部分时间都有效...但它比动脑筋解决问题更直观。
能够编写伪代码对程序员来说就像是一种超能力。永远不要低估它。
现在,你怎么知道两个矩形是否相交?答案是:
- 两个矩形有 4 种相交方式,无论是水平还是垂直。
- 它们必须水平和垂直相交才能真正重叠。
这些是您必须寻找的可能性:
- 红色矩形比黑色矩形大,黑色矩形完全在里面。
- 两个矩形在左侧(水平)或顶部(垂直)重叠。
- 红色矩形小到可以放在黑色矩形里面。
- 两个矩形在右侧(水平)或底部(垂直)重叠。
因为这段代码可以在很多地方使用,所以我把它从上下文中取出来并把它放在一个函数中,该函数接受坐标和 returns 一个布尔值(如果确实存在碰撞则为真):
// INTERSECT RECTs
boolean intersect(float x1, float y1, float w1, float h1, float x2, float y2, float w2, float h2)
{
boolean checkX = x1 < x2 && x1+w1 > x2 || x1 < x2+w2 && x1+w1 > x2+w2 || x1 > x2 && x1+w1 < x2+w2 || x1 < x2 && x1+w1 > x2+w2;
boolean checkY = y1 < y2 && y1+h1 > y2 || y1 < y2+h2 && y1+h1 > y2+h2 || y1 > y2 && y1+h1 < y2+h2 || y1 < y2 && y1+h1 > y2+h2;
return checkX && checkY;
}
这是处理矩形之间冲突的一种方法。您可以获取此信息并将其应用到您的游戏中,它会大放异彩。
这就是说,您还可以使用继承改进您的代码...
2。继承(在这种情况下:对于图形 objects)
计算机科学中的继承是一种使 class 获得另一个属性的方法。大多数人都是用家族来解释的:有parentclass有child人class继承parentclass'属性。
当您的多个 class 共享相同的属性或方法时,继承尤其有用。 Drawable objects 是一个很好的例子,因为它们都需要坐标。他们都需要一个方法来绘制。
正如您稍后将在示例游戏中看到的那样,我注意到我所有的矩形都需要这些模态变量:
protected float x, y, w, h; // x and y coordinate, width and height of the square
protected color fill, stroke;
protected float strokeWeight = 1;
所以我创建了一个名为 'Drawable' 的基 class。在更大的项目中,它可能是一整棵 classes 树的基础 class,像这样:
那么在这个例子中,Rat就是Walker的child,也就是Enemy的child,也就是Actor的child,也就是Actor的[=148] =] 的可绘制对象。
优点是每个 child 都继承了 parent 的所有内容。它既可以让您编写更少的代码,也可以让您只在一个地方而不是在所有地方修复错误。例如,如果您使用 objects 坐标的方式有误,您想在编写此逻辑的 class 中修复它,而不是在每个 [=] 中 154=].
继承还有许多其他优点,但现在让我们保持简单,好吗?
3。示例程序
这个非常简单:这是一个同时使用继承和冲突的示例。您可以将其复制并粘贴到 Processing IDE 中,它会 运行。花点时间看看这 3 个 class 是如何相互关联的,以及每个 child class 如何具有它的 parent.[=25= 的模态变量和函数]
Hero hero;
ArrayList<Bomb> bombs = new ArrayList<Bomb>();
int numberOfBombs = 20; // if you change this number the number of bombs will change too. Try it!
int hitCount = 0;
public void settings()
{
size(800, 600); //setup size of canvas
}
public void setup() {
hero = new Hero();
for (int i = 0; i < numberOfBombs; i++) {
bombs.add(new Bomb(random(20, width-20), random(1, 10)));
}
// This part serves no purpose but to demonstrate that you can gather objets which share a parent class together
ArrayList<Drawable> myDrawables = new ArrayList<Drawable>();
for (Bomb b : bombs) {
myDrawables.add(b);
}
myDrawables.add(hero);
for (Drawable d : myDrawables) {
d.Render();
// Even though hero and the bombs are different classes, they are in the same ArrayList because they share the Drawable parent class.
// Drawable has the Render() function, which may be called, but the child class will overshadow the Drawable's method.
// Proof is that the error message "Drawable child: Render() was not overshadowed." will not appear in the console.
}
}
public void draw() {
DrawBackground();
hero.Update();
hero.Render();
for (Bomb b : bombs) {
b.Update();
b.Render();
}
ShowHitCount();
}
public void DrawBackground() {
fill(0);
stroke(0);
rect(0, 0, width, height, 0); // dark background
}
public void ShowHitCount() {
textAlign (RIGHT);
textSize(height/20);
fill(color(200, 200, 0));
text(hitCount, width-20, height/20 + 20);
}
class Drawable {
protected float x, y, w, h; // 'protected' is like 'private', but child class retain access
protected color fill, stroke;
protected float strokeWeight = 1;
Drawable() {
this(0, 0, 0, 0);
}
Drawable(float x, float y, float w, float h) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
}
public void Render() { print("Drawable child: Render() was not overshadowed."); } // nothing to see here: this exists so we can overshadow it in the childs
public void Update() { print("Drawable child: Update() was not overshadowed."); } // same thing
}
class Hero extends Drawable { // 'extends' is keyword for 'will inherit from'
Hero() {
// 'super()' calls the parent's constructor
// in this example, I decided that the hero would be a red 40x60 rectangle that follows the mouse X position
super(mouseX - 20, height - 80, 40, 60);
fill = color(200, 0, 0);
stroke = color(250);
}
public void Update() { // when both parents and child have the same function (type and signature), the child's one prevail. That's overshadowing.
x = mouseX - w/2;
}
public void Render() {
fill(fill);
stroke(stroke);
strokeWeight(strokeWeight);
rect(x, y, w, h);
}
}
class Bomb extends Drawable {
protected float fallSpeed;
Bomb(float xPosition, float fallSpeed) {
// Bombs will be small blue squares falling from the sky
super(xPosition, -20, 20, 20);
this.fallSpeed = fallSpeed;
fill = color(0, 0, 200);
stroke = fill;
}
private void FallAgain() {
x = random(20, width-20);
fallSpeed = random(1, 10);
y = 0 - random(20, 100);
}
public void Update() {
y += fallSpeed;
// check for collision with the Hero
if (intersect(x, y, w, h, hero.x, hero.y, hero.w, hero.h)) {
hitCount++;
FallAgain();
}
// check if it fell lower than the screen
if (y > height) {
FallAgain();
}
}
public void Render() {
fill(fill);
stroke(stroke);
strokeWeight(strokeWeight);
rect(x, y, w, h);
}
}
// INTERSECT RECTs
boolean intersect(float x1, float y1, float w1, float h1, float x2, float y2, float w2, float h2)
{
boolean checkX = x1 < x2 && x1+w1 > x2 || x1 < x2+w2 && x1+w1 > x2+w2 || x1 > x2 && x1+w1 < x2+w2 || x1 < x2 && x1+w1 > x2+w2;
boolean checkY = y1 < y2 && y1+h1 > y2 || y1 < y2+h2 && y1+h1 > y2+h2 || y1 > y2 && y1+h1 < y2+h2 || x1 < y2 && y1+h1 > y2+h2;
return checkX && checkY;
}
4。奖励:帮助实施
所以...您看到了这一点,它让您想要改进您的程序。那挺好的。也许您想实现一些继承,也许只是碰撞。两者都可能很棘手,而且都不应该影响用户。
这就是所谓的'refactoring'.
让我们先实现一个Drawable class。剩下的就容易了。
第一步:找出与伯格、荷马和阿拉德。从您发布的代码中,我可以看出他们需要这些东西:
int x, y;
int speedX, speedY;
PImage img;
// To which I would add:
int w, h;
boolean isVisible;
我注意到您使用的是整数。很好,但我强烈建议使用 float 作为坐标。我在学习编码时也做过同样的事情,结果后悔没有早点使用 float。整数和浮点数都可能会为这个项目做一些技巧(需要时进行一些转换)。
另外,这里有一些他们共享的功能:
void Render()
void Update()
void Move()
// To which I would add:
void SetPosition()
void SetIsVisible()
boolean Crash() // so we can check if it intersect with given coordinates
到目前为止,您的 Drawable class 可能如下所示:
class Drawable {
public float x, y, w, h; // Making variables public while you could avoid it is bad practice, I'm doing it to avoid writing Get functions. Avoid doing this as much as possible, but bear with me for now.
protected float speedX, speedY;
protected PImage img;
protected boolean isVisible = true;
Drawable(float x, float y, float w, float h, String imagePath) {
this.x = x; // starting x position
this.y = y; // starting y position
this.w = w; // width if the object (your image in this case)
this.h = h; // height of the object (height of your image)
if (imagePath.length() > 0) { // if there is nothing in the string it won't try to load an image
img = loadImage(imagePath);
}
}
public void Render() {
if (isVisible && img != null) {
image(img, x, y);
}
}
public void Update() {
Move(); // I kept Move() out of Update() so you can overshadow Update() without having to re-code Move() later
}
protected void Move() {
// The 'normal' behavior of a Drawable would then to move according to it's speed.
// You can then change how they move by changing their speed values.
// Or... you can overshadow this function in a child class and write your own!
x += speedX;
y += speedY;
}
public void SetPosition(float x, float y) {
this.x = x;
this.y = y;
}
public void SetIsVisible(boolean isVisible) {
this.isVisible = isVisible;
}
public boolean Crash(float x, float y, float w, float h) {
// this function uses the 'intersect' function I wrote earlier, so it would have to be included in the project
return intersect(this.x, this.y, this.w, this.h, x, y, w, h);
}
}
到目前为止还不错,不是吗?这将为您的所有 objects 打下坚实的基础。现在,让我们看看如何将它实现到您现有的 class:
荷马:
class Homer extends Drawable // give Homer the power of the Drawable class!
{
Homer(float x, float y)
{
// I can read in the code that your image will be (60, 52), but you have to write the manipulation here
super(x, y, 60, 52, "homer.png");
img.resize (60, 52);
}
public void Update() {
// do Update stuff so Homer can move around
}
}
注意现在这个 class 是多么小,因为所有 Drawable 的东西都在别处处理了。
现在,这是沙拉 class:
首先,您可以删除 salad1, salad2, salad3
全局变量。我们会将它们放在一个列表中,如果你愿意,你可以拥有更多或更少的(你可以认为这是能够改变难度设置):
int numberOfSalads = 3;
ArrayList<Salad> salads = new ArrayList<Salad>();
在初始化沙拉的地方,可以循环初始化:
for (int i=0; i<numberOfSalads; i++) {
salads.add(new Salad(random(25,475), 900, 3);
}
当然,沙拉也会有一些修改 class:
class Salad extends Drawable {
Salad(float x, float y, float speedY)
{
super(x, y, 60, 52, "salad.png");
this.speedY = speedY; // Drawable will take it from here
img.resize (60, 52);
}
protected void Move() // I knew this would come in handy!
{
// I have no idea what's going on, just re-writing your stuff
y = y - speedY;
y = y + random(-5, 5);
if (this.y < 0)
{
this.y = 900; // once the salads y is less than 0 they restart at 900
this.x = random(25, 475);
speedY = speedY + 0.5;
}
}
}
到目前为止,还不错。还有许多其他地方您必须调整代码,但您应该注意到到目前为止您已经删除了更多您添加的行。这是好事。只要您的代码易于阅读,使其更短就意味着可以减少要修复的严重错误的地方。
另外,当你通过将它们全部放在一个地方(在这种情况下是 Drawable class)来避免重复相同的行(就像所有那些相同的渲染函数)时,你也避免了必须追捕每个如果您想进行一项更改,请迭代您的代码。这称为 DRY 代码。 DRY(不要重复自己)代码更容易调试和维护。根据经验,每次在不做任何更改的情况下复制和粘贴代码时,您应该问问自己是否可以将这些行集中在一个地方,无论是变量、函数还是 class.
我会让你编写汉堡包 class。我想你现在已经了解了如何与其他人打交道了。
现在,让我们来看看如何更新主循环,draw()
:
void draw ()
{
// As a general rule, all your game states should be dealt in the game loop.
// I like 'switch' statements for this kind of operations
// Also, try not to clutter the game loop. If you have a lot of code here, you should probably put them into functions
// it will make it easier to read and the game loop can very easily become a spaghetti nightmare if you're not careful.
switch(gameMode) {
case Menu:
// Do Menu stuff
break;
case Active:
drawBackground(); // Maybe this should be before the switch, I'm not sure how you want to deal with this
// Updates
user1.Update();
burger.Update();
for (Salad s : salads) {
s.Update();
}
// Check for collisions
// I may be mistaken but I think only the Homer can collide with stuff
if (burger.Crash(user1.x, user1.y, user1.w, user1.h)) {
// Do burger crash stuff
}
for (Salad s : salads) {
if (s.Crash(user1.x, user1.y, user1.w, user1.h)) {
// Do Salad crash stuff
}
}
// Render
user1.Render();
burger.Render();
for (Salad s : salads) {
s.Render();
}
break;
case End:
// Do End stuff
break;
}
}
这应该会让您走上正轨。
如果出于某种原因您只想使用相交方法:请记住您 objects 的宽度和高度是您用于他们的图像的宽度和高度。
您可能有疑问,请随时提出。玩得开心!
函数
boolean intersect(float x1, float y1, float w1, float h1, float x2, float y2, float w2,
float h2)
{
boolean checkX = x1 < x2 && x1+w1 > x2 || x1 < x2+w2 && x1+w1 > x2+w2 || x1 > x2 &&
x1+w1 < x2+w2 || x1 < x2 && x1+w1 > x2+w2;
boolean checkY = y1 < y2 && y1+h1 > y2 || y1 < y2+h2 && y1+h1 > y2+h2 || y1 > y2 &&
y1+h1 < y2+h2 || y1 < y2 && y1+h1 > y2+h2;
return checkX && checkY;
}
只检查 rect1 是否在 rect2 的内部
在函数中您不需要任何 or
语句
这是正确的函数
boolean intersect(float x1, float y1, float w1, float h1, float x2, float y2, float, w2, float h2)
{
boolean checkX = x1 < x2+w2 && x1+w1>x2;
boolean checkY = y1 < y2+h2 && y1+h1>y2;
return checkX && checkY;
}
你好,我这里有一个 'falling' 风格的游戏,我让荷马辛普森一家的头从天而降,避开沙拉并收集汉堡来增加分数。我已经实施了碰撞检测,所以当荷马击中沙拉时,他应该重新生成并扣除生命,这很好。但是,如果本垒打的头撞到沙拉的侧面而不是直接撞到沙拉的中央,比赛将冻结片刻,然后像什么都没发生一样继续进行。我不确定为什么会发生这种情况,并想知道我这样做是否有问题。下面是我的代码:
是碰撞检测不够准确还是我遗漏了其他问题?
PImage background;
PImage MenuBackground;
int y=0;//global variable background location
final int End = 0;
final int Active = 1;
final int Menu = 2;
int gameMode = Menu;
int score = 0;
int lives = 3;
Boolean BurgerCollisionInProgress = false;
Boolean BurgerCollisionInProgress2 = false;
Salad salad1;
Salad salad2;
Salad salad3;
Homer user1;
Burger Burger;
public void settings()
{
size(500,1000); //setup size of canvas
}
void menu()
{
background = loadImage("spaceBackground.jpg"); //image used for background
background.resize(500,1000); //resizes the background
gameMode = Active;
float rand = random(25,475);
int intRand = int(rand);
float rand2 = random(25,475);
int intRand2 = int(rand2);
float rand3 = random(25,475);
int intRand3 = int(rand3);
float rand4 = random(25,475);
int intRand4 = int(rand4);
user1 = new Homer(250,100); //declares new defender as user1
Burger = new Burger(intRand,900,2);
salad1 = new Salad(intRand2,900,3);
salad2 = new Salad(intRand3,900,3);
salad3 = new Salad(intRand4,900,3); //3 aliens declared with their x and y position and their speed they move at
draw();
}
void setup()
{
if(gameMode == 2)
{
MenuBackground = loadImage("simpMenu.png");
MenuBackground.resize(540,1000);
image(MenuBackground, 0, y);
textAlign(CENTER);
textSize(40);
fill(252, 3, 3);
text("Press 'p' to play", 250,500);
}
}
void draw ()
{
if (gameMode == Active)
{
if(crash() == false)
{
drawBackground();//calls the drawBackground method
textSize(32);
fill(22,100,8);
text("Score: " + score,75,40);
text("Lives: " + lives,75,80);
salad1.update();//calls the update method which holds the move and render methods for alien
salad2.update();
salad3.update();
user1.render();//calls the update method which holds the move and render methods for user
Burger.update();//calls the update method which holds the move and render methods for burger
if(Bcrash() == true && BurgerCollisionInProgress == false)
{
score = score+1;
BurgerCollisionInProgress = true;
Burger.y = 900;
float rand = random(25,475);
int intRand = int(rand);
Burger.x = intRand;
}
if(Bcrash() == false)
{
BurgerCollisionInProgress = false;
}
if(crash() == true && BurgerCollisionInProgress2 == false)
{
if (lives < 1)
{ gameMode = End;
textSize(28);
fill(22,100,8);
text("Game Over, press 'r' to restart",200,200);
}
else
{
lives = lives - 1;
BurgerCollisionInProgress2 = true;
menu();
}
if(crash() == false)
{
BurgerCollisionInProgress2 = false;
}
}
}
}
}
void drawBackground()
{
image(background, 0, y); //draw background twice adjacent
image(background, 0, y-background.width);
y -=2;
if(y == -background.width)
y=0; //wrap background
}
boolean crash()
{
if(user1.crash(salad1))
{
return true;
}
if(user1.crash(salad2))
{
return true;
}
if(user1.crash(salad3))
{
return true;
}
return false;
}
boolean Bcrash()
{
if(user1.crash(Burger))
{
return true;
}
return false;
}
荷马 class:
class Homer
{
PImage UserImage;
int x,y; //declaring variables
Homer(int x, int y)
{
this.x = x;
this.y = y;
UserImage = loadImage("homer.png");
UserImage.resize (60, 52);
} // end of Homer
void render()
{
//draw a Homer
image(UserImage,x,y);
} //end of void render
boolean crash(Salad A)
{
if((abs(x-A.x)<=30) && abs(y-A.y)<=30)
{
return true;
}
return false;
}// end of crash
boolean crash(Burger A)
{
if((abs(x-A.x)<=30) && abs(y-A.y)<=30)
{
return true;
}
return false;
}
} // end of class
汉堡Class:
class Burger
{
PImage burgerImage;
int x,y, speedX;
int speedY = 0;
Burger(int x, int y, int speedY)
{
this.x = x;
this.y = y;
this.speedY= speedY;
burgerImage = loadImage("food.png");
burgerImage.resize (60, 52);
}
void render()
{
image(burgerImage,x,y);
}
void move()
{
y = y - speedY;
float rand = random(25,475);
int intRand = int(rand);
if(this.y < 0)
{
this.y = 900;
this.x = intRand;
}
}
void update()
{
move();
render();
}
}
沙拉Class:
class Salad
{
float x,y;
float speedX, speedY; //declaring variables
PImage saladImage;
Salad(int x, int y, int speedY)
{
this.x = x;
this.y = y;
this.speedY = speedY;
saladImage = loadImage("salad.png");
saladImage.resize (60, 52);
} //end of salad
void move()
{
y=y-speedY;
float stepY = random(-5,5);
y = y + (int)stepY;
float rand = random(25,475);
int intRand = int(rand);
if(this.y < 0)
{
this.y = 900; // once the salads y is less than 0 they restart at 900
this.x = intRand;
speedY = speedY + 0.5;
}
} //end of void move
//draw a salad
void render()
{
image(saladImage,x,y);
} //end of void render
void update()
{
move();
render();
}
}// end of alien class
有几件小事使这比它可能更难。首先,您的相交方法不太正确。然后,可以改进处理坐标的方式。
我首先要做的是向您展示如何使矩形相交。之后,我将向您展示如何处理可绘制对象,使它们易于操作。然后,我将向您展示一些框架代码,用于一个简短、简单的游戏,其中包含物品掉落和碰撞,我会专门为您添加一些帮助,以便您可以将这些建议应用到您的游戏环境中。
1。碰撞
有很多方法可以处理冲突。其中大部分是应用数学,其中一些是利用颜色或不可见精灵的巧妙算法。可能还有一些方法我忘记了。
我们只会在矩形之间进行碰撞,因为您的程序看起来很 rectangle-friendly 而且这是更简单的方法。所以我们要写一个交叉点检测算法。
写算法首先要做的就是伪代码。我不是在开玩笑。用键盘输入 clakety-clak 并点击编译很容易。它大部分时间都有效...但它比动脑筋解决问题更直观。
能够编写伪代码对程序员来说就像是一种超能力。永远不要低估它。
现在,你怎么知道两个矩形是否相交?答案是:
- 两个矩形有 4 种相交方式,无论是水平还是垂直。
- 它们必须水平和垂直相交才能真正重叠。
这些是您必须寻找的可能性:
- 红色矩形比黑色矩形大,黑色矩形完全在里面。
- 两个矩形在左侧(水平)或顶部(垂直)重叠。
- 红色矩形小到可以放在黑色矩形里面。
- 两个矩形在右侧(水平)或底部(垂直)重叠。
因为这段代码可以在很多地方使用,所以我把它从上下文中取出来并把它放在一个函数中,该函数接受坐标和 returns 一个布尔值(如果确实存在碰撞则为真):
// INTERSECT RECTs
boolean intersect(float x1, float y1, float w1, float h1, float x2, float y2, float w2, float h2)
{
boolean checkX = x1 < x2 && x1+w1 > x2 || x1 < x2+w2 && x1+w1 > x2+w2 || x1 > x2 && x1+w1 < x2+w2 || x1 < x2 && x1+w1 > x2+w2;
boolean checkY = y1 < y2 && y1+h1 > y2 || y1 < y2+h2 && y1+h1 > y2+h2 || y1 > y2 && y1+h1 < y2+h2 || y1 < y2 && y1+h1 > y2+h2;
return checkX && checkY;
}
这是处理矩形之间冲突的一种方法。您可以获取此信息并将其应用到您的游戏中,它会大放异彩。
这就是说,您还可以使用继承改进您的代码...
2。继承(在这种情况下:对于图形 objects)
计算机科学中的继承是一种使 class 获得另一个属性的方法。大多数人都是用家族来解释的:有parentclass有child人class继承parentclass'属性。
当您的多个 class 共享相同的属性或方法时,继承尤其有用。 Drawable objects 是一个很好的例子,因为它们都需要坐标。他们都需要一个方法来绘制。
正如您稍后将在示例游戏中看到的那样,我注意到我所有的矩形都需要这些模态变量:
protected float x, y, w, h; // x and y coordinate, width and height of the square
protected color fill, stroke;
protected float strokeWeight = 1;
所以我创建了一个名为 'Drawable' 的基 class。在更大的项目中,它可能是一整棵 classes 树的基础 class,像这样:
那么在这个例子中,Rat就是Walker的child,也就是Enemy的child,也就是Actor的child,也就是Actor的[=148] =] 的可绘制对象。
优点是每个 child 都继承了 parent 的所有内容。它既可以让您编写更少的代码,也可以让您只在一个地方而不是在所有地方修复错误。例如,如果您使用 objects 坐标的方式有误,您想在编写此逻辑的 class 中修复它,而不是在每个 [=] 中 154=].
继承还有许多其他优点,但现在让我们保持简单,好吗?
3。示例程序
这个非常简单:这是一个同时使用继承和冲突的示例。您可以将其复制并粘贴到 Processing IDE 中,它会 运行。花点时间看看这 3 个 class 是如何相互关联的,以及每个 child class 如何具有它的 parent.[=25= 的模态变量和函数]
Hero hero;
ArrayList<Bomb> bombs = new ArrayList<Bomb>();
int numberOfBombs = 20; // if you change this number the number of bombs will change too. Try it!
int hitCount = 0;
public void settings()
{
size(800, 600); //setup size of canvas
}
public void setup() {
hero = new Hero();
for (int i = 0; i < numberOfBombs; i++) {
bombs.add(new Bomb(random(20, width-20), random(1, 10)));
}
// This part serves no purpose but to demonstrate that you can gather objets which share a parent class together
ArrayList<Drawable> myDrawables = new ArrayList<Drawable>();
for (Bomb b : bombs) {
myDrawables.add(b);
}
myDrawables.add(hero);
for (Drawable d : myDrawables) {
d.Render();
// Even though hero and the bombs are different classes, they are in the same ArrayList because they share the Drawable parent class.
// Drawable has the Render() function, which may be called, but the child class will overshadow the Drawable's method.
// Proof is that the error message "Drawable child: Render() was not overshadowed." will not appear in the console.
}
}
public void draw() {
DrawBackground();
hero.Update();
hero.Render();
for (Bomb b : bombs) {
b.Update();
b.Render();
}
ShowHitCount();
}
public void DrawBackground() {
fill(0);
stroke(0);
rect(0, 0, width, height, 0); // dark background
}
public void ShowHitCount() {
textAlign (RIGHT);
textSize(height/20);
fill(color(200, 200, 0));
text(hitCount, width-20, height/20 + 20);
}
class Drawable {
protected float x, y, w, h; // 'protected' is like 'private', but child class retain access
protected color fill, stroke;
protected float strokeWeight = 1;
Drawable() {
this(0, 0, 0, 0);
}
Drawable(float x, float y, float w, float h) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
}
public void Render() { print("Drawable child: Render() was not overshadowed."); } // nothing to see here: this exists so we can overshadow it in the childs
public void Update() { print("Drawable child: Update() was not overshadowed."); } // same thing
}
class Hero extends Drawable { // 'extends' is keyword for 'will inherit from'
Hero() {
// 'super()' calls the parent's constructor
// in this example, I decided that the hero would be a red 40x60 rectangle that follows the mouse X position
super(mouseX - 20, height - 80, 40, 60);
fill = color(200, 0, 0);
stroke = color(250);
}
public void Update() { // when both parents and child have the same function (type and signature), the child's one prevail. That's overshadowing.
x = mouseX - w/2;
}
public void Render() {
fill(fill);
stroke(stroke);
strokeWeight(strokeWeight);
rect(x, y, w, h);
}
}
class Bomb extends Drawable {
protected float fallSpeed;
Bomb(float xPosition, float fallSpeed) {
// Bombs will be small blue squares falling from the sky
super(xPosition, -20, 20, 20);
this.fallSpeed = fallSpeed;
fill = color(0, 0, 200);
stroke = fill;
}
private void FallAgain() {
x = random(20, width-20);
fallSpeed = random(1, 10);
y = 0 - random(20, 100);
}
public void Update() {
y += fallSpeed;
// check for collision with the Hero
if (intersect(x, y, w, h, hero.x, hero.y, hero.w, hero.h)) {
hitCount++;
FallAgain();
}
// check if it fell lower than the screen
if (y > height) {
FallAgain();
}
}
public void Render() {
fill(fill);
stroke(stroke);
strokeWeight(strokeWeight);
rect(x, y, w, h);
}
}
// INTERSECT RECTs
boolean intersect(float x1, float y1, float w1, float h1, float x2, float y2, float w2, float h2)
{
boolean checkX = x1 < x2 && x1+w1 > x2 || x1 < x2+w2 && x1+w1 > x2+w2 || x1 > x2 && x1+w1 < x2+w2 || x1 < x2 && x1+w1 > x2+w2;
boolean checkY = y1 < y2 && y1+h1 > y2 || y1 < y2+h2 && y1+h1 > y2+h2 || y1 > y2 && y1+h1 < y2+h2 || x1 < y2 && y1+h1 > y2+h2;
return checkX && checkY;
}
4。奖励:帮助实施
所以...您看到了这一点,它让您想要改进您的程序。那挺好的。也许您想实现一些继承,也许只是碰撞。两者都可能很棘手,而且都不应该影响用户。
这就是所谓的'refactoring'.
让我们先实现一个Drawable class。剩下的就容易了。
第一步:找出与伯格、荷马和阿拉德。从您发布的代码中,我可以看出他们需要这些东西:
int x, y;
int speedX, speedY;
PImage img;
// To which I would add:
int w, h;
boolean isVisible;
我注意到您使用的是整数。很好,但我强烈建议使用 float 作为坐标。我在学习编码时也做过同样的事情,结果后悔没有早点使用 float。整数和浮点数都可能会为这个项目做一些技巧(需要时进行一些转换)。
另外,这里有一些他们共享的功能:
void Render()
void Update()
void Move()
// To which I would add:
void SetPosition()
void SetIsVisible()
boolean Crash() // so we can check if it intersect with given coordinates
到目前为止,您的 Drawable class 可能如下所示:
class Drawable {
public float x, y, w, h; // Making variables public while you could avoid it is bad practice, I'm doing it to avoid writing Get functions. Avoid doing this as much as possible, but bear with me for now.
protected float speedX, speedY;
protected PImage img;
protected boolean isVisible = true;
Drawable(float x, float y, float w, float h, String imagePath) {
this.x = x; // starting x position
this.y = y; // starting y position
this.w = w; // width if the object (your image in this case)
this.h = h; // height of the object (height of your image)
if (imagePath.length() > 0) { // if there is nothing in the string it won't try to load an image
img = loadImage(imagePath);
}
}
public void Render() {
if (isVisible && img != null) {
image(img, x, y);
}
}
public void Update() {
Move(); // I kept Move() out of Update() so you can overshadow Update() without having to re-code Move() later
}
protected void Move() {
// The 'normal' behavior of a Drawable would then to move according to it's speed.
// You can then change how they move by changing their speed values.
// Or... you can overshadow this function in a child class and write your own!
x += speedX;
y += speedY;
}
public void SetPosition(float x, float y) {
this.x = x;
this.y = y;
}
public void SetIsVisible(boolean isVisible) {
this.isVisible = isVisible;
}
public boolean Crash(float x, float y, float w, float h) {
// this function uses the 'intersect' function I wrote earlier, so it would have to be included in the project
return intersect(this.x, this.y, this.w, this.h, x, y, w, h);
}
}
到目前为止还不错,不是吗?这将为您的所有 objects 打下坚实的基础。现在,让我们看看如何将它实现到您现有的 class:
荷马:
class Homer extends Drawable // give Homer the power of the Drawable class!
{
Homer(float x, float y)
{
// I can read in the code that your image will be (60, 52), but you have to write the manipulation here
super(x, y, 60, 52, "homer.png");
img.resize (60, 52);
}
public void Update() {
// do Update stuff so Homer can move around
}
}
注意现在这个 class 是多么小,因为所有 Drawable 的东西都在别处处理了。
现在,这是沙拉 class:
首先,您可以删除 salad1, salad2, salad3
全局变量。我们会将它们放在一个列表中,如果你愿意,你可以拥有更多或更少的(你可以认为这是能够改变难度设置):
int numberOfSalads = 3;
ArrayList<Salad> salads = new ArrayList<Salad>();
在初始化沙拉的地方,可以循环初始化:
for (int i=0; i<numberOfSalads; i++) {
salads.add(new Salad(random(25,475), 900, 3);
}
当然,沙拉也会有一些修改 class:
class Salad extends Drawable {
Salad(float x, float y, float speedY)
{
super(x, y, 60, 52, "salad.png");
this.speedY = speedY; // Drawable will take it from here
img.resize (60, 52);
}
protected void Move() // I knew this would come in handy!
{
// I have no idea what's going on, just re-writing your stuff
y = y - speedY;
y = y + random(-5, 5);
if (this.y < 0)
{
this.y = 900; // once the salads y is less than 0 they restart at 900
this.x = random(25, 475);
speedY = speedY + 0.5;
}
}
}
到目前为止,还不错。还有许多其他地方您必须调整代码,但您应该注意到到目前为止您已经删除了更多您添加的行。这是好事。只要您的代码易于阅读,使其更短就意味着可以减少要修复的严重错误的地方。
另外,当你通过将它们全部放在一个地方(在这种情况下是 Drawable class)来避免重复相同的行(就像所有那些相同的渲染函数)时,你也避免了必须追捕每个如果您想进行一项更改,请迭代您的代码。这称为 DRY 代码。 DRY(不要重复自己)代码更容易调试和维护。根据经验,每次在不做任何更改的情况下复制和粘贴代码时,您应该问问自己是否可以将这些行集中在一个地方,无论是变量、函数还是 class.
我会让你编写汉堡包 class。我想你现在已经了解了如何与其他人打交道了。
现在,让我们来看看如何更新主循环,draw()
:
void draw ()
{
// As a general rule, all your game states should be dealt in the game loop.
// I like 'switch' statements for this kind of operations
// Also, try not to clutter the game loop. If you have a lot of code here, you should probably put them into functions
// it will make it easier to read and the game loop can very easily become a spaghetti nightmare if you're not careful.
switch(gameMode) {
case Menu:
// Do Menu stuff
break;
case Active:
drawBackground(); // Maybe this should be before the switch, I'm not sure how you want to deal with this
// Updates
user1.Update();
burger.Update();
for (Salad s : salads) {
s.Update();
}
// Check for collisions
// I may be mistaken but I think only the Homer can collide with stuff
if (burger.Crash(user1.x, user1.y, user1.w, user1.h)) {
// Do burger crash stuff
}
for (Salad s : salads) {
if (s.Crash(user1.x, user1.y, user1.w, user1.h)) {
// Do Salad crash stuff
}
}
// Render
user1.Render();
burger.Render();
for (Salad s : salads) {
s.Render();
}
break;
case End:
// Do End stuff
break;
}
}
这应该会让您走上正轨。
如果出于某种原因您只想使用相交方法:请记住您 objects 的宽度和高度是您用于他们的图像的宽度和高度。
您可能有疑问,请随时提出。玩得开心!
函数
boolean intersect(float x1, float y1, float w1, float h1, float x2, float y2, float w2,
float h2)
{
boolean checkX = x1 < x2 && x1+w1 > x2 || x1 < x2+w2 && x1+w1 > x2+w2 || x1 > x2 &&
x1+w1 < x2+w2 || x1 < x2 && x1+w1 > x2+w2;
boolean checkY = y1 < y2 && y1+h1 > y2 || y1 < y2+h2 && y1+h1 > y2+h2 || y1 > y2 &&
y1+h1 < y2+h2 || y1 < y2 && y1+h1 > y2+h2;
return checkX && checkY;
}
只检查 rect1 是否在 rect2 的内部
在函数中您不需要任何 or
语句
这是正确的函数
boolean intersect(float x1, float y1, float w1, float h1, float x2, float y2, float, w2, float h2)
{
boolean checkX = x1 < x2+w2 && x1+w1>x2;
boolean checkY = y1 < y2+h2 && y1+h1>y2;
return checkX && checkY;
}