發表文章

目前顯示的是 10月, 2021的文章

矩形與矩形碰撞(皆旋轉矩形)

圖片
碰撞主題的最後,是凸多邊形碰撞的解法應用在矩形上,想法來自 Stack Overflow : How to check intersection between 2 rotated rectangles? 的回應 ,正如標題描述要處理的是,兩個有旋轉的矩形之碰撞檢定。 以四邊形 ABCD 和三角形 EFG 為例,上圖是邊界有相交,下圖是沒有,當兩凸多邊形未碰撞時,中間會有空隙一定找的到一條直線 L 使得兩個圖形各在直線的異側,如下圖: 顯然這樣的直線不是唯一,不只可以將直線平移,其至稍微旋轉一點也可以,在兩凸多邊形邊界未相交時,可以隔開兩個圖形的直線有無限多條,如下圖有 L、M、N 三條: 換句話說只要找出一條直線,可使得兩個圖形各落在直線的一側,就確定兩圖形沒有碰撞,那麼這樣的直線怎麼找呢?再觀察上圖,顯然隔開的直線可以取「平行多邊形的一邊的直線」,例如上圖的直線 L 就平行線段 FG,而直線 M 和直線 N 就平行線段 AB,其實也不是一定要平行多邊形的邊,例如上圖中斜率介於 L 和 M 之間的直線也能夠將兩圖形分開,只是我們從平行多邊形一邊的直線來著手。 再回來看四邊形 ABCD 在左邊,三角形 EFG 在右邊,被直線 L 隔開的圖: 如果兩凸多邊形被直線 L 分開,應該有什麼性質呢?我們考慮將兩個圖形沿著直線 L 的方向投影出去,也就是取和直線 L 垂直的直線 N ,將四邊形 ABCD 與三角形 EFG 都投影到直線 N 上,各自會得到線段,如下圖: 顯然投影的兩個線段不會相交,四邊形 ABCD 在直線 N 上的正射影是線段 A'C',而三角形 EFG 在直線 N 上的正射影是線段 F'G',兩個線段並沒有重疊,上圖是取「平行線段 CD 的直線 L 和垂直的直線 N」,如果看另外一組: 上圖是取「平行線段 EG 的直線 L 和垂直的直線 N」,同樣的將四邊形 ABCD 和三角形 EFG 投影到直線 N 上的結果,顯然洋紅色與青綠色兩個投影線段這次就有重疊了,兩個多邊形在「邊 EG 的法向量」上的正射影有重疊,表示我們無法用平行 EG 的直線將兩個圖形分開在兩邊,但這不代表兩個圖形就有碰撞,只是代表不能用平行線段 EG 的直線去隔開兩個圖形。可能用別的邊和法向量看投影,就可...

矩形與矩形碰撞(未旋轉)

