國際慣例先來上一組效果:
這樣的操作可以提前預測物體是否會碰撞,并且描繪出射線路徑
場景配置:
檢測代碼如下:
const AIM_LINE_MAX_LENGTH = 2440;
const { ccclass, property } = cc._decorator;
@ccclass
export default class Main extends cc.Component {
@property({ type: cc.Graphics, tooltip: '瞄準線作圖' })
graphic_line: cc.Graphics = null;
@property({ type: cc.Graphics, tooltip: '瞄準線作圖_1' })
graphic_line1: cc.Graphics = null;
@property({ type: cc.Node, tooltip: '小球節點' })
ballNode: cc.Node = null;
@property({ type: cc.Node, tooltip: 'a1' })
a1: cc.Node = null;
@property({ type: cc.Node, tooltip: 'a2' })
a2: cc.Node = null;
@property({ type: cc.Node, tooltip: 'a3' })
a3: cc.Node = null;
@property({ type: cc.Node, tooltip: 'a4' })
a4: cc.Node = null;
startLocation :cc.Vec2;
location :cc.Vec2;
ballNodePos: cc.Vec2;
a1WorldPos: cc.Vec2;
a2WorldPos: cc.Vec2;
a3WorldPos: cc.Vec2;
a4WorldPos: cc.Vec2;
private _isHaveGold: boolean = false;
private _cur_length: number = 0;
private _cur_length1: number = 0;
onLoad() {
cc.director.getPhysicsManager().enabled = true;
// cc.director.getPhysicsManager().debugDrawFlags = 1;
this.graphic_line.node.on(cc.Node.EventType.TOUCH_START, this.onTouchStart, this);
this.graphic_line.node.on(cc.Node.EventType.TOUCH_MOVE, this.onTouchMove, this);
this.graphic_line.node.on(cc.Node.EventType.TOUCH_END, this.onTouchEnd, this);
this.graphic_line.node.on(cc.Node.EventType.TOUCH_CANCEL, this.onTouchEnd, this);
cc.log(this.ballNode.getPosition())
this.ballNodePos = this.node.convertToWorldSpace(this.ballNode.getPosition())
this.a1WorldPos = this.node.convertToWorldSpace(this.a1.getPosition())
this.a2WorldPos = this.node.convertToWorldSpace(this.a2.getPosition())
this.a3WorldPos = this.node.convertToWorldSpace(this.a3.getPosition())
this.a4WorldPos = this.node.convertToWorldSpace(this.a4.getPosition())
cc.log("this.ballNodePos=====",this.ballNodePos.x,this.ballNodePos.y)
}
private refashPos(){
// cc.log("this.ballNode.convertToNodeSpace(this.a1.getPosition())===",this.ballNode.convertToWorldSpace(this.a1.getPosition()))
this.a1WorldPos = this.ballNode.convertToWorldSpaceAR(this.a1.getPosition())
this.a2WorldPos = this.ballNode.convertToWorldSpaceAR(this.a2.getPosition())
this.a3WorldPos = this.ballNode.convertToWorldSpaceAR(this.a3.getPosition())
this.a4WorldPos = this.ballNode.convertToWorldSpaceAR(this.a4.getPosition())
cc.log("this.a1WorldPos=====",this.a1WorldPos.x,this.a1WorldPos.y)
cc.log("this.a2WorldPos=====",this.a2WorldPos.x,this.a2WorldPos.y)
cc.log("this.a3WorldPos=====",this.a3WorldPos.x,this.a3WorldPos.y)
cc.log("this.a4WorldPos=====",this.a4WorldPos.x,this.a4WorldPos.y)
cc.log("----------------------------------------優美的分割線----------------------------------------")
}
private onTouchStart(touch: cc.Event.EventTouch) {
this._isHaveGold = false
this.graphic_line.clear();
this.graphic_line1.clear();
const start = touch.getStartLocation();
let angle = this.getAngle(start.x,start.y,this.ballNodePos.x,this.ballNodePos.y)
this.ballNode.setRotation(180-angle)
this._cur_length = 0;
this._cur_length1 = 0;
this.refashPos()
// 計算射線
this.drawRayCast(this.a1WorldPos, this.a3WorldPos.subSelf(this.a1WorldPos).normalizeSelf(),this.graphic_line);
this.drawRayCast(this.a2WorldPos, this.a4WorldPos.subSelf(this.a2WorldPos).normalizeSelf(),this.graphic_line1);
this.graphic_line.stroke();
this.graphic_line1.stroke();
if(this._isHaveGold == false){
cc.log("檢測物體丢失了")
}
else{
cc.log("檢測到物體了")
}
}
private onTouchMove(touch: cc.Event.EventTouch) {
this._isHaveGold = false
this.graphic_line.clear();
this.graphic_line1.clear();
const start = touch.getLocation();
let angle = this.getAngle(start.x,start.y,this.ballNodePos.x,this.ballNodePos.y)
this.ballNode.setRotation(180-angle)
this._cur_length = 0;
this._cur_length1 = 0;
this.refashPos()
// 計算射線
this.drawRayCast(this.a1WorldPos, this.a3WorldPos.subSelf(this.a1WorldPos).normalizeSelf(),this.graphic_line);
this.drawRayCast(this.a2WorldPos, this.a4WorldPos.subSelf(this.a2WorldPos).normalizeSelf(),this.graphic_line1);
this.graphic_line.stroke();
this.graphic_line1.stroke();
if(this._isHaveGold == false){
cc.log("檢測物體丢失了")
}
else{
cc.log("檢測到物體了")
}
}
private onTouchEnd(touch: cc.Event.EventTouch) {
this.graphic_line.clear();
this.graphic_line1.clear();
}
/**
* @description 計算射線
* @param startLocation 起始位置 世界坐标系
* @param vector_dir 機關方向向量
*/
private drawRayCast(startLocation: cc.Vec2, vector_dir: cc.Vec2,graphic:cc.Graphics) {
let lenghtType = 0
let Type = 0
if(graphic==this.graphic_line1){
lenghtType=this._cur_length1
Type=1
}
else{
lenghtType=this._cur_length
Type=0
}
// 剩餘長度
const left_length = AIM_LINE_MAX_LENGTH - lenghtType;
if (left_length <= 0) return;
// 計算線的終點位置
const endLocation = startLocation.add(vector_dir.mul(left_length));
// 射線測試
// 檢測給定的線段穿過哪些碰撞體,可以擷取到碰撞體線上段穿過碰撞體的那個點的法線向量和其他一些有用的資訊。
const results = cc.director.getPhysicsManager().rayCast(startLocation, endLocation, cc.RayCastType.Closest);
if (results.length > 0) {
this._isHaveGold = true
const result = results[0];
// 指定射線與穿過的碰撞體在哪一點相交。
const point = result.point;
// 畫入射線段
this.drawAimLine(startLocation, point,graphic);
// 計算長度
const line_length = point.sub(startLocation).mag();
// 計算已畫長度
if(Type==1){
this._cur_length1 += line_length;
}
else{
this._cur_length += line_length;
}
// 指定碰撞體在相交點的表面的法線機關向量。
const vector_n = result.normal;
// 入射機關向量
const vector_i = vector_dir;
// 反射機關向量
const vector_r = vector_i.sub(vector_n.mul(2 * vector_i.dot(vector_n)));
// 接着計算下一段
this.drawRayCast(point, vector_r,graphic);
} else {
// 畫剩餘線段
this.drawAimLine(startLocation, endLocation,graphic);
}
}
getAngle(px,py,mx,my){//獲得人物中心和滑鼠坐标連線,與y軸正半軸之間的夾角
var x = Math.abs(px-mx);
var y = Math.abs(py-my);
var z = Math.sqrt(Math.pow(x,2)+Math.pow(y,2));
var cos = y/z;
var radina = Math.acos(cos);//用反三角函數求弧度
var angle = Math.floor(180/(Math.PI/radina));//将弧度轉換成角度
if(mx>px&&my>py){//滑鼠在第四象限
angle = 180 - angle;
}
if(mx==px&&my>py){//滑鼠在y軸負方向上
angle = 180;
}
if(mx>px&&my==py){//滑鼠在x軸正方向上
angle = 90;
}
if(mx<px&&my>py){//滑鼠在第三象限
angle = 180+angle;
}
if(mx<px&&my==py){//滑鼠在x軸負方向
angle = 270;
}
if(mx<px&&my<py){//滑鼠在第二象限
angle = 360 - angle;
}
// cc.log("angle=======",angle)
return angle;
}
/**
* @description 畫瞄準線
* @param startLocation 起始位置 世界坐标系
* @param endLocation 結束位置 世界坐标系
*/
private drawAimLine(startLocation: cc.Vec2, endLocation: cc.Vec2,graphic:cc.Graphics) {
// 轉換坐标
// cc.log("startLocation=====",startLocation.x,startLocation.y)
const graphic_startLocation = graphic.node.convertToNodeSpace(startLocation);
// cc.log("graphic_startLocation====",graphic_startLocation.x,graphic_startLocation.y)
graphic.moveTo(graphic_startLocation.x, graphic_startLocation.y);
// 畫小圓圓
// 間隔
const delta = 20;
// 方向
const vector_dir = endLocation.sub(startLocation);
// 數量
const total_count = Math.round(vector_dir.mag() / delta);
// 每次間隔向量
vector_dir.normalizeSelf().mulSelf(delta);
for (let index = 0; index < total_count; index++) {
graphic_startLocation.addSelf(vector_dir)
graphic.circle(graphic_startLocation.x, graphic_startLocation.y, 2);
}
}
}
檢測類型介紹
- cc.RayCastType.Any
檢測射線路徑上任意的碰撞體,一旦檢測到任何碰撞體,将立刻結束檢測其他的碰撞體,最快。
- cc.RayCastType.Closest
檢測射線路徑上最近的碰撞體,這是射線檢測的預設值,稍慢。
- cc.RayCastType.All
檢測射線路徑上的所有碰撞體,檢測到的結果順序不是固定的。在這種檢測類型下一個碰撞體可能會傳回多個結果,這是因為 box2d 是通過檢測夾具(fixture)來進行物體檢測的,而一個碰撞體中可能由多個夾具(fixture)組成的,慢。更多細節可到 實體碰撞元件 檢視。
- cc.RayCastType.AllClosest
檢測射線路徑上所有碰撞體,但是會對傳回值進行删選,隻傳回每一個碰撞體距離射線起始點最近的那個點的相關資訊,最慢
- result傳回值介紹