圖片
這應該是旋轉主題裡面最簡單的吧,如果是用 Java Swing 矩形為框的眾多 JComponent 像 JLabel JButton 就有 .getBounds() 得到 Rectangle 物件,並有 .intersects() 方法可用。不過這個碰撞檢定剛好可視為旋轉矩形碰撞檢定的特例,我個人認為可以比較一下,所以未旋轉兩矩形的碰撞檢定還是做了個筆記。 未旋轉的擺正矩形,四個邊會平行兩軸,我們考慮矩形投影到 x 軸,會得到線段,如果兩個矩形有碰撞,則兩個投影線段會相交,相反地矩形沒有碰撞的話,投影到 x 軸的線段就會分開,我們可用線段的最左最右來比較是否重疊,可知其實就是比較原來矩形的左邊 x 坐標和右邊 x 坐標,如下圖: 如上圖,未碰撞可分兩種情形,一個是矩形 A 的最右邊小於矩形 B 的最左邊,也就是整個矩形落在另一個矩形的左側,不管矩形 A 上下如何移動,都和矩形 B 不會相交;同理當矩形 A 的最左 x 坐標大於矩形 B 的最右 x 坐標,兩矩形不會碰撞。 以此類推還有上下的: 如上圖,當其中一個矩形的上緣比另一個矩形的下緣還要低,則不會相交;當其中一個矩形的下緣比另一個的上緣還要更高,也不會相交。 參考 Java Code: public boolean isRectRectCollide(Rectangle rectA, Rectangle rectB) { if(rectA.Left() > rectB.Right() || rectA.Right() < rectB.Left()) { return false; // Rectangle.Left() 取得矩形左邊線段的 x 坐標 } if(rectA.Top() > rectB.Bottom() || rectA.Bottom() < rectB.Top()) { return false; // Rectangle.Top() 取得矩形上方線段的 y 坐標 } // 要注意電腦坐標系是 y 是以下為正 return true; } 圓形與圓形碰撞 圓形與矩形碰撞(無旋轉矩形) ...

圓形與矩形碰撞(有旋轉矩形)

圖片
承上篇,如果是寬高與兩軸不平行,也就是經過旋轉的矩形,如何檢定他和圓形的碰撞?如下圖: 因為上一篇已經完成了圓形與擺正的矩形之碰撞檢定,那就可以將不平行的矩形轉正,利用已完成的函式來解決這個問題。首先要將矩形轉正,就需要找到角度,能夠直接從物件中得到旋轉角度是最好的(我自己設計遊戲物件就有做旋轉角度的 getter setter),沒有的話可能要考慮多寫一個方法。 如上圖,找到矩形從擺正到現在傾斜是轉了θ度的話,接下來考慮將圓形對矩形的中心旋轉 ( - θ ) 度。為什麼要這麼做呢? 其實是將圓形與矩形兩個都對矩形中心旋轉 ( - θ ) 度。如此一來兩個圖形就符合上一篇的前提,這樣來判斷相交情形,就代表原來的兩圖形是否碰撞的結果是一致的,因為對同一個旋轉中心旋轉同樣的角度,之前之後的兩圖形相對位置並沒有改變。再加上矩形本身對自己的中心旋轉,其寬的一半、高的一半和中心坐標,還是一樣,所以只需要考慮圓形對矩形中心旋轉後的結果,又半徑也不受旋轉影響,就剩會影響「兩中心點水平差」和「兩中心點鉛直差」的新圓心坐標了,所以結論是: 目標是找出圓心對矩形中心旋轉 ( - θ ) 後的新坐標 將圓形與矩形對矩形中心旋轉 ( - θ ) 得到的結果: 有旋轉得新坐標的方法是最好,不難也可以再重寫,步驟大概有: 先平移 ( - 矩形中心 ),即平移後坐標 = 圓心坐標 - 矩形中心坐標, 乘上 ( - θ ) 的旋轉矩陣 [ [cos(-θ) , - sin(-θ)] , [ sin(-θ) , cos(-θ) ] ] 再平移矩形中心坐標,坐標加上 矩形中心坐標 得圓形旋轉後新坐標,就可以引入前一篇的函式: public boolean isCircRotateRectCollide(Circle circ, Rectangle rect) { double theta = Math.toRadians(rect.Angle() * (-1)); // 旋轉負的角度 Circle rotatedCirc = new Circle(); int transX = circ.X() - rect.X(); // 平移 int transY = circ.Y(...

圓形與矩形碰撞(無旋轉矩形)

圖片
承上一篇,接下來是檢定圓形和矩形的碰撞,想法來自這篇 StackOverflow : Circle-Rectangle collision detection (intersection) ,不過這個只能檢定擺正沒有經過旋轉的矩形,即邊長平行兩軸的矩形,例如: 檢定分成三個部份,由於圓形只會靠近一個角落,所以將矩形切割成四個部份,只討論一個角落,碰撞檢定是全部一起看的,只是接下來圖片的呈現只用右上角為例。 第一部份,最先排除足夠遠的可能 如上圖,當圓形夠遠的情形先排除掉,例如圖中紅色的區域,當圓形的圓心在紅色區域時,兩圖形必不相交,那判斷的依據是什麼呢? 當圓形的圓心和矩形的中心(對角線交點)的水平距離超過矩形寬度的一半加上圓半徑,圓形和矩形就足夠遠不會相交,同理圓心和矩形中心的鉛直距離超過矩形高度的一半加上圓半徑,也不會相交,這時候圓心就會落在紅色區域,兩圖形就不會碰撞。此處要用的數據有: 兩中心的水平差 = |圓心 x 坐標 - 矩形中心 x 坐標| 兩中心的鉛直差 = |圓心 y 坐標 - 矩形中心 y 坐標| 矩形寬度的一半 + 圓半徑 矩形高度的一半 + 圓半徑 第二部份,討論餘下之中,足夠近的情形。 經過第一部份,剩下的兩圖形之中心的可能,會被限制在「水平差 <= 矩形寬的一半 + 圓半徑」,而且「鉛直差 <= 矩形高的一半 + 圓半徑」,此時圓心會落在上圖的藍色與黃色區域內,如上圖。 在此情形下如果又更靠近,例如「鉛直差 <= 矩形高的一半」,又因為「水平差 <= 矩形寬的一半 + 圓半徑」則圓心落在黃色區域中,兩圖形就相交;同理如果「水平差 <= 矩形寬的一半」,又因為「鉛直差 <= 矩形高的一半 + 圓半徑」則圓心也落在黃色區域中,兩圖形相交。 要用的數據有: 兩中心的水平差 = |圓心 x 坐標 - 矩形中心 x 坐標| 兩中心的鉛直差 = |圓心 y 坐標 - 矩形中心 y 坐標| 矩形寬度的一半 矩形高度的一半 第三部份,細膩判斷角落的情形。 最後,就剩下在第二部份的圖片中藍色的正方形,這是以圓半徑圍成的正方形。圓心落在此處的圓可能與矩形相交,也可能不會: 如果圓形與矩形要有交點,考慮...

圓形與圓形碰撞

圖片
做遊戲的過程一定會碰到邊界碰撞的檢定,邊界較常見的就圓形和矩形,下面記錄一下,就先最簡單的圓形與圓形的碰撞,原理就是初中三年級的圓形幾何,當兩圓外切時連心線恰為兩半徑之和,當兩圓交於兩點時,兩半徑與連心線會形成三角形,故連心線 <= 半徑之和,則兩圓相交: 其實稱呼這個要求為疊合會更恰當,像是兩圓之位置關係為內離不能算是碰撞,因為邊界不相交,但是遊戲上多是從外部來靠近,即使邊界相交甚至到其一個物件全部落在另一個物件之中,碰撞已經發生了,所以不太去排除完全落在內部的情形,以下是 Java 程式碼: public boolean isCircCircCollide(Circle circA, Circle circB) { double distance = Math.sqrt( // 計算連心線長 Math.pow(circA.X() - circB.X(), 2) // Circle.X() 得圓心x坐標 + Math.pow(circA.Y() - circB.Y(), 2) ); // Circle.Radius() 得半徑 return circA.Radius() + circB.Radius() >= distance; } 圓形與圓形碰撞 圓形與矩形碰撞(無旋轉矩形) 圓形與矩形碰撞(有旋轉矩形) 矩形與矩形碰撞(未旋轉) 矩形與矩形碰撞(皆旋轉矩形)

[Java] JFrame JPanel 設背景圖片

在 JFrame 和 JPanel 中雖然有背景的設定 .setBackground() 但參數只能是顏色 Color.COLORNAME 或 new Color(r,g,b)。如果背景要放圖片,可能會想到這樣: import java.awt.Dimension; import javax.swing.ImageIcon; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; public class JFrameBackgroundDemo extends JFrame{ JFrameBackgroundDemo(){ JPanel myPanel = new JPanel(); // 用 Dimension 和 JFrame 的 .pack() 可保證大小正確 myPanel.setPreferredSize(new Dimension(800,600)); JLabel bgLabel = new JLabel(); bgLabel.setIcon(new ImageIcon("img/background.png")); bgLabel.setLocation(0, 0); bgLabel.setVisible(true); myPanel.add(bgLabel); this.add(myPanel); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.setVisible(true); this.pack(); } } 背景圖片路徑是 img/ 下的 background.png。想法是用一個 JLabel 使用該圖片然後定位在 (0, 0) 但這不是好辦法,因為他會推開其他 JComponent 而且當其他有動作時會被 JLabel 蓋住(我對 javax 的上下層邏輯實在無法理解),所以改用別的辦法,參考自 StackOverflow : Simplest way to set image as JPanel backgro...

[Java] 用 InputMap ActionMap KeyStroke 並解決按鍵遲鈍問題

這篇等於是結合前面兩篇 利用 getInputMap、getActionMap 和 KeyStroke 從外部新增 Action 和 javax 中 KeyListener 的遲鈍問題 的結果 概念上是用 KeyStroke.getKeyStroke("pressed RIGHT") 得到按下右鍵時,讓 InputMap 對應到 "rightAct" 字串,再讓 ActionMap 對應到一個動作,而這個動作物件是在外部再描述,讓 myLabel 裡的物件成員 ArrayList<Character> pressedKey 來增減內容,當沒有 'r' 時增加 'r' 進去,而且若是第一次按鍵就啓動 timer;而 KeyStroke.getKeyStroke("released RIGHT") 放開右鍵時,對應到 "rightCancel" 字串,由 ActionMap 對應到動件,在外描描述此動作,當 pressKey 之中有 'r' 時刪去,如果完全沒有按鍵時則 timer 停止。timer 會讓 update() 不停的執行,而 update() 內容也由外部覆寫,處理目前按下了左鍵還是右鍵,讓 myLabel 更新他的位置來左右移動。 GameObject.java package game; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.ArrayList; import javax.swing.JLabel; import javax.swing.Timer; public class GameObject extends JLabel implements ActionListener{ private int delay; private Timer timer; private ArrayList<Character> pressedKeyList = new ArrayList<>(); public GameOb...